Writing good commit messages is one of those things every developer agrees matters β and every developer occasionally skips. What if your git workflow could handle it for you? In this tutorial, youβll build a prepare-commit-msg git hook that reads your staged diff, sends it to a local Ollama model, and pre-fills your editor with a conventional commit message. No API keys, no cloud calls, everything runs on your machine.
If youβre new to Ollama, start with our complete Ollama guide to get set up.
How It Works
Git supports hooks β scripts that run at specific points in the commit workflow. The prepare-commit-msg hook fires after git creates the default commit message file but before your editor opens. Thatβs the perfect place to inject an AI-generated message.
The flow:
- You run
git commit - Our hook grabs the staged diff via
git diff --cached - The diff is sent to Ollama with a prompt requesting conventional commit format
- Ollamaβs response is written into the commit message file
- Your editor opens with the message pre-filled β edit or accept as-is
The Conventional Commit Format
Our generator follows the Conventional Commits spec:
type(scope): description
optional body
Common types: feat, fix, docs, style, refactor, test, chore, build, ci, perf. The scope is optional and describes the area of the codebase affected.
The Python Script
Create a file called ai_commit_msg.py β this is the core logic that the git hook will call:
#!/usr/bin/env python3
"""AI commit message generator using Ollama."""
import subprocess
import sys
import json
import urllib.request
OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL = "qwen2.5-coder:7b"
PROMPT_TEMPLATE = """Write a git commit message for the following diff using the Conventional Commits format.
Rules:
- First line: type(scope): description (max 72 chars)
- Types: feat, fix, docs, style, refactor, test, chore, build, ci, perf
- Scope is optional but preferred
- Add a blank line then a brief body if the change is non-trivial
- Do NOT wrap the message in a code block
- Output ONLY the commit message, nothing else
Diff:
{diff}"""
def get_staged_diff():
result = subprocess.run(
["git", "diff", "--cached", "--diff-algorithm=minimal"],
capture_output=True, text=True
)
return result.stdout.strip()
def generate_message(diff):
# Truncate very large diffs to stay within context limits
if len(diff) > 8000:
diff = diff[:8000] + "\n... (diff truncated)"
payload = json.dumps({
"model": MODEL,
"prompt": PROMPT_TEMPLATE.format(diff=diff),
"stream": False,
"options": {"temperature": 0.3, "num_predict": 200}
}).encode()
req = urllib.request.Request(
OLLAMA_URL,
data=payload,
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())["response"].strip()
def main():
if len(sys.argv) < 2:
print("Usage: ai_commit_msg.py <commit-msg-file>", file=sys.stderr)
sys.exit(1)
commit_msg_file = sys.argv[1]
# Skip if this is a merge, squash, or amend
if len(sys.argv) > 2 and sys.argv[2] in ("merge", "squash", "commit"):
sys.exit(0)
diff = get_staged_diff()
if not diff:
sys.exit(0)
try:
message = generate_message(diff)
except Exception as e:
print(f"AI commit msg failed ({e}), falling back to default", file=sys.stderr)
sys.exit(0)
# Read existing content (contains commented-out status info)
with open(commit_msg_file, "r") as f:
existing = f.read()
with open(commit_msg_file, "w") as f:
f.write(message + "\n" + existing)
if __name__ == "__main__":
main()
No external dependencies β just Pythonβs standard library and a running Ollama instance.
Setting Up the Git Hook
You have two options: per-repo or global.
Per-Repo Setup
Copy the script into your repo and create the hook:
# Copy the script somewhere accessible
cp ai_commit_msg.py .git/hooks/
# Create the hook
cat > .git/hooks/prepare-commit-msg << 'EOF'
#!/bin/sh
python3 "$(dirname "$0")/ai_commit_msg.py" "$1" "$2"
EOF
chmod +x .git/hooks/prepare-commit-msg
Global Setup (All Repos)
This is the better option if you want AI messages everywhere:
# Create a global hooks directory
mkdir -p ~/.git-hooks
cp ai_commit_msg.py ~/.git-hooks/
# Create the global hook
cat > ~/.git-hooks/prepare-commit-msg << 'EOF'
#!/bin/sh
python3 ~/.git-hooks/ai_commit_msg.py "$1" "$2"
EOF
chmod +x ~/.git-hooks/prepare-commit-msg
# Tell git to use it
git config --global core.hooksPath ~/.git-hooks
Now every git commit across all your repos will trigger the AI generator.
Try It Out
Stage some changes and commit:
echo "# My Project" > README.md
git add README.md
git commit
Your editor opens with something like:
docs: add project README
Initialize repository with a basic README file.
# Please enter the commit message for your changes...
Edit if you want, save, done. If Ollama isnβt running or anything fails, the hook silently falls back to the normal empty message β it never blocks your workflow.
Choosing a Model
The model you pick matters. You want something fast enough to not slow down commits but smart enough to parse diffs. Check our best Ollama models for coding for a full breakdown. Quick recommendations:
| Model | Size | Speed | Quality |
|---|---|---|---|
qwen2.5-coder:3b | 2 GB | ~1s | Good for simple changes |
qwen2.5-coder:7b | 4.5 GB | ~2-3s | Best balance (default) |
codellama:13b | 7 GB | ~4-5s | Handles complex refactors well |
Switch models by editing the MODEL variable in the script, or set it via environment variable by replacing the constant with:
MODEL = os.environ.get("AI_COMMIT_MODEL", "qwen2.5-coder:7b")
Pull your chosen model first:
ollama pull qwen2.5-coder:7b
Tips and Gotchas
- Large diffs: The script truncates diffs over 8,000 characters. For massive changes, consider breaking commits into smaller pieces β thatβs better practice anyway. Our git stash cheat sheet can help manage work-in-progress when splitting changes.
- Skipping the hook: Use
git commit --no-verifyorgit commit -m "your message"(the hook only fires when the editor would open with no pre-set message via-m). - Timeout: The script uses a 30-second timeout. If your model is slow on first load, increase it.
- Temperature: Set to 0.3 for consistent, predictable messages. Bump to 0.5-0.7 if you want more varied output.
Pair It With AI Code Review
This hook handles the writing side. For the review side, check out our guide on local AI code review with Ollama β you can build a pre-push hook that reviews your changes before they hit the remote.
What You Built
A zero-dependency Python script that plugs into gitβs hook system and generates conventional commit messages using a local LLM. No API keys, no subscriptions, no data leaving your machine. The hook is non-blocking β if anything fails, you just get the normal commit flow.
The entire setup takes under five minutes and saves you from writing βfix stuffβ at 11 PM ever again.