Recovering Lost Work
Git keeps a private diary of every place HEAD and each branch has pointed to: every commit, checkout, reset, merge and rebase. This diary, the reflog, lets you rescue commits after a hard reset, a botched rebase or a deleted branch — Git's safety net.
The mistake
Sam meant to undo only an uncommitted change, but ran a hard reset two commits back:
git log --oneline
588c59c (HEAD -> main) Add tiramisu recipe eb78da3 Add lasagne recipe 90c0089 Add omelette recipe c4f5e47 Add pancake recipe
git reset --hard HEAD~2 git log --oneline
HEAD is now at 90c0089 Add omelette recipe 90c0089 (HEAD -> main) Add omelette recipe c4f5e47 Add pancake recipe
The lasagne and tiramisu commits have vanished from the log. They haven't been deleted, though — nothing points to them any more, so git log doesn't show them.
Reading the reflog
git reflog
90c0089 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
588c59c HEAD@{1}: commit: Add tiramisu recipe
eb78da3 HEAD@{2}: commit: Add lasagne recipe
90c0089 (HEAD -> main) HEAD@{3}: commit: Add omelette recipe
c4f5e47 HEAD@{4}: commit (initial): Add pancake recipeEach line is a position HEAD has been at, newest first. HEAD@{0} is now, HEAD@{1} is the step before, and so on. The entry before the reset shows the commit Sam wants back.
Getting it back
Reset to that entry — by its hash or its reflog name:
git reset --hard HEAD@{1}
git log --oneline
HEAD is now at 588c59c Add tiramisu recipe 588c59c (HEAD -> main) Add tiramisu recipe eb78da3 Add lasagne recipe 90c0089 Add omelette recipe c4f5e47 Add pancake recipe
Everything is back. If you'd rather inspect first, create a branch at the old position instead — that rescues the commits without touching your current branch:
git branch rescued HEAD@{1}
Recovering a deleted branch
Deleting a branch that hasn't been merged needs -D, and Git prints the hash of its last commit — the quickest way to undo it:
git branch -D desserts
Deleted branch desserts (was e4dae99).
If that message has scrolled away, the reflog still remembers the commit:
git reflog -3
588c59c (HEAD -> main) HEAD@{0}: checkout: moving from desserts to main
e4dae99 HEAD@{1}: commit: Add panna cotta recipe
588c59c (HEAD -> main) HEAD@{2}: checkout: moving from main to dessertsgit branch desserts HEAD@{1}
git log --oneline -1 desserts
e4dae99 (desserts) Add panna cotta recipe
Branch reflogs and time-based references
Each branch has its own reflog too: git reflog show main. Entries can be addressed by position, main@{2}, or by time:
git diff main@{yesterday} main # what changed on main since yesterday
git show HEAD@{"2 hours ago"} # where HEAD was two hours ago
Limits of the reflog
- It's local. Each clone has its own reflog; it isn't pushed or cloned. A colleague can't recover your lost commit from theirs.
- It expires. By default entries are kept for 90 days, or 30 days for commits no branch can reach. After that,
git gcmay delete the orphaned commits. - It only knows commits. Changes you never committed or staged were never recorded, so a hard reset or
git restoreover them can't be undone. (Staged-but-never-committed content can sometimes be dug out withgit fsck --lost-found, but don't count on it.)
The lesson: once something is committed, you can almost always get it back. Commit often — even rough work — and tidy up later.
Try it yourself
Make three commits, run git reset --hard HEAD~3, then use git reflog to restore them. Next, create a branch with one commit, delete it with git branch -D, and bring it back.