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
Output
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
Output
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
Output
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 recipe

Each 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
Output
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
Output
Deleted branch desserts (was e4dae99).

If that message has scrolled away, the reflog still remembers the commit:

$
git reflog -3
Output
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 desserts
$
git branch desserts HEAD@{1}
git log --oneline -1 desserts
Output
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 gc may delete the orphaned commits.
  • It only knows commits. Changes you never committed or staged were never recorded, so a hard reset or git restore over them can't be undone. (Staged-but-never-committed content can sometimes be dug out with git 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.