Last active
June 17, 2025 06:00
-
-
Save antonl-dev/9705e8788dda98e6dd891bd9b521f6d3 to your computer and use it in GitHub Desktop.
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
| #!/bin/python3 | |
| import os | |
| import subprocess | |
| import requests | |
| import json | |
| import time | |
| import sys | |
| from itertools import cycle | |
| # Replace with your Gemini API key | |
| GEMINI_API_KEY = '' | |
| GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent' | |
| # Number of previous commits to fetch for context | |
| NUM_PREVIOUS_COMMITS = 100 | |
| # Number of commit message suggestions to generate | |
| NUM_SUGGESTIONS = 5 | |
| def spinner_animation(message): | |
| """Show a spinner animation while a task is running.""" | |
| spinner = cycle(['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷']) | |
| start_time = time.time() | |
| while True: | |
| elapsed_time = time.time() - start_time | |
| # Return control after 0.1 seconds | |
| if elapsed_time > 0.1: | |
| yield next(spinner) | |
| def get_previous_commit_messages(num_commits=5): | |
| """Get the specified number of previous Git commit messages.""" | |
| result = subprocess.run(['git', 'log', f'-{num_commits}', '--pretty=format:%h - %s (%cr)'], | |
| capture_output=True, text=True) | |
| commits = result.stdout.strip().split('\n') | |
| return commits | |
| def get_current_diff(): | |
| """Get the current Git diff.""" | |
| result = subprocess.run(['git', 'diff'], capture_output=True, text=True) | |
| return result.stdout | |
| def get_staged_diff(): | |
| """Get the staged Git diff.""" | |
| result = subprocess.run(['git', 'diff', '--staged'], capture_output=True, text=True) | |
| return result.stdout | |
| def get_branch_name(): | |
| """Get the current branch name.""" | |
| result = subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], capture_output=True, text=True) | |
| return result.stdout.strip() | |
| def generate_commit_message_with_gemini(diff, previous_commits, branch_name): | |
| """Generate a commit message using the Gemini API with context from previous commits.""" | |
| headers = { | |
| 'Content-Type': 'application/json', | |
| } | |
| # Format previous commits for context | |
| commit_history = "\n".join(previous_commits) | |
| prompt = f"""Generate a concise and descriptive Git commit message based on the following diff. | |
| Current branch: {branch_name} | |
| Previous commit history (for context): | |
| {commit_history} | |
| Current changes: | |
| {diff} | |
| all Identifiers are real case (`Identifier`) | |
| all other text in the message must be in lower case. | |
| Please format the commit message as: | |
| <type>: <scope>: <subject> | |
| - <indented bullet points with details if needed> | |
| - ... | |
| <non-indented high-level summary> | |
| type = [ feat, fix, docs, style, refac, test, chore ] | |
| Make sure the message follows conventional commit format and is meaningful. | |
| """ | |
| data = { | |
| "contents": [{ | |
| "parts": [{"text": prompt}] | |
| }], | |
| "generationConfig": { | |
| "candidateCount": NUM_SUGGESTIONS | |
| } | |
| } | |
| response = requests.post(f"{GEMINI_API_URL}?key={GEMINI_API_KEY}", headers=headers, data=json.dumps(data)) | |
| if response.status_code == 200: | |
| candidates = response.json().get('candidates', []) | |
| return [candidate['content']['parts'][0]['text'] for candidate in candidates] | |
| else: | |
| raise Exception(f"Failed to generate commit message: {response.text}") | |
| def run_task_with_spinner(emoji, task_name, task_func, *args): | |
| """Run a task with a spinner animation, then show a checkmark when complete.""" | |
| # Show spinner while task is running | |
| for spin in spinner_animation(task_name): | |
| sys.stdout.write(f"\r[{spin}] {emoji} {task_name}") | |
| sys.stdout.flush() | |
| try: | |
| result = task_func(*args) | |
| # Show checkmark when task completes successfully | |
| sys.stdout.write(f"\r[✓] {emoji} {task_name}\n") | |
| sys.stdout.flush() | |
| return result | |
| except Exception as e: | |
| # Show X mark if task fails | |
| sys.stdout.write(f"\r[✗] {emoji} {task_name} - Error: {str(e)}\n") | |
| sys.stdout.flush() | |
| raise e | |
| def main(): | |
| try: | |
| # Get the branch name | |
| branch_name = run_task_with_spinner("🌿", "Getting current branch", get_branch_name) | |
| # Get previous commit messages for context (but don't display them) | |
| previous_commits = run_task_with_spinner("📚", f"Fetching {NUM_PREVIOUS_COMMITS} previous commits for context", | |
| get_previous_commit_messages, NUM_PREVIOUS_COMMITS) | |
| # Get the current diff (but don't print it to stdout) | |
| unstaged_diff = run_task_with_spinner("🔍", "Analyzing unstaged changes", get_current_diff) | |
| # Get the staged diff | |
| staged_diff = run_task_with_spinner("📦", "Analyzing staged changes", get_staged_diff) | |
| # Determine which diff to use | |
| if staged_diff.strip(): | |
| diff_to_use = staged_diff | |
| diff_type = "staged" | |
| else: | |
| diff_to_use = unstaged_diff | |
| diff_type = "unstaged" | |
| # Generate a commit message using Gemini | |
| if GEMINI_API_KEY: | |
| if diff_to_use.strip(): | |
| print(f"\n🧠 Thinking... Asking Gemini for {NUM_SUGGESTIONS} suggestions based on {diff_type} changes...") | |
| commit_suggestions = run_task_with_spinner("🤖", f"Generating {NUM_SUGGESTIONS} suggestions with Gemini", | |
| generate_commit_message_with_gemini, | |
| diff_to_use, previous_commits, branch_name) | |
| if commit_suggestions: | |
| print(f"\n💡 Here are {len(commit_suggestions)} AI-crafted commit messages! Pick your favorite. 👇\n") | |
| number_emojis = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣"] | |
| for i, message in enumerate(commit_suggestions): | |
| emoji = number_emojis[i] if i < len(number_emojis) else f"#{i+1}" | |
| header = f"--- {emoji} Suggestion ---" | |
| # Pretty border | |
| print("✨" + "=" * (len(header) + 2) + "✨") | |
| print(f" {header} ") | |
| print("✨" + "=" * (len(header) + 2) + "✨") | |
| print(f"\n{message.strip()}\n") | |
| print("\n👉 Simply copy the message you like best and use it for your commit!") | |
| else: | |
| print("\n⚠️ Gemini did not return any suggestions.") | |
| else: | |
| print("\n⚠️ No changes detected. Make changes before generating a commit message.") | |
| else: | |
| print("\n⚠️ No Gemini API key provided. Please add your API key to use this feature.") | |
| except Exception as e: | |
| print(f"\n❌ An error occurred: {str(e)}") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment