Commit Message Builder and Checker

Proposed from the diff, checked against the spec.

  • Reads your diff
  • Spec grammar check
  • Trailers checked
A blank rounded white card folded like a small tent on a pale wooden desk, beside a stack of paper strips edged in soft pastel stripes

The guide

A Commit Message Is a Letter to Whoever Reads the Diff Next Year

Why the summary line, the body and the footers each have a job, what the Conventional Commits grammar adds, and why the message should start from the diff itself.

  • 50 characters the summary length the git manual advises, as a soft limit
  • BREAKING-CHANGE the hyphenated synonym of BREAKING CHANGE that git reads as a trailer
  • 25 percent share of trailer lines git needs when a block mixes in other text
  • +20 -12 line counts of the worked example: seven files in one staged diff
Read the full guide Fold the guide 8 min · 14 sources

A commit message is written once, in a hurry, by the one person who knows exactly why the change was made. It is read many times, slowly, by people who do not. That imbalance is the whole case for taking a few minutes over it. The author has the context in their head and the diff on their screen. The reader, a year later, has only the diff and whatever the message says.

Git was built for that reader. Linus Torvalds began it in April 2005 for the Linux kernel, a project where patches travel as email and are reviewed by people who never met the author. The habits that grew up there still shape how git treats a message: a short first line, a blank line, then prose, then a block of signed lines at the end.

The summary line does the most work

The git manual is gentle about it. Its discussion of commit messages says that, though not required, it is a good idea to begin with a single short line of no more than 50 characters summarising the change, then a blank line, then a fuller description. The text up to the first blank line is treated as the title, and git uses that title everywhere. When git format-patch turns a commit into an email, the title becomes the Subject line, by default after a “[PATCH]” prefix.

That is why the first line matters more than any other. It is what one-line logs show, what a reviewer scans in a list of hundreds, and what someone searches for when they want the discussion behind a change. The kernel’s guide to submitting patches puts it bluntly: the summary phrase becomes a globally unique identifier for the patch, it propagates into the changelog, and months later it may be the only thing a developer sees while scrolling through thousands of patches. The kernel allows 70 to 75 characters. Git’s own guide for its contributors keeps the 50 character soft limit, drops the full stop at the end, and starts the summary with the area being changed.

Both guides ask for the imperative mood: “make xyzzy do frotz”, as if giving the codebase an order, not “this patch makes” or “I changed”. It reads oddly at first, and then it reads like a list of instructions, which is what a log of changes is.

The body is for the why. The diff already shows what changed, line by line, and nobody needs it narrated. What the diff cannot show is the reason: the bug report, the measurement, the constraint that ruled out the obvious fix. The kernel guide asks for bodies wrapped at 75 columns and for each patch to stand on its own, without sending the reader to an earlier version or a link to find the explanation.

What a grammar adds

Prose conventions help people. They do little for programs. Conventional Commits 1.0.0 is a small specification that adds just enough structure for tools to read a message without guessing. The header becomes a type, an optional scope in parentheses, an optional exclamation mark, a colon and a space, and then the description. A body may follow one blank line later, and footers one blank line after that.

The types carry meaning. A fix patches a bug, which the specification ties to a PATCH release in Semantic Versioning. A feat adds a feature and maps to MINOR. A breaking change, marked either by the exclamation mark just before the colon or by a footer whose token is BREAKING CHANGE, maps to MAJOR whatever the type. Semantic Versioning itself defines MAJOR as the number you raise for incompatible API changes, so the commit log can decide the next version number instead of a person remembering. Other types such as docs, test, build, ci, refactor and perf are allowed and have no effect on the version unless they also carry a breaking change.

The rules are short but exact, and the exact parts are where messages go wrong. The colon must be followed by a space, and the description must follow that space at once. The scope is a noun in parentheses, so an empty pair is a mistake. The exclamation mark goes immediately before the colon, after the scope, not after the type.

Footer tokens use a hyphen wherever a space would be, as in Reviewed-by, and the only token allowed to contain a space is BREAKING CHANGE, which must be written in capitals. BREAKING-CHANGE is defined as its synonym. Everything else is case-insensitive.

Each of these is easy to break in a way a person barely notices. “Reviewed by: Z” looks like a footer to a human. Under the grammar it is not one, because its token holds a space; a parser reads it as ordinary text, and whatever tool depends on the footers never sees it. The same goes for a footer typed straight under the last paragraph of the body with no blank line before it. These slips cost nothing on the day and cause quiet failures later, when a changelog skips an entry or a version bump misses a break.

Trailers: where git and the specification meet

The footer idea did not start with Conventional Commits. The specification says outright that its footers are inspired by the git trailer convention. Git’s interpret-trailers manual describes a trailer as a key and value separated by a colon, where the key is made of ASCII letters, digits and hyphens. A trailer block is the last paragraph of the message and must have a blank line before it. Values may continue on following lines if those lines start with whitespace, much like folded email headers.

