A commit message is not a diary entry for you at 6 p.m. It is a note to someone who will bisect, revert, or blame this change in six months, possibly you. “fix” and “updates” and “wip” make that person guess. Guessing in git history is how a one-line revert becomes an afternoon.
You do not need a conventional-commits religion to write useful history. You need a subject that says what changed, a body when the why is not obvious, and commits that are small enough to revert without taking down a feature you did not mean to touch.
This is how to write commits that survive review and production, without turning every save into a ceremony.
What a good commit is for
Git is a time machine with comments. The comments are how you choose which time to land on.
A useful commit does one thing you could describe in a sentence. “Allow checkout without a phone number” is a thing. “Various checkout fixes and lint” is a bag. Bags are hard to revert. They are hard to review. They hide the risky line in 400 unrelated ones.
The subject line is the sentence. The body is for the tradeoff, the link to the ticket, and the thing you tried that did not work. If the subject is enough, skip the body. Empty bodies on non-obvious bugs are how the same bug returns.
Subject lines that survive blame
Write in the imperative, like git does: “Add,” “Fix,” “Remove,” not “Added” or “Fixes.” You are describing what the commit does when applied.
Keep it around 50–72 characters so git log --oneline stays readable. Put the ticket id at the end or in the body, not as the whole subject. PROJ-1843 is not a message. Skip GST for digital gift cards (PROJ-1843) is.
Say the outcome, not the file. “Update UserService.ts” tells nobody whether you fixed a leak or renamed a variable. “Stop logging access tokens in UserService” does.
Avoid “refactor” as the only word. Refactor to what? “Split billing from UserService so invoices can retry” is a refactor people can use.
Humor dies in git blame. Future you is not in the joke. A dry subject is a kindness.
When the body is worth writing
Write a body if a reviewer would ask why.
Why this approach, not the obvious one. Why a feature flag. Why you deleted tests that looked related. Why the migration is irreversible. Why this is a copy of a vendor workaround.
Do not paste the diff. Git already has the diff. Do not paste a changelog of every file. Do not write a blog post. A few short paragraphs beat a wall.
If you used a workaround, name the bug. “Work around postgres 14 planner choosing seq scan on orders(created_at); remove when we upgrade” is gold in two years.
Atomic commits without becoming a monk
Atomic does not mean one file. It means one idea. A migration plus the code that needs the new column can be one commit if they cannot ship separately. A typo fix in a README plus a payment change should not be one commit.
If you mixed formatting and logic, split them. Autoformat commits that touch a thousand lines make the real change invisible. Format in its own commit, or format the whole project once and stop.
Interactive rebase exists for the mess you made locally. Squash the “typo” and “actually typo” commits before you push to a shared branch. Do not rebase commits other people already based work on. That is how you become the person who force-pushes main.
WIP, fixup, and the branch that never ends
git commit --fixup and git rebase -i --autosquash are how you keep a feature branch readable while you iterate. Use them. Then squash before merge if your team prefers a linear story. If your team prefers merge commits, still avoid merging a branch whose last twenty messages are “wip.”
“WIP” on main is a defect. “WIP” on a draft PR is a signal. Label the PR draft. Do not make the commit message the only signal.
Co-authors, generated files, and noise
If a pair wrote it, Co-authored-by is correct and kind. If a bot formatted it, say so in the body so people do not hunt a human for a Prettier diff.
Do not commit node_modules, build output, or .env. If you did, the next commit should remove them and add the ignore rule. The message should say you removed secrets, and you should rotate those secrets. History still contains the file until you rewrite, which you may not want to do on a shared repo. Rotate first. Rewrite only with a plan.
Lockfiles belong in commits when the app depends on them. Explain in the body if you upgraded a transitive dependency on purpose.
Messages that help git bisect
Bisect needs commits that compile and pass a smoke test. A commit that is “halfway through the feature” will fail the bisect script for unrelated reasons. If you must checkpoint broken work, do it on a local branch and squash before the branch others will bisect.
When you fix a regression, mention the commit or PR that introduced it if you know. “Regressed in abc123 when we cached user.” That is a gift to the next bisect.
Team conventions without the cult
Conventional Commits (feat:, fix:) help changelog tools. They do not replace a clear subject. feat: stuff is still a bad commit. feat(checkout): allow guest email-only orders is fine.
If your CI parses prefixes, use them. If nobody parses them, do not pretend they add quality. Clarity is the quality.
Agree on language. Mixed “fixe” and “bugfix” and “hotfix” in the same repo make search harder. Pick “Fix” and move on.
A simple personal checklist before you push
- Can I revert this commit without taking extra features with it?
- Does the subject describe the change, not my mood?
- Would I understand this after a vacation?
- Did I leave a body for the non-obvious why?
- Did I avoid committing secrets and generated junk?
If you squash on merge, the PR title becomes the commit. Then the PR title must follow the same rules. A PR named “updates” becomes a permanent updates on main. That is how history rots even when individuals write careful local commits.
Rewriting history, briefly
commit --amend is for the last commit, unpushed, when you forgot a file. It is not for rewriting a week of shared work. If you already pushed and others pulled, add a new commit. Pride about a perfect log is cheaper than a broken branch for three teammates.
If you must rewrite a private branch, force-push to that branch only, tell anyone who checked it out, and never force-push main unless the team has a practiced incident process.
The point
Git already records what changed. Your job is to record what it meant. A useful commit is a small, reversible idea with a subject that can stand alone in --oneline. That is enough to make blame useful, bisect possible, and review humane.
Artikals is full of this kind of engineering hygiene because it is what actually scales a team. Fancy branching strategies do not save a log full of “fix2.” Write the sentence. Split the bag. Leave the why. Your teammates will not thank you in Slack. They will just stop dreading git log.