AI-Powered Git: Autocomplete Your Commits
I've always made an effort to write clear, concise git commit messages. They're not just breadcrumbs; they can be a great form of documentation that helps the team and "future me" understand the history of our codebase. When I'm diving into a project, whether it's one I haven't touched in months or something entirely new to me, good and descriptive commit messages can guide you through the code's history. They explain the 'why' behind changes, making it easier for everyone to grasp the project's development over time.
But I'll be honest – after a long coding session, staring at that blank commit message field can feel like a chore. It's tempting to just type "fixed stuff", or "wip" and call it a day. That's why I'm excited to share a little script I've been working on that's been a game-changer for my workflow. I've been using it for a couple of weeks now, and now I can't live without it. It's helping me write great PR and commit messages without the mental drain.
The Spark of Inspiration
Before I dive into the details, I want to give credit where it's due. This project was born from a brilliant idea I came across on Twitter. Tien Dinh (@tdinh_me) shared a concept that immediately resonated with me. It was one of those "why didn't I think of that?" moments.
My AI-Powered Git Commit Function
Building on Tien's concept, I've created a Zsh function that leverages OpenAI's GPT-40-mini model to generate commit messages based on my staged changes. But I didn't stop there – I extended it to help with pull request titles and descriptions too. Let me walk you through how it works and how you can start using it in your own workflow.
How It Works
- Analyzing Staged Changes: First, the function checks if I have any staged files. If I do, it generates a diff of these changes.
- AI-Generated Commit Message: Using the OpenAI API, it sends the diff along with a prompt I've crafted to generate a concise, meaningful commit message.
- User Interaction: I'm then presented with the generated commit message. I can use it as-is, edit it if I want to tweak something, or cancel if it's not quite right.
- Pull Request Creation: After committing, the function asks if I want to create a pull request. If I do, it can push my changes and use AI to generate a PR title and description based on my commit history.
- GitHub Integration: If I have the GitHub CLI installed, it automatically creates the pull request for me and opens it in my browser. Super convenient!
Setting It Up
To start using this function, follow these steps:
-
Create a new file in your dotfiles directory (or wherever you prefer) called (for example)
gcai.sh
. I think of it asGit Commit AI
script. -
Copy the function code into this file. You can find the full script in my dotfiles repository and in its entire at the end of til article.
-
Add the script file to your
.zshrc
file. I've added the following line to my.zshrc
:
export DOTFILES=$HOME/.dotfiles # or wherever you store your dotfiles
[ -f $DOTFILES/gcai.sh ] && source $DOTFILES/gcai.sh
- Install the OpenAI CLI and Github CLI (if not already installed).
python3.11 -m pip install --upgrade openai
brew install gh
-
Reload your terminal or run
source ~/.zshrc
orsource ~/.dotfiles/.zshrc
depending on where the file lives. -
Make sure you have the OpenAI API key set in your environment variables:
export OPENAI_API_KEY='your-api-key-here'
Now you're ready to use the gcai
function in your git workflow!
Using the Function
Here's a quick rundown of how to use the function:
- Stage your changes as usual with
git add
. - Instead of
git commit
, rungcai
. - Review the suggested commit message.
- Press Enter to use it, 'e' to edit it, or 'c' to cancel.
- If you choose to create a pull request, follow the prompts to push your changes and generate the PR description.
If you have already created the commits and want to generate a PR based on your commit history, you can run prai
from your branch. The name is as in pull request AI
.
The Benefits
Since I started using this function, I've noticed several improvements in my workflow:
- Time-saving: No more staring at the screen trying to come up with the perfect commit message.
- Consistency: The AI tends to generate messages in a consistent format, which helps maintain a clean git history.
- Detailed PRs: The pull request descriptions are often more comprehensive than what I would write manually, especially for larger changes.
- Fun: Using AI is fun! I've created more and smaller commits since implementing this function.
Customization and Improvement
This is just the beginning. There's lots of room for customization and improvement:
- You could modify the prompts to fit your team's commit message style guide.
- The function could be extended to support different AI models or APIs.
- You might add more interactive features, like suggesting code reviews based on the changes.
The script
# Function for generating and making a commit
ai_commit() {
# Check if there are staged files
STAGED_FILES=$(git diff --cached --name-only)
if [[ -z "$STAGED_FILES" ]]; then
echo "No files are staged for commit."
return 1
fi
# Get the diff of staged files
GIT_DIFF=$(git diff --cached)
# Build the prompt for the API
SYSTEM_PROMPT="You are a helpful assistant that writes effective git commit messages based on changes provided. Provide your response in a structured format with a subject line and an optional body. The subject line should be no longer than 50 characters if possible, and never exceed 72 characters. Use the imperative mood, start with a capital letter, and do not use punctuation at the end for the subject line. If you think a body is necessary to provide more context or explanation, include it after a blank line. Use the following structure:
SUBJECT: <subject line here>
BODY:
<body content here, if needed>"
USER_PROMPT="Generate a git commit message for the following changes:
$GIT_DIFF"
COMMIT_MESSAGE=$(openai api chat.completions.create \
-m gpt-4o-mini \
-g system "$SYSTEM_PROMPT" \
-g user "$USER_PROMPT" \
--temperature 0.7)
if [[ -z "$COMMIT_MESSAGE" ]]; then
echo "Failed to generate commit message or received empty message."
echo "Would you like to enter a commit message manually? (y/n)"
read -k1 MANUAL_INPUT
echo
if [[ "$MANUAL_INPUT" == "y" ]]; then
echo "Enter your commit subject line:"
read -r COMMIT_SUBJECT
echo "Enter your commit body (press Ctrl+D when finished, leave empty if not needed):"
COMMIT_BODY=$(cat)
else
echo "Commit canceled."
return 1
fi
else
# Extract the subject and body
COMMIT_SUBJECT=$(echo "$COMMIT_MESSAGE" | sed -n 's/^SUBJECT: //p')
COMMIT_BODY=$(echo "$COMMIT_MESSAGE" | sed -n '/^BODY:/,$p' | sed '1d')
fi
# Truncate the subject if it's too long
if [ ${#COMMIT_SUBJECT} -gt 72 ]; then
COMMIT_SUBJECT="${COMMIT_SUBJECT:0:69}..."
fi
# Display the commit message and staged files
echo
echo "Generated commit message:"
echo "-------------------------"
echo "Subject: $COMMIT_SUBJECT"
if [[ -n "$COMMIT_BODY" ]]; then
echo
echo "Body:"
echo "$COMMIT_BODY"
fi
echo "-------------------------"
echo
echo "Staged files:"
echo "-------------------------"
echo "$STAGED_FILES"
echo "-------------------------"
echo
# Prompt for user confirmation or editing
echo "Press 'e' to edit the commit message, 'c' to cancel, or any other key to confirm and commit:"
read -k1 USER_INPUT
echo
if [[ "$USER_INPUT" == "c" ]]; then
echo "Commit canceled."
return 1
elif [[ "$USER_INPUT" == "e" ]]; then
# Open the commit message in the default editor
TEMP_FILE=$(mktemp)
echo "$COMMIT_SUBJECT" > "$TEMP_FILE"
if [[ -n "$COMMIT_BODY" ]]; then
echo >> "$TEMP_FILE"
echo "$COMMIT_BODY" >> "$TEMP_FILE"
fi
"${EDITOR:-nano}" "$TEMP_FILE"
COMMIT_SUBJECT=$(head -n 1 "$TEMP_FILE")
COMMIT_BODY=$(tail -n +3 "$TEMP_FILE")
rm "$TEMP_FILE"
fi
# Commit the changes
if [[ -n "$COMMIT_BODY" ]]; then
git commit -m "$COMMIT_SUBJECT" -m "$COMMIT_BODY"
else
git commit -m "$COMMIT_SUBJECT"
fi
}
# Function for creating a pull request
ai_pr() {
# Get the current branch name
CURRENT_BRANCH=$(git branch --show-current)
# List of protected branches
PROTECTED_BRANCHES=("main" "master" "develop" "release")
# Check if the current branch is protected
if [[ " ${PROTECTED_BRANCHES[@]} " =~ " ${CURRENT_BRANCH} " ]]; then
echo "You are on a protected branch: $CURRENT_BRANCH"
echo "Let's create a new branch for your changes."
# Get the commit history for the current branch
COMMIT_HISTORY=$(git log -n 5 --pretty=format:"%s%n%b")
# Generate branch name using AI
SYSTEM_PROMPT="You are a helpful assistant that generates concise and descriptive git branch names based on commit messages. The branch name should be in kebab-case, start with a verb (e.g., add, update, fix, refactor), and be no longer than 50 characters. Do not include 'feature/' or 'fix/' prefixes. Provide only the branch name without any additional text or explanation."
USER_PROMPT="Generate a git branch name based on the following commit messages:\n\n$COMMIT_HISTORY"
SUGGESTED_BRANCH=$(openai api chat.completions.create \
-m gpt-4o-mini \
-g system "$SYSTEM_PROMPT" \
-g user "$USER_PROMPT" \
--temperature 0.7)
echo "Suggested branch name: $SUGGESTED_BRANCH"
echo "Do you want to use this branch name? (y/n)"
read -k1 USE_SUGGESTED_BRANCH
echo
if [[ "$USE_SUGGESTED_BRANCH" != "y" ]]; then
echo "Enter a new branch name (without 'feature/' or 'fix/' prefix):"
read -r NEW_BRANCH
else
NEW_BRANCH=$SUGGESTED_BRANCH
fi
# Determine the prefix
echo "Is this a new feature (f) or a bugfix (b)?"
read -k1 BRANCH_TYPE
echo
case $BRANCH_TYPE in
f|F)
GCAIPREFIX="feature/"
;;
b|B)
GCAIPREFIX="fix/"
;;
*)
echo "Invalid option. Using no prefix."
GCAIPREFIX=""
;;
esac
# Create and switch to the new branch with the appropriate prefix
FULL_BRANCH_NAME="${GCAIPREFIX}${NEW_BRANCH}"
git checkout -b "$FULL_BRANCH_NAME"
echo "Created and switched to new branch: $FULL_BRANCH_NAME"
# Push the new branch to origin
git push -u origin "$FULL_BRANCH_NAME"
CURRENT_BRANCH="$FULL_BRANCH_NAME"
else
# Check if the branch has an upstream branch
UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
if [[ -z "$UPSTREAM" ]]; then
echo "No upstream branch is set for $CURRENT_BRANCH."
echo "Choose an option:"
echo "1) Push to origin/$CURRENT_BRANCH"
echo "2) Enter a custom branch name"
read -k1 PUSH_OPTION
echo
case $PUSH_OPTION in
1)
git push -u origin "$CURRENT_BRANCH"
;;
2)
echo "Enter the name of the remote branch (without 'feature/' or 'fix/' prefix):"
read -r REMOTE_BRANCH
echo "Is this a new feature (f) or a bugfix (b)?"
read -k1 BRANCH_TYPE
echo
case $BRANCH_TYPE in
f|F)
GCAIPREFIX="feature/"
;;
b|B)
GCAIPREFIX="fix/"
;;
*)
echo "Invalid option. Using no prefix."
GCAIPREFIX=""
;;
esac
FULL_REMOTE_BRANCH="${GCAIPREFIX}${REMOTE_BRANCH}"
git push -u origin "$CURRENT_BRANCH:$FULL_REMOTE_BRANCH"
CURRENT_BRANCH="$FULL_REMOTE_BRANCH"
;;
*)
echo "Invalid option. Aborting."
return 1
;;
esac
else
# Push the current branch to its upstream
git push
fi
fi
# Get the repository URL
REPO_URL=$(git config --get remote.origin.url)
REPO_URL=${REPO_URL#*:}
REPO_URL=${REPO_URL%.git}
# Get the commit history for the current branch
COMMIT_HISTORY=$(git log origin/main.."$CURRENT_BRANCH" --pretty=format:"%h %s")
# Generate PR title and body using AI
SYSTEM_PROMPT="You are a helpful assistant that generates concise and informative pull request titles and descriptions based on git commit history. Provide your response in a structured format with a title (max 72 characters) and a body (using markdown formatting). Use the following structure:
TITLE: <title here>
BODY:
<body content here>"
USER_PROMPT="Generate a pull request title and body for the following commit history:\n\n$COMMIT_HISTORY"
# Call the OpenAI API using the CLI
AI_CONTENT=$(openai api chat.completions.create \
-m gpt-4o-mini \
-g system "$SYSTEM_PROMPT" \
-g user "$USER_PROMPT" \
--temperature 0.7 \
--max-tokens 500)
# Extract the title and body
PR_TITLE=$(echo "$AI_CONTENT" | sed -n 's/^TITLE: //p')
PR_BODY=$(echo "$AI_CONTENT" | sed -n '/^BODY:/,$p' | sed '1d')
# Truncate the title if it's too long
if [ ${#PR_TITLE} -gt 72 ]; then
PR_TITLE="${PR_TITLE:0:69}..."
fi
# Display the generated title and body
echo
echo "Generated title:"
echo "-------------------------"
echo "$PR_TITLE"
echo "-------------------------"
echo
echo "Generated body:"
echo "-------------------------"
echo "$PR_BODY"
echo "-------------------------"
echo
# Ask user if they want to use the AI-generated content or enter their own
echo "Do you want to use this AI-generated title and body? (y/n)"
read -k1 USE_AI_CONTENT
echo
if [[ "$USE_AI_CONTENT" != "y" ]]; then
echo "Enter a title for the pull request:"
read -r PR_TITLE
echo "Enter a body for the pull request (press Ctrl+D when finished):"
PR_BODY=$(cat)
fi
# Create the pull request using GitHub CLI
if command -v gh &> /dev/null; then
gh pr create --title "$PR_TITLE" --body "$PR_BODY"
# Open the pull request in the browser
gh pr view --web
else
echo "GitHub CLI (gh) is not installed. Please install it to create pull requests automatically."
echo "You can create a pull request manually at: https://github.com/$REPO_URL/pull/new/$CURRENT_BRANCH"
fi
}
# Main function that combines commit and PR creation
gcai() {
ai_commit
if [ $? -eq 0 ]; then
echo "Do you want to create a pull request? (y/n)"
read -k1 CREATE_PR
echo
if [[ "$CREATE_PR" == "y" ]]; then
ai_pr
fi
fi
}
# Main function that combines commit and PR creation
prai() {
ai_pr
}
Wrapping Up
I've found this AI-powered git commit function to be a real game-changer in my daily workflow. It's a small tool, but it addresses a common pain point in a clever way. I'm excited to continue refining it and seeing how it evolves. For the latest versions, then check out my dotfiles repository.
I'd love to hear your thoughts on this approach. Have you tried using AI in your development workflow? Do you see potential improvements or concerns? Let me know on x.com.
Remember, tools like this are meant to assist, not replace, your judgment. Always review the suggested messages and make sure they accurately represent your changes. The suggestions are not always perfect, but they can be a great starting point.
Want to learn more? Check out these resources:
Happy committing!