o'ailly

Chapter 6 — History Is Append-Only (For You)

Draft status: author draft; human verification pending. Outputs are real transcripts; the push rejection in the two-operator listing is a genuine refusal between real repositories.

The covenant

Chapter 1 proved the store cannot be silently rewritten — content is identity, history is a hash chain — and immediately named the soft spot: the names pointing into the store move freely, and moving a shared name backward or sideways is how git’s strongest guarantee gets converted into its worst accident. This chapter is the covenant that guards the soft spot, and its statement is one sentence with two clauses: published history is append-only; private history is yours to reshape until the moment it is published. The second clause was chapter 2’s repair window. The first is this chapter, and for the register’s operators it carries the force of volume two’s append-and-complete rule with higher stakes, because the ledger being protected is not one lineage’s estate but every colleague’s foundation. A force-push that rewrites a shared branch does not merely lose work; it forges the record — commits colleagues hold, cite, and build on cease to be part of the official past — and it does so with the authority of the shared remote, which is why fleets configure the capability away (protected branches, receive.denyNonFastForwards) rather than trusting each operator’s restraint. The register’s posture: the operator never force-pushes shared branches, treats the ability to do so as a configuration bug to report, and reserves --force-with-lease — the guarded form that refuses if the remote moved since last look — for the one legitimate venue: its own unmerged task branch after a sanctioned reshaping pass, where the only history rewritten is history nobody else has built on.

The public undo

The covenant would be unbearable without a compliant undo, and the ledger has always had one — the entry that records the reversal of an earlier entry:

mkdir work && cd work
git init -q -b main; git config user.email [email protected]; git config user.name operator
printf "retries = 3\n" > app.conf; git add -A; git commit -qm "baseline"
printf "retries = 9000\n" > app.conf; git commit -qam "raise retries aggressively"
git revert --no-edit HEAD >/dev/null
cat app.conf
git log --oneline
retries = 3
69b2794 Revert "raise retries aggressively"
2298e6a raise retries aggressively
b088612 baseline

The file is back to baseline and the history holds three entries: the mistake, standing; the reversal, explicit; the ledger, intact. Volume one’s reversibility ladder put “the undo that carries its own record” on the top rung, and revert is exactly that — the anti-commit computed and applied as a new commit, safe on published history because it appends. The register’s operational notes: revert the newest first when unwinding a sequence (reverts apply cleanly in reverse order); reverting a merge needs -m 1 to name which parent’s line survives, and un-reverting a reverted merge holds enough subtlety that the operator reads the documentation’s own essay before attempting it; and the revert’s message — auto-generated naming the target — earns a body stating why the reversal, because “Revert X” answers what while the incident that demanded it answers why, and chapter 2’s message discipline does not pause for emergencies. Under incident pressure the decision tree is short: revert now (reversible, auditable, fast — the register’s default), fix forward only when the revert itself would break consumers who adapted, and either way the estate’s ledger records the operation with the commit hashes as evidence.

The black box recorder

Private history’s freedom needs a safety net, because “yours to reshape” includes “yours to destroy by accident” — the mistaken reset --hard, the amend that vaporized a version, the branch deleted with work aboard. The net is the reflog: a per-repository, private journal of every place each ref has pointed, written automatically, consulted almost never until the day it is the only thing that matters:

mkdir work && cd work
git init -q -b main; git config user.email [email protected]; git config user.name operator
echo keep > a.txt; git add -A; git commit -qm "work worth keeping"
echo alsokeep > b.txt; git add -A; git commit -qm "later work, about to be lost"
git reset -q --hard HEAD~1
echo "after the mistaken reset: $(git log --oneline | wc -l) commit(s); b.txt exists: $([ -e b.txt ] && echo yes || echo no)"
git reflog --format="%h %gs" | head -3
git reset -q --hard HEAD@{1}
echo "after recovery:           $(git log --oneline | wc -l) commit(s); b.txt exists: $([ -e b.txt ] && echo yes || echo no)"
after the mistaken reset: 1 commit(s); b.txt exists: no
d0b519e reset: moving to HEAD~1
ab02515 commit: later work, about to be lost
d0b519e commit (initial): work worth keeping
after recovery:           2 commit(s); b.txt exists: yes

