Recovery with git reflog
The reflog is Git's black-box flight recorder. Every time HEAD or a branch moves — for any reason, including resets, rebases, checkouts, amends, and merges — Git appends an entry to a local log. When something goes wrong, the reflog tells you the exact SHA of every state your branch ever held, so you can rewind to a safe one.
What the reflog is (and is not)
It is local — never pushed, never shared. Each clone has its own reflog.
It is a record of pointer movements, not commits. A
reset --hardadds a reflog entry without creating a commit.It is not part of the commit graph.
git logwill not show reflog entries; you must usegit reflog(or--walk-reflogs).Entries expire by default after 90 days for reachable commits and 30 days for unreachable ones.
Looking at the reflog
Read the log of HEAD movements
git reflog
# b3f4a2c (HEAD -> main) HEAD@{0}: commit (amend): Fix login
# 9c1a2b3 HEAD@{1}: commit: Fix logn vlaidation
# 7f8d9e0 HEAD@{2}: reset: moving to HEAD~1
# 5a6b7c8 HEAD@{3}: commit: WIP — broken
# 3a4b5c6 HEAD@{4}: checkout: moving from feature to main
# Show per-branch reflog
git reflog show main
git reflog show feature
# Show the reflog with full commit data
git log -g --oneline
git log --walk-reflogs --pretty=fullerAddressing reflog entries
Ways to name a past state
HEAD@{0} # current
HEAD@{1} # one move ago
HEAD@{5} # five moves ago
HEAD@{yesterday} # where HEAD was 24 hours ago
HEAD@{2.hours.ago} # two hours ago
HEAD@{2026-05-19} # a calendar date
main@{1} # the previous tip of main
main@{0} # current tip of mainThe recovery recipe
Undo a disastrous reset
# You ran a bad command
git reset --hard HEAD~5 # oh no — wiped 5 commits of work
# Step 1: find the SHA before the disaster
git reflog
# 1a2b3c4 HEAD@{0}: reset: moving to HEAD~5
# 9f8e7d6 HEAD@{1}: commit: the work I just destroyed ← we want this
# Step 2: go back
git reset --hard HEAD@{1}
# Or by SHA, which is safer if the reflog has shifted:
git reset --hard 9f8e7d6
# Step 3: confirm
git log --onelineRetention and pruning
How long reflog entries last depends on two config knobs and whether git gc has run.
Reflog expiry settings
# Defaults git config --get gc.reflogExpire # 90.days git config --get gc.reflogExpireUnreachable # 30.days # Keep entries longer git config --global gc.reflogExpire 200.days git config --global gc.reflogExpireUnreachable 90.days # Manually expire entries older than 30 days git reflog expire --expire=30.days --all # Force-prune unreachable objects immediately (DESTRUCTIVE!) git reflog expire --expire-unreachable=now --all git gc --prune=now
When the reflog can save you
git reset --hardto the wrong commit.git commit --amendthat overwrote a commit you actually liked.git rebasethat produced a mess — even mid-rebase you cangit reset --hard ORIG_HEADor check the reflog.Deleted a local branch with
git branch -D feature— the reflog still has its last position.git checkout(old-style) that overwrote uncommitted edits — only if they were committed at some point.
When the reflog cannot save you
Working-tree edits that were never staged or committed (e.g., after
git restore <file>).Untracked files removed by
git clean.A commit that was deleted more than 30/90 days ago and
git gchas since pruned it.Anything that happened in a different clone — reflogs are not shared.
After
git gc --prune=nowpurges unreachable objects.
Branch reflog vs HEAD reflog
Two views of the same data
# Every place HEAD has been (across branches too) git reflog git reflog show HEAD # Only the positions main has held git reflog show main # Useful when HEAD jumped around (checkouts, detached HEAD) # but you only care about a specific branch's history.