reflog & Recovering Lost Work
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:
git log --oneline
git reset --hard HEAD~2
git log --oneline5de7c60 (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:
git reflog754eb8a (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 1Each 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:
git branch rescue HEAD@{1}
git log --oneline rescue -25de7c60 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:
git reset --hard rescue
git branch -d rescueHEAD 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:
git branch -D chemistryDeleted 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:
git stash drop
git fsck --unreachable --no-reflogsDropped refs/stash@{0} (c43ab7f190dbdc41b9598cbeb72636c066e7bbd7)
unreachable commit c43ab7f190dbdc41b9598cbeb72636c066e7bbd7
unreachable commit e200f41a8db29d1f4f606bb3e273a20d97b32688
unreachable commit ef3ee58635b10b6bb3855f25419f0decb70c27f1The 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 --hardorgit restoreover 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 logfirst. - 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
HEADand each branch;git reflogshows it with names likeHEAD@{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_HEADholds the position before a rebase, merge or reset;git fsck --unreachablefinds 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
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?
It is a local diary of reference movements, which makes it a safety net.
2. After git reset --hard HEAD~2 by mistake, where were you just before the reset?
HEAD@{0} is the reset itself; HEAD@{1} is the previous position.
3. Which of these can Git NOT recover?
Git only keeps what it was given through add or commit.
4. Why does a fresh clone not have your reflog?
Each repository keeps its own reflog of its own movements.
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
