Git path · Undoing safely · Topic 7 of 13

The reflog: Git's safety net

Not started

Every time HEAD or a branch moves, Git appends a line to a log under .git/logs/: the old hash, the new hash and what caused the move. That is the reflog. Because resets, rebases and amends only move pointers and never delete commits straight away, the reflog almost always still names the commit you thought you lost, and getting it back is one reset or branch command.

Why it matters: Almost every 'I destroyed my work' moment after a bad reset, rebase or branch delete can be undone in one command, if you know the reflog is there.

  1. 1Every move is written down

    Each time HEAD or a branch changes (commit, reset, rebase, switch, merge), Git appends the old and new hash to a log.

  2. 2Commits outlive their branches

    A reset or rebase only moves pointers. The old commits stay in the database and the reflog still names them.

  3. 3It is local and it expires

    Reflogs are never pushed or cloned. By default entries for unreachable commits expire after 30 days, others after 90.

  4. 4Only commits are covered

    Changes you never committed (or stashed) are not in any object, so the reflog cannot help with them.

1

How it works

The explanation, with examples you can run.

Lose three commits, then find them

git init -q -b main demo && cd demofor n in 1 2 3; do echo "v$n" > notes.txt; git add notes.txt; git commit -q -m "Notes v$n"; donegit reset --hard HEAD~2git log --oneline
HEAD is now at 46f1e57 Notes v146f1e57 Notes v1

git log follows parents from the branch tip, so v2 and v3 have disappeared from view. The reflog has not forgotten:

git reflog
46f1e57 HEAD@{0}: reset: moving to HEAD~2a8c64d8 HEAD@{1}: commit: Notes v3d4cbec6 HEAD@{2}: commit: Notes v246f1e57 HEAD@{3}: commit (initial): Notes v1

HEAD@{n} means "where HEAD was n moves ago". One move ago it was at v3, so reset back to that:

git reset --hard 'HEAD@{1}'git log --oneline
HEAD is now at a8c64d8 Notes v3a8c64d8 Notes v3d4cbec6 Notes v246f1e57 Notes v1

The quotes keep shells such as PowerShell from treating the braces specially. If you are not sure which entry you want, look first with git show 'HEAD@{2}' or git log -3 'HEAD@{2}', or create a branch on it (git branch rescue 'HEAD@{2}') instead of resetting.

Other ways to read it

  • Per branch. git reflog show feature lists moves of one branch; feature@{1} is its previous tip. After a bad rebase, git reset --hard 'feature@{1}' puts the branch back as it was before.
  • By time. main@{yesterday} or main@{2.hours.ago} resolve through the reflog to where the branch pointed then.
  • Rebase leaves a marker. Rebase also saves the old tip as ORIG_HEAD, so git reset --hard ORIG_HEAD right after a rebase undoes it.
  • Deleted branch. git branch -D prints the hash it was at (Deleted branch tmp (was a8c64d8).), and HEAD's reflog lists the commits you had checked out.

Limits

  • Local only. The reflog is never pushed or cloned. It shows what happened in this repository on this machine.
  • It expires. git gc prunes old entries: by default after 90 days (gc.reflogExpire), or 30 days for entries whose commits are no longer reachable from any ref (gc.reflogExpireUnreachable). After that, unreachable commits can be removed for good.
  • Commits only. Edits that were never committed or stashed exist only in the working tree; reset --hard overwrites them and nothing records them. Commit or git stash before anything risky. As a last resort, git fsck --lost-found can find blobs you once staged with git add.
2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.You ran git reset --hard HEAD~3 by mistake. How do you get the three commits back?
  2. 2.You deleted a branch with git branch -D. What's the quickest way to find its tip?
  3. 3.Can a fresh clone see your reflog?

0 of 3 right so far. Get all three to finish this topic.