Chapter 2 — The Commit as a Unit of Meaning
Draft status: author draft; human verification pending. Outputs are real transcripts from scratch repositories the listings build.
The entry, not the save
Every discipline in this chapter descends from one reframing: a commit is not a save; it is a ledger entry. The save mentality — accumulated changes flushed to safety whenever anxiety or a session boundary strikes — produces exactly the commits that make supervisors distrust machine operators: forty files, six unrelated intentions, a message that says “updates”. The ledger mentality asks of every commit the question volume two asked of every transaction: what single truth does this entry record? — and the costs of ignoring the question are concrete enough to enumerate, because each lands on a different chapter of this book. A monolithic commit cannot be reviewed well: the reviewer of chapter 8 must untangle which hunks serve which intention, and review quality degrades toward skimming — the operator’s trust-earning surface, squandered. It cannot be reverted alone: chapter 6’s public undo works commit-wise, so the emergency rollback of the bad half drags the good half with it. And it cannot be blamed precisely: chapter 4’s bisection identifies guilty commits, and a bisection that lands on a six-intention monolith has answered “which commit?” while leaving “which change?” — the question that actually matters — as manual archaeology. Review, revert, bisect: three machines that consume commits, all of which run better on small, single-truth entries. The operator does not shape commits to be tidy. It shapes them because everything downstream eats what it commits.
The register makes the discipline easier than it is for humans, which is worth saying plainly as encouragement. An interactive developer’s working tree accretes changes organically — exploration, side-fixes, drive-by cleanups — and untangling them at commit time requires the hunk-level staging this reader cannot use. A session-bound operator’s changes are already the output of deliberate, enumerated actions: volume one’s operators compose edits one intention at a time and verify each before the next; volume two’s ledger discipline records each world-action singly. The commits this chapter wants are those same units, carried one step further into the shared ledger. An operator that works in single truths and commits in monoliths is throwing away structure it already had.
The staging area is your transaction
Git’s staging area — the index — bewilders newcomers as pure ceremony: why not commit the working tree directly? For this book’s reader the answer is immediate, because volume two built the same machinery under a different name: the index is the staged copy in the atomic-swap pattern — the place where the next entry is assembled, inspected, and made exactly right while the working tree (the operator’s live workspace) churns on undisturbed. The semantics have one sharp edge that one-shot operators must know cold, because it bites precisely when a session edits, stages, and edits again:
mkdir work && cd work
git init -q -b main; git config user.email [email protected]; git config user.name operator
echo v1 > f && git add -A && git commit -qm base
echo v2 > f
git add f
echo v3 > f
git status --porcelain
git commit -qm "advance to v2"
echo "committed content: $(git show HEAD:f)"
echo "working tree: $(cat f)"
MM f
committed content: v2
working tree: v3
git add does not mark a file for committing; it snapshots the file’s
content at that moment into the index. The later edit (v3) exists only in
the working tree; the commit faithfully recorded the staged v2; and the
porcelain status told the whole story in two characters — MM, staged
modification and unstaged modification, the two-column code whose first
column describes index-vs-HEAD and second column working-tree-vs-index. An
operator that reads MM and commits anyway is choosing to publish v2 while
holding v3, which is occasionally exactly right (the staged version was the
reviewed one) and more often a session about to be confused by its own
ledger. The composition rule that prevents the accident is the same
edit-then-verify rhythm as ever: stage, then read status porcelain, then
commit — never add in one breath and commit in a distant later one with
edits between.
The index also answers the operator’s scoping instrument. git add -A is
the monolith machine: everything changed, everything staged, strays
included — the rm $f of this domain, correct only when “everything” is
genuinely one truth. The precise tool is the pathspec — staging by explicit
path or disciplined pattern — and with it, a working tree holding two
truths becomes two clean entries:
mkdir work && cd work
git init -q -b main; git config user.email [email protected]; git config user.name operator
printf "retries = 5\n" > service.conf
echo "notes on the outage" > incident.md
git add -A && git commit -qm "initial state"
sed -i "s/5/8/" service.conf
echo "root cause: dns" >> incident.md
git add service.conf
git commit -qm "raise retries to 8 for flaky upstream"
git add incident.md
git commit -qm "record outage root cause"
git log --oneline --stat | head -8
9a47ba2 record outage root cause
incident.md | 1 +
1 file changed, 1 insertion(+)
322bbcb raise retries to 8 for flaky upstream
service.conf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
fd86b0f initial state
incident.md | 1 +
One session, two intentions, two entries — each with a one-file stat a
reviewer absorbs at a glance, each revertable alone, each carrying its own
why. The log’s --stat rendering is the payoff made visible: history
that reads as a ledger. The boundary test, adapted from volume two’s
transaction rule: stage together exactly what a successor must never
half-see — the rename and every reference to the renamed thing, the
schema change and its migration entry, the fix and its test. And the
converse: changes that merely happened in the same session share no claim
on the same commit, however convenient -A makes their bundling.
Splitting below the file: the index takes patches
One staging problem seems to demand the interactive tool this reader
cannot use: two truths tangled inside one file — the bug fix and the
drive-by rename sharing a function, where add -p’s keystroke-driven
hunk picking is the human answer. The register’s answer is that the
index accepts patches, not just files, and patches are text an
operator can compose: git diff emits the file’s full change; the
operator splits that diff — keeping the fix’s hunks, dropping the
rename’s — and git apply --cached stages exactly the edited patch,
leaving the working tree untouched and the remainder for the second
commit. The craft caveats: hunk headers carry line offsets, so the
operator splits at hunk boundaries (whole hunks kept or dropped — the
common case, since distinct truths rarely share a hunk) rather than
editing hunk interiors, and verifies the split with the chapter’s
standing pair — diff --staged shows truth one, diff shows truth
two, both read before either commits. When the truths do share a
hunk, the honest fallback is simpler than patch surgery: edit the file
to contain only truth one (volume one’s file disciplines), commit,
restore truth two, commit — the working tree as staging area, two
clean entries, no tool heroics. Both routes close the last gap between
this chapter’s ideal and practice: there is no mixture the register
cannot separate into single truths; there are only mixtures whose
separation costs more than not creating them, which is what the
cadence section’s advice was quietly pricing all along.
What never enters the ledger
Staging discipline has a structural ally that decides most cases before any
session weighs them: the ignore policy. Chapter 1 drew the estate/repository
boundary in principle; .gitignore is where the boundary is enforced, and
the register treats it as policy code — versioned, reviewed, committed
first:
mkdir work && cd work
git init -q -b main; git config user.email [email protected]; git config user.name operator
printf "*.tmp\nestate.db*\n/artifacts/\n" > .gitignore
git add .gitignore && git commit -qm "ignore policy: scratch, estates, artifacts"
echo x > work.tmp; mkdir artifacts; echo y > artifacts/build.bin; echo z > estate.db
git status --porcelain
echo "--- why is estate.db invisible?"
git check-ignore -v estate.db work.tmp
--- why is estate.db invisible?
.gitignore:2:estate.db* estate.db
.gitignore:1:*.tmp work.tmp
The porcelain status printed nothing — three fresh files, all invisible,
because the committed policy already classifies them: scratch stays
scratch, estates stay private, artifacts stay in the artifact store. That
silence is the demonstration: an operator whose ignore policy is right
commits with -A far more safely, because the sweep can only gather what
policy admits. And check-ignore -v is the accountability query — which
rule, which line, decided this file’s fate — the first diagnostic when a
file refuses to stage or a stray appears where policy should have caught
it. Three composition rules complete the practice. The policy is written
for the categories this series already defined (scratch patterns, estate
files, build outputs, credentials), not accreted one annoyed filename at a
time. It ships in the repository’s first commits, because a policy that
arrives after the strays is archaeology. And it is not a security control:
ignore prevents accidental staging only — a secret that does get
committed is published history, and the recovery is chapter 6’s grim
exception (history rewriting coordinated with every holder, plus rotation
of the secret itself, because the chain remembers what the rewrite
removes from view). Cheap policy up front; no good options after.
The mirrored question — what does belong despite instinct — has a
register answer too. Generated files earn a commit exactly when colleagues
must review them or reproducibility requires them pinned: lockfiles yes
(they are the build’s truth, and their diffs are review material);
compiled outputs and rendered artifacts no (they are derivable, they bloat
every clone, and their diffs are noise — volume two’s artifact index is
their home). Empty commits — entries with a message and no diff — are
legitimate exactly where a ledger needs a marker whose evidence lives
elsewhere: a release point, a recorded decision; --allow-empty exists
because sometimes the claim is the content. Both rules are one principle
seen twice: the ledger records what its readers must weigh, and nothing
else.
Renames are inferred, not recorded
One storage fact shapes commit composition enough to earn its place
here: git does not record renames. The ledger stores snapshots (chapter
1’s trees); “renamed” is a conclusion tools draw at read time by
noticing a vanished path and an appeared path with sufficiently similar
content — which is why log --follow and blame -C exist as options
rather than defaults, and why their inference has a breaking point. A
rename combined with heavy edits in the same commit can drop below the
similarity threshold, at which point every reading tool sees an
unrelated deletion and creation: the file’s history amputates (chapter
3’s trap, now with its mechanism), blame restarts at zero, and review
displays a full-file replacement where a reviewer needed a diff. The
composition rule follows with unusual crispness: rename in one
commit, edit in the next — the move at near-100% similarity, trivially
inferred forever after, and the edit reviewed as the modest diff it is.
The same logic generalizes to every mechanical/semantic mixture (the
reformat-plus-fix, the move-plus-refactor): inference-dependent
readers, human and machine, survive the mechanical layer only when it
arrives pure. It is chapter 2’s one-truth rule again, but with teeth
the style argument lacked — mix the truths here and the tooling itself
starts telling worse stories about your history, to everyone, for the
file’s whole remaining life.
Commit cadence: entries at observable stages
One question remains before message craft: when, during a long
autonomous session, should entries land? Volume one answered for
operations (make each stage’s completion observable); the ledger version
is: commit at every observable stage — after each verified unit, not
at the session’s end in one heap, not at anxiety intervals mid-thought.
The payoffs compound across this series’ concerns. A session that dies
mid-task leaves a clean committed prefix plus a working tree holding
exactly the interrupted stage — volume one’s retry doctrine (read the
evidence, resume at the proven point) gets its evidence from status and
log instead of forensics. Review inherits stages instead of heaps.
Bisection inherits fine-grained history. And the estate’s run registry
gains its natural join: a session’s registry row, its ledger operations,
and its commit range tell one story in three registers. The cadence has a
floor as well as a ceiling — commits smaller than an observable stage
(one per file touched, one per command run) shred meaning as surely as
monoliths bury it; the unit is the verified stage: the test now passing,
the config now valid, the subsystem now migrated. Where safety wants
snapshots faster than meaning accrues, the private-branch checkpoint
pattern from this chapter’s close covers the gap: checkpoint freely,
reshape before sharing, publish stages.
The message is the claim
If the diff is the entry’s evidence, the message is its claim — the one part of the ledger written purely for future readers, and the part machine operators most reliably squander. The register’s composition, demonstrated whole and then dissected:
mkdir work && cd work
git init -q -b main; git config user.email [email protected]; git config user.name operator
echo x > f && git add -A
git commit -q -m "cap GPU power at 500W at boot" -m "PSU trips on transient spikes when both cards boost together; capping at boot prevents the trip window before the daemon applies profiles. Verified: vendor tool reads 500 after reboot." -m "Ledger-Op: gpu-power-cap:2026-08
Co-Authored-By: operator-session-93 <[email protected]>"
git log -1 --format="SUBJECT: %s%nBODY: %b" | head -6
echo "--- trailers, parsed:"
git log -1 --format=%B | git interpret-trailers --parse
SUBJECT: cap GPU power at 500W at boot
BODY: PSU trips on transient spikes when both cards boost together; capping at boot prevents the trip window before the daemon applies profiles. Verified: vendor tool reads 500 after reboot.
Ledger-Op: gpu-power-cap:2026-08
Co-Authored-By: operator-session-93 <[email protected]>
--- trailers, parsed:
Ledger-Op: gpu-power-cap:2026-08
Co-Authored-By: operator-session-93 <[email protected]>
The mechanics first, since they are the register’s whole reason this works
without an editor: repeated -m flags become paragraphs, so
subject-body-trailers composes in one shot, no $EDITOR trap, no here-doc
gymnastics required (though git commit -F - with a here-doc is the equal
citizen for messages built by tooling). The anatomy carries fifty years of
convention worth honoring because every tool downstream assumes it. The
subject is the claim compressed: imperative mood, capitalized, no period,
targeted under fifty characters and hard-capped by convention around
seventy-two, because --oneline views, forge UIs, and shortlog digests
show the subject alone — it is the entry’s row in every summary the
supervisor will ever scan. The body answers the question the diff cannot:
why — the situation that demanded the change, the alternative rejected,
and (house discipline from volume one) the verification performed, stated
as evidence. What the body never does is narrate the diff — “changed X to
Y” restates what show displays authoritatively; the reviewer has the
diff, and needs the reasons. And the trailers are the provenance block:
machine-parseable Key: value lines at the message’s end, extracted
cleanly by interpret-trailers as the transcript shows — attribution
(Co-Authored-By), issue linkage, and, for this book’s reader, the key
that closes the loop with volume two: a Ledger-Op: trailer carrying the
estate’s idempotency key binds the commit to the operation that produced
it, making “which session did this and what else did it do” a join instead
of an investigation.
Rehearse the entry
Volume one’s doctrine — rehearse anything you cannot take back — lands here with unusual grace, because the staging design gives the rehearsal for free. The staged entry can be read exactly as it will be recorded before recording it:
mkdir work && cd work
git init -q -b main; git config user.email [email protected]; git config user.name operator
echo a > f && git add -A && git commit -qm base
echo b >> f && echo temp > scratch.tmp
git add f
git diff --staged --stat
git diff --stat
f | 1 +
1 file changed, 1 insertion(+)
diff --staged answers “what will this commit contain?”; plain diff
answers “what am I leaving behind?” — here, nothing staged-but-unwanted
and nothing wanted-but-unstaged (the scratch file, untracked, correctly
appears in neither). That pair of reads, run before every commit, is the
proof-of-target discipline: the first is the entry’s preview, the second
the check that no intended change was orphaned. The full-text form
(diff --staged, unabridged) is the actual rehearsal for consequential
entries — bounded, per volume one, with --stat first and the full diff
only at the size the stat justifies — and git commit --dry-run adds the
final formality, reporting what would be committed without committing.
An operator that reads its staged diff before committing catches, at the
cheapest possible moment, every accident this chapter has named: the
stray file -A swept in, the v3-vs-v2 surprise, the second truth hiding
in the first truth’s entry. Thirty seconds of read against an immutable
entry in a shared ledger — volume one’s economics have rarely priced
anything so lopsidedly.
Wrong-sized anyway: the private repair window
Discipline notwithstanding, operators will sometimes commit and then see
the flaw — the typo in the subject, the file that belonged in the previous
entry, the truth that turned out to be two. The repair instruments exist
and are non-interactive; what bounds them is audience, and the bound is
absolute enough to state before the tools. A commit that has been pushed
to a shared branch is published history — other operators may already
hold it, build on it, cite its hash in their own ledgers — and repairing
it in place is chapter 6’s cardinal sin, forgery-shaped even when
innocent. A commit that exists only locally is a draft entry, and
drafts are the operator’s to reshape freely. Within that window: commit
--amend re-opens the newest entry (the chapter 1 demonstration showed
its mechanics — a new commit, the old abandoned), --amend --no-edit
folds a forgotten file into it, and deeper reshaping — combining fixup
commits into their targets across the last few entries — runs
non-interactively through the door volume one taught for every
editor-insisting tool: rebase --autosquash with the sequence editor
scripted (GIT_SEQUENCE_EDITOR=: accepts the generated plan verbatim),
consuming the commit --fixup=<target> entries the session dropped as it
noticed flaws. The pattern that keeps checkpoint anxiety and entry
discipline compatible: commit checkpoints freely on the private branch
while working — safety is cheap — then spend one reshaping pass before
the branch is shared, so what publishes is the ledger the work deserved.
The boundary, restated once because everything in chapter 6 hangs on it:
reshape drafts, never publications.
Reading an entry like an operator
Composition is half the craft; the other half is consuming commits others
made — the inheritance problem again — and volume one’s four-question
transcript routine adapts to the ledger entry nearly clause for clause.
First the claim against the evidence: does the subject describe what the
diff actually does? The disagreement cases are the diagnostic gold — a
subject narrower than its diff (“fix typo” touching four hundred lines)
flags either a careless bundler or a change hiding inside a trivial one,
and both readings demand the full diff before trust; a subject broader
than its diff flags work that was intended and not completed, the open
intent of volume two wearing git’s clothes. Second the shape: the
--stat silhouette before any content — file count, spread across
subsystems, insert/delete balance — because shape anomalies (the
one-line fix touching thirty files; the “refactor, no behavior change”
that is 90% insertions) are cheaper to catch than content anomalies and
usually decisive about how deeply to read. Third the provenance: author,
committer, trailers — who claims this work, which operation produced it
(the Ledger-Op join, when the convention holds), and whether the
verification the body claims is stated as evidence (“tests pass”) or as
hope (“should work”) — volume one’s evidence-theater detector, applied to
messages. Fourth the absence check: what the entry should contain and
does not — the test that should accompany the fix, the migration that
should accompany the schema change, the documentation the new flag owed —
because an entry’s gaps, like a transcript’s silences, are findings that
no amount of reading its contents will surface. The routine takes under a
minute against a well-shaped entry, longer against a monolith — which is
itself the economics of this chapter, experienced from the consumer’s
side, and the fairest argument for imposing on one’s own commits the
discipline one’s own reviews will wish for.
The reading commands compose to the routine’s rhythm, bounded per volume
one throughout. git show --no-patch --format=fuller <hash> serves
questions one and three in a dozen lines — full message, both identities,
both dates — without a byte of diff; show --stat adds the silhouette
for question two; and the full show, the expensive read, is spent only
on entries the cheaper reads flagged, with pathspec narrowing (show
<hash> -- path/) when only one file’s role is in question. The pager
trap applies to all of them under interactive detection and to none of
them under capture — but the operator that sets GIT_PAGER=cat in its
preamble never has to remember which, which was volume one’s argument
for preambles the day it made it.
The entry, then: one truth, staged precisely, previewed exactly, claimed in a subject the summaries will carry, justified in a body the diff cannot supply, attributed in trailers machines can parse, and repaired only while it is still yours alone. Ledger entries of that shape are what make the next chapter possible at all — because history worth reading is made of commits that were written to be read, and reading history is the operator’s next superpower.