How to Write a Good Git Commit Message: The 50/72 Rule and Conventional Commits
A good Git commit message has a short imperative subject of about 50 characters, a blank line, and a body that explains why the change was made, wrapped near 72 characters. That is the whole 50/72 rule. Conventional Commits adds a structured prefix on top, which pays off when tools read your history. This guide covers both, and the same habits apply if you version-control your Markdown notes with Git.
Quick answer
Write the subject so it finishes the sentence "If applied, this commit will...". Keep it near 50 characters. Leave one blank line. Then use the body to say why the change exists, not to repeat the diff.
Fix off-by-one in pagination offset
The first page skipped one row because the offset was computed
from a one-based page number. Subtract one before multiplying so
page 1 starts at row 0, matching the API contract.The 50/72 rule
Git's own documentation says that, though not required, it's a good idea to start with a single short line of no more than 50 characters, followed by a blank line and a fuller description. The text up to the first blank line is treated as the commit title, and Git uses that title throughout. For example, git format-patch turns a commit into an email and puts the title on the Subject line.
The numbers do real work:
- Subject, 50 characters. The limit forces you to summarize, which is the point. If you can't fit the change in one line, the commit may be doing too much.
- Body, 72 characters per line. Wrapping near 72 keeps messages readable in terminals and web-based repository views.
- The blank line. Pro Git calls it critical: tools like rebase get confused if you run the summary and body together.
Treat 50 as a prompt, not a wall. Running to 55 to keep a word that matters beats a cryptic cut.
Imperative mood and a body that explains why
Pro Git says to write the message in the imperative: "Fix bug", not "Fixed bug" or "Fixes bug". That matches the messages Git generates itself for merges and reverts, so your history reads in one voice.
The body is where the commit earns its value. The diff already shows what changed. Pro Git notes that the Git project expects the longer explanation to include your motivation and to contrast the new behavior with the old. Answer three questions: what was wrong, why this fix, and what to watch for.
Here is the same change, before and after:
# Before
fixed stuff
# After
Cache user lookups for the request lifetime
Profile pages called the user service once per comment, which
meant 40+ calls on busy threads. Memoize by user ID for the
duration of the request. Cross-request caching is left out on
purpose; it needs an invalidation plan first.The "after" version lets a teammate run git log six months from now and understand a decision without opening the diff.
Conventional Commits in one minute
The Conventional Commits 1.0.0 specification gives the subject a fixed shape: a type, an optional scope in parentheses, an optional !, a colon and space, then a short description. An optional body and optional footers follow, each after a blank line.
feat(search): add fuzzy matching for tag names
Tags with typos now return near matches ranked by edit distance.
Refs: #214The two core types map to Semantic Versioning:
featadds a feature and corresponds to a MINOR release.fixpatches a bug and corresponds to a PATCH release.- Other types such as
docs,refactor,perf,test,build,ci,chore, andstyleare allowed and carry no version effect on their own.
A breaking change on any type means a MAJOR release. Mark it with a BREAKING CHANGE: footer, or put a ! before the colon, as in feat(api)!: drop the v1 endpoints. The scope must be a noun naming a part of the codebase, such as search or parser.
Where Conventional Commits helps, and where it adds ceremony
It helps when software reads your history, not just people. Automated version bumps and generated release notes depend on a predictable prefix, and a shared format keeps a large team consistent. If you keep a CHANGELOG.md, structured commits make that file far cheaper to produce. A team can also state the convention in its CONTRIBUTING.md so new contributors see it up front.
It adds ceremony when only humans read the history. On a solo notes repo or a small side project, agonizing over chore versus refactor costs more than it returns.
The two systems combine fine, with one catch: the prefix eats subject characters. feat(search): uses 14 of your 50, so the description has to be tight. Everything else, imperative mood and a body that explains why, carries over unchanged.
Common problems and fixes
- Subject runs past 50 characters. Move detail into the body and keep the subject to the headline.
- Past tense ("Fixed", "Added"). Rewrite as a command: "Fix", "Add".
- Body only repeats the diff. Replace it with the reason: what failed, what you considered, what you left out.
- No blank line after the subject. Add one, or tools may treat your whole message as the title.
- One commit, three unrelated changes. Split it. A clear message is much easier to write for a focused commit.
Doing this with Carets
Commit messages are plain text, and the habit sticks when you draft the message before you commit. Carets is a native notes and code editor for iPhone, iPad, and Mac that handles Markdown, plain text, and code in one place, so a message template or a team's commit convention can live next to your code snippets as ordinary files.
Keep a commit-template.txt in a project, organize it with tags, and open it from any device. Your files stay plain text, which keeps them portable and future-proof. Carets also gives code and config files syntax highlighting, which helps when you write messages that quote a snippet.
Download Carets for iPhone, iPad, and Mac on the App Store.
Frequently Asked Questions
Is the 50-character limit strict?
No. Git's documentation calls it a good idea, not a requirement. Treat 50 as a prompt to summarize, and go slightly over when a word carries real meaning.
Why use the imperative mood?
Pro Git recommends "Fix bug" over "Fixed bug" or "Fixes bug", which matches the messages Git writes for merges and reverts. It also reads cleanly as "If applied, this commit will fix bug".
Do I need Conventional Commits?
Only when a team or tool needs to read your history, for example to bump versions automatically. For a personal repository, the 50/72 shape on its own is enough.