You ran git reset --hard to undo a local experiment and overshot. git log now stops two commits earlier. The files look older. The work you wanted is not in the working tree. Those commits are not deleted yet — Git still keeps a local diary of where HEAD has been. This page is how to read that diary and put the branch back.
Reset modes (--soft, mixed, --hard) live in When Push Fails. Use that page to choose a reset; use this one when a hard reset already went too far. If you still need init, stage, and commit, start with Git Without the Panic.
Warm-up: a local overshoot lab
A single local repo is enough. Create a throwaway folder, make three commits, then reset too far on purpose:
mkdir git-reflog-lab
cd git-reflog-lab
git init
echo 'Project notes' > notes.txt
git add notes.txt
git commit -m "Add notes"
git branch -M main
echo 'Deploy checklist' >> notes.txt
git add notes.txt
git commit -m "Add deploy checklist"
echo 'Rollback plan' >> notes.txt
git add notes.txt
git commit -m "Add rollback plan"
git log --oneline
Three commits, newest first (hashes will differ):
c8e4b71 Add rollback plan
a2d9e03 Add deploy checklist
f1b0c44 Add notes
Now pretend the last two were a mistake:
git reset --hard HEAD~2
git log --oneline
Log now shows only the first commit:
f1b0c44 Add notes
The checklist and rollback commits look gone. Stay inside git-reflog-lab for the rest of the page. Delete the folder when you finish.
Note: —hard also discards uncommitted edits in tracked files. Prefer --soft or mixed until you are sure — details in When Push Fails. This lab commits first so the “lost” work still exists as objects Git can find.
Mental model: reflog is HEAD’s diary
git log walks commits reachable from the current branch. After a hard reset, the branch pointer moved backward, so the dropped commits no longer appear there.
Reflog records every time HEAD moved on this machine: commits, resets, switches. It is a local log in .git/logs/, not part of the history you push. That is why a commit can vanish from git log and still show up in git reflog.
Each line is “HEAD was at this SHA, because of this action.” The newest move is HEAD@{0}. One move ago is HEAD@{1}, and so on. The SHA in the first column is the stable id; the {n} index is only a convenience.
Note: Reflog does not replace a clean branch habit. Keep experiments off the shared default with Stay Off Main so you reset less often in the first place.
Hero use case: find the SHA, then restore
Immediately after the overshoot, ask Git where HEAD has been:
git reflog
Sample output (your hashes will differ):
f1b0c44 HEAD@{0}: reset: moving to HEAD~2
c8e4b71 HEAD@{1}: commit: Add rollback plan
a2d9e03 HEAD@{2}: commit: Add deploy checklist
f1b0c44 HEAD@{3}: commit: Add notes
HEAD@{0} is the reset you just did. The line to recover is the tip from before that reset — here HEAD@{1} / c8e4b71 (Add rollback plan). Inspect before you move anything:
git show 'HEAD@{1}'
You should see the rollback-plan commit message and its diff. Quote the HEAD@{n} form so the shell does not treat the braces as syntax.
Restore the current branch in place
To put main back on that tip:
git reset --hard 'HEAD@{1}'
git log --oneline
All three commits should be listed again:
c8e4b71 Add rollback plan
a2d9e03 Add deploy checklist
f1b0c44 Add notes
You can pass the SHA instead of HEAD@{1} — same result, and the SHA does not shift when you later move HEAD.
Park the commit on a recovery branch
If you would rather inspect without moving main, skip the in-place reset above (or overshoot again with git reset --hard HEAD~2). Copy the SHA from reflog and create a branch there:
git switch -c recover c8e4b71
Use your SHA, not the sample. main stays at the overshot commit. recover points at the restored tip. Compare the two:
git log --oneline -n 3
git switch main
git log --oneline -n 3
On recover you should see the three commits; on main, only Add notes. Keep the recovery branch if you still want those commits; delete it later like any merged feature branch in Stay Off Main.
Note: Once a commit is on a remote and teammates may have it, prefer git revert over reset-and-force-push — same rule as in When Push Fails. Reflog recovery is for work that still lives only on this clone (or that you accept rewriting locally).
HEAD@{n} vs the SHA, and dangling commits
HEAD@{1} means “where HEAD was one recorded move ago.” Run another reset, switch, or commit, and every index shifts. The SHA in the first column does not. After you have moved HEAD again, recover with the hash, not with a remembered {n}:
git reset --hard c8e4b71
Or create the branch from that hash with git switch -c recover c8e4b71 as above.
Until a branch (or tag) points at them again, the dropped commits are dangling: still in the object database, but not reachable from git log on main. Git expires old reflog entries on the order of weeks, then garbage-collects objects nothing points to. Recover sooner rather than later; this is not a forever undo buffer.
Note: You do not need to run expire or git gc yourself for this skill. The useful takeaway is the window: reflog is how you find the SHA while Git still has it.
What reflog cannot do
Reflog is local. A clone that never had the commit has no diary entry and no object to reset to. If you never pushed, another machine cannot recover what only this .git contained.
Never-committed file contents are also out of reach. If you edited notes.txt and ran git reset --hard without committing, there is no SHA for those bytes:
echo 'unsaved thought' >> notes.txt
git reset --hard
The extra line is gone from the file. git reflog still only lists HEAD moves — commits — not unsaved working-tree edits.
Note: Reflog will not help a teammate’s clone, a fresh checkout, or work you never committed. Push (or at least commit) anything you cannot afford to lose; use revert on published mistakes instead of hoping a diary on your laptop is enough.
Quick reference card
| Goal | Command |
|---|---|
| See where HEAD has been | git reflog |
| Inspect a reflog entry | git show 'HEAD@{1}' |
| Restore current branch to that entry | git reset --hard 'HEAD@{1}' |
| Restore using a stable SHA | git reset --hard <sha> |
| Keep current branch; recover on a new one | git switch -c recover <sha> |
| Confirm reachable history | git log --oneline --decorate -n 10 |
Practice drills
Stay inside git-reflog-lab (recreate the three commits if you already cleaned up). Try these without peeking.
- Make three commits on
main, rungit reset --hard HEAD~2, and confirmgit log --onelineshows only the first commit. - Run
git reflog, restore withgit reset --hard 'HEAD@{1}'(or the SHA of the dropped tip), and confirm all three commits are back. - Overshoot with
HEAD~2again. Copy the pre-reset SHA from reflog and rungit switch -c recover <sha>. Confirmmainis still at the reset tip andrecoverhas the missing commits. - Before restoring, run
git showon the reflog SHA (or'HEAD@{1}') and match the commit message and diff to the work you thought was gone. - After you are back on a known commit, append an uncommitted line to
notes.txt, rungit reset --hard, and confirm the extra line is gone and reflog has no entry that restores those bytes.
Solid answers — clear and portable, not the only ones:
# 1
echo 'Deploy checklist' >> notes.txt
git add notes.txt && git commit -m "Add deploy checklist"
echo 'Rollback plan' >> notes.txt
git add notes.txt && git commit -m "Add rollback plan"
git reset --hard HEAD~2
git log --oneline
# 2
git reflog
git reset --hard 'HEAD@{1}'
git log --oneline
# 3
git reset --hard HEAD~2
git reflog
git switch -c recover <sha> # pre-reset tip from reflog
git log --oneline -n 3
git switch main
git log --oneline -n 3
# 4
git show 'HEAD@{1}'
# or: git show <sha>
# 5
echo 'never committed' >> notes.txt
git reset --hard
git reflog
# working tree matches the last commit; the extra line is gone
If those five feel routine, you already have the rescue loop: overshoot, read the diary, inspect the SHA, restore in place or on a recovery branch, and know what reflog will not bring back. Reflog stays a rescue tool, not everyday workflow — the same framing as When Push Fails. Everyday snapshots still start in Git Without the Panic. Keep experiments off the shared default with Stay Off Main. When a bug snuck into history and you need to hunt which commit broke the build, Find the Break is the search skill; this page is only for the moment a reset went too far.