Six months from now, someone will point a tool at a single line of code and ask: why is this here? The answer lives in the commit that introduced it — if that commit said anything useful. A message of "fix", "update" or "changes" answers nothing, and the trail goes cold exactly when you need it most.
Good commit messages are not ceremony. They are the cheapest documentation you will ever write, produced at the one moment you fully understand the change: right after making it.
Explain the why, not the what
The diff already shows what changed — which lines, which files. What it cannot show is why. A message that says "increase timeout to 30s because the export endpoint is slow for large accounts" tells a future reader something the code never could: the reason, the constraint, the trade-off. That context is what makes a change safe to modify later.
A useful default shape is a short summary line under about fifty characters, written as an instruction ("Add retry to upload"), followed by a blank line and a paragraph of reasoning when the change is not obvious. Trivial changes need only the summary; subtle ones deserve the paragraph.
One logical change per commit
A commit that bundles a bug fix, a rename and a new feature is impossible to describe honestly and impossible to revert cleanly. Keeping each commit to one logical change makes the history readable, makes reverting a single mistake possible, and makes review far easier because each step has one intention.
This does not mean tiny commits for their own sake; it means each commit should be a coherent unit you could describe in one sentence without an "and". If your summary needs an "and", it is probably two commits.
History is a feature, not exhaust
Treated well, the commit log becomes a searchable record of every decision the codebase ever made — why a workaround exists, when a bug was introduced, what a confusing function is actually for. Tools that trace a line back to its origin turn that record into instant answers, but only if the messages held real information.
The habit costs a few extra seconds per commit and repays them many times over the first time a production incident sends you spelunking through history at speed. Write for the tired person debugging at midnight. It will often be you.
The subject line answers "what", the body answers "why" — and both matter
A commit's subject line should describe what changed concisely enough to scan a long list of commits quickly, while the body — often entirely optional for a trivial change, essential for anything non-obvious — exists specifically to record why the change was made, a piece of context the diff itself cannot show no matter how carefully it is read: why this particular approach was chosen over an alternative, what specific bug or requirement prompted the change, what was deliberately considered and rejected along the way. A commit message that only restates what the diff already makes visible wastes the one part of a commit that could have preserved something the diff structurally cannot.
Imperative mood as a small convention with a real, checkable payoff
Writing a commit subject as 'Fix null pointer in checkout' rather than 'Fixed null pointer in checkout' or 'Fixes null pointer in checkout' follows a widely shared convention specifically because it reads naturally as completing the sentence 'if applied, this commit will...', which is genuinely useful in practice: Git itself writes automatically generated commit messages, like the default merge commit message, in exactly this same imperative mood, and consistent use of it lets a reader mentally complete that same sentence for every commit in a log, at a glance, without stopping to parse an inconsistent mix of tenses.
Why a commit message deserves the same care as the code it describes
Treating a commit message as an afterthought typed in ten seconds right before pushing, while treating the actual code change with real care, undervalues something that will likely be read far more times than any individual line of the diff itself — a commit's message shows up in `git blame`, in `git log`, in a pull request list, and in a release changelog, each a different context where a hastily written, uninformative message costs a future reader real time trying to reconstruct context that a few extra minutes of care at commit time could have preserved directly.
Referencing an issue number without letting it replace an actual explanation
Including a ticket or issue number in a commit message is genuinely useful for cross-referencing, but a commit message that says nothing more than 'Fixes JIRA-4821' outsources the entire explanation to a separate system that may not even be accessible to whoever is reading the commit later, and definitely is not visible directly in the terminal while running `git log` or `git blame` — the issue reference is a valuable addition to a real explanation written directly in the commit message, not a substitute for writing one at all.
Why 'various fixes' is close to the least useful commit message possible
A message like 'various fixes' or 'updates' technically satisfies the requirement that a commit have a message, while providing essentially none of the information any future reader of `git log` or `git blame` would actually be looking for — it fails the basic test of describing what changed clearly enough to be useful on its own, without needing to open the diff, which is the same bar every message discussed throughout this article is measured against, and 'various fixes' fails it about as completely as a message can while still technically being one.
Splitting an unrelated bundle of changes into separate commits before writing any message at all
A single commit that bundles a bug fix together with an unrelated formatting cleanup and a small refactor cannot be described honestly in one clear sentence, and the difficulty of writing its message is itself a signal that the commit should have been split into several smaller ones in the first place — good commit messages and well-scoped, atomic commits are not two separate skills, the second is usually a prerequisite for the first, since no amount of careful wording can make an inherently mixed-purpose commit describable in a single, clear, honest sentence.
Why a commit message should be written before, not after, the diff is finalized
Writing the commit message first, as a short statement of intent before finishing the actual change, forces a moment of clarity about what the change is actually meant to accomplish, and frequently reveals that the diff in progress has quietly grown to include something the original intent never covered — catching that scope creep by writing the message first, rather than retrofitting a description onto whatever the diff happened to become, is a small habit that keeps both the commit and its message honestly aligned with each other.
Why a commit message should describe the change relative to the codebase, not relative to the ticket
A message like 'implement the feature described in the ticket' is meaningless without also having the ticket open, while 'add rate limiting to the login endpoint' stands on its own regardless of whether the reader has any other context available — writing every commit message as though the reader has no access to any external ticket, chat log, or conversation, describing the change entirely in terms the codebase itself would recognize, is what keeps the message useful in the specific, common situation where the external context has long since become unavailable.
Why a commit message is a better place for context than a code comment sometimes is
A comment explaining why a particular line exists lives permanently in the source and is visible to anyone reading that file, which is exactly right for context that matters every time the code is read — but context about why a change was made at a specific point in time, in response to a specific now-resolved situation, often fits more naturally in the commit message that introduced it, discoverable through `git blame` exactly when someone is trying to understand that specific line's history, without permanently cluttering the file itself with historical narration that stops being relevant once the situation it describes has passed.
Why a team benefits from a shared, lightweight commit message template
A short, agreed structure — a one-line imperative summary, a blank line, then a brief explanation of why — gives everyone on a team a consistent starting point rather than each person inventing their own format independently, and Git itself supports configuring a commit message template file that pre-fills this structure automatically every time `git commit` opens an editor, turning the convention into something the tooling gently reinforces rather than something purely dependent on individual memory and discipline.
Why automated commit-message linting only catches the mechanical half of the problem
Tools exist to enforce commit message format automatically — a required prefix, a maximum subject line length, imperative mood — and they genuinely help with consistency, but they cannot verify that a message actually explains why a change was made, only that it is shaped correctly; treating a passing format check as confirmation that a commit message is actually good conflates the mechanical, checkable half of the problem with the substantive half no automated tool can currently evaluate.
Why a good commit message sometimes takes longer to write than the code itself
For a genuinely subtle fix — a race condition, an obscure edge case discovered through painful debugging — writing a commit message that actually captures the reasoning can reasonably take longer than the one-line code change itself, and that time is not wasted, since it is often the only place that specific hard-won understanding gets written down at all, before it fades from the author's own memory within a few months.
Why a commit message written for a teammate is also written for a future automated tool
Change logs, release notes, and increasingly AI-assisted tools that summarize a release all draw directly on commit messages as their raw source material, which means a clear, well-structured message benefits not only the human reader this whole article has focused on, but also every automated process downstream that depends on commit history actually containing something worth summarizing in the first place.
Why a commit message is worth reading back before actually committing
Pausing to reread a drafted commit message once more before finalizing it, the same way one might reread an email before sending it, catches an outdated or actually inaccurate description a surprising fraction of the time — code changes during the course of writing a commit more often than authors expect, and a message drafted early can drift out of sync with what the diff actually ended up containing by the time it is finally committed.