The “lost” commit was never lost — chapter 1’s amend demonstration already showed that rewriting abandons rather than destroys — and the reflog is the index of the abandoned: every entry a former position, with the operation that moved it (reset: moving to…, commit:, the narrative of the repository’s recent life), addressable by the HEAD@{n} syntax the recovery uses. The register’s facts to hold: the reflog is local and private (it does not push, it does not clone — a fresh clone starts an empty one, which is one more reason inherited repositories get briefings, not assumptions); it expires (defaults measured in weeks — a recovery deferred is a recovery forfeited); and it covers ref movements, so work that was never committed was never in its jurisdiction — the register’s oldest rule, commit at observable stages, is also the rule that keeps everything inside the recorder’s reach. For the operator, the reflog converts the scariest moments of repository life into volume one’s calm protocol: stop, read the recorder (reflog with a format, bounded), identify the last good position, move back with intent, and record in the session ledger what was recovered and how it was endangered.

The horizon: how long abandoned means recoverable

The recorder’s expiry deserves its own honest accounting, because “nothing is ever really lost” is folklore with a clock on it. Abandoned objects — the amended-away commits, the reset-away work — persist in the store as unreachable objects until garbage collection actually removes them, and gc’s own rules keep them well past the reflog entries that name them: reflog entries expire (order of ninety days for reachable history, thirty for the unreachable, tunable), and unreachable objects get a grace period beyond that before pruning. The operational reading has two halves. For recovery: inside the horizon, everything this chapter promised holds, and even a deleted branch — whose own reflog dies with it — remains recoverable through HEAD’s reflog (which recorded every checkout and commit on it) or, past the reflogs entirely, through fsck --lost-found, the deep sweep that inventories every unreachable commit in the store and parks them for inspection: slower, nameless, but exhaustive — the recovery ladder’s last rung before “restore from a colleague’s clone.” For hygiene: the horizon means private mistakes genuinely do fade — the repository does not archive every keystroke forever, which is by design and fine — so anything worth guaranteed survival gets the only guarantee the system offers: reachability. A ref pointing at it — a branch, a tag, volume two’s estate holding the hash of an artifact commit — exempts an object from every expiry. The rule compresses to one register sentence: the recorder buys you weeks; the ledger buys you forever; know which one you are trusting before you need either. And the session-bound corollary, since this reader’s weeks pass without continuity of memory: a recovery deferred to a future session is a recovery delegated to the estate — the endangered hash goes into the ledger now, this session, with the reason, or the future session that finally has time will have a recorder with nothing left to say.

The lease, precisely

Because --force-with-lease is this chapter’s one sanctioned crossing of a published boundary, its mechanics deserve the precision the register gives every dangerous instrument. The lease is compare-and-swap — volume two’s optimistic concurrency, applied to a ref: “move origin/task to my new history only if it still points where I last saw it.” Plain --force is the unconditional write, and the difference is exactly the lost-update demonstration from volume two’s opening chapter: between an operator’s last fetch and its forced push, a colleague (or a CI bot, or yesterday’s own forgotten session) may have pushed to the task branch, and the unconditional force erases that work without ever learning it existed, while the lease refuses and reports. The caveat that keeps the lease honest is its reference point: bare --force-with-lease compares against the local remote-tracking ref — your last fetch’s knowledge — so a fetch that happened moments before the push makes the lease current, while a stale tracking ref makes the lease a guarantee about last Tuesday. The ritual, therefore: fetch, verify the branch state is the one your reshaping consumed (the counts, chapter 3), then push with the lease, in one composed sequence — and if even that window feels wide, the explicit form (--force-with-lease=task:<sha>) pins the expectation to an exact hash, closing it entirely. An operator that force-pushes its own task branch through that ritual has rewritten nothing anyone held; an operator that skips the fetch has a lease against its own ignorance — validity theater, in volume one’s phrase, and the reason the ritual is stated here rather than left to be derived.

