Writing Good Commit Messages
Commit messages are notes for the future — for your teammates, your reviewers, and yourself in six months. The diff already shows what changed; the commit message exists to explain why. A well-written commit message can save hours of detective work later. A bad one (“fix stuff”, “asdf”, “WIP”) is a permanent low-effort marker in the history of the project.
The widely accepted format
The 7 rules of a great commit message (Tim Pope, ca. 2008)
Subject line ≤ 50 chars in the imperative mood Blank line, then a longer body wrapped at 72 characters. Explain WHAT changed and WHY — not how. The diff shows how. Reference issues, design docs, links to context as needed. - Bullet lists are fine. - Use the present-tense imperative: "Add", "Fix", "Refactor". Refs: #1234
The seven rules
1. Separate subject from body with a blank line. This is the most-broken rule.
2. Limit the subject line to 50 characters. Git and many tools truncate at 50–72.
3. Capitalise the subject line. "Add", not "add".
4. Do not end the subject line with a period. It is a title, not a sentence.
5. Use the imperative mood. "Fix typo", not "Fixed typo" or "Fixes typo". Pretend you are giving an order to the codebase.
6. Wrap the body at 72 characters. Long lines wrap badly in many tools.
7. Use the body to explain WHAT and WHY, not HOW. The diff already shows how.
Good examples
A small, clear commit message
Fix off-by-one error in pagination Pages were rendering one item less than expected on the last page. The cause was an inclusive comparison in calculatePages() that should have been exclusive. Closes #214
A medium-sized message with context
Replace bcrypt with argon2id for password hashing bcrypt is showing its age — it has a hard 72-byte input limit and tooling around it is in maintenance mode. argon2id is the current OWASP-recommended algorithm and is more resistant to GPU-based attacks. Migration plan: - Existing hashes still verify (we keep the bcrypt fallback) - New passwords are stored with argon2id - A background job will gradually re-hash old passwords on successful login Refs: SEC-37
Bad examples (and why)
Don't do these
"fix" ← what did you fix? "updated stuff" ← which stuff? "asdf" ← what does this mean? "final changes" ← they are never the final changes "more fixes" ← more of what? "WIP" ← never commit "WIP" to a shared branch "." ← please no
Imperative mood — the trick
A good test: the subject line should complete the sentence:
If applied, this commit will __________
✓ "Add login form" ← complete: "...add login form" ✓ "Fix race condition in queue" ← complete ✗ "Added login form" ← past tense — wrong ✗ "Adds login form" ← present tense — wrong ✗ "Adding login form" ← gerund — wrong
Git itself uses imperative mood in messages it generates (like "Merge branch ..." and "Revert ..."). Matching that style keeps the log readable.
Multi-line messages
For larger changes, the body is where the real value lives. A typical structure:
Subject line — a 50-character summary.
Blank line.
Body — answer "Why did we do this?", "What problem did it solve?", "What alternatives did we consider?". Wrap at 72 chars.
Blank line.
Footer — references to issues, breaking changes, co-authors. Examples:
Refs: #1234,BREAKING CHANGE: …,Co-authored-by: ….
Commit messages for fixes
A bug-fix commit benefits from describing: (1) what the user- visible symptom was, (2) the root cause, (3) the fix.
Fix crash when search query contains a slash User report: searching for "and/or" crashes the page. Root cause: the search route used `encodeURI` instead of `encodeURIComponent`, which left forward slashes unencoded. The router then interpreted "and/or" as two path segments and 404’d, but the error path threw because the query parser expected a string. Fix: switch to encodeURIComponent and add a regression test. Fixes #481
Conventional Commits
A popular convention (used by many open-source projects) puts a type prefix on every commit:
feat: a new feature fix: a bug fix docs: documentation only style: formatting, no logic change refactor: code change that neither fixes a bug nor adds a feature perf: performance improvement test: adding or fixing tests chore: routine maintenance, build configs, etc. ci: CI/CD configuration # With scope (optional): feat(auth): add OAuth login
These prefixes are friendly to automated tooling — release- please, semantic-release, and similar tools auto-generate changelogs and version numbers from them.
References and footers
Common footers
Refs: #1234, ABC-42 ← reference issues Fixes: #1234 ← GitHub auto-closes the issue on merge Closes: #1234 ← same Co-authored-by: Name <email> ← gives credit on GitHub Signed-off-by: Name <email> ← used by Linux kernel (-s flag) BREAKING CHANGE: ... ← signals semver-major change
Tips for writing better messages
Write the subject as if you were emailing a colleague: "what does this commit do?"
If you cannot summarise it in 50 chars, you probably committed too much. Use
git add -pto split it.Read your commit message back. Does it tell you anything the diff does not?
When in doubt, write a longer body. Future-you will thank present-you.
Use the team’s style — every team has slightly different conventions. Follow them.
A commit message template
~/.gitmessage
# Subject: imperative, ≤ 50 chars # # Body: explain WHAT changed and WHY, wrap at 72 chars. # Reference issues at the bottom. # # Refs:
git config --global commit.template ~/.gitmessage