Skip to content

Troubleshooting and Recovery

Things go wrong with Git. Commits end up on the wrong branch, force pushes destroy history, secrets get committed, merge conflicts spiral out of control, and sometimes the repository itself gets corrupted. This guide is your recovery playbook - a reference for undoing every common mistake and diagnosing problems when Git behaves unexpectedly.


The Recovery Decision Tree

Before reaching for a specific command, identify your situation:

What happened Recovery
Bad commit message git commit --amend (if unpushed)
Committed to wrong branch git cherry-pick + git reset
Want to undo last commit (keep changes) git reset --soft HEAD~1
Want to undo last commit (discard changes) git reset --hard HEAD~1
Accidentally deleted a branch git reflog + git branch
Bad merge git revert -m 1 <merge-commit> (shared) or git reset (local)
Committed a secret Revoke secret, then git filter-repo
Pushed something bad to shared branch git revert (never force push shared branches)
Lost commits after rebase git reflog + git reset
Repository corruption git fsck + git gc

Recovering Lost Commits

From the Reflog

The reflog records every HEAD movement. Even after git reset --hard, the old commits exist in the object database and appear in the reflog:

# See recent HEAD history
git reflog

# Find the commit you want
git reflog | grep "commit: Add user auth"

# Recover by resetting to that point
git reset --hard HEAD@{3}

# Or create a branch at that point (safer)
git branch recovery HEAD@{3}

From git fsck

If reflog entries have expired (after 30+ days), use git fsck:

# Find unreachable (dangling) commits
git fsck --unreachable | grep commit

# Examine a dangling commit
git show <commit-hash>

# Recover it
git branch recovery <commit-hash>

# Or use --lost-found to dump all recoverable objects
git fsck --lost-found
ls .git/lost-found/commit/

Undoing Common Mistakes

Wrong Commit Message

# Fix the last commit message (unpushed only)
git commit --amend -m "Corrected message"

Committed to the Wrong Branch

# You committed to main instead of feature/auth
# Step 1: Note the commit hash
git log --oneline -1
# a1b2c3d Accidental commit on main

# Step 2: Move the commit to the right branch
git switch feature/auth
git cherry-pick a1b2c3d

# Step 3: Remove from main
git switch main
git reset --hard HEAD~1

Undo the Last Commit (Keep Changes)

# Soft reset: uncommit but keep changes staged
git reset --soft HEAD~1

# Mixed reset: uncommit and unstage, changes in working dir
git reset HEAD~1

Accidental git reset --hard

# Find where HEAD was before the reset
git reflog
# a1b2c3d HEAD@{1}: commit: Important work

# Recover
git reset --hard HEAD@{1}

Bad Merge

# On a shared branch: create a revert commit
git revert -m 1 <merge-commit-hash>
# -m 1 keeps the mainline parent, reverting the merged branch's changes

# On a local branch: reset before the merge
git reset --hard HEAD~1

Accidental git add (Staged Something Wrong)

# Unstage a specific file
git restore --staged secret.env

# Unstage everything
git restore --staged .

Git LFS (Large File Storage)

Git isn't designed for large binary files. Each version of a large file is stored as a full blob, and the repository size grows linearly with each change. Git LFS solves this by replacing large files with lightweight pointers in the repository and storing the actual file content on a separate server.

Setup

# Install Git LFS
brew install git-lfs    # macOS
sudo apt install git-lfs  # Debian/Ubuntu

# Initialize LFS in a repository
git lfs install

# Track file patterns
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/videos/**"

# Check what's tracked
git lfs track
cat .gitattributes

How It Works

When you git add a tracked file, LFS replaces it with a pointer file:

version https://git-lfs.github.com/spec/v1
oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393
size 12345678

The actual file content is uploaded to the LFS server during git push and downloaded during git pull or git checkout.

Migrating Existing Files to LFS

# Migrate existing large files in history
git lfs migrate import --include="*.psd,*.zip" --everything

# Check which files are taking the most space
git lfs migrate info

LFS migration rewrites history

git lfs migrate import rewrites commit history to replace files with LFS pointers. This requires force pushing and coordination with your team. All collaborators must re-clone.


History Rewriting at Scale

git filter-repo

git filter-repo is the recommended tool for large-scale history rewriting. It's faster and safer than the older git filter-branch:

# Remove a directory from all history
git filter-repo --path old-vendor/ --invert-paths

# Remove files by pattern
git filter-repo --path-glob '*.log' --invert-paths

# Change author email across all history
git filter-repo --email-callback 'return email.replace(b"old@email.com", b"new@email.com")'

# Reduce to only a subdirectory (extract a project from a monorepo)
git filter-repo --subdirectory-filter services/auth

BFG Repo-Cleaner

BFG is simpler for common tasks:

# Remove all files larger than 100MB from history
bfg --strip-blobs-bigger-than 100M

# Remove specific files
bfg --delete-files passwords.txt

# Replace sensitive strings
bfg --replace-text replacements.txt

After any history rewriting:

git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --all
git push --force --tags

Performance Diagnosis

When Git feels slow, these tools help identify the bottleneck:

Trace Environment Variables

# General trace (shows internal commands)
GIT_TRACE=1 git status

# Performance timing per operation
GIT_TRACE_PERFORMANCE=1 git status

# Pack protocol traces (for fetch/push)
GIT_TRACE_PACKET=1 git fetch

# HTTP request details
GIT_CURL_VERBOSE=1 git fetch

Common Performance Fixes

Symptom Likely cause Fix
git status slow Large working directory Enable core.fsmonitor
git log slow Large history without commit graph git commit-graph write
git clone slow Large repository Partial clone + sparse checkout
git push/fetch slow Many packfiles git repack -a -d
git gc slow Too many loose objects Already running GC, just wait

Common Error Messages Decoded

Error Meaning Fix
fatal: not a git repository No .git directory in this or parent directories cd to a repo or git init
error: failed to push some refs Remote has commits you don't have git pull then git push
CONFLICT (content): Merge conflict Both branches changed the same lines Edit files, remove markers, git add, git commit
detached HEAD HEAD points to a commit, not a branch git switch main or git switch -c new-branch
fatal: refusing to merge unrelated histories Two repos with no common ancestor git pull --allow-unrelated-histories
error: Your local changes would be overwritten Uncommitted changes conflict with the operation git stash then retry
warning: LF will be replaced by CRLF Line ending conversion happening Check core.autocrlf and .gitattributes
fatal: bad object HEAD Corrupted HEAD reference Check .git/HEAD, may need to rewrite it

Repository Corruption and Repair

Corruption is rare but can happen from disk failures, interrupted operations, or filesystem bugs.

Detecting Corruption

# Full integrity check
git fsck --full

# Common output:
# broken link from commit abc123 to tree def456
# dangling commit 789abc
# missing blob fed321

Repair Strategies

  1. Missing objects: If you have a remote, fetch the missing objects:

    git fetch origin
    

  2. Corrupted index: Delete and rebuild:

    rm .git/index
    git reset
    

  3. Corrupted HEAD: Manually fix the reference:

    echo "ref: refs/heads/main" > .git/HEAD
    

  4. Corrupted packfile: Remove and re-fetch:

    mv .git/objects/pack/pack-*.pack /tmp/backup/
    mv .git/objects/pack/pack-*.idx /tmp/backup/
    git fetch origin
    

  5. Last resort: Clone fresh and copy local branches:

    git clone origin-url fresh-clone
    cd fresh-clone
    git remote add broken ../broken-repo
    git fetch broken    # Salvage what you can
    


Exercises


Further Reading


Previous: Monorepos and Scaling Git | Back to Index

Comments