Knowing which side of the boundary you stand on

The covenant’s two clauses meet at a question every reshaping session must answer first: is this history published? — and the register answers it with queries, not memory, because memory is what session-bound operators do not have. The containment checks: branch -r --contains HEAD names every remote branch that already holds the commit about to be reshaped (any answer at all means the boundary is behind you); rev-list --count @{upstream}..HEAD counts the commits that exist only locally — the reshapeable surplus — while its inverse counts what the remote holds that you lack; and for the subtler case of shared-but-unmerged (a colleague fetched your task branch even though no integration happened), the fleet’s convention carries what no query can — which is why chapter 5’s dispatch discipline treats task branches as single-owner by naming, making “who else might hold this?” answerable from the branch name itself. The pre-reshape ritual assembles in three lines: fetch (fresh knowledge), containment check (which side of the boundary), count check (what exactly is in scope) — and only then the autosquash pass, whose scope the counts just defined. The habit’s cost is seconds; what it prevents is the covenant’s only innocent violation mode — the operator that rewrote published history believing it private, whose sincerity will comfort nobody rebuilding on Monday.

The refusal that saves the fleet

The covenant’s enforcement at the remote is a refusal every operator meets weekly, and its correct reading is the difference between fleets that converge and fleets that clobber. Two real operators, one real shared remote:

mkdir work && cd work
git init -q --bare -b main origin.git
git clone -q origin.git op-a 2>/dev/null && ( cd op-a && git config user.email [email protected] && git config user.name op-a && echo base > f && git add -A && git commit -qm base && git push -q origin main 2>/dev/null && git branch -qu origin/main )
git clone -q origin.git op-b 2>/dev/null
( cd op-a && echo "colleague progress" >> f && git commit -qam "advance the work" && git push -q 2>/dev/null )
( cd op-b && git config user.email [email protected] && git config user.name op-b && echo "stale change" > g && git add -A && git commit -qm "work from a stale view" && git push origin main 2>&1 | grep -E "rejected|fast-forward" | head -2 )
( cd op-b && git fetch -q && git merge -q --no-edit origin/main && git push -q origin main 2>/dev/null && echo "after fetch and merge: push accepted" )
git -C origin.git log --oneline main | head -4
 ! [rejected]        main -> main (fetch first)
hint: Updates were rejected because the remote contains work that you do not
after fetch and merge: push accepted
252aa35 Merge remote-tracking branch 'origin/main'
b0b0056 work from a stale view
9a536b6 advance the work
67d744e base

Operator B pushed from a stale view; the remote refused — “the remote contains work that you do not” — and the resolution was never force but integration: fetch the colleague’s progress, merge it (chapter 5’s protocol, judgment and all), push the union. The final log holds everything: A’s advance, B’s work, and the merge that joined them — against the alternative timeline where --force “fixed” the rejection by erasing A’s commit from the shared record. The non-fast-forward refusal is the exact analog of volume two’s BUSY: coordination working, addressed to you, meaning someone else did legitimate work; reconcile before publishing. Its diagnostic reading follows the same taxonomy — routine when the fleet is active (fetch, integrate, retry); suspicious only when it contradicts the topology (a refusal on a branch only you own means another seat is writing where it should not — a finding for the fleet briefing, not a bigger hammer). And the recovery story when someone has forced a shared branch completes the chapter’s arc: the distributed design means every clone that held the erased commits still holds them — chapter 1’s full-replica inheritance as the fleet’s collective reflog — so the response is volume one’s incident protocol (stop, inventory who holds what, re-push the erased work, then fix the configuration that allowed the erasure), and the uncomfortable truth-telling afterward belongs in the record, because a forged ledger quietly repaired is a ledger nobody should trust twice.

The unprotected zone

