A good Git commit message has a short subject line in the imperative mood, kept under about 50 characters, then a blank line, then an optional body wrapped at around 72 characters that explains why the change was made. That is the whole convention, and it is the single cheapest habit you can pick up that makes your history readable to other people (and to future you).
If you want a one-line version to copy now:
git commit -m "Fix login redirect after session timeout"Short, imperative, capitalized, no trailing period. The rest of this page explains where that rule comes from, when to add a body, what the imperative mood actually means, and the Conventional Commits format you will see on a lot of team projects.
Why commit messages matter at all
When you work alone on a weekend project, you can get away with git commit -m "stuff" and nobody suffers but you. The moment another person reads your history, the message becomes the only explanation of why a line changed. The diff already tells anyone what changed; the message has to carry the reasoning the diff cannot. If you are just starting to track a project, the habit begins the moment you make your first commit, whether you are following how to add Git to an existing project or starting fresh.
The places a message gets read are exactly the places you are under pressure:
- Running
git logto find when a bug was introduced. Once you know how to read history with git log, good messages are what make that history worth reading. - Running
git blameon a confusing line and landing on the commit that wrote it. - Reviewing a pull request where the commit messages are the table of contents.
- Six months later, trying to remember why you deleted code that now looks important.
A message like fixed it helps in none of those. A message like Fix race condition in cache eviction under high write load answers the question before you even open the diff. This is the whole game with Git as a beginner: the tooling is there to let other people understand your changes without asking you, and the commit message is where that understanding lives.
The 50/72 rule
The convention almost everyone follows comes straight out of how Git itself displays commits. The format is:
Summarize the change in 50 characters or less
Add a more detailed explanation here if it is needed. Wrap the
body at about 72 characters. Explain what and why, not how (the
diff already shows how). Leave a blank line between the subject
and the body, because tools depend on it.
- Bullet points are fine in the body
- Use a hyphen or asterisk, with a blank line between paragraphsTwo numbers, two reasons:
Subject under ~50 characters. This is a soft target, not a hard error. Git does not reject a longer subject, but git log --oneline, GitHub's commit list, and most UIs truncate around 50 to 72 characters with an ellipsis. If your subject runs past that, the important words get cut off in exactly the views people scan fastest. Fifty characters forces you to name the change, not narrate it.
Body wrapped at ~72 characters. Git does not wrap text for you. When you run a plain git log, it indents the body by four spaces, so a body written as one long line either runs off the screen or wraps raggedly. Wrapping at 72 leaves room for that indent inside an 80-column terminal and keeps the body readable everywhere.
The blank line between subject and body is the one part that is not cosmetic. Git treats the first line as the subject and everything after the blank line as the body. Tools like git shortlog, git log --format=%s, and the GitHub UI all rely on it. Skip the blank line and your whole message becomes one mushed-together subject.
For anything but a trivial change, do not use -m. Run a bare git commit, which opens your editor, so you can write a real subject and body without fighting the shell:
git commitIf your editor is not set, point Git at the one you want:
git config --global core.editor "nano"Use the imperative mood
This is the rule that feels strange at first and then becomes automatic. Write the subject as a command, as if you are telling the codebase what to do:
Add retry logic to the webhook sender
Remove the deprecated v1 auth endpoint
Fix off-by-one error in paginationNot the past tense, not a description of what you did:
Added retry logic to the webhook sender
Removes the deprecated v1 auth endpoint
Fixing off-by-one error in paginationThe reason is not style police. Git's own generated messages are imperative: a merge says Merge branch 'feature', a revert says Revert "...". The clean mental test comes from the Git project's own guideline: a properly formed subject should complete the sentence "If applied, this commit will ___". "If applied, this commit will Add retry logic" reads correctly. "If applied, this commit will Added retry logic" does not. Matching that mood keeps your hand-written commits consistent with the ones Git writes for you.
A few more subject conventions that ride along with it:
- Capitalize the first word.
- Do not end the subject with a period. It is a title, not a sentence, and the period just wastes one of your ~50 characters.
- Do not start with a vague verb like "Update" or "Change" with no object. "Update files" tells the reader nothing; "Update API base URL for the staging environment" tells them everything.
Good and bad messages, side by side
| Bad message | Why it fails | Better version |
|---|---|---|
fix | No subject, no context, no idea what was fixed | Fix crash when uploading a zero-byte file |
stuff | Says nothing | Add rate limiting to the public search endpoint |
Fixed the bug. | Past tense, trailing period, "the bug" is unknowable later | Fix double-charge on retried checkout requests |
WIP | Fine on a private branch, never in shared history | Squash WIP commits before merging (see below) |
Update user.js | Names the file (the diff already does) not the change | Validate email format before saving the user |
asdkjfh | A keyboard mash from committing in a hurry | Revert accidental commit of debug logging |
The pattern in every "better" version is the same: name the behavior that changed, in the imperative, specifically enough that someone reading git log a year from now does not have to open the diff to understand the gist.
Conventional Commits
On a lot of team projects you will see commit subjects that look like this:
feat: add password reset flow
fix: prevent duplicate orders on network retry
docs: clarify the rate-limit headers in the API guide
refactor: extract the pricing calculator into its own module
chore: bump the linter to the latest minorThat is the Conventional Commits format. It is the 50/72 imperative convention with one extra rule bolted on: the subject starts with a type, an optional scope in parentheses, a colon, and then the imperative description. The structure is:
type(optional scope): description
[optional body]
[optional footer]The common types are:
| Type | Meaning |
|---|---|
feat | A new feature |
fix | A bug fix |
docs | Documentation only |
style | Formatting, whitespace, no code-behavior change |
refactor | Code change that neither fixes a bug nor adds a feature |
test | Adding or fixing tests |
chore | Build, tooling, dependencies, housekeeping |
A scope narrows it down, for example fix(auth): handle expired refresh tokens. A breaking change is flagged either with a ! after the type, like feat!: drop support for the legacy v1 config format, or with a BREAKING CHANGE: line in the footer.
The reason teams adopt it is not tidiness for its own sake: the type prefix is machine-readable. Tools can read the log and bump the version automatically (a fix: is a patch, a feat: is a minor, a breaking change is a major), generate a changelog grouped by type, and trigger CI behavior off the type. If your project runs that kind of release automation, often through a GitHub Actions pipeline, the convention is what feeds it.
You do not have to use Conventional Commits on a personal project. But if a repo already uses it, match it, because the tooling on the other end is parsing your subjects and will silently do the wrong thing if you do not.
Fixing messy messages before they ship
Beginners often worry that a bad message is permanent. It is not, as long as you have not shared the commit yet. While work sits on your local branch, you can rewrite it freely:
- The very last commit's message is one command away with
git commit --amend. See how to undo or rewrite the last commit for the full walk-through, including amending the message without changing the files. - A messy string of
WIPcommits can be collapsed into one clean commit with a clear message using interactive rebase to squash before you open a pull request.
The one boundary to respect is the same one that governs all history rewriting: rewrite freely while the commits are private, but once you have pushed and shared them, treat the history as fixed. For an already-pushed commit you would not amend the message in place; you would correct it with a new commit, which is the reset versus revert distinction. If you need a refresher on why, the trade-offs are spelled out in merge versus rebase. The practical upside is that you can commit sloppily while you work (commit early, commit often) and clean the messages up in one pass right before the work goes out for review.
A workflow that makes good messages easy
The convention is simpler to keep than to read about. In practice:
- Stage only the changes that belong in one logical commit, not the whole working tree. Keeping commits focused is what makes a one-line subject possible in the first place; this is the real payoff of understanding the staging area. If you are starting from scratch, set up Git for a new project first so you have a clean repo to commit into.
- Run a bare
git commit(no-m) for anything non-trivial so your editor opens. - Write an imperative subject under ~50 characters, then a blank line, then a body that explains why if the change is not self-explanatory.
- Before sharing, amend or squash to clean up anything sloppy.
Small, focused commits with honest messages turn your history into documentation you get for free. On a team workflow, that history is what code review, debugging, and onboarding all run on.
Sources
Authoritative references this article was fact-checked against.
- git-commit: official Git documentationgit-scm.com
- Pro Git: Contributing to a Project (commit guidelines)git-scm.com
- Conventional Commits 1.0.0 specificationconventionalcommits.org





