The order to work in
Top to bottom. Most incidents end at step 1, and the order matters more than any individual command — reaching for fsck first is the standard mistake, and it reports nothing useful for anything that just happened.
git reflog— every positionHEADhas held, newest first, with the operation that caused each move. Costs nothing to read and is usually the whole answer.git reset --hard HEAD@{1}— undoes the last thing that movedHEAD, whatever it was: a reset, a rebase, a merge, an amend, a bad checkout.git cherry-pick <hash>— when you want one commit back rather than the whole chain. A reflog hash is a commit like any other.git branch <name> <hash>— for a deleted branch.git branch -Dprinted the hash when it deleted it, andHEAD’s reflog still has it.git fsck --lost-found— last, not first. It finds only what the reflog has already forgotten, so it matters after entries expire and reports nothing before then.
Reading the reflog
Reflogs “record when the tips of branches and other references were updated in the local repository” (git-reflog). They are local, they are not pushed, and they are the single most valuable file in the repository.
| Expression | Means |
|---|---|
HEAD@{0} | Where HEAD is now. |
HEAD@{2} | “Where HEAD used to be two moves ago” — the docs’ own wording. |
main@{1} | Where the branch main pointed one move ago. Branches have their own reflogs. |
main@{one.week.ago} | Where it pointed by date rather than by count. Works anywhere a commit is accepted. |
git reflog show <branch> | That branch’s reflog. An alias for git log -g, so every git log option works on it. |
ORIG_HEAD | The start point of the most recent big operation only. Overwritten by the next one; the reflog is not. |
Grep it for the commit message, not the branch name — a branch name also matches the checkout: lines, whose hash is the branch you moved to, not the one you want:
git reflog | grep 'Try the experiment'
The four cases
| What happened | What to do |
|---|---|
| An agent hard-reset commits away | git reset --hard HEAD@{1}. The commits were never touched — only the branch pointer moved, and every one of them still names its parent, which is why recovering the tip recovers the chain. |
git commit --amend replaced something you needed | The original is the next line down in the reflog. git show HEAD@{1} to read it, git reset --hard HEAD@{1} to take it back. |
| A branch was deleted | git branch <name> <hash>. A branch is a 41-byte file holding a hash, so restoring one is not a special operation — it is creating a branch at a commit you already have. |
| A rebase went wrong | git reflog shows a rebase (start): checkout <base> entry; the line immediately before it is where your branch stood. ORIG_HEAD also holds it, until the next operation overwrites it. |
What is genuinely unrecoverable
Staging writes a blob into the object database, and from that moment the content survives almost anything — git reset --hard moves pointers and deletes no objects. Content that was never staged exists only in the working tree, and there is nothing to recover it from.
So staged-but-never-committed work comes back: git fsck --lost-found reports it as a dangling blob, and git cat-file -p <hash> prints it. --lost-found also “write[s] dangling objects into .git/lost-found/commit/ or .git/lost-found/other/… If the object is a blob, the contents are written into the file, rather than its object name” (git-fsck), so you can read it without plumbing at all.
| Genuinely gone | Why |
|---|---|
Edits never git add-ed, after --hard | They existed only in the working tree. No object was ever written. |
Untracked files after git clean -fd | The one irreversible command in this course. Note that reset --hard does not touch untracked files — only clean does. |
Anything after the reflog expires and gc runs | See the window below. Weeks, not minutes — but not forever. |
Your actual recovery window
Two settings decide how long the reflog remembers. gc.reflogExpire “removes reflog entries older than this time; defaults to 90 days”; gc.reflogExpireUnreachable does the same for entries not reachable from the current tip and “defaults to 30 days” (git-gc). The harsher default applies to exactly the entries you care about — the page says they “are generally created as a result of using git commit --amend or git rebase”.
| Command | Why |
|---|---|
git config --get gc.reflogExpire | Unset means the 90-day default. |
git config --get gc.reflogExpireUnreachable | Unset means 30 days. This is the number that matters after a rebase. |
git config --global gc.reflogExpire never | Costs almost nothing in disk and buys an unlimited window. Worth setting deliberately. |
When the reflog does not have it
The standard advice is to ask a colleague for their clone. Check what that actually buys you first, because a clone carries less than people assume.
| Place to look | What is actually there |
|---|---|
| Someone else’s clone | Only what was pushed. A clone receives no reflogs and no unreachable objects — a fresh one has a single clone: reflog line, and an abandoned commit is not in it at all. Verified 2026-08-01. |
| The remote | Whatever was pushed, including branches you deleted locally. git ls-remote origin lists everything it still has. |
| Another worktree | The same object database — so a commit made in any linked worktree is already in yours, with no fetch. |
| Your editor’s local history | Not git, and often the only copy of an unstaged edit. The one place worth looking after --hard on unstaged work. |
The habit that makes all of this unnecessary: git add early and often, even mid-task, even on work you are unsure about. It costs nothing, commits nothing, and moves your work across the only line that matters.
- Pro Git §7.7 — Reset DemystifiedThe three trees, and
resetas one command that walks down them and stops where you tell it. - git-reflogWhat a reflog is, the
HEAD@{n}reading, and the time form. - git-fsckWhy
--unreachablefinds nothing for a fresh mistake: fsck takes all reflogs as heads unless--no-reflogsis given. - git-gcThe 90-day and 30-day expiry defaults — your real recovery window.
- Oh Shit, Git!?! — Katie Sylor-MillerRecipes for specific disasters, in the wording people actually search for.