The git recovery guide — undoing every mistake.
Most git "disasters" are recoverable. The data is still there — git almost never actually deletes anything for 30 days. You just need to know where to look. This is the working field guide: each scenario, the exact diagnostic, the exact recovery command. Print it out and tape it next to your monitor.
01The most important command — reflog
Before anything else: git reflog. The reflog is git's local history of where your branches have pointed. Every commit, reset, rebase, checkout, merge — all recorded with a timestamp and a unique reference. The reflog is local-only and retained for 30 days by default.
$ git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~3
e4f5g6h HEAD@{1}: commit: Add feature X
i7j8k9l HEAD@{2}: commit: Refactor utils
m0n1o2p HEAD@{3}: pull: Fast-forward
Every line is a state your repository was in. If you've lost work, the SHA of the commit you wanted is almost certainly here. To go back to any state: git reset --hard HEAD@{2}.
The reflog is the answer to 80% of git disasters. Memorize the command.
02You committed to the wrong branch
Common: you meant to commit on feature-x but you were still on main. The commit is on main, you need it on feature-x.
# If you haven't pushed yet:
$ git log --oneline -1 # note the SHA
$ git reset --hard HEAD~1 # undo on main
$ git checkout feature-x
$ git cherry-pick <SHA> # apply to feature branch
If you pushed already, don't reset main — that requires a force push and rewrites history others may have pulled. Instead, cherry-pick the commit to feature-x and revert it on main with git revert.
03Undo your last commit (keep the changes)
You committed too early — the message is wrong, or you want to add more files.
$ git reset --soft HEAD~1
# Your changes are staged, ready to re-commit
$ git commit -m "Better message"
Three reset modes:
--soft— moves HEAD, keeps changes staged. "Re-do the commit"--mixed(default) — moves HEAD, keeps changes unstaged. "Re-stage and re-commit"--hard— moves HEAD, throws away changes. "Pretend I never did that"
04You force-pushed and broke main
Worst-case scenario: you ran git push --force on main and overwrote commits other people had. Don't panic. The reflog still has them.
$ git reflog
# Find the SHA of where main pointed before the force-push
$ git reset --hard <good-SHA>
$ git push --force-with-lease # restore on remote
--force-with-lease is safer than --force: it refuses to push if someone else has pushed in the meantime, preventing you from overwriting their work a second time.
git config --global push.default current and never use --force on shared branches. Use --force-with-lease instead, and only on your own feature branches.05You deleted a branch with unmerged commits
You ran git branch -D feature-x and git happily deleted it. The branch is gone, but the commits aren't.
$ git reflog
# Find the last commit that was on feature-x (look for "checkout: moving from feature-x")
$ git checkout -b feature-x <SHA>
If the reflog doesn't show what you need, run git fsck --lost-found. This scans every dangling object in the repo and surfaces commits that aren't reachable from any branch. The lost commit will be in there.
06You dropped a stash
Stashes have their own reflog. git stash drop doesn't immediately delete — it just removes the reference. The commit is still in the repo.
$ git fsck --no-reflog | grep dangling
# Lists dangling commits — your stash is one of them
$ git show <SHA> # inspect the contents
$ git stash apply <SHA> # reapply it
07You completed a bad merge
You merged a feature branch into main and the result is broken. You haven't pushed yet.
$ git reset --hard HEAD~1
# For a merge commit, HEAD~1 is the parent on main
# HEAD^2 would be the parent from the feature branch
If you've pushed the merge, don't reset. Revert it instead:
$ git revert -m 1 <merge-SHA>
# -m 1 means: keep the changes from the first parent (main)
# and undo the changes from the second parent (feature branch)
08You deleted a file in a commit and need it back
You deleted important.js three commits ago. You need it back.
# Find when the file was deleted
$ git log --diff-filter=D --summary -- important.js
# Restore it from the commit BEFORE deletion
$ git checkout <commit-before-deletion>^ -- important.js
$ git commit -m "Restore important.js"
The ^ after the SHA means "parent of." So <deletion-commit>^ is the commit where the file still existed.
09You committed a secret
You committed an API key, password, or .env file. Even if you remove it in the next commit, the secret is in your history forever — and if you've pushed, it's compromised.
Step 1: rotate the secret immediately. Assume it's been scraped by bots within seconds of pushing to a public repo. Don't waste time on git first.
Step 2: rewrite history to remove it. The cleanest tool is git filter-repo (Python package, replaces the older filter-branch):
$ pip install git-filter-repo
$ git filter-repo --path .env --invert-paths
$ git push --force-with-lease --all
This rewrites every commit that touched .env, removing it. Then force-push all branches. Everyone with a clone needs to delete their local repo and re-clone — their history no longer matches yours.
10You botched a cherry-pick or rebase
You started a rebase, hit conflicts, made a mess. You want to abort and go back.
$ git rebase --abort
$ git cherry-pick --abort
$ git merge --abort
# If you've already finished and want to undo:
$ git reflog
$ git reset --hard HEAD@{n} # where n is before the rebase started
11Prevention — habits that save you
Most disasters are preventable:
- Always check
git statusbeforegit commit— confirms what's being committed. - Always check
git log --onelinebefore pushing — confirms what's going up. - Use
--force-with-lease, never--force— adds a safety check. - Protect main on GitHub/GitLab. Require pull requests. Block force pushes. Block direct commits.
- Configure
git config --global rerere.enabled true— git records how you resolve conflicts and replays them automatically next time you hit the same conflict. - Run a pre-commit hook for secrets.
git-secretsfrom AWS Labs catches common patterns before they're committed.
∞The principle
Git is a content-addressable database. It almost never deletes data — it just removes references. As long as the reflog hasn't been garbage-collected (which is 30 days by default, configurable with gc.reflogExpire), your data is recoverable. The trick is knowing where to look. git reflog and git fsck --lost-found together cover almost every recovery scenario.
Most engineers are afraid of git because they don't trust it. Once you've recovered from a few "disasters," that fear inverts: you trust git completely, and you experiment freely, because you know nothing is actually lost. That confidence is the dividend of learning the recovery commands. It pays back every week.