PushBackLog

Atomic Commits

Advisory enforcement Complete by PushBackLog team
Topic: delivery Topic: version-control Skillset: engineering Technology: git Stage: execution Stage: review

Atomic Commits

Status: Complete
Category: Delivery
Default enforcement: Advisory
Author: PushBackLog team


Tags

  • Topic: delivery, version-control
  • Skillset: engineering
  • Technology: git
  • Stage: execution, review

Summary

An atomic commit contains exactly one logical change — no more, no less. Each commit should be independently sensible: the codebase should be in a consistent, working state before and after it. Bundling multiple unrelated changes into one commit makes git history opaque, makes bisection to find regressions harder, and makes reverting a specific change without affecting others impossible.


Rationale

Commits are the unit of rollback

When a deployment causes a production incident, the first tool is rollback. But which change caused the problem? If ten unrelated changes were bundled into one commit, rolling back means reverting all ten — including changes that are fine and may be urgently needed. Atomic commits make individual changes independently revertable: git revert <commit-sha> affects exactly the change it describes, nothing more.

Git history as documentation

The git log is a narrative of how the system evolved. “Refactor user service + fix payment bug + update dependencies” describes nothing useful. “Fix null pointer exception when user has no billing address” explains exactly what changed and why. A well-maintained history of atomic, descriptively-named commits is one of the most valuable forms of documentation in a codebase — searchable, permanent, and linked to code.

Bisection requires atomicity

git bisect binary-searches the commit log to find the commit that introduced a bug. This only works if each commit represents one coherent change and the test suite can identify the fault. If commits are large, multi-concern bundles, bisect finds the bundle but not the specific change — reducing its utility.


Guidance

What “one logical change” means

A logical change is a single coherent unit of intent. Examples:

AtomicNot atomic
fix: handle null billing address in checkoutfix payment bug, add user avatar, update README
feat: add POST /api/users endpointadd users endpoint and refactor auth middleware
chore: upgrade lodash to 4.17.21upgrade all dependencies
refactor: extract parseAddress() into utilsrefactor + add tests + fix bug found during refactor

When refactoring uncovers a bug, commit the bug fix separately from the refactoring. When a feature requires a dependency upgrade, commit the upgrade separately.

Staging partial changes with git

Git’s staging area allows committing part of a file:

# Interactively stage hunks (parts of files)
git add -p

# Or interactively stage lines in a specific file
git add -p src/checkout.ts

# Review staged changes before committing
git diff --staged

This allows a developer to write multiple changes in a single working session and then separate them into atomic commits before pushing.

Squashing and reorganising before push

If working on a branch with messy WIP commits, clean up before the branch is reviewed:

# Interactively rebase the last 5 commits — squash, reorder, rename
git rebase -i HEAD~5

# Commands available in interactive rebase:
# pick   — keep as is
# reword — keep but edit the message
# squash — combine with previous commit
# fixup  — combine with previous, discard message
# drop   — remove the commit

Important: only rewrite history for commits that have not been pushed to a shared branch. Never force-push to main or a branch others have checked out.

Relationship to Conventional Commits

Atomic commits work hand-in-hand with Conventional Commits. The commit type (fix, feat, chore, etc.) only makes sense for one logical change. A commit subject of fix: handle null billing address is atomic and descriptive. fix: address multiple bugs is a sign that the commit contains multiple changes that should be separated.

Work-in-progress commits

During active development, WIP commits are acceptable — they mark progress and serve as checkpoints. The important discipline is to clean up before creating a PR:

# WIP commits during development — these are fine
git commit -m "wip: start user validation"
git commit -m "wip: add tests"
git commit -m "wip: fix edge case"

# Before pushing for review — squash into atomic commits
git rebase -i origin/main
# → reorganise into: feat: add user email validation
# → or: feat: validate email + fix: handle existing-email error (two commits)

Review checklist

  • Each commit contains exactly one logical change
  • The commit message describes the what and the why of the change
  • The codebase is in a compilable, testable state at every commit
  • Unrelated changes (formatting, refactoring, bug fixes discovered en route) are committed separately
  • WIP commits are squashed before submitting for review