Skip to content

Instantly share code, notes, and snippets.

@antonl-dev
Last active June 17, 2025 06:00
Show Gist options
  • Select an option

  • Save antonl-dev/9705e8788dda98e6dd891bd9b521f6d3 to your computer and use it in GitHub Desktop.

Select an option

Save antonl-dev/9705e8788dda98e6dd891bd9b521f6d3 to your computer and use it in GitHub Desktop.
#!/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