Chapter 19Lesson 05~180 minutes

Checkpoint Lab — Refs, Reflogs, Packed Refs, Symbolic References, and Reference Transactions

Checkpoint safe reference automation by creating tool-owned refs, moving them with expected-old guards, coordinating a multi-ref transaction, inspecting reflogs, packing storage, and rejecting stale writers.

CheckpointCAS-style refsRef transactionReflog evidence

Learning objectives

  • Predict and verify guarded ref transitions using captured old object IDs.
  • Create explicit reflogs for custom automation refs.
  • Commit two related guarded ref movements through one update-ref transaction.
  • Prove pack-refs changes storage rather than logical ref values.
  • Simulate stale single-ref and multi-ref writers and respond by re-reading/recomputing.

1. Checkpoint scenario — release automation needs safe temporary names

You will create three known commits and two tool-owned refs. You will predict/verify guarded movement, inspect reflogs, update both refs transactionally, pack refs without changing their meaning, then simulate a stale writer and prove that Git prevents it from overwriting concurrent state.

2. Predictions before running the lab

  1. If refs/automation/candidate is at A and you run a guarded A → B update, what must be true before it succeeds?
  2. If two refs are queued in one transaction and one expected old value is wrong, should either requested new value be committed?
  3. After pack-refs --all, should git rev-parse refs/automation/candidate change?
  4. If process 1 observed B, process 2 later moves the ref to C, should process 1's B-based guarded update succeed?

3. Create an isolated repository and three deterministic commit positions

Git Bash, Bash, or zsh

mkdir git-ref-checkpoint
cd git-ref-checkpoint
git init -b trunk repo
cd repo
git config user.name "Ref Checkpoint"
git config user.email "ref-checkpoint@example.invalid"

printf "state=A\n" > state.txt
git add state.txt
git commit -m "A: baseline"
A=$(git rev-parse HEAD)

printf "state=B\n" > state.txt
git commit -am "B: validated candidate"
B=$(git rev-parse HEAD)

printf "state=C\n" > state.txt
git commit -am "C: next candidate"
C=$(git rev-parse HEAD)

git log --oneline --decorate --graph

PowerShell

New-Item -ItemType Directory git-ref-checkpoint | Out-Null
Set-Location git-ref-checkpoint
git init -b trunk repo
Set-Location repo
git config user.name "Ref Checkpoint"
git config user.email "ref-checkpoint@example.invalid"

Set-Content state.txt 'state=A'
git add state.txt
git commit -m "A: baseline"
$A = git rev-parse HEAD

Set-Content state.txt 'state=B'
git commit -am "B: validated candidate"
$B = git rev-parse HEAD

Set-Content state.txt 'state=C'
git commit -am "C: next candidate"
$C = git rev-parse HEAD

4. Preflight repository identity, ref backend, and existing names

git status --short --branch
git rev-parse --absolute-git-dir
git rev-parse --show-ref-format
git show-ref --head
git reflog list

git show-ref --verify --quiet refs/automation/candidate
echo "candidate exists exit=$?"
git show-ref --verify --quiet refs/automation/verified
echo "verified exists exit=$?"

5. Create two tool-owned refs with explicit reflogs

git update-ref \
  --create-reflog \
  -m "checkpoint: create candidate at A" \
  refs/automation/candidate "$A" ""

git update-ref \
  --create-reflog \
  -m "checkpoint: create verified at A" \
  refs/automation/verified "$A" ""

git for-each-ref \
  --format='%(refname) %(objectname)' \
  refs/automation

git reflog show refs/automation/candidate
git reflog show refs/automation/verified

Both names should resolve to A and both should have a reflog because the creation requested one explicitly.

6. Prediction 1 — candidate A → B succeeds only if candidate is still A

OBSERVED=$(git rev-parse refs/automation/candidate)
test "$OBSERVED" = "$A"

git update-ref \
  -m "checkpoint: candidate A to B" \
  refs/automation/candidate "$B" "$OBSERVED"

git rev-parse refs/automation/candidate
git reflog show -2 refs/automation/candidate

Verify that the new value is B and the reflog includes the reason.

7. Prepare a two-ref state transition

The intended logical transition is: candidate B → C and verified A → B. Capture both actual old values first:

OLD_CANDIDATE=$(git rev-parse refs/automation/candidate)
OLD_VERIFIED=$(git rev-parse refs/automation/verified)

test "$OLD_CANDIDATE" = "$B"
test "$OLD_VERIFIED" = "$A"

8. Prediction 2 — commit both guarded ref movements in one transaction

Git Bash, Bash, or zsh

{
  echo start
  echo "update refs/automation/candidate $C $OLD_CANDIDATE"
  echo "update refs/automation/verified $B $OLD_VERIFIED"
  echo prepare
  echo commit
} | git update-ref --stdin

git for-each-ref \
  --format='%(refname) %(objectname)' \
  refs/automation

PowerShell

@(
  'start'
  "update refs/automation/candidate $C $OLD_CANDIDATE"
  "update refs/automation/verified $B $OLD_VERIFIED"
  'prepare'
  'commit'
) | git update-ref --stdin

git for-each-ref --format='%(refname) %(objectname)' refs/automation

Expected: candidate is C and verified is B. If either old-value check had failed, the transaction would not have committed the requested pair.

9. Verify reflog evidence after the transaction

git reflog show -4 refs/automation/candidate
git reflog show -4 refs/automation/verified
git reflog exists refs/automation/candidate
git reflog exists refs/automation/verified

The exact message generated for transaction-based updates can vary by command/version context; the important evidence is the old/new movement and continued reflog presence.

10. Prediction 3 — packing changes storage, not logical ref values

BEFORE_CANDIDATE=$(git rev-parse refs/automation/candidate)
BEFORE_VERIFIED=$(git rev-parse refs/automation/verified)

