Recovery is work you need to do when the repo's integrity is uncertain:
- a branch tip moved unexpectedly
- a rebase went badly
- commits seem lost
- refs look wrong
- an object appears missing or corrupt
Git has several ways back from that moment, but the first step is usually simple: stop, take inventory, and avoid making the state harder to inspect.
Stop Making It Worse, Then Start with Reflogs
When recovery starts, avoid turning uncertainty into permanent loss:
- do not run aggressive cleanup blindly
- do not prune unreachable data casually
- do not rewrite more refs until you know what moved
Cleanup commands have their place, but cleanup and recovery want different things. Recovery wants time and evidence, and the first triage pass can stay very small:
git reflog -10
git show-ref --head
git fsck --full
That is often enough to tell you whether the problem is "a name moved," "a name disappeared," or "something is actually damaged." If a branch tip moved, a reset happened, or HEAD no longer points where you expected, the reflog is often the fastest route back because it records local ref movement over time. "Lost" usually just means unnamed: the commit may still be there, and only the ref has moved. The first move is usually:
- inspect the reflog
- identify the commit you want back
- create or move a ref deliberately once you are sure
That is much safer than flailing around with random resets while the situation is still unclear. Once you find the commit you care about, name it before doing anything else. A temporary branch or tag prevents re-loss while you investigate, especially after a bad rebase, reset, or deleted branch.
Recovery Tools Tell Different Truths
When the ref space feels confusing, git show-ref is one of the fastest ways to see what names actually exist and where they point.
Recovery often starts with separating what names exist, what names used to exist, and what unnamed objects still exist. Reflogs help with movement over time, while show-ref helps with the current advertised name graph. The other recovery workhorse is git cat-file: once you have an object ID, cat-file helps answer basic forensic questions:
- what kind of object is this?
- what does this commit point to?
- what tree does it name?
- what blob content exists here?
git fsck- Command that verifies object connectivity and validity in the repository database.
git fsck verifies object connectivity and validity in the database — it distinguishes corruption from confusion. git fsck helps separate:
- dangling but valid objects
- unreachable history
- missing objects
- malformed objects
- ref database issues
Not every alarming repository state is corruption. Some of it is simply unnamed or currently unreachable data, and the dangling and unreachable objects fsck reports often come from recent history rewrites, deleted refs, aborted rebases, or interrupted operations rather than catastrophic damage.
Git's storage model is conservative enough that old commits often survive long enough to be recovered if you do not rush to clean them away.
git fsck --lost-found writes dangling objects into .git/lost-found/. It is rarely the first tool to reach for, but it is good to know it exists: when the normal names are gone and you need Git to materialize stray objects in a more discoverable way, --lost-found can help. Not glamorous. Still useful. Recovery has a lot of moves like that.
git verify-pack belongs in recovery as well as in performance work.
If the problem looks like:
- pack corruption
- suspicious pack contents
- missing packed objects
- storage-level inconsistency
then verify-pack helps inspect what the pack indexes say and what the pack actually contains. You do not need a special recovery script before you can get your bearings again, just some commands:
git reflog --date=iso
git show-ref --heads --tags
git cat-file -t <object>
git cat-file -p <object>
git fsck --full
git verify-pack -v .git/objects/pack/pack-*.idx
Those commands answer:
- which names moved?
- which names still exist?
- what object am I actually looking at?
- is the repository damaged or just confusing?
- do the packs look sane?
Deleted Branches, Safe Repair, and When Other Clones Matter
When a branch disappears, the underlying commit often still exists in the branch reflog, the HEAD reflog, or another clone. The recovery path:
- find the old tip
- confirm the commit
- create the branch again
The hard part is staying off the repository long enough to inspect what still exists:
git branch recovered HEAD@{1}
git update-ref refs/heads/recovered <sha>
(Use whichever form matches what evidence you actually have: a reflog entry or a raw commit ID.)
Safe repair means:
- inspect first
- create new refs rather than overwriting old ones immediately
- preserve evidence until you understand it
- recover by addition before recovery by deletion
Destructive repair is different:
- pruning aggressively
- deleting packs or refs before inspection
- forcing rewrites while the cause is still unclear
If objects are missing or corrupt, other copies of the repository become important — another local clone, a remote, a backup, a mirror. Git's distributed nature helps here: if one clone has damage but another still has the objects, recovery becomes much simpler.
Worked Example: Bad Rebase
Suppose you rebased a topic branch and the result is wrong. The old series is gone from the branch, and you want it back. The first move is the reflog:
git reflog topic
That shows the branch's recent movement. Before the rebase, topic pointed somewhere else. The reflog entry just before the rebase started is usually the one you want.
Once you identify the old tip:
git log --oneline topic@{1} -5
git diff topic@{1} topic
The first command confirms the old series looks right. The second shows what the rebase actually changed. If the old tip is the one you want back:
git branch topic-backup topic
git reset --hard topic@{1}
The backup branch is cheap insurance. The reset moves the branch back to where it was before the rebase. The rebased commits are still in the object database for a while, but the branch name now points to the original series again.
Most Git recovery has this shape: the data never left, a name moved, and the reflog tells you where it was.
Worked Example: Corrupt Pack
Suppose git status or git log starts producing errors like:
error: inflate: data stream error (incorrect data check)
fatal: loose object abc123... is corrupt
or:
error: packfile .git/objects/pack/pack-XYZ.pack does not match index
fatal: packed object abc123... corrupt
This is a different situation from a moved ref. Something in the object store is actually damaged. Start with fsck:
git fsck --full
That will report missing objects, corrupt objects, and connectivity problems. Read the output carefully. It tells you whether the damage is isolated or widespread.
If the damage is in a packfile, verify-pack can help narrow it:
git verify-pack -v .git/objects/pack/pack-*.idx 2>&1 | grep -i error
Now the question is: do you have another copy of this repository? A remote, a colleague's clone, another local clone, a mirror. If yes, the recovery path is often straightforward:
git remote add recovery /path/to/good/clone
git fetch recovery
Git will pull the objects it is missing from the good clone. After the fetch, run fsck again to confirm the damage is repaired.
If the corrupt pack is the problem and you have a good remote, you can also remove the damaged pack and re-fetch:
mkdir -p .git/objects/pack/damaged
mv .git/objects/pack/pack-XYZ.pack .git/objects/pack/damaged/
mv .git/objects/pack/pack-XYZ.idx .git/objects/pack/damaged/
git fetch origin
git fsck --full
Move the damaged files aside rather than deleting them. If the fetch brings back the missing objects and fsck is clean, the repository is healthy again. Keep the damaged files until you are sure, then remove them.
If no other copy exists and the damage is limited, fsck --lost-found can sometimes recover dangling objects that are still intact:
git fsck --lost-found
ls .git/lost-found/other/
That is a last-resort tool, kept in reserve for narrow cases. But it exists, and in those cases it helps.
The key difference from the rebase example: ref-level recovery is about finding a name. Pack-level recovery is about finding intact object bytes. The reflog solves the first problem. Another clone solves the second.