Skip to content
C Codeloom
Git

Git reflog Recovery Tutorial

Use git reflog to recover lost commits, branches, and stashes after rebases, resets, and bad merges. A practical walkthrough of how Git remembers where HEAD has been.

·4 min read · By Codeloom
Intermediate 8 min read

What you'll learn

  • What the reflog actually records and where
  • How to recover from reset --hard and bad rebases
  • Restoring a branch you deleted by accident
  • Finding lost stashes and detached commits
  • How reflog expiry and gc can eat your safety net

Prerequisites

  • Basic Git: commit, branch, checkout, reset, rebase

What and Why

Almost every “I just lost two days of work” Git story ends with somebody discovering git reflog. It is a local log of every move HEAD and your branches have made: every commit, checkout, reset, rebase, merge, and stash. Even when commits are no longer reachable from any branch, they exist in the object database and the reflog still points at them.

If you have not run git gc and the entries have not expired, your work is almost certainly recoverable. You just need to know how to ask.

Mental Model

The reflog is not part of the public history. It is a per-repository, per-ref log stored under .git/logs/. There is one for HEAD (.git/logs/HEAD) and one per branch (.git/logs/refs/heads/<branch>).

Each entry records: the previous SHA, the new SHA, who made the change, when, and a one-line description of the action. Time-based references like HEAD@{2} mean “the value of HEAD two moves ago”; HEAD@{yesterday} works too.

Important: the reflog is local. Cloning, pushing, and pulling do not propagate it. If you nuke a branch and re-clone, the reflog is gone.

Hands-on Example

Imagine you just ran git reset --hard HEAD~3 on a branch and your three commits seem to be gone.

$ git reflog
2f1c0aa HEAD@{0}: reset: moving to HEAD~3
9b7d4e2 HEAD@{1}: commit: fix off-by-one in pagination
1a3cf80 HEAD@{2}: commit: add cache headers
7e22f51 HEAD@{3}: commit: extract pagination helper
3d8aa90 HEAD@{4}: checkout: moving from main to feature/cache

The three commits you wanted are HEAD@{1} through HEAD@{3}. Bring them back:

git reset --hard HEAD@{1}

Now your branch tip is the original commit. Same idea for a botched rebase:

git reflog
# find the line ending in "rebase (start): checkout ..."
git reset --hard HEAD@{8}    # one step before the rebase started

Deleted a branch? The branch ref is gone, but the commits are not. Find the tip from HEAD’s reflog or the deleted branch’s log file if it still exists, then recreate the branch:

git reflog | grep 'feature/payments'
git branch feature/payments 9b7d4e2

Lost a stash you cleared? Stashes live as commits referenced by refs/stash, and that ref has its own reflog:

git fsck --no-reflogs --unreachable | grep commit
git stash apply <sha>
time ->

work, work, work
C1 -- C2 -- C3        on feature
            ^ HEAD@{3}

git reset --hard HEAD~2
C1                    branch tip
 ^ HEAD@{0}

reflog:
HEAD@{0} reset:   ... -> C1
HEAD@{1} commit:  ... -> C3   <- still in object db
HEAD@{2} commit:  ... -> C2
HEAD@{3} commit:  ... -> C1

recovery:
git reset --hard HEAD@{1}     branch tip jumps back to C3
What the reflog records as you move around

Common Pitfalls

  • Running git gc --prune=now: deletes unreachable objects immediately, including everything your reflog used to protect. Avoid it after a mistake until you have recovered.
  • Re-cloning: throws away the local reflog. If something is broken, do recovery on the existing clone first.
  • Confusing HEAD@{n} with HEAD~n: the first is “n moves ago in time,” the second is “n commits back in graph”. They are completely different.
  • Expecting reflog on shared remotes: GitHub and friends do not expose your collaborators’ reflogs. Push tags or branches to share recovery points.
  • Reflog expiry: by default, reachable entries expire after 90 days and unreachable after 30. Long-dormant disasters may be unrecoverable.

Practical Tips

  • Before any risky operation, note the current SHA: git rev-parse HEAD and paste it into a scratch file.
  • Use git reflog show <branch> to see only that branch’s history of moves.
  • git log -g walks the reflog like a normal log, with diff support.
  • Tweak retention with gc.reflogExpire and gc.reflogExpireUnreachable if you want a longer safety net.
  • Treat git reset --hard and git rebase like surgery: focused, deliberate, and reversible because of the reflog.

Wrap-up

The reflog is Git’s local undo journal. As long as you act before gc cleans up, almost any mistake involving commits is reversible. Memorize three commands: git reflog, git reset --hard HEAD@{n}, and git branch <name> <sha>. They will save you more times than you can count.