Skip to content
Knowledge Hub

Commit messages with Conventional Commits, and atomic commits Intermediate

Estimated reading time: 3 min read

This page answers: how to write commit messages that both people and tools can read, and how much change one commit should contain.

Per Conventional Commits 1.0.0:

<type>[optional scope]: <description>
[optional body]
[optional footer(s)]

The spec’s main rules:

  • feat is required for a new feature, fix is required for a bug fix. Other types (docs, refactor, test, chore, perf, ci…) are allowed, but the spec does not define their meaning.
  • Breaking change: ! right before the : (feat(api)!: ...), or a BREAKING CHANGE: <description> footer. That token must be uppercase; BREAKING-CHANGE is a synonym.
  • Footers take the form Token: value or Token #value; tokens use - instead of spaces (Reviewed-by, Refs).

Loading diagram…

A breaking change bumps MAJOR regardless of type: fix!: is also MAJOR.

Before — the log tells you nothing:

a1b2c3d fix
d4e5f6a update order service
9f8e7d6 wip

After — each line answers “what changed, where”:

fix(order): round the discounted total to whole VND
Previously a 10% discount on a 199,999 VND order returned 179,999.1 and
stored a decimal in a bigint column, making the reconciliation job off by 1 VND.
Round down at the final step, keep integers in the intermediate steps.
Refs: PAY-482

Why the subject should be ≤ 50 characters

Section titled “Why the subject should be ≤ 50 characters”
Source Recommendation
git commit docs (DISCUSSION) First line “no more than 50 characters”, then a blank line, then the description
Pro Git Wrap the body at about 72 characters; imperative mood (“Fix bug”, not “Fixed bug”) to match the messages git merge and git revert generate

The first line is a title used everywhere: git log --oneline, PR titles, format-patch emails, the GitHub UI (truncated when too long). The body answers why; the diff already says what.

Atomic commits: separate refactors from behavior changes

Section titled “Atomic commits: separate refactors from behavior changes”

Atomic commit = one logical change that builds and passes tests on its own. The most important rule: refactors (no behavior change) and behavior changes go in separate commits.

Before — one commit doing three things:

feat(order): support percentage vouchers
src/order/pricing.ts | 180 +++++---- (rename variables, extract functions, change logic)
src/order/order.module.ts | 12 +- (change DI)
package.json | 2 +- (upgrade lodash)

After — three commits, each reviewable and revertable on its own:

chore(deps): upgrade lodash 4.17.20 → 4.17.21
refactor(order): extract pricing into PricingService # old tests pass, no behavior change
feat(order): support percentage vouchers # small diff, only the new logic
Task Combined commit Atomic commits
Review Renames and logic are mixed together The refactor commit only needs “does behavior change?” — passing old tests is the evidence
Voucher causes a bug revert also removes the refactor and the lodash upgrade git revert exactly the feat commit
git bisect Points at a 200-line commit Points at a 20-line commit

Tools:

  • git add -p: stage hunk by hunk, splitting one file into several commits.
  • git commit --fixup=<sha> + git rebase -i --autosquash: fix an old commit in a PR (see Branch, merge, rebase).

Next: Branch, merge, rebase.