Auto-Generating Git Commit Messages with a Local LLM (No Cloud, No API Keys)

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:

  • ✅ Fully local (privacy-friendly)
  • ✅ No SaaS, no API keys
  • ✅ Works offline
  • ✅ Respects human-written messages
  • ✅ Uses standard Git hooks (no wrappers or aliases)

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:

  1. Do not override human intent If a developer provides a commit message, keep it.
  2. Do not interfere with Git internals Skip merges and amended commits.
  3. Never block a commit If the LLM fails, Git must still work.
  4. Generate only the commit message No explanations, no markdown, no noise.
  5. Bound input size Prevent slow inference or token overflow.


Why prepare-commit-msg?

Git provides multiple hooks, but only one is designed to create or modify commit messages before the editor opens:

  • prepare-commit-msg ✅
  • commit-msg ❌ (validation only)

Using the correct hook ensures:

  • No editor opens
  • Git behaves predictably
  • CI pipelines and tooling remain unaffected


The implementation

This hook:

  • Reads the staged diff
  • Sends it to a local Ollama model
  • Writes only the generated commit message

.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:

  • llama3:8b
  • phi-3:mini
  • deepseek-coder:6.7b

They’re faster and less likely to add unnecessary prose.


Final thoughts

This setup improves commit quality while staying:

  • Git-native
  • Developer-friendly
  • Privacy-preserving

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:

  • Install the automation once
  • Apply it to all current and future repositories
  • Keep repositories clean (no committed hook files)
  • Retain an easy per-repository opt-out

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:

  • Invisible to daily workflows
  • Consistent across repositories
  • Easy to disable when needed
  • Fully aligned with Git’s intended configuration model

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.

To view or add a comment, sign in

More articles by Ibrahim Timor

Explore content categories