High Performance Git

Section V · Write Pressure, Diagnosis, and Recovery

Chapter 22

Recovery and Repair

Pencil sketch of two people fixing a small boat together.

Recovery is work you need to do when the repo's integrity is uncertain:

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:

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:

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:

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:

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:

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:

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:

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:

Destructive repair is different:

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.