Recovering lost commits with git reflog Jump to heading
The moment after a git reset --hard that took the wrong argument is a bad one, and it is made worse by the fact that Git says nothing reassuring. The work is almost certainly still there. Git does not delete commits when a branch stops pointing at them; it leaves them unreachable, and the reflog keeps a record of where every ref has been. This recipe turns that property into a procedure, as part of History Rewriting & Recovery.
When to use this approach Jump to heading
- A
git reset --harddiscarded commits you needed. - A branch was deleted before it was merged, or before you finished with it.
- A rebase went wrong and the original commits are no longer on any branch.
git commit --amendreplaced a commit whose original content you now want back.- A
git checkoutof another branch appeared to lose work that was committed on a detachedHEAD. - The work was never committed or staged at all — in which case stop here; Git cannot help, and the answer is your editor’s local history.
Step 1 — Stop before doing anything else Jump to heading
# Do NOT run any of these while recovering:
# git gc --prune=now
# git reflog expire --expire-unreachable=now --all
# rm -rf the clone and re-clone
# Safe: take a copy of the whole clone before poking at it
cp -a . ../repo-recovery-copy What changed: you now have a second copy of the object store, so an experiment that makes things worse costs nothing.
Step 2 — Find the lost tip in the reflog Jump to heading
# Where has HEAD been? Newest first.
git reflog
# The same, with dates, which is usually how you recognise the right entry
git reflog --date=iso | head -20
# If the work was on a named branch, that branch has its own reflog
git reflog show feature/payments What changed: nothing yet — you are reading a log. Typical output looks like this, and the entry before the destructive operation is what you want:
a1b2c3d HEAD@{0}: reset: moving to origin/main
9f8e7d6 HEAD@{1}: commit: add refund reconciliation <- the work
5c4b3a2 HEAD@{2}: commit: extract settlement helper
# Confirm the candidate is a real commit and see what it contains
git show --stat 9f8e7d6 | head -20 Step 3 — Inspect the candidate before restoring it Jump to heading
Restoring the wrong SHA wastes time and can overwrite a second thing. Two commands make the check quick.
# What is in this commit, and what is its ancestry?
git log --oneline -5 9f8e7d6
# What would change if you moved your branch there?
git diff --stat HEAD 9f8e7d6 What changed: nothing — but you now know whether this SHA is the tip of the work you lost or an earlier step along the way.
Step 4 — Restore it onto a new branch Jump to heading
Do not reset the current branch to the recovered SHA. Create a new branch: it is non-destructive, and it lets you compare the two before deciding.
# Give the lost work a name — nothing else changes
git branch recovered-work 9f8e7d6
# Look at it without disturbing your current checkout
git log --oneline main..recovered-work
git diff main...recovered-work --stat What changed: the commits are reachable again, so garbage collection can no longer remove them, and the pressure is off.
# Verify the recovered branch contains what you expected
git show --stat recovered-work | head -20
git log --oneline -3 recovered-work Then integrate it the ordinary way — merge, rebase, or cherry-pick the specific commits you want, exactly as described in Cherry-Pick & Backporting. Recovery and integration are separate decisions, and conflating them is how a second mistake follows the first.
SAFETY WARNING — resist the temptation to
git reset --hard recovered-workon the branch you are standing on. If your current branch has commits made since the incident, that reset discards them, and now you have two recoveries to perform. Merge or cherry-pick instead, and keep the recovery branch until the work is safely integrated and pushed.
Step 5 — Fall back to git fsck when the reflog cannot help Jump to heading
The reflog only records refs in your clone. If the branch was deleted in a fresh clone, or the work was committed on a detached HEAD you never named, git fsck finds it by scanning the object store directly.
# Every object nothing points at
git fsck --lost-found --no-reflogs
# Dangling commits, newest content first
git fsck --lost-found --no-reflogs 2>/dev/null \
| awk '/dangling commit/ {print $3}' \
| xargs -I{} git show --stat --oneline -s {} \
| head -40 What changed: nothing — but you now have a candidate list even without a reflog entry. Recover the same way as Step 4:
git branch recovered-from-fsck <sha>
git log --oneline -3 recovered-from-fsck Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Why does the reflog not show a branch someone else deleted? Jump to heading
The reflog is per clone and per ref: it records where refs in your repository have pointed, so a branch that only ever existed on the remote or on a colleague’s machine leaves no trace in yours. Ask the person who had it checked out to look in their reflog, or ask your hosting platform, which usually keeps a server-side record for a period.
Can I recover uncommitted changes? Jump to heading
Only if they were staged at some point. Staged content is written to the object store, so git fsck --lost-found can surface it as a dangling blob even though no commit references it. Changes that were only ever in the working tree, never added, are not in Git at all — the editor’s local history or the filesystem’s snapshots are the only recourse.
How long do I have before recovery becomes impossible? Jump to heading
By default about 30 days for unreachable objects, but that is a schedule rather than a promise: an explicit git gc --prune=now removes them immediately. Treat any lost-work situation as urgent, and above all do not run garbage collection while trying to recover — it is the single action that turns a recoverable situation into an unrecoverable one.
Related Jump to heading
- History Rewriting & Recovery — the parent guide: when a rewrite is justified, and how to do one without needing this page afterwards.
- When to Use git revert vs git reset — the distinction that prevents most of the resets people have to recover from.
- Safe git rebase -i for Shared Branches — how to rewrite without producing the situation this recipe repairs.