backend / git / 05_reflog_recovery.md

Reflog and Recovery

6 interview angles 5 min read source

Reflog and Recovery

The reflog is git’s safety net. Every time HEAD or a branch pointer moves, git records it locally. Even after a “destructive” operation (hard reset, rebase, force-push, branch delete), the commits are still reachable for ~90 days via reflog.

If you remember nothing else: before panicking about lost commits, run git reflog.

What the reflog records

git reflog

Output:

a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e4f5g6h HEAD@{1}: commit: Add user serializer
i7j8k9l HEAD@{2}: commit: Add user model
m0n1o2p HEAD@{3}: pull: Fast-forward
q3r4s5t HEAD@{4}: checkout: moving from main to feature

Every operation that moved HEAD is logged with:

  • The commit SHA HEAD pointed to.
  • A reference name HEAD@{N} (N = how many moves ago).
  • What kind of operation (commit, reset, checkout, rebase, etc.).
  • Sometimes a message.

Per-branch reflog

git reflog show feature              # reflog for the feature branch
git reflog show main                 # reflog for main

Each branch has its own reflog. Useful when “the branch was at X yesterday, what is it now?”

Recovering lost commits

Scenario: hard reset went too far

git reset --hard HEAD~5              # oops, lost 5 commits

git reflog                           # find the pre-reset SHA
# e4f5g6h HEAD@{1}: commit: ...

git reset --hard e4f5g6h             # restore

Or:

git reset --hard HEAD@{1}            # one move ago

Scenario: deleted a branch

git branch -D feature                # gone — or is it?

git reflog --all                     # search all reflogs
# e4f5g6h refs/heads/feature@{0}: commit: Add ...

git checkout -b feature e4f5g6h      # recreate the branch at that SHA

If the branch’s reflog is gone, search all reflogs:

git reflog --all | grep "<keyword from commit message>"

Scenario: rebase went wrong, branch is wrecked

git rebase main                      # interactive rebase, finished badly

git reflog
# d4e5f6g HEAD@{0}: rebase finished: returning to refs/heads/feature
# c1b2a3z HEAD@{8}: rebase: ...
# b9d8e7f HEAD@{9}: rebase: checkout main
# a1b2c3d HEAD@{10}: commit: original commit before rebase  ← here

git reset --hard a1b2c3d             # back to pre-rebase state

Or use the named alias:

git reset --hard ORIG_HEAD           # git records this before rebase/merge/reset

ORIG_HEAD is set automatically before destructive operations — a quick way back without searching the reflog.

Scenario: force push wiped the remote

The remote is gone, but your local clone still has the commits. Push them back:

git push --force-with-lease origin main

If your local was also reset, find the SHA via reflog and reset there first.

If you have no local copy of the commits (no one has them), they’re truly gone — git’s distributed nature means there’s no central recovery.

Scenario: stash got dropped

git stash drop (or git stash pop on a stash that conflicted and you discarded) leaves orphaned commits.

git fsck --no-reflog | grep "dangling commit"
# dangling commit a1b2c3d4...
git show a1b2c3d4                    # is this it?
git stash apply a1b2c3d4             # apply if so

What’s NOT in the reflog

  • Untracked files. Reflog tracks commits, not files. git clean -fd wipes untracked files with no recovery.
  • Working-tree changes never committed. If you lose unstaged changes, only your editor’s undo or filesystem snapshots can save you.
  • Operations on a different machine. Reflog is local. A coworker’s reset won’t appear in your reflog.

Reflog expiration

Default policy:

  • Reachable commits: kept indefinitely.
  • Unreachable commits: kept for 90 days (gc.reflogExpireUnreachable).
  • Reflog entries themselves: kept for 90 days (gc.reflogExpire).

Tweakable per repo:

git config gc.reflogExpire 365           # keep reflog entries for 1 year
git config gc.reflogExpireUnreachable 90 # keep unreachable commits for 90 days

git gc (garbage collection) eventually removes the orphaned commits. By default it runs automatically when there are too many loose objects.

git fsck — find dangling objects

git fsck --lost-found                # report all dangling objects, save to .git/lost-found/
git fsck --no-reflog                 # ignore reflog when checking reachability

Useful for true forensics — recovering from a gc that ran before you noticed the loss.

ORIG_HEAD, FETCH_HEAD, MERGE_HEAD

Special refs git sets automatically:

Ref Meaning
ORIG_HEAD where HEAD was before the last rebase/merge/reset (your “undo” target)
FETCH_HEAD where the last git fetch ended up
MERGE_HEAD the other parent of an in-progress merge (only set during merge)
HEAD@{N} reflog reference; N moves ago
HEAD@{2.hours.ago} reflog reference by time
branch@{upstream} (or @{u}) the upstream branch this tracks

git diff @{u} = “show difference between my branch and its upstream.”

Stash recovery

Stashes are commits in disguise. Listed:

git stash list
# stash@{0}: WIP on feature: a1b2c3d Add user model

Even after git stash drop stash@{0}, the underlying commit lives in object storage until garbage collection. Recover via git fsck --no-reflog | grep dangling.

Practical workflow

When you panic:

  1. Don’t run more git commands. Especially not git gc.
  2. git reflog — find the SHA you want.
  3. git reset --hard <sha> or git checkout -b recovery <sha> to retrieve it.
  4. If reflog doesn’t have it, git fsck --lost-found for dangling objects.

Common interview confusions

  • “Git permanently deletes commits with --hard.” — no, they’re orphaned but kept ~90 days. Reflog finds them.
  • “Reflog is shared across machines.” — purely local. Coworker’s reflog is on their machine.
  • “Force-push deletes commits forever.” — only on the remote. Local clones still have them; you can push them back if you act before they GC.
  • “There’s no way to recover a dropped stash.”git fsck --no-reflog | grep dangling and git stash apply <sha> usually finds it.

Interview angle

  • “What’s the reflog and when do you use it?” — local log of every HEAD/branch movement. Use it to recover from destructive operations: hard reset, bad rebase, deleted branch, dropped stash.
  • “You hard-reset and lost 3 commits. How do you recover?”git reflog to find the pre-reset SHA, git reset --hard <sha>. Or git reset --hard ORIG_HEAD if it’s the operation you just did.
  • “How long does the reflog keep dropped commits?” — ~90 days for unreachable commits, then git gc may collect them. Configurable via gc.reflogExpireUnreachable.
  • “You force-pushed over a teammate’s work — can they recover?” — yes, if their local clone still has the commits (their reflog has them). They can push back. If only the remote had them, they’re gone.
  • “What’s ORIG_HEAD?” — git automatically sets it before destructive operations (rebase, merge, reset). Quick way back without searching the reflog.
  • “How do you find a dropped stash?”git stash list (might still be there), or git fsck --no-reflog | grep dangling and inspect each dangling commit.