Writing Good Commit Messages
A commit message is a note to the people who will read the history later — including yourself in six months. The code shows what changed; the message should say why. A few simple conventions make a project's history easy to scan and genuinely useful.
The shape of a message
Git and the tools built on it treat the first line specially, so messages follow a standard layout:
Fix total when a basket contains free items
Items priced at zero were skipped by the discount loop, so the
total ignored the 10% discount when a free gift was in the basket.
Apply the discount to every line and add a test for the case.
Fixes #142
- A summary line of about 50 characters (72 at most). It's what
git log --oneline, GitHub and email tools show. - A blank line. Git uses it to separate the summary from the body.
- An optional body, wrapped at around 72 characters, explaining the reason for the change, any trade-offs, and what a reader can't see from the code alone.
Many one-line changes need no body at all: "Fix typo in README" says everything.
Write the summary as a command
The common convention is the imperative mood — as if giving an order to the code: "Add", "Fix", "Remove", "Rename". A handy test is that the summary should complete the sentence "If applied, this commit will …". Git itself uses this style for the messages it writes, such as "Merge branch 'feature'" and "Revert 'Add banner'".
| Weak | Better |
|---|---|
| fixed stuff | Fix crash when the recipe file is empty |
| changes | Rename "ingredients" field to "items" |
| Added a function to calculate the total price of the basket and also updated readme | Add basket total calculation (and a separate commit: "Document basket API in README") |
| WIP | Validate email format on sign-up form |
| Updated index.html | Add newsletter sign-up to home page |
"Updated index.html" is a common trap: the file name is already visible in the commit. Say what the change achieves.
What a body is for
- Why the change was needed — the bug, the request, the measurement.
- Why this way — alternatives you rejected and why.
- Side effects — anything surprising, such as a changed default.
- References — issue numbers or links. On GitHub, a line like
Fixes #142orCloses #142closes that issue automatically when the commit reaches the main branch.
Conventional Commits
Some teams use a stricter, machine-readable format called Conventional Commits. The summary starts with a type and an optional scope:
feat(cart): add discount codes at checkout fix: handle empty recipe files docs: explain how to run the tests refactor(search): extract query parser chore: update dependencies feat!: drop support for the old export format
| Type | Used for |
|---|---|
feat | A new feature |
fix | A bug fix |
docs | Documentation only |
refactor | Restructuring code without changing behaviour |
test | Adding or fixing tests |
chore, build, ci | Maintenance, build system, automation |
A ! after the type, or a body line starting with BREAKING CHANGE:, marks a change that breaks compatibility. The payoff is automation: tools can generate a changelog and choose the next version number from the commit types. Follow whatever your project already does — consistency matters more than the particular style.
Messages in practice
A history with clear messages reads like a project diary:
git log --oneline
1e97ea1 (HEAD -> main) Move pancakes to desserts folder 16773b1 Add cheese to omelette 0787efe Sweeten pancakes for the kids' breakfast 90c0089 Add omelette recipe c4f5e47 Add pancake recipe
Fixing a message
Made a typo in the message you just committed? git commit --amend lets you rewrite it, as long as you haven't shared the commit yet (lesson 17). Older messages can be reworded with an interactive rebase (lesson 27).
Try it yourself
Run git log --oneline -20 in any project you've cloned (or browse its commits on GitHub) and pick three messages: one that's clear, one that's vague, and one that would benefit from a body. Rewrite the vague one so it completes "If applied, this commit will …".