Skip to content
gitcomm.

Documentation

Commit Anatomy

The parts of a commit message, what belongs in each one, and the rules that make a subject readable six months later.

On this page

A commit message has three parts. Most people only ever write the first one, and that is where most commit history becomes unreadable.

text
feat(auth): add two-factor authentication

Rate-limited the verification endpoint to five attempts per
minute per device. Failed attempts now return the same response
as an unknown code, so an attacker cannot enumerate registered
devices.

Closes #482
Refs: SEC-1193

The first line is the subject. The gap-separated paragraphs after it are the body. The Key: value pairs at the end are footers. Everything is optional except the subject.

The subject line

The subject is the only part most tools show. It is the line you see in git log --oneline, in a blame annotation, and in a GitHub or GitLab list. Treat it as the entire message and make it readable on its own.

  1. Start with a capital letter when the history does. gitcomm detects this and matches it.
  2. Use the imperative mood, as in add or fix, not added or fixes. The subject describes what the commit does, so the present tense reads correctly in a log.
  3. Leave out the trailing period. Nothing else in the message has one.
  4. Keep it under 72 characters so it never wraps in a terminal.
  5. Say what changed, not that something changed. fix null token in refresh beats fix bug.
text
fix(cart): resolve quantity not updating on click

fix cart bug
Fixed the cart.
update

Only the first line survives. The rest of that list is what you are looking at when someone greps a log six months from now.

The body

The body exists to answer the question the subject raises. It is wrapped at about 72 columns and separated from the subject by one blank line.

text
fix(cart): resolve quantity not updating on click

The click handler read quantity from the stale cart snapshot that
the reducer keeps for optimistic rendering. Read from the live
cart instead, which is already committed before the render pass
runs.

Fixes #1197

Write the body when the reason is not obvious from the subject. A rename with no logic change needs nothing; a one-character fix that changes behaviour for every request deserves a paragraph.

Footers and trailers

A footer is a Token: value line in the last paragraph. These are not prose; they are parsed by Git, GitHub, and GitLab, so the token has to match exactly.

TokenEffect
Closes #123Closes the issue when the commit lands on the default branch.
Fixes #123, Resolves #123Same thing, shorter or longer spellings both work.
Refs #123Links the issue without closing it.
BREAKING CHANGE: ...Marks a major change. Needs its own paragraph, not a subject footer.
Co-authored-by: Name <email>Adds a co-author to the commit. Must be the last footer.
Signed-off-by: Name <email>Adds a sign-off trailer, used by git commit -s and some DCO workflows.
text
feat(api): add cursor pagination to /users

Offsets become slow past a few thousand rows. Switching to a
keyset cursor keeps page cost flat as the table grows.

Closes #301
BREAKING CHANGE: ?limit now takes a cursor instead of an offset.

There is no need to type these by hand. Many editors and GitHub's web UI insert them for you when you reference an issue.

The conventional form

Conventional Commits adds a fixed structure to the subject: a type, an optional scope, and a description.

text
feat(auth): add two-factor authentication
│    │       │
│    │       └─ description: what changed, imperative, no period
│    └───────── scope: the area of the project affected, optional
└────────────── type: the category of change

The scope comes from the files in the diff. When gitcomm sees an auth directory it offers auth as the scope without being told. The ten types are listed separately, with an example of each.

The format is not mandatory. gitcomm reads your existing history and matches whichever style it finds, so a repository of plain sentences keeps getting plain sentences. Force the other direction with --force-conventional.

Reading your own history

Before writing a new message, look at what is already there. This is the fastest way to match your own conventions.

bash
# What the last twenty subjects look like
git log --oneline -20

# How many commits per file, which hints at your scopes
git log --name-only --pretty=format: -20 | sort | uniq -c | sort -rn

If the subjects are inconsistent, that is usually a sign nobody agreed on a convention. Adopting Conventional Commits in a repository that has none of it is a larger decision than a tool change, and --force-conventional is a reasonable first step while you decide.