Every time HEAD moves — a commit, a checkout, a reset, a rebase, an amend, a merge — git records the previous and new SHA in the reflog. The reflog is purely local; it is not pushed and not part of the repository's normal history. But it is the reason that "I did git reset --hard and lost everything" is almost always recoverable.
Default retention is 90 days for reachable commits and 30 days for unreachable ones, controlled by gc.reflogExpire and gc.reflogExpireUnreachable.
The basic command
git reflog
Output looks like:
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
4e5f6g7 HEAD@{1}: commit: feat: pricing table
89abcde HEAD@{2}: commit: feat: hero section
fedcba9 HEAD@{3}: pull: Fast-forward
Each entry is: SHA, ref-relative position, action, message.
HEAD@{0} is where you are now. HEAD@{1} is where you were one move ago. And so on.
Reflog for specific refs
git reflog defaults to HEAD. Every branch has its own reflog too:
git reflog show main
git reflog show origin/main # remote-tracking branches also have one
This is useful when you want to know "where did this branch's tip used to be?" — e.g., before someone rebased and force-pushed.
Recovering a lost commit
The classic case: you ran git reset --hard HEAD~3 and immediately regretted it.
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 4e5f6g7 HEAD@{1}: commit: feat: pricing table <-- this is the work I want back
git reset --hard 4e5f6g7
Or, equivalently:
git reset --hard HEAD@{1}
The work is back. The reflog still records the destructive reset and the recovery, so even this recovery is undoable.
Recovering a deleted branch
Deleting a branch (git branch -D) removes the branch ref but leaves the commits in place — for now. Find the tip of the deleted branch in the HEAD reflog (the entry just before the delete):
git reflog
# 4e5f6g7 HEAD@{2}: checkout: moving from feature/x to main
# ...
git branch feature/x 4e5f6g7
If you can't find it in the HEAD reflog, broader options:
git fsck --lost-found
This lists dangling commits and dangling blobs — work that has no ref pointing at it. Inspect with git show <sha> and recreate a branch when you find what you want.
Recovering work after a botched rebase
A rebase rewrites commits, leaving the originals dangling. The reflog of the branch you rebased shows where it used to point:
git reflog show feature/x
# 1a2b3c4 feature/x@{0}: rebase finished: returning to refs/heads/feature/x
# 5d6e7f8 feature/x@{1}: rebase: hero
# abcdef0 feature/x@{2}: branch: Created from main
git reset --hard feature/x@{2}
This is also why you should always create a backup branch before a complex rebase:
git branch backup/feature-x
git rebase main
# if it goes wrong:
git reset --hard backup/feature-x
Time-based references
The reflog supports time-based syntax:
git show HEAD@{yesterday}
git show HEAD@{2.hours.ago}
git show main@{1.week.ago}
git diff main@{1.week.ago} main
Useful for "what did this branch look like before I started this morning?".
Inspecting individual reflog entries
Show the diff between where you are now and a previous reflog position:
git diff HEAD@{1}
Show the commit a reflog entry pointed to:
git show HEAD@{3}
Limit the reflog to a date range:
git reflog --since="2 days ago"
Practical recipes
"I just ran git reset --hard. Get my work back."
git reflog
# find the SHA from before the reset
git reset --hard <sha>
"I deleted a branch I shouldn't have."
git reflog
# find the last entry that was on that branch
git branch <branch-name> <sha>
"My rebase produced garbage. Start over."
git reflog show <branch>
# find the entry just before the rebase started ("rebase: checkout ..." or "branch: Created ...")
git reset --hard <branch>@{N}
"Did anyone force-push to main? Where did it used to point?"
git reflog show origin/main
# look at HEAD@{1} or earlier entries
(This works only if your local has fetched recently enough to have the old state. The remote's own reflog is private to the server.)
"What's been happening on this branch this morning?"
git reflog show <branch> --since="6 hours ago"
When the reflog will not save you
- Untracked files. The reflog only knows about commits. A
git clean -fdor agit reset --hardover untracked files cannot be undone. - Garbage collection has run. After
gc.reflogExpireUnreachable(default 30 days), unreachable commits can be permanently pruned bygit gc. - You're in a different clone. The reflog is local. If the lost work happened on another machine, that machine's reflog is what you need.
- You've rewritten everything since. Once you make many new commits or run an aggressive
git gc, older orphaned commits may be cleaned out faster.
If you fear loss is imminent, freeze garbage collection:
git config gc.auto 0
Then recover before turning it back on.
Pitfalls
- The reflog is local-only. Never assume someone else's reflog has what you want.
HEAD@{N}is by reflog position, not by date.HEAD@{1.day.ago}is by date. Don't confuse the two.- Force-pushes to your branch overwrite your remote-tracking reflog with each fetch. If you suspect a force-push and you haven't fetched yet, don't fetch — your local copy of
origin/<branch>is still what it was before the force-push.
Summary
git refloglists every move HEAD has made.git reflog show <ref>does the same for a specific branch.- After any destructive command,
git reflogis your first stop. - Recover with
git reset --hard <sha>or by recreating a branch at the lost SHA. - The reflog is local and time-bounded — back up before risky operations and don't wait weeks to recover.
- For deeper rescues,
git fsck --lost-foundfinds dangling objects.