Auto-Generating Git Commit Messages with a Local LLM (No Cloud, No API Keys)
Writing good Git commit messages matters—but doing it repeatedly is tedious. With modern local LLMs now fast and accessible, we can automate this task without sending code to the cloud, without API keys, and without changing standard Git workflows.
This article shows how to automatically generate high-quality Git commit messages using a local LLM (via Ollama), powered by Git’s native hook system.
Why this approach?
The goal is to improve developer experience while preserving Git’s native behavior:
You still run:
git add .
git commit
…but the commit message is generated automatically.
Design principles
Before writing any code, the logic must be clear:
Why prepare-commit-msg?
Git provides multiple hooks, but only one is designed to create or modify commit messages before the editor opens:
Using the correct hook ensures:
The implementation
This hook:
.git/hooks/prepare-commit-msg
#!/usr/bin/env bash
COMMIT_MSG_FILE="$1"
COMMIT_SOURCE="$2"
# Skip merges and amendments
if [[ "$COMMIT_SOURCE" == "merge" || "$COMMIT_SOURCE" == "commit" ]]; then
exit 0
fi
# Get staged diff (cap size)
DIFF=$(git diff --cached | head -n 400)
if [[ -z "$DIFF" ]]; then
exit 0
fi
PROMPT=$(cat <<'EOF'
Output ONLY a Git commit message.
Rules:
- No explanations
- No quotes
- No markdown
- Conventional Commits format
- Max 72 chars title
- Optional body separated by a blank line
Diff:
EOF
)
PROMPT="$PROMPT
$DIFF"
RESPONSE=$(curl -s --max-time 10 http://localhost:11434/api/generate \
-H "Content-Type: application/json" \
-d "{
\"model\": \"llama3\",
\"prompt\": $(jq -Rs . <<< \"$PROMPT\"),
\"stream\": false
}" | jq -r '.response')
# Final safety check
if [[ -n "$RESPONSE" ]]; then
echo "$RESPONSE" > "$COMMIT_MSG_FILE"
fi
Make the hook executable:
chmod +x .git/hooks/prepare-commit-msg
Example result
Instead of opening an editor, Git commits directly with something like:
docs: add README license information
Or, when needed:
feat(api): add request validation
Reject invalid payloads before persistence.
No extra text. No explanations. Just the commit message.
Enable for all repositories (optional)
By default, Git hooks apply to a single repository. If you want this automation enabled globally, Git provides a native configuration option.
Enable once, use everywhere
mkdir -p ~/.git-hooks
mv .git/hooks/prepare-commit-msg ~/.git-hooks/
chmod +x ~/.git-hooks/prepare-commit-msg
git config --global core.hooksPath ~/.git-hooks
From this point on, every repository on your machine will automatically use the hook.
Disable for a single repository
git config core.hooksPath .git/hooks
To re-enable the global hook later:
git config --unset core.hooksPath
This behavior is built into Git and requires no aliases, wrappers, or workflow changes.
Recommended models
For this task, smaller models perform better:
They’re faster and less likely to add unnecessary prose.
Final thoughts
This setup improves commit quality while staying:
It’s a small automation—but one that compounds over time, especially in teams that care about clean history, changelogs, and release automation.
Local AI doesn’t have to be flashy to be useful. Sometimes it just needs to stay out of the way and do one thing well.Making the Hook Global (Apply to All Repositories)
By default, Git hooks live inside a single repository under .git/hooks. If you want this automation to apply across all repositories on your machine, Git provides a built-in, supported mechanism: a global hooks path.
This keeps everything Git-native—no aliases, no wrappers, no workflow changes.
Why use a global hook?
Using a global hook allows you to:
This is especially useful for engineers working across many projects.
Step 1: Create a global hooks directory
mkdir -p ~/.git-hooks
This directory will hold your global Git hooks.
Step 2: Move the hook to the global directory
Create the hook file:
nano ~/.git-hooks/prepare-commit-msg
Paste the same script shown earlier in the article, unchanged.
Make it executable:
chmod +x ~/.git-hooks/prepare-commit-msg
Git only executes hooks that are marked as executable.
Step 3: Configure Git to use the global hooks path
Set the configuration globally:
git config --global core.hooksPath ~/.git-hooks
This updates your ~/.gitconfig to include:
[core]
hooksPath = ~/.git-hooks
From this point on, every Git repository on this machine will automatically use the hook.
Verifying the configuration
Check the active hooks path:
git config --global --get core.hooksPath
Expected output:
~/.git-hooks
Then run:
git commit
The commit message should be generated automatically, exactly as before.
Per-repository opt-out (important)
If a specific repository should not use the global hook, you can override it locally:
git config core.hooksPath .git/hooks
To re-enable the global hook later:
git config --unset core.hooksPath
Repository-level configuration always takes precedence over global settings—this is intentional behavior in Git.
Why this matters
This approach ensures the automation is:
No tooling surprises. No vendor lock-in. Just better commit messages—everywhere.
I’ve improved the hook script and added a section explaining how to enable the Git hook globally using Git’s native core.hooksPath, so it works across all repositories while still allowing per-repo opt-out.
Ibrahim Timor, automating commit messages is a brilliant step towards cleaner workflows.