High Performance Git

Section V ยท Diagnosis and Recovery

Chapter 21

Recovery and Repair

Pencil sketch of two people fixing a small boat together.

Recovery starts when the repository feels wrong enough that you stop trusting your next command:

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.

That usually means:

The earlier maintenance chapter matters here. Cleanup commands are not evil, but cleanup and recovery want different things. Recovery wants time and evidence. Cleanup wants to remove stale state.

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" often just means unnamed. The commit may still be there; the ref is what moved.

The first move is usually:

That is much safer than flailing around with random resets while the situation is still unclear.

If you find the commit you care about, the safest next move is often to name it before doing anything else.

A temporary branch or tag is cheap insurance. It turns:

into:

It is especially useful after a bad rebase, reset, or deleted branch.

Recovery Tools Tell Different Truths

git show-ref is not flashy. In recovery, that is part of the charm. When the ref space feels confusing, show-ref is one of the fastest ways to see what names actually exist and where they point.

Recovery often starts with separating:

Reflogs help with movement over time. show-ref helps with the current visible name graph.

git cat-file is the other recovery workhorse.

Once you have an object ID, cat-file helps answer basic forensic questions:

Git recovery is much more tractable than recovery in systems with less legible internal state because objects are inspectable directly.

git fsck
Command that verifies object connectivity and validity in the repository database.

git fsck verifies the connectivity and validity of objects in the database.

That makes it the right tool when the question becomes:

"Is this repository merely confusing, or is something actually broken?"

git fsck helps distinguish:

That distinction matters. Not every alarming repository state is corruption. Some of it is simply unnamed or currently unreachable data.

git fsck distinguishes dangling and unreachable objects. Those often come from recent history rewrites, deleted refs, aborted rebases, or interrupted operations rather than catastrophic damage.

The right response is often:

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.

That is more storage-forensic than reflog recovery, but both belong in the same toolbox.

You do not need a special recovery shell script before you can get your bearings again. This small set already covers a lot:

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

That is enough to answer several important questions quickly:

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 is usually simple:

The hard part is not the mechanics. The hard part is keeping your hands off the repository long enough to inspect what still exists.

The mechanics really are that plain:

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 usually means:

Destructive repair is different:

There are times for destructive cleanup. They are rarely the opening move.

One reality is blunt: if objects are truly missing or corrupt, other copies of the repository become important.

That means:

Git's distributed nature helps here in a very practical way. If one clone has damage but another still has the objects, recovery becomes much simpler.

When things feel wrong, a practical order of operations is usually:

  1. stop mutating the repository casually
  2. inspect reflogs and current refs
  3. inspect relevant objects directly with cat-file
  4. run fsck if integrity is in doubt
  5. use verify-pack if the question is pack-level
  6. recover by naming the desired commit again
  7. only clean up once the repository is understood

This sequence keeps you from turning confusion into data loss.

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.

This is the most common shape of Git recovery: 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, not a first move. But it exists, and in narrow 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.