A command-line tool that reads your staged git changes and generates a Conventional Commits-style commit message using a local LLM via Ollama — no cloud API, no API keys, nothing leaves your machine.
You review the generated message and choose to accept it, edit it, or cancel — CommitMate never commits anything without your explicit approval.
Writing good commit messages consistently is tedious, and generic "wip" or "fix stuff" commits make a project's history much less useful. CommitMate looks at what you've actually staged and proposes a properly formatted message you can accept as-is or tweak, without sending your code to any external service.
- Python 3.9 or later
- Ollama installed and running locally
- A model pulled in Ollama. The tool defaults to
llama3:ollama pull llama3
qwen2.5-coder:7bis also recommended and performed better in testing on multi-file diffs (see Known Limitations below). If you want to use it, pull it too and pass--model qwen2.5-coder:7b:ollama pull qwen2.5-coder:7b
- Ollama's server running (usually automatic after install, or start manually):
ollama serve
Install directly from PyPI:
pip install commitmateThis registers a commitmate command on your system, callable from any git repository.
If you want to modify the source, run tests, or contribute:
git clone https://github.com/theishanpathak/commitmate.git
cd commitmate
pip install -e .The -e (editable) install links the commitmate command back to your local source files, so any changes you make take effect immediately without reinstalling.
- Stage some changes:
Tip: for best results, stage related changes together rather than many unrelated files at once. Smaller local models can struggle to summarize a diff spanning several unrelated files into a single, well-formatted commit message.
git add <files>
- Run:
commitmate
- CommitMate will show a generated commit message and prompt you to choose:
- accept — commits immediately with the generated message
- edit — opens the message in your
$EDITOR(falls back tonanoon Mac/Linux,notepadon Windows) so you can revise it before committing - cancel — exits without committing; your staged changes are left untouched
git_utils.py— the only module that shells out to git (viasubprocess). Retrieves the staged diff and file list, and performs the actual commit.ollama_client.py— a thin HTTP wrapper around Ollama's local/api/generateendpoint. Knows nothing about commit messages or git; it just sends a prompt and returns whatever text comes back.message_gen.py— pure, I/O-free logic. Builds the prompt (asking the model to return structured JSON rather than free text), and parses/validates/assembles that JSON into the finaltype(scope): descriptionmessage.exceptions.py— a small hierarchy of custom exceptions (e.g.NotAGitRepositoryError,NoStagedChangesError,OllamaConnectionError,InvalidModelResponseError) so failures are caught and reported cleanly instead of surfacing as raw stack traces.cli.py— the composition root. Orchestrates the pipeline above, retries generation a few times if the model's response fails validation, and drives the accept/edit/cancel loop usingrichfor terminal output.
The model is prompted to return a JSON object (reasoning, type, scope, description, body) rather than a fully formatted commit message string. The actual type(scope): description text is then assembled in plain Python. This was a deliberate pivot after an earlier version — which asked the model to return the final formatted string directly — proved unreliable: local models frequently added preambles, markdown fences, or trailing explanations despite explicit instructions not to. Constraining the model to fill in a small number of discrete fields, and letting Python own the final formatting, removed that failure mode almost entirely.
A reasoning field is requested first in the JSON schema, before the other fields. Since JSON keys are generated in order, this gives the model a place to briefly "think" about the change before committing to a type/description, without breaking strict JSON output. This measurably improved how often the description stayed within a reasonable length.
If the model's response fails validation (wrong type, description too short, malformed JSON, etc.) after a few attempts, CommitMate shows a friendly message suggesting the diff may be too large or span too many unrelated files, rather than surfacing the raw internal validation error.
- Multi-file diffs can produce longer subject lines. Smaller/general-purpose local models (tested:
llama3, the default) sometimes struggle to keep the commit description under the ~72-character convention when a diff spans multiple unrelated files. In testing,qwen2.5-coder:7bperformed noticeably better at staying within length limits on the same diffs. Consider passing--model qwen2.5-coder:7bif you run into this. - No diff truncation. Very large staged diffs are sent to the model in full, with no length guard. This could hit context limits or slow down generation on large changes.
- Self-referential prompt confusion. If a staged diff itself contains text that looks like prompt instructions (for example, testing CommitMate on a diff to its own
message_gen.py, which contains the prompt-building code), the model can occasionally get confused about what's an instruction versus what's data to summarize. - GUI editors need a
--wait-style flag. Theeditflow waits for your editor process to exit before continuing. Terminal editors (nano,vim) work out of the box. GUI editors need an explicit wait flag, e.g.:Without it, editors like VS Code return immediately and CommitMate will read the file back before you've finished editing.export EDITOR="code --wait"
Run the test suite with:
pip install -e ".[dev]"
pytestTests currently cover the pure logic in message_gen.py (prompt cleanup, response validation, and message assembly). git_utils.py and ollama_client.py involve subprocess/network calls and aren't covered yet — mocking those is a natural next step.
$EDITOR— controls which editor opens during theeditflow. Defaults tonano(Mac/Linux) ornotepad(Windows) if unset.--model— choose which Ollama model to use. Defaults tollama3:See Known Limitations above for whycommitmate --model qwen2.5-coder:7b
qwen2.5-coder:7bmay give better results on multi-file diffs.
requestsrich
(See requirements.txt.)
Contributions are welcome. After cloning and installing with pip install -e . (see Installation above), you can make changes and test them immediately with the commitmate command, no reinstall needed.
If you want to build the distributable package yourself:
pip install build
python -m buildThis produces a .whl and .tar.gz in dist/, which you can install into a fresh virtual environment to verify the packaging is correct before opening a PR.
Ishan Pathak theishanpathak.com