Undoing Changes, Reset, Restore, Revert, Reflog, and Lost-Work Recovery: Guided Hands-On Workflow and Core Operations
Practice safe recovery operations in a disposable repository: restore working/index state, observe soft/mixed/hard reset, revert shared-style history, recover deleted/reset-away commits from reflog, and inspect lost objects with fsck.
Learning objectives
- Undo unstaged and staged changes independently without changing unrelated layers.
- Observe and verify soft, mixed, and hard reset effects on HEAD, index, and working tree.
- Use revert to record an additive correction suitable for published history.
- Recover a deleted branch or reset-away commit using reflog evidence and safety refs.
- Use fsck/lost-found as secondary object discovery without pruning recovery evidence.
1. Create the disposable recovery laboratory
This lab deliberately creates mistakes and destructive reset states. Use only the new directory below.
Git Bash, Bash, or zsh
mkdir git-recovery-lab
cd git-recovery-lab
git init -b trunk
git config user.name "Recovery Lab"
git config user.email "recovery-lab@example.invalid"
printf "mode=safe\nlimit=10\n" > app.conf
printf "# Recovery Service\n" > README.md
git add app.conf README.md
git commit -m "Create recovery baseline"
git status --short --branch
git log --oneline -3
git reflog -5
2. Recover an unwanted working-tree edit
# Change mode=safe to mode=broken in app.conf
git status --short
git diff -- app.conf
git show :app.conf
git restore -- app.conf
git diff -- app.conf
git status --short
git show :app.conf displays the indexed version.
Because the index still contains the desired baseline, restore
copies that content into the working tree. HEAD and index never
move.
3. Unstage one path without discarding the working copy
# Add timeout=20 to app.conf
git add app.conf
git status --short
git diff --staged -- app.conf
git restore --staged -- app.conf
git status --short
git diff -- app.conf
git diff --staged -- app.conf
The working-tree edit survives; only the proposed index snapshot
returns to HEAD. If you decide the working edit is also wrong,
inspect it again and run git restore -- app.conf as a
separate decision.
4. Reset --soft: undo a private commit while keeping it staged
printf "notes=soft-demo\n" > soft.txt
git add soft.txt
git commit -m "Temporary soft-reset commit"
SOFT_OLD=$(git rev-parse HEAD)
SOFT_PARENT=$(git rev-parse HEAD^)
git status --short
git log --oneline -3
git reset --soft HEAD^
git rev-parse HEAD
git status --short
git diff --staged -- soft.txt
Expected: HEAD moves to SOFT_PARENT;
the index and working tree still contain soft.txt, so
it appears staged. Recover the commit exactly by moving the branch
back while leaving the clean state controlled:
git reset --hard "$SOFT_OLD"
git status --short
SOFT_OLD was captured first.
5. Reset --mixed: undo a private commit and leave its changes unstaged
printf "notes=mixed-demo\n" > mixed.txt
git add mixed.txt
git commit -m "Temporary mixed-reset commit"
MIXED_OLD=$(git rev-parse HEAD)
git reset --mixed HEAD^
git status --short
git diff -- mixed.txt
git diff --staged -- mixed.txt
The branch moves back and the index matches the new HEAD. The working-tree file is retained, so the content becomes untracked/unstaged depending on whether the target commit tracked that path. This is why mixed reset is often an “uncommit locally” operation without discarding the working files.
git add mixed.txt
git commit -m "Restore mixed-reset demo as a normal commit"
6. Reset --hard: demonstrate destructive synchronization
printf "version=one\n" > destructive.txt
git add destructive.txt
git commit -m "Add destructive-reset baseline"
HARD_TARGET=$(git rev-parse HEAD)
printf "version=two\n" > destructive.txt
git add destructive.txt
git commit -m "Temporary destructive-reset commit"
HARD_OLD=$(git rev-parse HEAD)
printf "uncommitted tracked edit\n" >> destructive.txt
git status --short
git diff -- destructive.txt
git reflog -5
git reset --hard "$HARD_TARGET"
git status --short
cat destructive.txt
The branch, index, and working tree all now match
HARD_TARGET. The uncommitted tracked edit is gone. The
old commit can still usually be located in the local reflog;
recreate a safety ref if needed:
git branch recovered-hard-demo "$HARD_OLD"
git show --stat --oneline recovered-hard-demo
7. Revert a bad commit while preserving history
git switch trunk
printf "dangerous=true\n" >> app.conf
git add app.conf
git commit -m "Enable unsafe production mode"
BAD=$(git rev-parse HEAD)
git show --stat "$BAD"
GIT_EDITOR=true git revert "$BAD"
git log --oneline -4
git show --stat --summary HEAD
The original bad commit remains an ancestor; the new revert commit records the inverse change. If this branch were already published, that additive graph shape avoids rewriting commit IDs other systems may reference.
8. Recover a deleted branch tip from local reflog evidence
git switch -c experiment
printf "valuable experiment\n" > experiment.txt
git add experiment.txt
git commit -m "Create valuable experiment"
EXPERIMENT_TIP=$(git rev-parse HEAD)
git switch trunk
git branch -D experiment
git reflog --all --date=local | head -20
git show "$EXPERIMENT_TIP"
git branch recovered-experiment "$EXPERIMENT_TIP"
git log --oneline -2 recovered-experiment
Deleting the branch removes the ref, not necessarily the commit object immediately. The commit was also recently checked out/committed, so HEAD reflog evidence can help locate it. Saving the OID in this lab makes the verification deterministic; in a real incident, search reflog output and inspect candidates before creating a recovery ref.
9. Use fsck as a secondary discovery tool, not the first response
Reflog is usually easier when you know the operation that moved a
ref. git fsck becomes useful when you need to inspect
object connectivity or find objects no current ref reaches.
git switch -c fsck-demo trunk
printf "orphanable evidence\n" > orphan.txt
git add orphan.txt
git commit -m "Create recoverable fsck commit"
FSCK_TIP=$(git rev-parse HEAD)
git reset --hard HEAD^
git fsck --no-reflogs --unreachable
git fsck --no-reflogs --lost-found
git show "$FSCK_TIP"
--no-reflogs intentionally asks fsck not to treat
reflogs as roots so the reset-away commit can be reported as
unreachable/dangling. --lost-found writes dangling
commit/blob material under Git's internal lost-found area; it does
not guarantee semantic recovery and does not replace backups.
10. Challenge — choose the narrowest recovery tool
- A good file is staged accidentally but its working-tree edit must remain. Which command changes only the index?
- A private local commit should disappear from history but remain staged for editing. Which reset mode?
- A bad commit was pushed to a production branch and deployment logs already refer to it. Reset or revert?
- A branch was deleted ten minutes ago. Which evidence source should you inspect before fsck?
- A commit was reset away and no ordinary ref reaches it, but it probably still exists. Which connectivity tool can search object state?
11. Cleanup
Git Bash / Bash / zsh
git status --short --branch
cd ..
pwd
rm -rf git-recovery-lab
PowerShell
git status --short --branch
Set-Location ..
Get-Location
Remove-Item -Recurse -Force git-recovery-lab
12. Knowledge check
Question 1. What survived
git restore --staged -- app.conf?
Question 2. Which reset mode leaves both index and working tree unchanged?
--soft. It moves HEAD/current branch only.
Question 3. Why did git reset --hard require a
warning even in a clean teaching repository?
Question 4. Why can revert be pushed normally after a published bug?
Question 5. Why use git fsck --no-reflogs in the
lost-object demonstration?
13. Summary
You recovered each Git layer separately, observed soft/mixed/hard reset behavior, preserved published history with revert, restored a deleted ref from local evidence, and used fsck only after simpler recovery evidence. The recurring pattern was inspect → select source/destination → mutate narrowly → verify.
Authoritative references
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.