This chapter’s safety nets share one prerequisite that must be said in warning type: they catch committed work. The reflog records ref movements; the store holds objects; and uncommitted changes are neither — which makes the small family of commands that overwrite the working tree the only genuinely unrecoverable destroyers in daily git, and the place volume one’s blast-radius doctrine applies at full strength. restore <file> (and its ancestor spelling checkout -- <file>) replaces the working copy with the committed version — correct when discarding is the intent, fatal when the working copy was the only home of an hour’s work; reset --hard does it tree-wide; clean deletes untracked files that no git mechanism has ever seen. The register’s handling is exactly its handling of rm: proof-of-intent before dispatch (the diff of what is about to be discarded, read — discarding unread changes is deleting a file unlisted), clean -n always rehearsed before clean -f (the dry-run exists; volume one’s doctrine requires it), and the structural cure outranking all vigilance: chapter 2’s commit cadence keeps the unprotected zone minutes wide, because work that commits at observable stages has, at any instant, almost nothing standing outside the nets. The zone cannot be closed — a working tree is by design the one place git lets state exist without history — but a fleet whose habits keep it narrow has converted this warning from a hazard into a footnote, which is where every hazard in this series is sent to live.

Tombstones for finished work

Chapter 5’s lifecycle deletes integrated branches, and the covenant adds the pattern for the exceptions — work a fleet wants findable forever without keeping branch namespace cluttered: the archive tag. A branch whose story matters after death (the abandoned approach whose reasoning future sessions will want, the release line no longer maintained, the experiment that answered its question negatively) gets an annotated tag under a reserved namespace — archive/session-95-linear-retry, message stating why it ended (volume two’s reason column, once more) — and then the branch itself dies on schedule. The mechanics lean on facts already established: the tag holds the commits reachable forever (the horizon section’s only guarantee), annotated tags carry their own provenance, and the namespace keeps branch listings clean while tag -l 'archive/*' remains one query — the graveyard with an index, which is precisely what volume one’s quarantine pattern prescribed for state too meaningful to delete and too dead to keep underfoot. The reading side completes it: chapter 3’s briefing treats a rich archive namespace as signal (this fleet finishes its stories), and the searcher who wonders “was linear retry ever tried?” finds the tombstone, its reason, and the full branch behind it — institutional memory of the negative result, which every research tradition knows is the memory most often lost and most expensive to lose.

The copy that is not a move

One instrument lives so near the covenant’s edge that operators misuse it in both directions: cherry-pick, which applies an existing commit’s change elsewhere as a new commit — same diff, same message by default, different parent, therefore (chapter 1’s arithmetic) a different hash. Misreading one: treating the pick as a move — the original still stands, and a fleet that picks a fix to a release branch while believing it relocated has two copies whose divergence nobody owns. Misreading two: panic at the duplicate — the same change under two hashes looks like history confusion until the mechanics are held. In practice the eventual merge usually resolves quietly, because both sides carry identical content and content-level merging has nothing to fight over; and where duplicates must be reasoned about before that, the patch-identity instruments exist for the purpose — rebase skips already-applied duplicates by patch-id, and log --cherry-mark annotates a range’s commits as equivalent-or-not across branches — though the graph reads strangely to chapter 3’s queries in the interim. The register’s rules make the instrument boring, which is the goal. Cherry-pick records its lineage: -x appends the (cherry picked from commit …) line, turning the copy into a citation — provenance across branches, the trailer discipline’s cousin, and non-negotiable for backports, where the whole point is that a future reader can join the release branch’s fix to its mainline original. Its legitimate genres are few and named: the backport to a maintenance branch (the canonical case), the hotfix promoted ahead of its branch’s integration, the salvage of one good entry from an abandoned branch (chapter 5’s triage). And its anti-genre is the one the covenant exists to prevent at scale: pick-based workflows that copy work between long-lived branches instead of merging it, manufacturing parallel histories of the same truths whose reconciliation is everyone’s eventual unpaid debt. Copy with citation, for the named genres, and let integration integrate — the pick is a scalpel, and fleets that use it as a conveyor rediscover why the merge exists.

