Chapter 10Lesson 05~145 minutes

Checkpoint Lab — Undoing Changes, Reset, Restore, Revert, Reflog, and Lost-Work Recovery

Checkpoint recovery across separate disposable clones: restore an unstaged file, unstage safely, undo a private local commit, revert a published bad commit, recover a deleted branch, dry-run clean, preserve evidence, and exercise fsck discovery.

CheckpointFive failuresEvidence reportRecovery decision tree

Learning objectives

  • Choose the narrowest recovery tool for five distinct Git failure states.
  • Predict ref/index/working-tree effects before each recovery command.
  • Preserve published history by reverting instead of rewriting.
  • Recover deleted/lost tips from reflog/object evidence and immediately create safety refs.
  • Use clean dry-run and exact OID reporting as mandatory operational safeguards.

1. Checkpoint scenario — five failures in independent disposable clones

One recovery repository will act as the source of truth, with a local bare remote and multiple clones. Separating scenarios prevents one recovery action from hiding another and models the reality that reflogs are clone-local.

2. Build the source repository and bare remote

Git Bash, Bash, or zsh

mkdir git-recovery-checkpoint
cd git-recovery-checkpoint
git init --bare origin.git

git clone origin.git seed
cd seed
git switch -c trunk
git config user.name "Checkpoint Maintainer"
git config user.email "checkpoint-maintainer@example.invalid"

printf "mode=stable\nlimit=10\n" > app.conf
printf "# Recovery Checkpoint\n" > README.md
git add app.conf README.md
git commit -m "Create checkpoint baseline"
git push -u origin trunk
cd ..
git --git-dir=origin.git symbolic-ref HEAD refs/heads/trunk

git clone origin.git worktree-case
git clone origin.git staged-case
git clone origin.git local-commit-case
git clone origin.git published-case
git clone origin.git deleted-branch-case

Preflight: all remotes are local paths, identities are fake, and every clone is disposable.

3. Use this decision record for every scenario

1. Symptom:
2. Affected layer:
3. Desired source state:
4. Is the mistake published/shared?
5. Evidence captured:
6. Narrowest correction:
7. Verification:
8. Recovery fallback / safety OID:

Do not write “used reset” as the rationale. Record why that exact destination layer and source were selected.

4. Scenario A — recover an unstaged file

cd worktree-case
git config user.name "Case A"
git config user.email "case-a@example.invalid"

# Change mode=stable to mode=broken
git status --short
git diff -- app.conf
git show :app.conf

# Prediction: only working tree changes.
git restore -- app.conf
git status --short
git diff -- app.conf
cd ..

Decision: working tree was wrong; index held the desired version; nothing was published.

5. Scenario B — unstage the wrong file without discarding it

cd staged-case
git config user.name "Case B"
git config user.email "case-b@example.invalid"

printf "local experimental note\n" >> README.md
git add README.md
git status --short
git diff --staged -- README.md

# Prediction: index returns to HEAD; working edit remains.
git restore --staged -- README.md
git status --short
git diff -- README.md
git diff --staged -- README.md
cd ..

6. Scenario C — undo one unwanted private local commit

cd local-commit-case
git config user.name "Case C"
git config user.email "case-c@example.invalid"

printf "temporary=true\n" >> app.conf
git add app.conf
git commit -m "Temporary private experiment"
UNWANTED=$(git rev-parse HEAD)
PARENT=$(git rev-parse HEAD^)

git status --short
git log --oneline -3
git reflog -5

# Prediction: HEAD moves to PARENT, changes remain unstaged.
git reset --mixed HEAD^
git rev-parse HEAD
git status --short
git diff -- app.conf

# Preserve evidence of the old commit before cleaning up local file state.
git branch evidence-unwanted "$UNWANTED"
git restore -- app.conf
git status --short
cd ..

Decision: the commit had never been pushed; mixed reset changed local history while preserving its content long enough to inspect it, and a safety ref preserves the old commit.

7. Scenario D — correct a published bad commit with revert

cd published-case
git config user.name "Case D"
git config user.email "case-d@example.invalid"

printf "dangerous=true\n" >> app.conf
git add app.conf
git commit -m "Enable dangerous production mode"
BAD=$(git rev-parse HEAD)
git push origin trunk

git ls-remote origin refs/heads/trunk
git show --stat "$BAD"

# Prediction: BAD remains in history and a new inverse commit is added.
GIT_EDITOR=true git revert "$BAD"
REVERT=$(git rev-parse HEAD)
git log --oneline -4
git push origin trunk
git ls-remote origin refs/heads/trunk
cd ..

