diff --git a/aicmt/cli.py b/aicmt/cli.py index ec15fc4..27731be 100644 --- a/aicmt/cli.py +++ b/aicmt/cli.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import sys +from typing import List from .git_operations import GitOperations from .ai_analyzer import AIAnalyzer from .cli_interface import CLIInterface @@ -9,59 +10,59 @@ console = Console() -class GitCommitAssistant: +class AiCommit: def __init__(self): self.git_ops = GitOperations() self.ai_analyzer = AIAnalyzer() self.cli = CLIInterface() + def _handle_changes(self) -> List[dict]: + """Handle and return git changes""" + staged_changes = self.git_ops.get_staged_changes() + if staged_changes: + self.cli.display_info("Found staged changes, analyzing only those changes.") + return staged_changes + return self.git_ops.get_unstaged_changes() + + def _create_new_commits(self, approved_groups: List[dict]) -> None: + """Process approved commit groups""" + for group in approved_groups: + try: + self.git_ops.stage_files(group["files"]) + self.git_ops.commit_changes( + f"{group['commit_message']}\n\n{group['description']}" + ) + self.cli.display_success(f"Created commit: {group['commit_message']}") + except Exception as e: + self.cli.display_error(f"Failed to create commit: {str(e)}") + + def _handle_push(self) -> None: + """Handle pushing changes if confirmed""" + if self.cli.confirm_push(): + try: + self.git_ops.push_changes() + self.cli.display_success("Successfully pushed all commits!") + except Exception as e: + self.cli.display_error(f"Failed to push commits: {str(e)}") + def run(self): """Main execution flow""" try: self.cli.display_welcome() - # Display repository info self.cli.display_repo_info(self.git_ops.repo.working_dir, self.git_ops.get_current_branch()) - # Get changes - changes = [] - staged_changes = self.git_ops.get_staged_changes() - if staged_changes: - changes = staged_changes - self.cli.display_info("Found staged changes, analyzing only those changes.") - else: - changes = self.git_ops.get_unstaged_changes() + changes = self._handle_changes() - # Display current changes with detailed info self.cli.display_changes(changes) - - # Analyze changes with AI self.cli.display_ai_analysis_start(self.ai_analyzer.base_url, self.ai_analyzer.model) commit_groups = self.ai_analyzer.analyze_changes(changes) approved_groups = self.cli.display_commit_groups(commit_groups) - self.cli.display_groups_approval_status(len(approved_groups), len(commit_groups)) - # Create commits for approved groups - for group in approved_groups: - try: - # Stage files for this group - self.git_ops.stage_files(group["files"]) - - # Create commit - self.git_ops.commit_changes(group["commit_message"] + "\n\n" + group["description"]) - - self.cli.display_success(f"Created commit: {group['commit_message']}") - except Exception as e: - self.cli.display_error(f"Failed to create commit: {str(e)}") - continue + self.cli.display_groups_approval_status(len(approved_groups), len(commit_groups)) - # Ask user if they want to push changes - if self.cli.confirm_push(): - try: - self.git_ops.push_changes() - self.cli.display_success("Successfully pushed all commits!") - except Exception as e: - self.cli.display_error(f"Failed to push commits: {str(e)}") + self._create_new_commits(approved_groups) + self._handle_push() except KeyboardInterrupt: self.cli.exit_program("\nOperation cancelled by user.") @@ -71,22 +72,12 @@ def run(self): def cli(): + # Create and run assistant try: - # First parse command line arguments parse_args() - - # Create and run assistant - try: - assistant = GitCommitAssistant() - assistant.run() - except ValueError as ve: - # Configuration related errors - console.print(f"[bold red]Configuration Error:[/bold red] {str(ve)}") - sys.exit(1) - except Exception as e: - # Other runtime errors - console.print(f"[bold red]Runtime Error:[/bold red] {str(e)}") - sys.exit(1) - except KeyboardInterrupt: - console.print("\nProgram interrupted by user") + assistant = AiCommit() + assistant.run() + except Exception as e: + # Other runtime errors + console.print(f"[bold red]Error:[/bold red] {str(e)}") sys.exit(1) diff --git a/aicmt/git_operations.py b/aicmt/git_operations.py index 3ce2ac6..871f6e0 100644 --- a/aicmt/git_operations.py +++ b/aicmt/git_operations.py @@ -314,3 +314,64 @@ def get_commit_history(self, max_count: int = 10) -> List[Dict]: return commits except git.GitCommandError as e: raise git.GitCommandError(f"Failed to get commit history: {str(e)}", e.status, e.stderr) + + def get_commit_changes(self, commit_hash: str) -> List[Change]: + """Get changes from a specific commit + + Args: + commit_hash: Hash of the commit to analyze + + Returns: + List[Change]: List of changes in the commit + + Raises: + git.GitCommandError: If there is an error getting the commit changes + """ + try: + commit = self.repo.commit(commit_hash) + parent = commit.parents[0] if commit.parents else self.repo.tree("4b825dc642cb6eb9a060e54bf8d69288fbee4904") + + changes = [] + diff_index = parent.diff(commit) + + for diff in diff_index: + status = "error" + content = "" + insertions = diff.insertions if hasattr(diff, "insertions") else 0 + deletions = diff.deletions if hasattr(diff, "deletions") else 0 + + try: + if diff.deleted_file: + status = "deleted" + content = "[File deleted]" + insertions, deletions = 0, diff.a_blob.size if diff.a_blob else 0 + elif diff.new_file: + if diff.b_blob: + try: + content = diff.b_blob.data_stream.read().decode('utf-8') + status = "new file" + insertions = len(content.splitlines()) + deletions = 0 + except UnicodeDecodeError: + status = "new file (binary)" + content = "[Binary file]" + else: + status = "new file" + content = "[Empty file]" + else: + status = "modified" + content = self.repo.git.diff(f"{parent.hexsha}..{commit.hexsha}", diff.b_path) + stats = self.repo.git.diff(f"{parent.hexsha}..{commit.hexsha}", "--numstat", diff.b_path).split() + if len(stats) >= 2: + insertions = int(stats[0]) if stats[0] != "-" else 0 + deletions = int(stats[1]) if stats[1] != "-" else 0 + + except Exception as e: + status = "error" + content = f"[Unexpected error: {str(e)}]" + + changes.append(Change(file=diff.b_path or diff.a_path, status=status, diff=content, insertions=insertions, deletions=deletions)) + + return changes + except git.GitCommandError as e: + raise git.GitCommandError(f"Failed to get commit changes: {str(e)}", e.status, e.stderr) \ No newline at end of file