History operations are ledger operations

Everything this chapter does to the shared ledger belongs in the private one, and the join closes the trilogy’s bookkeeping. A revert, a sanctioned reshape, a reflog recovery, a non-fast-forward reconciliation — each is a world-action in volume two’s exact sense, and each lands in the estate with the currency this chapter mints: hashes. The revert’s row carries the reverted and reverting commits; the reshape’s row carries the before-and-after branch tips (the before hash being, note, the reshaped history’s only durable name once the reflog horizon passes — the estate outlives the recorder, which is the previous section’s rule applied to the operator’s own paper trail); the recovery’s row carries what was endangered, by which operation, and where it was restored. The dividend arrives at review and incident time: when chapter 8’s reviewer asks “this branch’s history looks rewritten — sanctioned?”, the answer is a ledger row with a timestamp and a reason rather than a shrug; when a fleet postmortem asks who force-pushed what and when, the honest seats have receipts and the gap in receipts localizes the question. And the worked-incident narrative assembles all of it: the Monday force-push discovered (fleet briefing counts contradict the remote), the response ledgered step by step — inventory of holders, the erased commits’ hashes recovered from a colleague’s clone, the re-push, the config fix — and the journal entry written for the searcher who, a year later, types MATCH 'force push' and inherits the whole case instead of the folklore version. The covenant protects the shared record; the estate proves the covenant was kept; and an operator holding both has what this series has been assembling from its first chapter — an account of its conduct that does not depend on anyone’s trust in its word. The same join runs the other direction with equal force: the estate’s rows carry commit hashes wherever this volume taught the two records to meet — chapter 1’s boundary made the hash their foreign key, chapter 2’s Ledger-Op trailer runs the join the other way — and this chapter is where those hashes acquire their guarantee — an estate that cites 81305e9 cites something the covenant promises will still mean 81305e9, verbatim, for as long as the shared history stands. Cross-referenced records are only as strong as the weaker register; the covenant makes both strong.

Reshaping, sanctioned

The covenant’s private clause deserves its operating limits stated as positively as its public clause was stated prohibitively, because reshaping is not a guilty pleasure — it is how chapter 2’s published stages get made from checkpoint-grade drafts. The sanctioned pass, run on a task branch before first publication (or after, with --force-with-lease, while the branch remains unmerged and unshared by convention): rebase --autosquash folding the fixups (the scripted sequence editor from chapter 2), message rewording where claims sharpened, and — where the branch’s base drifted — rebase onto current main so the eventual integration diff is honest (chapter 5’s drift counsel; a rebase is also where rerere pays out). The register’s guards around the pass: it runs with a clean tree and a fresh calibration read of status and the branch counts; it never crosses the publication boundary without the lease guard, and never crosses a merge it shares with anyone under any guard; and its result gets the same four-question read as any inherited entry before pushing, because a reshaping pass is an author reviewing its own ledger, and chapter 2’s standards do not soften for self-review. Where rewriting is demanded on published history — the committed secret, the license violation, the legal removal — the operator recognizes the situation as the coordinated surgery it is: every holder must participate (the erased content lives in every clone and every reflog until they act), the secret is rotated regardless (history rewriting is not revocation — chapter 2 said it first), and the operation belongs to the supervisor’s authority with the fleet’s tooling (filter-repo-class instruments), not to any session’s initiative. The covenant, finally, is what makes the whole trilogy’s economics work: because published history only appends, everything built on it — the estates citing hashes, the bisections framing ranges, the reviews trusting diffs — builds on rock; and because private history reshapes freely, the rock is made of considered entries rather than keystroke archaeology. Append in public, reshape in private, and never confuse the two — the fleet’s whole version-control ethics, in twelve words. What remains is enforcement that does not depend on every seat’s memory of this chapter, which is what hooks are for, and where the next chapter begins.

1 / 1