Git Commit Messages That Help Future-You: A Practical Standard
Git blame tells you who changed a line of code. The commit message tells you why. Without a good commit message, you know the what (the diff) and the who (the author), but not the why. And the why is almost always the most important piece of information when you are trying to understand code six months later.
I spent years writing commit messages like "fix bug," "update styles," and "WIP." Then I needed to debug a production issue by tracing through git history, and those useless messages cost me hours.
Here is the standard I adopted that makes git history genuinely useful.
The Format
Every commit message follows this structure:
type(scope): short description (under 72 characters)
Longer explanation of what changed and WHY it changed.
Include context that will not be obvious from the diff.
Refs: #123, #456
The type indicates the nature of the change: feat (new feature), fix (bug fix), refactor (code restructuring without behavior change), docs (documentation), test (test additions/fixes), chore (build, deps, CI changes), perf (performance improvement).
The scope indicates which part of the codebase: feat(search): add fuzzy matching, fix(auth): handle expired refresh tokens, refactor(tools): extract shared card component.
Why Type and Scope Matter
When scanning git log, type and scope let you filter mentally:
Looking for when search broke? Scan for fix(search) commits.
Looking for new features in the last sprint? Filter for feat commits.
Wondering why the bundle size increased? Check for feat and chore(deps) commits.
Without type and scope, every commit message looks the same in a list. You have to open each diff to understand what it touched.
The Body: Explaining Why
The commit body answers: "Why was this change necessary? What was the context?"
Bad commit body: "Fixed the function to use the correct variable." This restates the diff. I can see that from the code change.
Good commit body: "Users reported that filtering by category returned stale results. The issue was that the cached results were being used even when the filter parameter changed. The cache key now includes the filter parameters, so different filters produce different cache entries."
The good body tells future-me: what the symptom was (stale results), what the root cause was (cache key did not include filter params), and what the fix strategy was (include filters in cache key). Six months later, if a similar caching bug appears, this commit message helps me understand the pattern.
Rules I Follow
One logical change per commit. If fixing a bug requires changing three files, that is one commit. If fixing a bug AND adding a feature, that is two commits. Each commit should be a single, coherent change that could be reverted independently.
Never commit commented-out code. If you need the code later, it is in git history. Commented-out code in commits creates confusion: is this intentional? Should someone uncomment it? Was it accidentally left in?
Write the message before committing. Do not stare at the diff and summarize it. Think about what you would want to know in six months and write that.
Use imperative mood in the subject: "add fuzzy matching" not "added fuzzy matching." The convention matches how git itself writes messages (Merge branch, Revert commit). It reads as "applying this commit will: add fuzzy matching."
Interactive Rebase: Cleaning Up Before Sharing
During development, I commit frequently with rough messages. Before pushing (before creating a PR), I rebase interactively and clean up:
Squash "WIP" commits into meaningful commits. A feature that took 15 rough commits during development becomes 2-3 clean commits in the PR.
Edit messages to be descriptive after the work is done. You write better messages when you have the full context of the completed change, not while you are in the middle of figuring it out.
Reorder commits to tell a logical story. The reviewer should be able to read commits in order and understand the progression of the change.
Commit Messages and Code Review
In a PR, each commit should be reviewable independently. The reviewer can go commit-by-commit and understand the progression:
Commit 1: "refactor(search): extract search query building into separate function" — pure refactoring, no behavior change. Reviewer verifies the refactoring is correct.
Commit 2: "feat(search): add fuzzy matching to tool name search" — the actual feature, using the refactored code. Reviewer focuses on the new behavior.
Commit 3: "test(search): add tests for fuzzy matching" — tests for the new feature. Reviewer verifies test coverage.
This is dramatically easier to review than one giant commit titled "add search improvements" with 500 changed lines.
When Commits Do Not Matter
For personal projects with no collaborators: commit messages matter less because you are the only audience. But I still write decent messages because future-me is a stranger with no memory of past-me's context.
For rapid prototyping: during hackathons or exploratory spikes, rough messages are fine. The code might be thrown away entirely. Do not let commit message perfectionism slow down exploration.
For trivial changes: "fix typo in README," "update deps," "bump version" — these do not need bodies or detailed explanations. The type makes them scannable and that is sufficient.
Tools That Help
Commitlint: enforces commit message format in CI. PRs with non-conforming messages fail the check. This maintains consistency across team members without relying on memory.
Conventional Commits: the formalized version of the type(scope): description format. Many tools (semantic-release, auto-changelog) parse conventional commits to automatically generate changelogs and determine version bumps.
Git hooks (Husky + commitlint): validate commit message format locally before the commit is created. Catches formatting issues immediately instead of in CI.
The Investment
Writing good commit messages takes an extra 30-60 seconds per commit. Over a year of development, that is maybe 3-4 hours of additional time.
The return: every time you use git blame to understand code, every time you review git log to trace a bug, every time a colleague reads your PR — good messages save minutes. Those minutes compound across the team and across time.
A codebase with good commit messages is a codebase with searchable, understandable history. That history is a form of documentation that never goes stale because it describes what actually happened, not what someone planned to document.
Written by AIDToolStack Editorial
The AIDToolStack editorial team reviews developer tools, AI products, and MCP servers with hands-on testing and honest assessments. We use the tools we review every day in production.
References & Further Reading
Related Posts
Sponsor Our Newsletter
Reach thousands of developers who are actively evaluating AI tools, MCP servers, and dev infrastructure. Our weekly newsletter goes to engaged technical decision-makers.
All sponsored content is clearly labeled per our editorial policy.