feat: improved parsing of commit message in backend

This commit is contained in:
Sam Chau
2024-09-19 13:19:40 +09:30
parent 352895661e
commit fab2493d0a
5 changed files with 160 additions and 35 deletions

View File

@@ -2,33 +2,50 @@
import os
import logging
from .utils import extract_data_from_yaml, determine_type
from .utils import extract_data_from_yaml, determine_type, parse_commit_message
logger = logging.getLogger(__name__)
def get_incoming_changes(repo, branch):
incoming_changes = []
diff = repo.git.diff(f'HEAD...origin/{branch}', name_only=True)
changed_files = diff.split('\n') if diff else []
for file_path in changed_files:
if file_path:
full_path = os.path.join(repo.working_dir, file_path)
file_data = extract_data_from_yaml(full_path) if os.path.exists(full_path) else None
file_data = extract_data_from_yaml(full_path) if os.path.exists(
full_path) else None
# Correcting the git show command
commit_message = repo.git.show(f'HEAD...origin/{branch}', '--format=%B', '-s', file_path).strip()
raw_commit_message = repo.git.show(f'HEAD...origin/{branch}',
'--format=%B', '-s',
file_path).strip()
parsed_commit_message = parse_commit_message(
raw_commit_message
) # Parse commit message using the util function
incoming_changes.append({
'name': file_data.get('name', os.path.basename(file_path)) if file_data else os.path.basename(file_path),
'id': file_data.get('id') if file_data else None,
'type': determine_type(file_path),
'status': 'Incoming',
'file_path': file_path,
'commit_message': commit_message, # Include commit message here
'staged': False,
'modified': True,
'deleted': False
'name':
file_data.get('name', os.path.basename(file_path))
if file_data else os.path.basename(file_path),
'id':
file_data.get('id') if file_data else None,
'type':
determine_type(file_path),
'status':
'Incoming',
'file_path':
file_path,
'commit_message':
parsed_commit_message, # Use parsed commit message
'staged':
False,
'modified':
True,
'deleted':
False
})
return incoming_changes
return incoming_changes

View File

@@ -3,31 +3,34 @@
import os
import yaml
import logging
import re
logger = logging.getLogger(__name__)
def extract_data_from_yaml(file_path):
logger.debug(f"Extracting data from file: {file_path}")
try:
with open(file_path, 'r') as f:
content = yaml.safe_load(f)
logger.debug(f"File content: {content}") # Log the full file content
logger.debug(
f"File content: {content}") # Log the full file content
if content is None:
logger.error(f"Failed to parse YAML file or file is empty: {file_path}")
logger.error(
f"Failed to parse YAML file or file is empty: {file_path}")
return None
# Check if expected keys are in the content
if 'name' not in content or 'id' not in content:
logger.warning(f"'name' or 'id' not found in file: {file_path}")
return {
'name': content.get('name'),
'id': content.get('id')
}
logger.warning(
f"'name' or 'id' not found in file: {file_path}")
return {'name': content.get('name'), 'id': content.get('id')}
except Exception as e:
logger.warning(f"Error reading file {file_path}: {str(e)}")
return None
def determine_type(file_path):
if 'regex_patterns' in file_path:
return 'Regex Pattern'
@@ -37,6 +40,7 @@ def determine_type(file_path):
return 'Quality Profile'
return 'Unknown'
def interpret_git_status(x, y):
if x == 'D' or y == 'D':
return 'Deleted'
@@ -54,3 +58,89 @@ def interpret_git_status(x, y):
return 'Untracked'
else:
return 'Unknown'
def parse_commit_message(commit_message):
# Default placeholders for missing parts of the commit message
placeholders = {
'type': 'Unknown Type',
'scope': 'Unknown Scope',
'subject': 'No subject provided',
'body': 'No body provided',
'footer': 'No footer provided'
}
# Mapping of commit types and scopes to canonical forms
type_mapping = {
'feat': 'New Feature',
'feature': 'New Feature',
'new': 'New Feature',
'fix': 'BugFix',
'bugfix': 'BugFix',
'bug': 'BugFix',
'docs': 'Documentation',
'documentation': 'Documentation',
'doc': 'Documentation',
'style': 'Style Change',
'formatting': 'Style Change',
'format': 'Style Change',
'lint': 'Style Change',
'refactor': 'Refactor',
'refactoring': 'Refactor',
'restructure': 'Refactor',
'redesign': 'Refactor',
'perf': 'Performance Improvement',
'performance': 'Performance Improvement',
'optimize': 'Performance Improvement',
'optimisation': 'Performance Improvement',
'test': 'Test',
'testing': 'Test',
'chore': 'Maintenance',
'maintenance': 'Maintenance',
'maintain': 'Maintenance'
}
scope_mapping = {
'regex': 'Regex Pattern',
'regex pattern': 'Regex Pattern',
'format': 'Custom Format',
'custom format': 'Custom Format',
'profile': 'Quality Profile',
'quality profile': 'Quality Profile'
}
# Regex patterns for each part of the commit message
type_pattern = r'^(?P<type>feat|feature|new|fix|bugfix|bug|docs|documentation|doc|style|formatting|format|lint|refactor|refactoring|restructure|redesign|perf|performance|optimize|optimisation|test|testing|chore|maintenance|maintain)'
scope_pattern = r'\((?P<scope>regex|regex pattern|format|custom format|profile|quality profile)\)'
subject_pattern = r':\s(?P<subject>.+)'
body_pattern = r'(?P<body>(?:- .+\n?)+)' # Handles multiple lines in the body
footer_pattern = r'(?P<footer>(Fixes|Resolves|See also|Relates to)\s.+)'
# Initialize result with placeholders
parsed_message = placeholders.copy()
# Parse the type and scope
type_scope_match = re.match(
f'{type_pattern}{scope_pattern}{subject_pattern}', commit_message,
re.IGNORECASE)
if type_scope_match:
matched_type = type_scope_match.group('type').lower()
matched_scope = type_scope_match.group('scope').lower()
# Map the matched values to their canonical forms
parsed_message['type'] = type_mapping.get(matched_type, 'Unknown Type')
parsed_message['scope'] = scope_mapping.get(matched_scope,
'Unknown Scope')
parsed_message['subject'] = type_scope_match.group('subject').strip()
# Match and extract the body part
body_match = re.search(body_pattern, commit_message, re.MULTILINE)
if body_match:
parsed_message['body'] = body_match.group('body').strip()
# Match and extract the footer (if present)
footer_match = re.search(footer_pattern, commit_message)
if footer_match:
parsed_message['footer'] = footer_match.group('footer').strip()
return parsed_message

