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¶
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¶
-
Missing objects: If you have a remote, fetch the missing objects:
-
Corrupted index: Delete and rebuild:
-
Corrupted HEAD: Manually fix the reference:
-
Corrupted packfile: Remove and re-fetch:
-
Last resort: Clone fresh and copy local branches:
Exercises¶
Further Reading¶
- Pro Git - Chapter 7: Git Tools - stashing, searching, rewriting, debugging
- Git LFS Documentation - large file storage setup and usage
- git-filter-repo Documentation - history rewriting tool
- BFG Repo-Cleaner Documentation - simple history cleaning
- Official git-fsck documentation - repository integrity verification
Previous: Monorepos and Scaling Git | Back to Index