Recovery and Bisect Lab¶
Practise getting work back and finding a bad commit: recover after a hard reset, rescue a deleted branch, undo a merge with ORIG_HEAD, and run an automated bisect. Run every task in a throwaway repository.
Setup¶
Create a fresh sandbox with a short history.
mkdir -p /tmp/git-lab && cd /tmp/git-lab
rm -rf recovery && git init recovery && cd recovery
git config user.name "Amina Yusuf"
git config user.email "amina@example.com"
printf 'v1\n' > app.py && git add app.py && git commit -m "Add app"
printf 'v2\n' >> app.py && git commit -am "Add feature"
printf 'v3\n' >> app.py && git commit -am "Add tests"
Use example identities only; never paste a real email, token or private remote URL into a lab.
Reflog Recovery¶
1. Recover After a Hard Reset¶
Hard-reset the branch back two commits, confirm they are gone from git log, then restore them with the reflog.
Solution
git reset --hard HEAD~2
git log --oneline # only "Add app"
git reflog # HEAD@{1} is the pre-reset tip
git reset --hard HEAD@{1}
git log --oneline # all three back
2. Recover the Non-Destructive Way¶
Reset back two commits again, but this time rescue the lost tip onto a new branch instead of moving the current one.
Solution
lost=$(git rev-parse HEAD) # note the tip first
git reset --hard HEAD~2
git branch recovered "$lost"
git log --oneline recovered # the work is safe on 'recovered'
git reset --hard recovered # or bring main back to it
3. Rescue a Deleted Branch¶
Create a branch with a commit, switch away, delete it with -D, then recreate it from the SHA the delete printed.
Solution
git switch -c feature/x
printf 'x\n' > x.txt && git add x.txt && git commit -m "Add x"
git switch main
git branch -D feature/x # prints: Deleted branch feature/x (was <sha>)
git branch feature/x <sha> # or: git branch feature/x $(git reflog | awk '/Add x/{print $1; exit}')
git log --oneline feature/x -1
ORIG_HEAD¶
4. Undo a Merge in One Command¶
Create a branch with a commit, merge it into main, then undo the merge with ORIG_HEAD.
Solution
git switch -c feature/y
printf 'y\n' > y.txt && git add y.txt && git commit -m "Add y"
git switch main
git merge --no-ff --no-edit feature/y
git reset --hard ORIG_HEAD # main is back to the pre-merge commit
git log --oneline
Bisect¶
5. Build a History with a Hidden Bug¶
Create a calc.py that works, add a few commits, break it in the middle, then add more commits so the bug is buried.
Solution
rm -rf /tmp/git-lab/bisect && git init /tmp/git-lab/bisect && cd /tmp/git-lab/bisect
git config user.name "Amina Yusuf" && git config user.email "amina@example.com"
printf 'def add(a,b):\n return a+b\n' > calc.py
git add calc.py && git commit -m "Add calc"
for i in 1 2 3; do echo "# note $i" >> notes.txt; git add notes.txt; git commit -m "Note $i"; done
printf 'def add(a,b):\n return a-b # BUG\n' > calc.py
git add calc.py && git commit -m "Refactor calc"
for i in 4 5 6; do echo "# note $i" >> notes.txt; git add notes.txt; git commit -m "Note $i"; done
6. Find the Bad Commit Manually¶
Start a bisect with the current commit bad and the first commit good, and mark each checkout by testing add(2,1).
Solution
git bisect start HEAD $(git rev-list --max-parents=0 HEAD)
# at each checkout:
python3 -c "import calc; print(calc.add(2,1))" # 3 = good, else bad
git bisect good # or: git bisect bad
# repeat until: <sha> is the first bad commit
git bisect reset
7. Automate the Bisect¶
Write a test script that exits 0 when add(2,1)==3, then let git bisect run find the culprit unattended.
Solution
cat > test.sh <<'EOF'
#!/bin/sh
python3 -c "import calc,sys; sys.exit(0 if calc.add(2,1)==3 else 1)"
EOF
chmod +x test.sh
git bisect start HEAD $(git rev-list --max-parents=0 HEAD)
git bisect run ./test.sh # names the "Refactor calc" commit
git bisect reset
8. Read and Fix the Culprit¶
Show the diff of the commit bisect found, then revert only that change.
Solution
git show <first-bad-sha> # shows the a+b -> a-b change
git revert --no-edit <first-bad-sha>
python3 -c "import calc; print(calc.add(2,1))" # 3 again
Cleanup¶
cd /tmp && rm -rf /tmp/git-lab