Skip to content

Instantly share code, notes, and snippets.

@jamesparsloe
Created January 10, 2025 10:30
Show Gist options
  • Save jamesparsloe/68c692d293c0950503e0e0158414f98f to your computer and use it in GitHub Desktop.
Save jamesparsloe/68c692d293c0950503e0e0158414f98f to your computer and use it in GitHub Desktop.
Silly little script I use to sync my Obsidian notes to GitHub with a descriptive commit message
"""
Commit with a message generated by Claude and push.
"""
import argparse
import os
import subprocess
import anthropic
def get_git_diff() -> str:
"""Get the git diff of staged changes."""
try:
# Stage all changes first
subprocess.check_output(["git", "add", "."])
# Get diff of staged changes
diff = subprocess.check_output(["git", "diff", "--cached"]).decode("utf-8")
return diff
except subprocess.CalledProcessError as e:
print(f"Error getting git diff: {e}")
return ""
def get_changed_files() -> list[str]:
"""Get list of changed files."""
try:
# Stage all changes first
subprocess.check_output(["git", "add", "."])
# Get staged files
staged = (
subprocess.check_output(["git", "diff", "--cached", "--name-only"])
.decode("utf-8")
.splitlines()
)
return list(set(staged))
except subprocess.CalledProcessError as e:
print(f"Error getting changed files: {e}")
return []
def generate_commit_message(diff: str, files: list[str]) -> str:
client = anthropic.Client()
# Truncate diff if too long
if len(diff) > 4000:
diff = diff[:4000] + "\n...[diff truncated]"
prompt = f"""Based on the following git diff and changed files, write a clear and descriptive commit message.
The subject line should be in present tense, concise (max 50 chars), and start with a verb. Lines in the body should be wrapped at 72 characters. For changes to markdown files, give the file name and then a short description of the changes. For code, just describe the behaviour change.
Changed files:
{', '.join(files)}
Diff:
{diff}
Generate only the commit message, nothing else."""
try:
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
temperature=0,
messages=[{"role": "user", "content": prompt}],
)
return message.content[0].text
except Exception as e:
print(f"Error generating commit message: {e}")
return "sync" # Fallback to original message
def git_commit_and_push(message: str) -> tuple[bool, str]:
"""Execute git commit and push commands."""
try:
# Commit with generated message
subprocess.check_output(["git", "commit", "-m", message])
# Push changes
subprocess.check_output(["git", "push"])
return True, "Successfully committed and pushed changes"
except subprocess.CalledProcessError as e:
return False, f"Error in git operations: {e}"
def main():
# Set up argument parser
parser = argparse.ArgumentParser(
description="Commit and push changes with AI-generated commit message"
)
parser.add_argument(
"-y", "--yes", action="store_true", help="Skip confirmation prompt"
)
args = parser.parse_args()
# Ensure ANTHROPIC_API_KEY is set
if not os.getenv("ANTHROPIC_API_KEY"):
print("Error: ANTHROPIC_API_KEY environment variable not set")
return
# Stage all changes first
try:
subprocess.check_output(["git", "add", "."])
except subprocess.CalledProcessError as e:
print(f"Error staging changes: {e}")
return
# Get git changes
diff = get_git_diff()
files = get_changed_files()
if not diff and not files:
print("No changes detected")
return
# Generate commit message
commit_message = generate_commit_message(diff, files)
# Show the message to user and ask for confirmation if -y not specified
print(f"\nProposed commit message:\n{commit_message}")
if not args.yes:
confirm = input("\nProceed with this commit message? (Y/n): ").lower()
should_proceed = confirm in ["y", ""]
else:
should_proceed = True
if should_proceed:
success, message = git_commit_and_push(commit_message)
print(message)
else:
print("Commit aborted")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment