Why your future self will thank you for clean history.
A commit message is the only documentation that travels with the code forever. Comments get deleted, wikis go stale, tickets get archived — but git log is permanent. Teams that write good commits can navigate their own history. Teams that don't are doing archaeology in their own codebase within six months.
A good commit has a clear subject line in imperative mood ("Add rate limiting to auth endpoint", not "Added rate limiting" or "rate limiting stuff"), an optional body that explains why the change was made (not what — the diff shows what), and contains exactly one logical change.
fix: prevent duplicate webhook deliveries
The webhook dispatcher was firing on both the DynamoDB stream event
and the API Gateway trigger. Deduplicate by checking idempotency key
before dispatch.
Compare that to what most repositories actually contain:
fix stuff
wip
updates
The first commit is searchable, revertable, and self-documenting. The second set is noise. Six months from now, "fix stuff" tells you nothing — you'll have to read the diff, reconstruct the context, and guess at intent. The 10 seconds you saved writing a lazy message costs 10 minutes every time someone encounters it.
Conventional Commits add a lightweight prefix to every commit subject that makes your history machine-readable without sacrificing human readability. The format is simple:
feat: add dark mode toggle to settings
fix: correct timezone offset in standup scheduler
chore: upgrade TypeScript to 5.7
docs: add API rate-limit documentation
refactor: extract validation logic from route handler
The prefix tells you the category of change at a glance. feat means new user-facing behavior. fix means a bug was corrected. chore means maintenance that doesn't affect users. This costs nothing — you're already writing a subject line, you're just prepending 4–8 characters.
The payoff is automatic changelogs, semantic versioning (a feat bumps the minor version, a fix bumps the patch), and instant filtering (git log --grep="^fix:" shows you every bug fix in the last quarter). Teams that adopt Conventional Commits report that their PRs get reviewed faster because the reviewer knows what kind of change to expect before reading a single line of diff.
You don't need tooling to start. Just agree on the prefixes as a team and enforce them in code review. Add automation later if you want — but the convention is useful on its own.
A commit should represent exactly one logical change. Not "everything I did today," not "all the files I touched for this feature," and definitely not "WIP" — a message that communicates nothing and makes the commit unrevertable without side effects.
Atomic commits matter for two practical reasons. First, revertability: if a commit does one thing, reverting it undoes one thing. If a commit does three things and one of them is broken, reverting it undoes all three — including the two that were fine. You're now fixing collateral damage from your own rollback.
Second, bisectability: git bisect binary-searches your commit history to find which commit introduced a bug. It's one of Git's most powerful debugging tools — but it only works when each commit is a coherent unit. If your commits mix unrelated changes, bisect will point you to a commit that contains both the guilty change and three innocent ones, and you're back to reading diffs manually.
The "WIP" commit is the enemy of both properties. It says "I wasn't done thinking" — which means the commit contains partially-completed work that can't be reverted cleanly, can't be bisected meaningfully, and can't be understood in isolation. If you need to save work-in-progress, use git stash or a draft branch. Don't pollute shared history with checkpoints that have no semantic value.
The PR description test
If you can't describe your commit in one line without saying "and", it's probably two commits.