git rev-parse --show-ref-format
git pack-refs --all

AFTER_CANDIDATE=$(git rev-parse refs/automation/candidate)
AFTER_VERIFIED=$(git rev-parse refs/automation/verified)

test "$BEFORE_CANDIDATE" = "$AFTER_CANDIDATE"
test "$BEFORE_VERIFIED" = "$AFTER_VERIFIED"

git show-ref
git for-each-ref --format='%(refname) %(objectname)' refs/automation

The tests prove logical identity survives the storage optimization. Do not judge success by counting files under .git/refs.

11. Prediction 4 — simulate process 1 becoming stale

Process 1 records candidate at C. Process 2 moves candidate C → A. Process 1 then tries to move the candidate to B while still expecting C.

PROCESS1_SEEN=$(git rev-parse refs/automation/candidate)
test "$PROCESS1_SEEN" = "$C"

git update-ref \
  -m "process 2: C to A" \
  refs/automation/candidate "$A" "$C"

git update-ref \
  -m "process 1: stale C to B" \
  refs/automation/candidate "$B" "$PROCESS1_SEEN"
echo "stale writer exit=$?"

git rev-parse refs/automation/candidate
git reflog show -4 refs/automation/candidate

Expected: process 1 fails and candidate remains A. That refusal prevents a lost update.

12. Correct stale-writer handling — re-read, re-evaluate, then guard again

Suppose the application policy determines that moving A → B is still valid after reviewing process 2's change:

NOW=$(git rev-parse refs/automation/candidate)
test "$NOW" = "$A"

git update-ref \
  -m "checkpoint: recomputed transition A to B" \
  refs/automation/candidate "$B" "$NOW"

git rev-parse refs/automation/candidate

The key is the re-evaluation. A blind retry would defeat the purpose of expected-old protection.

13. Bonus failure — one stale expected value prevents a two-ref transaction

Capture the correct values, then deliberately supply a wrong old value for verified:

CAND_BEFORE=$(git rev-parse refs/automation/candidate)
VER_BEFORE=$(git rev-parse refs/automation/verified)

{
  echo start
  echo "update refs/automation/candidate $C $CAND_BEFORE"
  echo "update refs/automation/verified $C $A"
  echo prepare
  echo commit
} | git update-ref --stdin
echo "transaction exit=$?"

git rev-parse refs/automation/candidate
git rev-parse refs/automation/verified

Because verified is B rather than the deliberately expected A, the transaction should fail; candidate must remain at its pre-transaction value as well.

14. Verify object reachability through ordinary porcelain

git log --graph --decorate --oneline --all
git show --stat refs/automation/candidate
git show --stat refs/automation/verified
git branch --contains "$A"
git branch --contains "$B"

Tool-owned refs are ordinary Git refs even though branch porcelain does not list them as local branches. They participate in object reachability and revision resolution.

15. Write the operating policy this lab demonstrates

Concern Checkpoint policy
Namespace Tool-owned state lives under refs/automation/*
Single-ref concurrency Always use observed expected-old OID
Multi-ref logical update Use update-ref transaction with expected values
Recovery evidence Create reflogs explicitly for tool-owned refs and use meaningful reasons where supported
Storage Never parse loose/packed storage as application state
Stale writer Fail, re-read, recompute, then attempt a new guarded update
Remote publication Use server capability/policy such as atomic push where required; verify support

16. Verification checklist

  • Exactly three known commits A, B, C were created in a disposable repository.
  • candidate and verified were created under refs/automation/ with explicit reflog requests.
  • Candidate A → B succeeded only after checking the observed A value.
  • A two-ref transaction moved candidate B → C and verified A → B together.
  • Reflogs remained inspectable after the transaction.
  • pack-refs --all did not change either logical OID.
  • A stale writer expecting C was rejected after another process moved candidate to A.
  • The correct response was re-read/recompute/re-guard, not blind overwrite.
  • A deliberately stale multi-ref transaction left both requested changes uncommitted.
  • No ref-storage file was manually edited in the checkpoint.

17. Cleanup

Save any evidence you want to study, then confirm the disposable parent path before deletion.

Git Bash / Bash / zsh

cd ../..
pwd
rm -rf git-ref-checkpoint

PowerShell

Set-Location ../..
Get-Location
Remove-Item -Recurse -Force git-ref-checkpoint

18. Knowledge check

Question 1. Why did the first guarded A → B candidate update succeed?

Question 2. What does packing refs prove if the OIDs before and after are identical?

Question 3. Process 1's stale update fails after process 2 moved the ref. Is the failure evidence of corruption?

Question 4. One old-value check in a multi-ref transaction is wrong. What should happen?

Question 5. Why use explicit reflogs for custom automation refs?

19. What Chapter 19 adds to a production Git operating model

You can now treat refs as a concurrent naming database rather than loose hash files. That means querying logical refs through Git, separating symbolic names from direct object IDs, retaining local movement evidence deliberately, using expected-old values to prevent lost updates, coordinating multi-ref changes transactionally, and keeping storage/backend details out of application logic.

20. Chapter checkpoint summary

Safe ref automation is optimistic concurrency control: observe the current value, compute the intended transition, update only if the observed value still holds, and fail closed when it does not. Reflogs help explain prior local values, while ref transactions coordinate several names. Neither mechanism replaces server authorization or permanent audit systems.

Next chapter

Merge Algorithms, Conflict Engineering, rerere, and Custom Merge Drivers

Chapter 20 returns to integration and studies how Git selects merge bases, represents conflict stages, reuses resolutions with rerere, and safely controls custom merge behavior.

Authoritative references

 git-update-ref
 git-reflog
 git-pack-refs
 git-for-each-ref
 git-push

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.