open atlas
↑ Back to track
Git, from zero to senior GIT · 09 · 05

Disaster recovery

When a reset, rebase, or branch delete seems to destroy work, escalate calmly: git reflog finds moved tips, git fsck --lost-found surfaces dangling objects, git cat-file -p inspects them. Aggressive git gc prunes unreachable objects. The only true loss is never-committed work.

GIT Senior ◷ 18 min
Level
FoundationsJuniorMiddleSenior

You run git reset --hard HEAD~5 to undo a messy merge, glance at the screen, and realise the five commits you wanted are gone from the branch — an afternoon of work, apparently deleted. Or you delete a branch, then remember it had the only copy of a fix. The instinct is panic, but here is the reassuring truth: git almost never actually destroys a committed object the moment you “lose” it. The commit is still in the object store, just no longer pointed to — and there is a calm, ordered way to get it back.

By the end of this lesson you will have a disaster-recovery ladder you can climb in escalating order — reflog, then fsck, then inspecting and restoring dangling objects — and you will know the one true loss git cannot help with and the deadline that ends recovery.

Goal

After this lesson you can recover seemingly-lost commits in escalating order — git reflog, then git fsck --lost-found, then git cat-file -p to inspect and restore a dangling object — and explain how git gc sets the deadline after which unreachable objects are truly gone.

1

First rung: git reflog — almost every “lost” commit after a reset, rebase, or branch-delete is here. The reflog is a local journal of everywhere HEAD and your branch tips have pointed. A reset --hard, a botched rebase, even a branch -D all leave the old position recorded. Find it and reset back:

git reflog                       # list recent HEAD positions with SHAs
# e.g. abc123 HEAD@{1}: reset: moving to HEAD~5
git reset --hard abc123          # restore the branch to before the reset

This handles the overwhelming majority of self-inflicted accidents. The commits were never deleted — only the branch label moved — and the reflog remembers where it was. Try this before anything more exotic.

2

Second rung: git fsck --lost-found — find dangling objects when even the reflog cannot help. The reflog is per-clone and expires (default ~90 days for reachable, ~30 for unreachable entries); a fresh clone has none, and git gc can trim it. When reflog draws a blank, ask Git to scan the entire object database for objects nothing points to:

git fsck --full --unreachable    # list all unreachable objects
git fsck --lost-found            # write dangling commits/blobs to .git/lost-found/

--lost-found drops dangling commit objects into .git/lost-found/commit/ and blobs into .git/lost-found/other/, named by SHA. A “dangling” commit is one no branch, tag, or reflog reaches — exactly what a deleted branch’s tip becomes. fsck finds it even when nothing remembers its name.

3

Third rung: git cat-file -p to inspect a candidate, then restore it. fsck gives you a list of SHAs but no context. Print any object to see whether it is the one you want before acting:

git cat-file -t <sha>            # what kind of object is this? (commit/blob/tree)
git cat-file -p <sha>            # print it: commit message, author, tree, parent

Once you have identified the right dangling commit, give it a name again so it stops being unreachable:

git branch recovered <sha>       # re-anchor the lost commit on a branch

For a single lost file (a dangling blob), git cat-file -p <blob-sha> > recovered.txt writes its contents straight back to disk. Naming the object is what saves it: an object with a ref is reachable, and reachable objects are never pruned.

4

The deadline: git gc is what finally, permanently deletes unreachable objects. Recovery works because Git keeps unreachable objects around — but not forever. Garbage collection (git gc, run automatically by many commands) prunes objects that are both unreachable and older than a grace period (default two weeks). An explicit aggressive run skips much of the safety net:

git gc --prune=now               # immediately delete ALL unreachable objects

That is the point of no return: after gc --prune=now, a dangling commit that reflog already forgot is genuinely gone. So the recovery rule is act before gc. And one loss git can never undo: changes you never committed — unstaged or unsaved edits — are not objects at all, so reflog, fsck, and cat-file have nothing to find. Commit early; an object can be recovered, an un-saved edit cannot.

Why this works

Why can git recover a “deleted” commit at all? Because Git is content-addressed and append-only at the object level. Writing a commit stores an immutable object keyed by its SHA; operations like reset, rebase, and branch-delete only move refs (branch and HEAD pointers), they do not erase objects. A commit with no ref pointing at it becomes unreachable but still physically present in .git/objects. Reflog and fsck are simply two ways to rediscover the SHA of an object that lost its label. Garbage collection is the only thing that actually removes objects — which is why every recovery technique is really a race against the next gc.

Worked example

A deleted branch, recovered two ways.

A developer finishes a tricky fix on hotfix-auth, then — thinking it was already merged — runs git branch -D hotfix-auth. It was not merged. The tip commit now has no ref pointing to it.

Easy path (reflog still has it). They run git reflog, spot def456 HEAD@{6}: checkout: moving from hotfix-auth to main, read the SHA of the deleted tip, and run git branch hotfix-auth def456. The branch is back, every commit intact, total elapsed time under a minute. Most recoveries end here.

Hard path (fresh clone, no reflog). Suppose this happened on a CI machine that then re-cloned, wiping the reflog. They run git fsck --full --no-reflogs --lost-found, which lists several dangling commits in .git/lost-found/commit/. They git cat-file -p each candidate, reading commit messages until they find “Fix auth token refresh” with the right author and date. They re-anchor it: git branch recovered-auth <sha>, then verify with git log. The work is back — but they note that had someone run git gc --prune=now first, the object would have been pruned and unrecoverable. The calm checklist held: reflog, then fsck, then cat-file, then a branch — and never panic-run gc.

Common mistake

The worst move during a recovery is to “tidy up” by running git gc --prune=now or git reflog expire --expire=now --all — exactly the commands that destroy the objects you are trying to save. Under stress people reach for cleanup, but recovery wants the opposite: leave the repo alone, stop running destructive commands, and work read-only (reflog, fsck, cat-file are all safe) until you have re-anchored the object on a branch. The second classic mistake is over-trusting recovery and not committing often enough — reflog and fsck only see committed objects, so the discipline that makes them powerful is committing your work in the first place.

Check yourself
Quiz

You ran `git reset --hard HEAD~3` and lost three commits. The reflog still works. What is the correct first step?

Recap

When a reset, rebase, or branch -D seems to destroy committed work, recover in escalating order. git reflog is the first rung — it journals every place HEAD and your branches pointed, so you usually just reset --hard to the pre-accident SHA. When the reflog is gone (fresh clone, expired, or trimmed), git fsck --lost-found scans the whole object store for dangling objects nothing references. Use git cat-file -p to inspect a candidate SHA, then git branch <name> <sha> to re-anchor it — naming an object makes it reachable and safe. The deadline is git gc: an aggressive --prune=now permanently deletes unreachable objects, so always recover before gc and never panic-run cleanup commands. The one loss git cannot undo is work you never committed, because it was never an object. That is the whole of this rescue unit — and of the git track.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.