Recover Lost Commits with git reflog
Undo a bad reset, rescue commits from a deleted branch, and find work you thought was gone. Reflog is the quiet hero of git.
Every git user has a "I just lost two hours of work" story. Usually it is a stray git reset --hard, a botched rebase, or a branch deleted before it was merged. Most of those stories should end differently: the commits are almost certainly still there, and git reflog can get them back.
What reflog actually is
git reflog is a log of every position HEAD has pointed to on your machine. Commits, checkouts, merges, rebases, resets — each of them records an entry. Entries live for about 90 days by default (30 for unreachable commits). Nothing you do in your working tree is truly gone during that window.
git reflog
Typical output:
a1b2c3d (HEAD -> main) HEAD@{0}: commit: fix login
7d8e9f0 HEAD@{1}: reset: moving to HEAD~3
c4d5e6f HEAD@{2}: commit: refactor auth
b1c2d3e HEAD@{3}: commit: add tests
e7f8a9b HEAD@{4}: commit: wire up API
Read that top-down as "what I did recently." HEAD@{0} is the current state. HEAD@{1} is where you were one move ago, and so on. If anything on that list was a mistake, you can rewind to just before it.
Scenario 1 — You ran git reset --hard and want it back
You meant to reset one commit, reset three, and now the last two commits you wrote are gone from git log. Do not panic.
git reflog
Find the entry before the reset. In the example above, that is HEAD@{2} (the reset is at HEAD@{1}, and the commit immediately before the reset is at HEAD@{2}).
Restore it:
git reset --hard HEAD@{2}
You are now back exactly where you were before the reset. Working tree, commits, and all.
Tip — if you have not committed your working tree changes, do that before running reflog recovery commands. Reflog covers committed history, not unstaged edits.
Scenario 2 — You deleted a branch that had unmerged work
You ran git branch -D feature/old-thing, and only after the shell returned realized the branch was not merged yet. git branch no longer shows it.
The branch pointer is gone, but the commits are still there. Reflog will still list them:
git reflog
Look for lines mentioning the branch name or the last commit message you remember. You can also use the standalone form:
git reflog show --all | grep -i "feature/old-thing"
Once you have the commit hash (any commit on the lost branch — the tip is best), recreate the branch from it:
git branch feature/old-thing a1b2c3d
git checkout feature/old-thing and you are back where you were.
Scenario 3 — A rebase went sideways
You ran git rebase -i and made a mess. Conflicts, wrong commits kept, lines accidentally deleted from the todo list.
git rebase --abort is the right move during a rebase. But if you already finished the rebase and only now noticed the damage:
git reflog
You will see rebase -i (start) and rebase -i (finish) entries. The commit immediately before the (start) is your pre-rebase state:
a1b2c3d HEAD@{0}: rebase -i (finish): returning to refs/heads/feature
7d8e9f0 HEAD@{1}: rebase -i (start): checkout ...
c4d5e6f HEAD@{2}: commit: actual work you want back
Restore:
git reset --hard HEAD@{2}
The rebase is undone as if it never happened.
Scenario 4 — You committed to the wrong branch, then blew it away
Happens most often with a hotfix: you commit a change, realize you were on main when you should have been on a branch, reset main back, and then — in frustration — force-push. Now the commit is nowhere.
If you have not done anything that would prune reflog (which is hard to do accidentally), the commit is still findable. Look for it:
git reflog | grep -i "hotfix keyword"
If the commit shows up:
git checkout -b hotfix/correct-branch a1b2c3d
This creates a new branch starting at the lost commit and checks it out. From there you can push normally.
Scenario 5 — "I do not remember what I did, I just know it is broken"
When you are not sure what went wrong, get a bigger picture than git reflog alone gives:
git reflog --date=iso --pretty=format:'%h %gd %gs' | head -50
That adds timestamps and formats the output into something scannable. You can also view the reflog of the index (useful when recovering staged-but-never-committed work in certain cases):
git reflog show stash
git reflog show --all
git fsck is another angle — it finds "dangling" commits (commits no branch or tag points to, but which still exist):
git fsck --lost-found
Output includes lines like dangling commit a1b2c3d.... Inspect each with git show a1b2c3d until you find the work. Then git branch rescue a1b2c3d pins a branch to it so it stops being dangling.
Make reflog last longer
Default expiration is 90 days for reachable entries and 30 for unreachable. If you want a safer window, set it globally:
git config --global gc.reflogExpire 180.days
git config --global gc.reflogExpireUnreachable 90.days
On a machine with plenty of disk, this is a cheap upgrade. The reflog is text — doubling its retention costs near nothing.
Reflog is per-clone
One important thing that trips people up: reflog is local to your machine. If you deleted the branch on the remote and then cloned fresh on a new laptop, the new laptop does not know about those old commits. Recover on the machine that did the damage, or look for someone on the team who still has a clone with the commits.
If nobody does and the commits were pushed to the remote at any point, the hosting provider (GitHub, GitLab, etc.) may still have them — the REST API often exposes dangling commit SHAs through the events endpoint for a limited time.
The habit that saves you
git reflog is the first command to run any time you feel that "wait, what just happened" jolt. Most of the time the lost work is in the top five lines. It takes five seconds and has rescued countless late-night commits. Build the reflex.
SysEmperor