Last active
December 2, 2025 16:38
-
-
Save leliel12/4a15a0d129bea4f6801cc233354659d4 to your computer and use it in GitHub Desktop.
git plugin for autocommit with claude
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Git AI Commit - Automated commit creation using Claude AI | |
| Author: Juan BC | |
| Email: [email protected] | |
| License: BSD-3-Clause | |
| Installation: | |
| 1. Copy this script to a directory in your PATH (e.g., ~/.local/bin/) | |
| $ cp git-iacommit ~/.local/bin/ | |
| 2. Make it executable: | |
| $ chmod +x ~/.local/bin/git-iacommit | |
| 3. Ensure ~/.local/bin is in your PATH (add to ~/.bashrc or ~/.zshrc if needed): | |
| export PATH="$HOME/.local/bin:$PATH" | |
| 4. Now you can use it as a git subcommand: | |
| $ git iacommit | |
| Requirements: | |
| - Python 3.6+ | |
| - Claude CLI installed and configured | |
| - Git repository | |
| """ | |
| import argparse | |
| import subprocess | |
| import sys | |
| PROMPT = """Create a git commit with the staged changes and any relevant unstaged files. Review the changes, write an appropriate commit message following the repository's style, and create the commit. | |
| DO NOT PUSH! | |
| """ | |
| def is_git_repo(): | |
| """Check if the current directory is a Git repository""" | |
| try: | |
| subprocess.run( | |
| ["git", "rev-parse", "--git-dir"], | |
| check=True, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| return True | |
| except subprocess.CalledProcessError: | |
| return False | |
| def has_changes(): | |
| """Check if there are any staged changes or changes in tracked files""" | |
| try: | |
| # Check for staged changes | |
| staged = subprocess.run( | |
| ["git", "diff", "--cached", "--quiet"], | |
| capture_output=True, | |
| ) | |
| has_staged = staged.returncode != 0 | |
| # Check for changes in tracked files (unstaged) | |
| tracked = subprocess.run( | |
| ["git", "diff", "--quiet"], | |
| capture_output=True, | |
| ) | |
| has_tracked = tracked.returncode != 0 | |
| return has_staged or has_tracked | |
| except subprocess.CalledProcessError: | |
| return False | |
| def get_changes_summary(): | |
| """Get a detailed summary of what changes exist""" | |
| staged_count = 0 | |
| unstaged_count = 0 | |
| try: | |
| # Count staged changes | |
| result = subprocess.run( | |
| ["git", "diff", "--cached", "--numstat"], | |
| check=True, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.stdout.strip(): | |
| staged_count = len(result.stdout.strip().split('\n')) | |
| # Count unstaged changes in tracked files | |
| result = subprocess.run( | |
| ["git", "diff", "--numstat"], | |
| check=True, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.stdout.strip(): | |
| unstaged_count = len(result.stdout.strip().split('\n')) | |
| except subprocess.CalledProcessError: | |
| pass | |
| return staged_count, unstaged_count | |
| def get_status_summary(): | |
| """Get a summary of git status""" | |
| try: | |
| result = subprocess.run( | |
| ["git", "status", "--short"], | |
| check=True, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| return result.stdout.strip() | |
| except subprocess.CalledProcessError: | |
| return None | |
| def get_last_commit_message(): | |
| """Get the last commit message""" | |
| try: | |
| result = subprocess.run( | |
| ["git", "log", "-1", "--pretty=%B"], | |
| check=True, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| return result.stdout.strip() | |
| except subprocess.CalledProcessError: | |
| return None | |
| def run_commit(dry_run=False, verbose=False): | |
| """Execute Claude to create the commit""" | |
| try: | |
| if verbose: | |
| status = get_status_summary() | |
| if status: | |
| print("π Current repository status:") | |
| print(status) | |
| print() | |
| print("π€ Running Claude to create the commit...") | |
| print() | |
| if dry_run: | |
| print("π Dry-run mode: Claude will review changes but won't create the commit") | |
| print() | |
| cmd = ["claude", PROMPT, "-p"] | |
| result = subprocess.run(cmd, check=True, text=True) | |
| if result.returncode == 0: | |
| print() | |
| print("β Commit created successfully") | |
| # Display the commit message | |
| commit_message = get_last_commit_message() | |
| if commit_message: | |
| print() | |
| print("π Commit message:") | |
| print("-" * 50) | |
| print(commit_message) | |
| print("-" * 50) | |
| return result.returncode | |
| except subprocess.CalledProcessError as e: | |
| print(f"β Error running Claude: {e}", file=sys.stderr) | |
| return e.returncode | |
| except FileNotFoundError: | |
| print( | |
| "β Error: Claude is not installed or not in PATH", | |
| file=sys.stderr | |
| ) | |
| print(" Install Claude CLI from: https://github.com/anthropics/anthropic-tools", | |
| file=sys.stderr) | |
| return 1 | |
| except KeyboardInterrupt: | |
| print("\nβ οΈ Operation cancelled by user", file=sys.stderr) | |
| return 130 | |
| def main(): | |
| """Main function""" | |
| parser = argparse.ArgumentParser( | |
| description="Create commits automatically using Claude AI", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| ) | |
| parser.add_argument( | |
| "-v", "--verbose", | |
| action="store_true", | |
| help="Show additional information about the changes" | |
| ) | |
| parser.add_argument( | |
| "-n", "--dry-run", | |
| action="store_true", | |
| help="Run without creating the commit (only shows what would be done)" | |
| ) | |
| args = parser.parse_args() | |
| # Check if we are in a Git repository | |
| if not is_git_repo(): | |
| print("β Error: Not in a Git repository", file=sys.stderr) | |
| sys.exit(1) | |
| # Check if there are any changes | |
| if not has_changes(): | |
| print("β οΈ No changes to commit", file=sys.stderr) | |
| print(" - No files in staging area", file=sys.stderr) | |
| print(" - No changes in tracked files", file=sys.stderr) | |
| print(" Use 'git add' or modify tracked files", file=sys.stderr) | |
| sys.exit(1) | |
| # Show summary of changes if verbose | |
| if args.verbose: | |
| staged, unstaged = get_changes_summary() | |
| print(f"π Changes detected:") | |
| print(f" - {staged} file(s) in staging") | |
| print(f" - {unstaged} modified file(s) without staging") | |
| print() | |
| # Execute the commit | |
| exit_code = run_commit(dry_run=args.dry_run, verbose=args.verbose) | |
| sys.exit(exit_code) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment