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:

TXT
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
  1. A summary line of about 50 characters (72 at most). It's what git log --oneline, GitHub and email tools show.
  2. A blank line. Git uses it to separate the summary from the body.
  3. 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'".

WeakBetter
fixed stuffFix crash when the recipe file is empty
changesRename "ingredients" field to "items"
Added a function to calculate the total price of the basket and also updated readmeAdd basket total calculation (and a separate commit: "Document basket API in README")
WIPValidate email format on sign-up form
Updated index.htmlAdd 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 #142 or Closes #142 closes 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:

TXT
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
TypeUsed for
featA new feature
fixA bug fix
docsDocumentation only
refactorRestructuring code without changing behaviour
testAdding or fixing tests
chore, build, ciMaintenance, 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
Output
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 …".