[KongchangAI]
· 3 min read· 1,753 words

A Guide to Writing Git Commit Messages: Turn Commits into Readable Documentation

A Guide to Writing Git Commit Messages: Turn Commits into Readable Documentation

Use three-part commit messages answering what/why/impact to make git blame painless.

The article argues that bad commit messages like "fix stuff" add troubleshooting time months later, while structured messages answer 90% of git blame questions. It proposes a three-part structure: an imperative subject plus a body covering what changed, why, and impact/surprises. Real examples show how good messages prevent misunderstanding. To make good format the default, it recommends templates, avoiding -m for non-trivial changes, squashing before merging, and using AI cautiously (write the why yourself). Solo projects need this most.

Why Your Commit Messages Need a Rewrite

Six months from now, you run git blame on a strange line of code and find the commit message is just three words: "fix stuff." And yes, you wrote it yourself. You have no idea what you fixed or why. So you have to dig through PRs and chat logs that may no longer exist.

This isn't an isolated case. According to developer community analysis, a bad commit message (like "fix bug") can add 20 minutes to troubleshooting months later, while a structured commit message can answer 90% of git blame questions. Commit messages are the only documentation right next to the code, yet most people waste them.

Commit message illustration

The Three Questions You'll Ask in the Future

When you run git blame, you never ask "what is this code doing"—the code itself tells you. What you really want to know is: why does it exist.

The three questions are always the same: what changed, why it changed, and what impact it has. A commit message that answers these three in order lets you close the terminal and get back to work immediately; if it can't, you have to start digging.

The author gives a real bad example: update handler. Later he discovered this commit added a timeout to an API call. The diff could tell him "what was done," but not "why"—was this timeout arbitrary? Was it in response to a real hang? Because he didn't know, he didn't even dare to touch that line of code.

By contrast, if the message said "the upstream payment API hangs for 30 seconds under high load; a 5-second timeout with one retry is faster for users," the reader would immediately know this number isn't random and would understand what removing it would break.

The "why" is the first thing to die. Code carries "what was done," and the diff preserves it forever; but the reasoning in your head evaporates quickly. Production incidents, slow endpoints, edge cases customers hit—write them down while memory is fresh, because the code itself physically cannot store these.

"Impact" is the most often skipped line and the one you regret missing most. For example: "This also changes the structure of the cache key, so old cache entries will be invalid after deployment." That one sentence can save someone a confusing afternoon.

git blame is a command in the Git version control system used to trace the last modifier of each line in a file. It displays line by line the commit hash, author, and commit time for that line, usually with the -L parameter to limit the line range. The value of git blame lies in locating "responsibility" and "context," but it can only point to a commit message; if that message is barren, the trace stops there. Therefore, commit messages are effectively the "metadata layer" of git blame, determining whether you can understand the historical intent of the code without leaving the terminal.

The Commit Structure I Actually Use

A good commit consists of a subject line and a body, separated by a blank line. Git treats the first line specially, so it carries a lot of weight.

Keep the subject under 50 characters, in the imperative mood: write "Add timeout to payment handler," not "Added" or "Adding." It should complete the sentence: "If applied, this commit will..."

After a blank line comes the body, wrapped at about 72 characters per line, where the real work happens. The author's body has three paragraphs, no labels, written in order:

  1. Explain what changed in plain language, not a restatement of the diff but a human-readable summary.
  2. Why: this is the paragraph that justifies the change's existence.
  3. Impact and surprises: side effects, preconditions, any surprising details.

Most commit bodies are only four to six lines. Some are just one line, because the change is trivial and self-explanatory (like "Fix typo in error string"). This structure scales down.

One thing to avoid: treating a ticket number as the whole message. "JIRA-4471" means nothing to future readers and requires a login and a valid link. Ticket systems migrate, get archived, or even shut down, while git history outlives them all. Ticket numbers can go at the end as a reference, but the message itself must stand on its own.

Imperative subject lines are a convention widely followed in the Git community, originating from commit messages Git generates itself (like "Merge branch..."). It requires the subject to start with a verb in its base form, with the implied subject "This commit will..." The benefit is that git log --oneline reads like a list of actions, making it easy to scan and generate changelogs. Meanwhile, the 50-character limit is to fit the default terminal width of git log and avoid truncation. The 72-character wrap in the body comes from traditional terminal and mailing list display habits, ensuring it doesn't wrap in git log or email patches.

