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.
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-1193The 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.
- Start with a capital letter when the history does. gitcomm detects this and matches it.
- Use the imperative mood, as in
addorfix, notaddedorfixes. The subject describes what the commit does, so the present tense reads correctly in a log. - Leave out the trailing period. Nothing else in the message has one.
- Keep it under 72 characters so it never wraps in a terminal.
- Say what changed, not that something changed.
fix null token in refreshbeatsfix bug.
fix(cart): resolve quantity not updating on click
fix cart bug
Fixed the cart.
updateOnly 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.
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 #1197Write 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.
| Token | Effect |
|---|---|
Closes #123 | Closes the issue when the commit lands on the default branch. |
Fixes #123, Resolves #123 | Same thing, shorter or longer spellings both work. |
Refs #123 | Links 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. |
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.
feat(auth): add two-factor authentication
│ │ │
│ │ └─ description: what changed, imperative, no period
│ └───────── scope: the area of the project affected, optional
└────────────── type: the category of changeThe 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.
# 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 -rnIf 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.