reflog & Recovering Lost Work

Git · Lesson 24 of 28 7 min read Updated

Sooner or later you will run git reset --hard on the wrong commit, delete a branch you still needed, or finish a rebase that went badly. It feels like your work is gone. Usually it is not. Git keeps a private diary of every place HEAD and your branches have pointed to, called the reflog (reference log), and committed work stays in the repository for weeks even when no branch points at it.

This lesson shows how to read the reflog and use it to rescue commits, branches and even dropped stashes, plus the one kind of loss Git cannot undo.

A mistake to recover from

Meera keeps her physics exam notes in Git, one commit per chapter. She means to undo a small edit but types the wrong command and resets her branch back two commits:

Terminal
git log --oneline
git reset --hard HEAD~2
git log --oneline
Output
5de7c60 (HEAD -> main) Add notes: Chapter 4
e3ba543 Add notes: Chapter 3
754eb8a Add notes: Chapter 2
5a40b1e Add notes: Chapter 1
HEAD is now at 754eb8a Add notes: Chapter 2
754eb8a (HEAD -> main) Add notes: Chapter 2
5a40b1e Add notes: Chapter 1

Chapters 3 and 4 have disappeared from the log. They have not been deleted, though: the commits still exist, and nothing points at them any more.

Reading the reflog

git reflog lists every movement of HEAD, newest first:

Terminal
git reflog
Output
754eb8a (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
5de7c60 HEAD@{1}: commit: Add notes: Chapter 4
e3ba543 HEAD@{2}: commit: Add notes: Chapter 3
754eb8a (HEAD -> main) HEAD@{3}: commit: Add notes: Chapter 2
5a40b1e HEAD@{4}: commit (initial): Add notes: Chapter 1

Each line shows a commit, a name like HEAD@{1} meaning "where HEAD was one move ago", and what caused the move: a commit, reset, checkout, merge, rebase and so on. The line just before the reset, HEAD@{1}, is exactly where Meera was before her mistake.

Getting the commits back

The safest rescue is to put a branch on the lost commit first, so it can never be lost again, then inspect it:

Terminal
git branch rescue HEAD@{1}
git log --oneline rescue -2
Output
5de7c60 Add notes: Chapter 4
e3ba543 Add notes: Chapter 3

Both chapters are there. Now she can move main back to that commit and delete the temporary branch:

Terminal
git reset --hard rescue
git branch -d rescue
Output
HEAD is now at 5de7c60 Add notes: Chapter 4
Deleted branch rescue (was 5de7c60).

If you are confident, git reset --hard HEAD@{1} does it in one step. Either way, check git reflog before doing anything else after a mistake, because every new command adds lines and shifts the numbers.

Recovering a deleted branch

When you delete a branch with -D, Git prints the commit it pointed to. That message is your rescue note:

Terminal
git branch -D chemistry
Output
Deleted branch chemistry (was e200f41).

To bring it back, create the branch again at that commit: git branch chemistry e200f41. If the message has scrolled away, search the reflog for the last time you were on the branch, for example git reflog | grep chemistry, and use the hash on the line where you switched away from it.

Undoing a rebase or merge

Before a rebase, merge or reset moves a branch in a big way, Git saves the old position in a special reference called ORIG_HEAD. Straight after a rebase you regret, git reset --hard ORIG_HEAD puts the branch back. Later on, find the line ending in rebase (start) in the reflog and reset to the entry just before it.

Every branch also has its own reflog: git reflog show main lists only main's movements. Reflog names can even use time: git show main@{yesterday} or git diff HEAD@{2.hours.ago} work.

Finding a dropped stash

A stash is a commit too, but dropping one removes it from the stash list and the stash reflog, so git reflog does not show it. git fsck (file system check) can list commits that nothing points to:

Terminal
git stash drop
git fsck --unreachable --no-reflogs
Output
Dropped refs/stash@{0} (c43ab7f190dbdc41b9598cbeb72636c066e7bbd7)
unreachable commit c43ab7f190dbdc41b9598cbeb72636c066e7bbd7
unreachable commit e200f41a8db29d1f4f606bb3e273a20d97b32688
unreachable commit ef3ee58635b10b6bb3855f25419f0decb70c27f1

The first hash matches the one in the "Dropped" message, and git stash apply c43ab7f brings the changes back. In a bigger repository the list is longer; git show <hash> on each candidate helps you find the right one. (e200f41 is the deleted chemistry branch from above, and ef3ee58 is an internal commit the stash created to record the index.)

How long does Git keep lost commits?

Reflog entries do not last forever. By default, entries for commits that are no longer on any branch are kept for about 30 days, and other entries for about 90 days (the settings gc.reflogExpireUnreachable and gc.reflogExpire). After that, garbage collection (git gc, which also runs automatically now and then) may delete the unreachable commits for good. So recover sooner rather than later.

Two more limits matter:

  • The reflog is local. It records what happened in your clone, is never pushed, and a fresh clone starts with an empty one.
  • Git can only recover what was committed (or at least staged). Edits you never added are not in Git at all. A git reset --hard or git restore over unsaved, unstaged edits cannot be undone.

Common mistakes

  • Running more commands before checking the reflog. Each one shifts the HEAD@{n} numbers. Look first, then act.
  • Resetting straight to a reflog entry without looking. Create a rescue branch and inspect it with git log first.
  • Expecting the reflog on a new clone or on GitHub. It only exists in the repository where the moves happened.
  • Waiting weeks. Lost commits are eventually garbage-collected.

Key points

  • The reflog records every position of HEAD and each branch; git reflog shows it with names like HEAD@{1}.
  • After a bad reset --hard, create a branch at the right reflog entry (or reset to it) to recover commits.
  • A deleted branch's last commit is printed when you delete it; recreate the branch at that hash.
  • ORIG_HEAD holds the position before a rebase, merge or reset; git fsck --unreachable finds dropped stashes.
  • The reflog is local and expires (about 30 to 90 days); uncommitted, unstaged edits cannot be recovered.

Exercise

In a practice repository with at least four commits: (1) run git reset --hard HEAD~3, then recover all three commits using the reflog; (2) create a branch with one commit, delete it with -D, and bring it back; (3) stash a change, drop the stash, and recover it with git fsck.

Show solution
Terminal
git reset --hard HEAD~3
git reflog -3
git reset --hard HEAD@{1}
git switch -c test-branch
git commit --allow-empty -m "Test commit"
git switch main
git branch -D test-branch
git branch test-branch <hash-from-delete-message>
git stash
git stash drop
git fsck --unreachable --no-reflogs
git stash apply <hash-from-drop-message>

HEAD@{1} is the position just before the reset. The delete and drop messages each print the hash you need; replace the placeholders with them. In PowerShell, write 'HEAD@{1}' in quotes.

Quick Quiz

1. What does the reflog record?

2. After git reset --hard HEAD~2 by mistake, where were you just before the reset?

3. Which of these can Git NOT recover?

4. Why does a fresh clone not have your reflog?

Learn Git with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call