Two lines in that manual matter more than they look. The block counts as trailers only if it is all trailers, or if it contains at least one trailer git itself generates or is configured for, such as Signed-off-by, and at least 25 percent of its lines are trailers. And the separator git reads by default is the colon alone.

Put those rules beside the specification and a few gaps appear. A footer of “BREAKING CHANGE: …” is valid Conventional Commits but not a git trailer, because the key has a space. “Closes #12” uses the specification’s space and hash separator, which git does not read as a trailer. If either sits in a block with no Signed-off-by line, git may decide the paragraph holds no trailers at all.

That is a good reason to prefer the hyphenated BREAKING-CHANGE when other trailers share the block. Signed-off-by itself has a meaning that depends on the project. Git’s documentation says it may certify that the committer has the right to submit the work under the project’s license, and points to the Developer Certificate of Origin used by both the kernel and git. Co-author lines follow the same key, colon, value pattern, so git interpret-trailers --parse lists them like any other trailer.

Start from the diff

The best time to write the message is with the staged diff in front of you, and the best order is to read the diff first. A unified diff is compact. GNU diffutils describes it as a two line header naming the old and new files, followed by hunks that each begin with a line such as ”@@ -2,4 +2,6 @@”, giving the start line and line count on each side, with a count of one left out.

Inside a hunk, context lines begin with a space, removed lines with a minus, and added lines with a plus. Git adds its own header line, diff --git a/old b/new, and extended lines that record renames with a similarity index, file modes as six octal digits, new and deleted files, and binary files whose contents are not shown.

Reading those lines answers questions a message should cover. Was something renamed, and is the rename part of the point? Did a script become executable? Did a binary file arrive? Did the removed lines include an exported function that other code may call? None of that is visible from memory at the end of a long day, and all of it is in the diff. Git’s numstat option prints the added and deleted lines per file, with a dash for binary files, which makes a good check on any summary.

A worked example shows the whole route. A staged diff in an invented project holds seven files: 20 insertions and 12 deletions (+20 -12), which is what git diff --staged --shortstat reports. One file is a new image, shown only as binary. One is a shell script whose mode went from 100644 to 100755 with no line changed. One was renamed from util.ts to helpers.ts at 80 percent similarity. A test file was added, a document gained three lines in place of its old last line, which had no newline at the end, and two files in the parser folder removed the export of a function called parseLegacy.

From that, path rules can say a few things with confidence. The test file is a test and the document is documentation, so the change is not docs only or tests only; the type is a human choice, and here it is feat. Most of the changed lines outside tests and docs sit in the parser folder, so parser is a fair scope. The removed export is a possible break that someone must confirm. The header “feat(parser)!: stream tokens and drop parseLegacy” is 49 characters, inside the 50 the git manual suggests, and a footer starting with BREAKING CHANGE explains what was removed.

What a checker can and cannot know

A tool can count, parse and compare. It can confirm that every hunk holds as many lines as its header declares, that a header follows the grammar to the character, and that git would read the trailers the way the author meant. It can point at line 5, column 9 and say the token there has a space in it.

It cannot know why the change was made. It cannot tell a feature from a fix by file names, or know whether a removed export still has callers in another repository. It sees only the diff it is given, not the history around it or the unstaged work beside it. Those parts of the message are the author’s job, and they are the parts a reader most needs.

Those limits are the point of the division of labour. Let the tool hold the rules that people forget, the exact separator and the blank line in the right place, and spend the saved attention on the one paragraph only the author can write.

Wikipedia’s history of diff traces the unified format to Wayne Davison in 1990 and its arrival in GNU diff a month later. More than three decades on, the format is still how changes travel between people, and the message on top of it is still the only place the reason is written down. A short, exact message, checked against the diff it describes, is a small kindness to that future reader.

Sources

Every source consulted for this guide. Links checked October 24, 2026.

  1. Conventional Commits 1.0.0 conventionalcommits.org (CC BY 3.0)
  2. git-commit Documentation Git reference manual, git-scm.com (GPL)
  3. git-diff Documentation Git reference manual, git-scm.com (GPL)
  4. diff-format Documentation Git reference manual, git-scm.com (GPL)
  5. git-interpret-trailers Documentation Git reference manual, git-scm.com (GPL)
  6. git-format-patch Documentation Git reference manual, git-scm.com (GPL)
  7. SubmittingPatches Documentation Git project, git-scm.com (GPL)
  8. Unified Format (Comparing and Merging Files) GNU diffutils manual (GNU FDL)
  9. Detailed Description of Unified Format GNU diffutils manual (GNU FDL)
  10. Submitting patches: the essential guide to getting your code into the kernel The Linux Kernel documentation
  11. Developer Certificate of Origin The Linux Foundation and its contributors
  12. Semantic Versioning 2.0.0 semver.org (CC BY 3.0)
  13. diff - Wikipedia Wikipedia (CC BY-SA)
  14. Git - Wikipedia Wikipedia (CC BY-SA)