Reflog & Recovery
This chapter has been referenced as a safety net throughout the entire series — Course 1's reset --hard warning, Course 2's force-push danger, Course 3's own interactive rebase chapter. Now it gets full treatment: what the reflog actually is, and the step-by-step recovery process for the most common "I broke it" scenarios.
What the Reflog Actually Tracks
Building on Chapter 1's object model: every time a ref (HEAD, a branch) moves — a commit, a checkout, a reset, a rebase step — git records that movement in the reflog, a local, personal log of where your refs have recently pointed. It's not part of the object database itself and isn't shared when you push — purely a local safety net.
9c8b7a6 HEAD@{0}: commit: Add validation logic
4f3e2d1 HEAD@{1}: reset: moving to HEAD~1
a1b2c3d HEAD@{2}: commit: Add login form HTML
f9e8d7c HEAD@{3}: rebase (start): checkout main
Each line is a snapshot of where HEAD pointed at that moment, most recent first. Crucially, this includes commits that no branch currently points to anymore — exactly the case after a hard reset or a rewritten rebase, where the "old" commits still technically exist in git's object database, just unreachable by any current branch or tag.
git reset --hard isn't always unrecoverable — the reflog is the literal mechanism why. The commit object itself (Chapter 1) still exists in .git/objects/ even when no ref points to it anymore; the reflog is how you find its hash again to point something back at it.
Recovery Scenario: Undoing a Bad Hard Reset
$ git reset --hard HEAD~3
HEAD is now at f9e8d7c Some much older commit
$ # Find where you were before the reset
$ git reflog
9c8b7a6 HEAD@{1}: commit: Add validation logic ← this is what you lost
f9e8d7c HEAD@{0}: reset: moving to HEAD~3
$ # Reset forward again, to the commit you actually wanted
$ git reset --hard 9c8b7a6
HEAD is now at 9c8b7a6 Add validation logic
Recovery Scenario: A Rebase Went Badly Wrong
Interactive rebase (Chapter 2) can be aborted mid-way with --abort — but what if it already finished, and the result is wrong? The reflog still has the pre-rebase state.
4f3e2d1 HEAD@{0}: rebase (finish): returning to refs/heads/feature
9c8b7a6 HEAD@{4}: rebase (start): checkout HEAD~4 ← look just before this line
b2c1d0e HEAD@{5}: commit: Last commit before the rebase started
$ # Point your branch back at the pre-rebase state
$ git reset --hard b2c1d0e
Recovery Scenario: Recovering After a Force-Push
Course 2, Chapter 9 covered this from the perspective of the person who got overwritten — usually, their own local copy is fine since it was never touched. But if you're the one trying to recover what you pushed over:
git gc until you've finished — it exists to clean up storage, not to be run during a rescue.
Finding a Specific Lost Commit, Not Just "the previous state"
If you're not sure which reflog entry you need, search by commit message or content rather than scanning the whole log manually:
$ git reflog --grep="validation"
$ # Search ALL commit objects (not just reachable ones) by content
$ git fsck --unreachable | grep commit
git fsck --unreachable is the deeper fallback when even the reflog has expired or doesn't have what you need — it lists every commit object still physically present in .git/objects/, including ones with no ref or reflog entry pointing to them at all, as long as garbage collection hasn't pruned them yet.
Command Reference
| Command | What it does |
|---|---|
| git reflog | Shows the local history of where HEAD/branches have pointed recently |
| git reflog --grep="text" | Searches reflog entries by message content |
| git reset --hard <hash> | Points your current branch back at a specific commit found via reflog |
| git fsck --unreachable | Lists commit objects with no current ref pointing to them, reflog or not |
| git gc | Garbage collection — can permanently prune unreachable objects; avoid mid-recovery |
Chapter 3 Quick Reference
- The reflog is a local, personal log of where HEAD/branches have pointed — not pushed, not shared
- Includes unreachable commits — exactly what a hard reset or rewritten rebase leaves behind, still physically present
- Recovery pattern: git reflog → find the hash you want → git reset --hard <hash>
- Force-push recovery: your own reflog if you have it; otherwise the original author's untouched local copy
- The reflog isn't infinite — expires over time, and git gc can permanently prune unreachable objects
- git fsck --unreachable — deeper fallback for commits the reflog itself no longer references
- Never run git gc mid-recovery — it's the one command that can make this chapter's techniques stop working
- Next chapter: submodules and subtrees — proper setup, contrasted with the broken-gitlink trap from earlier courses