View File

@@ -59,6 +59,7 @@ const ChangeRow = ({
const handleViewDiff = e => {
e.stopPropagation();
console.log('Change Object: ', JSON.stringify(change, null, 2));
setShowDiff(true);
};

View File

@@ -5,8 +5,7 @@ import {GitCommit} from 'lucide-react';
const DiffCommit = ({commitMessage}) => {
if (!commitMessage) return null;
const [firstLine, ...restLines] = commitMessage.split('\n');
const [commitType, commitSummary] = firstLine.split(': ');
const {type, scope, subject, body, footer} = commitMessage;
return (
<div className='bg-gray-100 dark:bg-gray-800 p-4 rounded-lg shadow-sm'>
@@ -21,15 +20,15 @@ const DiffCommit = ({commitMessage}) => {
</h4>
<div className='mb-2'>
<span className='inline-block px-2 py-1 rounded text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 mr-2'>
{commitType}
{type}
</span>
<span className='text-sm font-semibold text-gray-700 dark:text-gray-300'>
{commitSummary}
{`${scope ? `(${scope})` : ''} ${subject}`}
</span>
</div>
{restLines.length > 0 && (
{body && (
<ul className='list-disc pl-5 space-y-1'>
{restLines.map((line, index) => (
{body.split('\n').map((line, index) => (
<li
key={index}
className='text-sm text-gray-600 dark:text-gray-400'>
@@ -38,6 +37,11 @@ const DiffCommit = ({commitMessage}) => {
))}
</ul>
)}
{footer && (
<div className='mt-2 text-sm text-gray-600 dark:text-gray-400'>
{footer}
</div>
)}
</div>
</div>
</div>
@@ -45,7 +49,13 @@ const DiffCommit = ({commitMessage}) => {
};
DiffCommit.propTypes = {
commitMessage: PropTypes.string
commitMessage: PropTypes.shape({
type: PropTypes.string.isRequired,
scope: PropTypes.string,
subject: PropTypes.string.isRequired,
body: PropTypes.string,
footer: PropTypes.string
})
};
export default DiffCommit;

View File

@@ -77,7 +77,8 @@ const ViewDiff = ({
title={titleContent}
size='4xl'>
<div className='space-y-4'>
<DiffCommit commitMessage={commitMessage} />
<DiffCommit commitMessage={commitMessage} />{' '}
{/* Passing the commitMessage object */}
<div className='border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden'>
<div className='bg-gray-50 dark:bg-gray-800 p-2 text-sm font-medium text-gray-600 dark:text-gray-300 border-b border-gray-300 dark:border-gray-600'>
Diff Content
@@ -103,7 +104,13 @@ ViewDiff.propTypes = {
diffContent: PropTypes.string,
type: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
commitMessage: PropTypes.string,
commitMessage: PropTypes.shape({
type: PropTypes.string,
scope: PropTypes.string,
subject: PropTypes.string,
body: PropTypes.string,
footer: PropTypes.string
}),
isIncoming: PropTypes.bool.isRequired
};