reflog & recovering lost work
How git reflog records every place HEAD has pointed so you can recover commits that seem lost after a bad reset, deleted branch, or botched rebase.
What you'll learn
- Why commits that become unreachable after reset --hard are almost never truly deleted — and how reflog proves it
- How to read git reflog output and identify the SHA of the state you want to restore
- The difference between resetting the current branch back to a lost commit vs. creating a safe recovery branch
Before you start
The last lesson kept warning that reset --hard has “no recycle bin” — then hinted the warning was almost a lie. Here is the truth behind that “almost.” A commit you reset away is not erased; only the label pointing to it is gone, and Git keeps a private journal of every label it has ever moved. That journal is the reflog, and it is the single most reassuring command in Git.
Why commits are not deleted when they become unreachable
Every time you run git commit, Git writes a permanent snapshot object to the object store inside .git/objects. That object does not disappear when you run git reset --hard, delete a branch, or rebase. What changes is merely the pointer — the branch label or HEAD — that told Git which commit to show you. The commit itself sits in the object store until Git’s garbage collector decides to prune it, which by default happens no sooner than 30 days for unreachable commits and 90 days for entries in the reflog.
The technical term for a commit that no label points to is an orphaned or unreachable commit. It is invisible to normal commands like git log, but it has not been erased.
What the reflog is
The reflog (reference log) is a local journal that records every position HEAD has occupied, in reverse-chronological order. Every commit, reset, checkout, rebase step, merge, and branch switch appends a new line. The entries are numbered HEAD@{0} (most recent), HEAD@{1}, HEAD@{2}, and so on, where the number in braces is written as HEAD@{n} in backtick notation throughout this lesson.
Each entry stores the SHA of the commit HEAD pointed to at that moment, the operation that moved it, and a timestamp. This makes the reflog your personal time-machine for a repository — one that works even when no remote exists.
git reflog
a3f9c12 HEAD@{0}: reset: moving to HEAD~3
e87b504 HEAD@{1}: commit: Add payment retry logic
c4d2a91 HEAD@{2}: commit: Wire up Stripe webhook
1f0e38a HEAD@{3}: commit: Scaffold checkout controller
8ab5d07 HEAD@{4}: checkout: moving from main to feature/checkout
8ab5d07 HEAD@{5}: pull: Fast-forward
3c21e60 HEAD@{6}: commit: Update README
Reading this output: the bad reset --hard is at HEAD@{0}. The three commits you thought you lost are at HEAD@{1}, HEAD@{2}, and HEAD@{3}. The topmost of those — the one that was your branch tip before the reset — is e87b504.
The recovery loop
Step 1 — find the SHA
Run git reflog and scan for the last good state. Look at the description column: commit: entries name the commit message, and reset: or checkout: entries describe the operation. You want the SHA of the commit that was HEAD just before things went wrong.
If the output is long, pipe it through a pager:
git reflog | head -20
You can also inspect a specific entry before committing to any recovery action:
git show e87b504
Step 2 — choose your recovery method
Option A — move the current branch back (destructive, only if certain)
git reset --hard e87b504
This repoints the current branch to e87b504 and restores the working directory to match. It is the direct inverse of the bad reset. Use this only when you are sure e87b504 is exactly where you want the branch to be.
Option B — create a recovery branch (safer, recommended)
git switch -c recover-branch e87b504
# or, with older Git:
git checkout -b recover-branch e87b504
This creates a new branch at e87b504 without touching your current branch at all. You can inspect the recovered work, cherry-pick individual commits, or merge — without any risk of making the situation worse. This is the safer default whenever you are not 100% certain of the target SHA.
Reflog for a specific branch
git reflog without arguments shows HEAD’s history. To see what happened to a specific branch pointer (useful when you deleted the branch entirely), use:
git reflog show feature/checkout
The output format is the same: SHA, operation, message. Find the SHA of the last commit on that branch and recover it with git switch -c as above.
Last resort: git fsck
If the reflog has expired or you are working in a fresh clone, git fsck can scan the entire object store for commits that are no longer reachable from any reference:
git fsck --lost-found
Dangling commits are written as files under .git/lost-found/commit/. You can inspect them with git show <sha> and then use git switch -c to recover whichever you need. This is slower and noisier than reflog, but it reaches further back.
Quick reference
| Situation | Command |
|---|---|
| See HEAD’s full movement history | git reflog |
| See a specific branch’s history | git reflog show feature/checkout |
| Inspect an entry before recovering | git show HEAD@{3} or git show e87b504 |
| Move current branch back (certain) | git reset --hard e87b504 |
| Create a safe recovery branch | git switch -c recover-branch e87b504 |
| Scan for all orphaned commits | git fsck --lost-found |
In one breath
A commit is never deleted by reset --hard, a deleted branch, or a botched rebase — only the pointer to it moves. The commit object sits in .git/objects for ~30-90 days, orphaned but intact. The reflog is Git’s local journal of every position HEAD has held (HEAD@{0} newest), so git reflog shows you the SHA of the last good state; recover it safely with git switch -c recover-branch <sha> (a new label, nothing else touched) rather than the destructive git reset --hard <sha>. git reflog show <branch> rescues a deleted branch; git fsck --lost-found is the deeper last resort. The two hard limits: the reflog is local-only (a fresh clone has none) and it cannot recover what was never committed — which is the whole case for committing early and often.
Practice
Before the quiz, sort the recoverable from the lost: (a) you reset --hard HEAD~3 over three good commits — recoverable? how? (b) you git branch -D feature on an unmerged branch — recoverable? which command finds its tip? (c) you had two hours of uncommitted edits and reset --hard HEAD wiped them — recoverable? And explain why exactly one of these three is genuinely beyond rescue.
Quick check
A question to carry forward
That closes the Git course — fifteen lessons, from “why version control” all the way to rescuing a commit you thought you had destroyed. But look down at how you did every bit of it. Each command — git commit, git push, git rebase, git reflog — you typed into the same dark window: a terminal, running a shell. You have leaned on that shell this entire time and never once stopped to study it. What is actually reading those commands? How does it know what ~ means, where files live, how to chain one tool’s output into another’s input, or run a hundred of them in a loop while you sleep? Git was one powerful tool reached through the terminal; the terminal itself is the meta-skill that makes every tool — Git, Python, your whole workflow — faster. So the final section turns the camera around to the window you have been typing into all along, and it begins with the simplest question there is: what is the shell?