Initial upload of auto-changelog-generator
This commit is contained in:
126
src/changeloggen/git_client.py
Normal file
126
src/changeloggen/git_client.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class FileChange:
|
||||
"""Represents a single file change with its diff content."""
|
||||
file_path: str
|
||||
change_type: str
|
||||
diff_content: str
|
||||
staged: bool = False
|
||||
lines_added: int = 0
|
||||
lines_removed: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
self._calculate_line_changes()
|
||||
|
||||
def _calculate_line_changes(self):
|
||||
lines_added = 0
|
||||
lines_removed = 0
|
||||
for line in self.diff_content.split('\n'):
|
||||
if line.startswith('+') and not line.startswith('+++'):
|
||||
lines_added += 1
|
||||
elif line.startswith('-') and not line.startswith('---'):
|
||||
lines_removed += 1
|
||||
self.lines_added = lines_added
|
||||
self.lines_removed = lines_removed
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChangeSet:
|
||||
"""Represents a collection of file changes from git diff."""
|
||||
staged_changes: list[FileChange] = field(default_factory=list)
|
||||
unstaged_changes: list[FileChange] = field(default_factory=list)
|
||||
commit_hash: Optional[str] = None
|
||||
commit_message: Optional[str] = None
|
||||
author: Optional[str] = None
|
||||
timestamp: Optional[str] = None
|
||||
|
||||
@property
|
||||
def all_changes(self) -> list[FileChange]:
|
||||
"""Return all changes (staged and unstaged)."""
|
||||
return self.staged_changes + self.unstaged_changes
|
||||
|
||||
@property
|
||||
def total_files_changed(self) -> int:
|
||||
"""Return total number of files changed."""
|
||||
return len(self.all_changes)
|
||||
|
||||
@property
|
||||
def total_lines_added(self) -> int:
|
||||
"""Return total lines added across all changes."""
|
||||
return sum(change.lines_added for change in self.all_changes)
|
||||
|
||||
@property
|
||||
def total_lines_removed(self) -> int:
|
||||
"""Return total lines removed across all changes."""
|
||||
return sum(change.lines_removed for change in self.all_changes)
|
||||
|
||||
|
||||
def parse_change_type(status_char: str) -> str:
|
||||
"""Parse git status character to change type."""
|
||||
status_map = {
|
||||
'A': 'added',
|
||||
'M': 'modified',
|
||||
'D': 'deleted',
|
||||
'R': 'renamed',
|
||||
'C': 'copied',
|
||||
'M': 'modified',
|
||||
'??': 'untracked',
|
||||
}
|
||||
return status_map.get(status_char, 'modified')
|
||||
|
||||
|
||||
def get_diff_lines(diff_output: str) -> list[str]:
|
||||
"""Parse diff output into individual file diffs."""
|
||||
if not diff_output.strip():
|
||||
return []
|
||||
lines = diff_output.split('\n')
|
||||
file_diffs = []
|
||||
current_diff = []
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('diff --git'):
|
||||
if current_diff:
|
||||
file_diffs.append('\n'.join(current_diff))
|
||||
current_diff = [line]
|
||||
else:
|
||||
current_diff.append(line)
|
||||
|
||||
if current_diff:
|
||||
file_diffs.append('\n'.join(current_diff))
|
||||
|
||||
return file_diffs
|
||||
|
||||
|
||||
def parse_file_diff(diff_text: str, staged: bool = False) -> Optional[FileChange]:
|
||||
"""Parse a single file diff into a FileChange object."""
|
||||
lines = diff_text.split('\n')
|
||||
|
||||
file_path = None
|
||||
change_type = 'modified'
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('diff --git'):
|
||||
parts = line.split(' ')
|
||||
if len(parts) >= 4:
|
||||
old_path = parts[2].lstrip('a/')
|
||||
new_path = parts[3].lstrip('b/')
|
||||
file_path = new_path if new_path else old_path
|
||||
elif line.startswith('new file mode'):
|
||||
change_type = 'added'
|
||||
elif line.startswith('deleted file mode'):
|
||||
change_type = 'deleted'
|
||||
elif line.startswith('similarity index'):
|
||||
change_type = 'renamed'
|
||||
|
||||
if not file_path:
|
||||
return None
|
||||
|
||||
return FileChange(
|
||||
file_path=file_path,
|
||||
change_type=change_type,
|
||||
diff_content=diff_text,
|
||||
staged=staged
|
||||
)
|
||||
Reference in New Issue
Block a user