Real Commits: Before and After

Database migration. Before: migrate db. After: subject "Add nullable discount_code to orders," with a body explaining that the column is nullable so the migration can run on the existing 80,000 rows without backfilling a default value, and that the application code treats null as "no discount code applied." The before version is a landmine—six months later someone will wonder why this column is nullable instead of defaulting to an empty string. Schema decisions are exactly the kind of thing you forget and then repeatedly question.

Performance optimization. Before: perf. After: "Switch catalog lookup to binary search," with a body noting the list is pre-sorted, has 50,000 rows, and that linear scanning showed up in profiling. Now any reader knows the precondition (the list must stay sorted) and won't accidentally break it.

Logging fix. Before: fix logging—useless, because logging changes are often subtle and load-bearing. After: "Add request ID to structured log context," with a body explaining that every log line within a request now carries the same ID so you can grep the full trace, and noting that this depends on middleware running first.

The pattern is clear: the before messages are all two words and all worthless; the after messages take about 40 extra seconds but answer the question before it's asked. Those 40 seconds feel like a tax, but they're actually a high-interest loan to your future self—avoiding one 20-minute troubleshooting session pays back 30 careful commits.

Make Good Format the Default

Relying on willpower will fail. The author doesn't trust himself to have the discipline to write good messages every time, so he lets the environment push him.

First, a commit template. Git lets you point to a text file via configuration. The template uses comment lines to remind you of the what / why / impact order. When you run git commit without -m, the editor opens with these prompts, and you delete the comments after filling them in. The structure is right in front of you, and the friction disappears.

Second, ban -m for non-trivial changes. The -m flag is a breeding ground for bad messages—typing in a cramped shell leaves no room for thought. Open the editor and give yourself space. Typos in single-line flags eventually become commits like "fix stuff."

Third, squash before merging. In-progress commits can say "wip" or "oops forgot import"—it doesn't matter because they never reach the main branch. Squash the whole branch into one clean commit with a good message describing the finished change. The history others read should be the story of "what shipped," not the mess of "how I got here."

The author also now uses AI to draft initial messages: paste the diff, ask for a what/why/impact format message, then personally correct the "why," because the model can only guess his reasoning. The model almost always gets "what" right, and that's the boring part anyway.

One warning: don't let a tool write the "why" for you and commit without reading it. The model infers intent from the diff and is sometimes confidently wrong. A wrong "why" in permanent history is worse than a blank one.

Git commit templates are configured via git config commit.template <path> to point to a text file. When you run git commit without -m, Git pre-fills the editor with the file's contents, where lines starting with # are comments and won't be recorded in the final commit message. Templates can include prompts, checklists, or fixed structures to help developers naturally follow conventions with each commit. They aren't mandatory, but by reducing cognitive load, they make good format the default path.

Solo Projects Need This Even More

If you're working on a solo project and think it doesn't matter because no one will read it—you're exactly the person who benefits most. There's no teammate to ask; git history is your only record. Your past self is a stranger, and you can only converse with him through commit messages.

Start with one change today: for any change bigger than a typo, stop using git commit -m. Open the editor and write three short paragraphs answering what, why, and impact. A month later, when git blame lands on it, feel the difference.

To go further, set up a commit template so the three prompts greet you every time. That one line of configuration has helped the author's commit history more than any amount of "trying to remember."

The payoff doesn't show up the day you write the message; it shows up later: when a confusing line of code has a clear explanation attached, you close the terminal in ten seconds instead of digging for twenty minutes. A good commit is a message to someone who has forgotten everything—and that someone is you.

One-line Summary

Answer what/why/impact with a three-part commit message so git blame is no longer an archaeological dig.

Paragraph Summary

The article points out that bad commit messages like "fix stuff" cause extra troubleshooting time months later, while structured messages can answer 90% of git blame questions. The author proposes a three-part structure: an imperative subject summarizing what changed, and a body explaining what changed, why, and the impact and surprises. Real before-and-after comparisons show how good messages prevent misunderstanding. To make good format the default, he recommends using a commit template, avoiding -m for non-trivial changes, squashing before merging, and using AI cautiously for drafts (the why must be written by hand). Solo projects especially need good commit messages, because git history is the only record of reasoning.

#git commit message#commit message best practices#git blame#commit template#imperative mood commit
Share:

Related articles