Decision: the bad OID was already shared through the remote, so additive history preserved provenance.

8. Scenario E — recover a deleted branch tip

cd deleted-branch-case
git config user.name "Case E"
git config user.email "case-e@example.invalid"
git fetch origin
git switch -c experiment origin/trunk

printf "valuable branch work\n" > experiment.txt
git add experiment.txt
git commit -m "Create valuable branch work"
LOST=$(git rev-parse HEAD)

git switch trunk
git branch -D experiment

git log --oneline --all --decorate --max-count=10
git reflog --all --date=local | head -30
git show "$LOST"

# Prediction: creating a new ref makes the commit reachable again.
git branch recovered-experiment "$LOST"
git merge-base --is-ancestor "$LOST" recovered-experiment
git log --oneline -2 recovered-experiment
cd ..

Decision: branch ref was deleted, but the commit still existed and local reflog/known OID evidence identified it. Recovery created a ref; no object reconstruction was necessary.

9. Mandatory clean dry-run demonstration

Use a separate clone so deleting an untracked file cannot affect the recovery scenarios.

git clone origin.git clean-case
cd clean-case
printf "local scratch only\n" > scratch.tmp
mkdir scratch-dir
printf "nested scratch\n" > scratch-dir/nested.tmp

git status --short
git clean -n
git clean -nd

# Stop here unless you verified that scratch.tmp is disposable.
git clean -f -- scratch.tmp
git status --short
test -f scratch-dir/nested.tmp
cd ..
The forced clean is intentionally narrow. It deletes only the named disposable file after dry-run. The directory remains, proving why -d changes scope. Do not substitute broad git clean -fdx.

10. Preserve a compact evidence report

Git Bash / Bash / zsh

{
  echo "published_bad=$BAD"
  echo "published_revert=$REVERT"
  echo "deleted_branch_tip=$LOST"
  echo "local_unwanted=$UNWANTED"
} > recovery-evidence.txt
cat recovery-evidence.txt

This report is intentionally outside the repositories. In real operations, pair exact OIDs with incident/ticket/deployment identifiers in the system of record.

11. Optional lost-object inspection without pruning

In local-commit-case, temporarily remove the safety branch only after copying its OID into the evidence report, then ask fsck to ignore reflog roots:

cd local-commit-case
git branch -D evidence-unwanted
git fsck --no-reflogs --unreachable
git fsck --no-reflogs --lost-found
git show "$UNWANTED"
git branch recovered-unwanted "$UNWANTED"
cd ..
No reflog expiry or pruning is performed. The goal is to observe discoverability while the object still exists, then immediately recreate a safety ref.

12. Verification checklist

  • Scenario A ends clean with the working-tree file restored from the index.
  • Scenario B keeps the README working edit but removes it from the index.
  • Scenario C moves local HEAD back one commit, proves content became unstaged, and preserves the old OID under a safety/recovery branch.
  • Scenario D leaves the bad published commit in ancestry and adds/pushes a distinct revert commit.
  • Scenario E recreates a branch pointing at the deleted branch tip.
  • The clean demonstration ran git clean -n/-nd before a narrow forced deletion.
  • Evidence report records exact old/revert/lost OIDs.
  • No reflog expiry, prune-now, broad hard reset, or force push was used.

13. Knowledge check

Question 1. Why did Scenario B use restore --staged instead of reset --hard?

Question 2. Why was mixed reset appropriate in Scenario C but not Scenario D?

Question 3. What made the deleted-branch commit recoverable?

Question 4. Why did the clean lab delete only scratch.tmp?

Question 5. If fsck reports a missing blob rather than an unreachable commit, what changes?

14. What Chapter 10 adds to a production Git operating model

You now have a recovery sequence that begins with evidence and layer identification, distinguishes local rewrite from shared correction, preserves old OIDs before invasive operations, and treats reflog/fsck/clean/GC as tools with specific boundaries. This lowers the chance that the recovery attempt destroys more information than the original mistake.

15. Chapter checkpoint summary

The safest Git recovery is not the most powerful command. It is the narrowest state transition that restores the intended layer while preserving unrelated work and shared provenance.

Next chapter

Tags, Signed Releases, Semantic Versioning, Changelogs, and Support Lines

Chapter 11 builds on stable, recoverable history by creating immutable release identifiers, support lines, and provenance that deployments and incident reports can reference consistently.

Authoritative references

 git-restore
 git-reset
 git-revert
 git-reflog
 git-clean
 git-fsck

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.