Git recovery
The commands you need twice a year, at the worst possible moment. Not add, commit and push - the other ones.
What this page covers
Undoing commits
reset against revert, and what --soft, --mixed and --hard each do to your files.
Recovering lost work
reflog, the thing that makes almost everything recoverable, and how to use it under pressure.
Wrong branch
Committed to main again. Moving the work without losing it.
Escaping a rebase or merge
Aborting cleanly, and finishing when aborting is not an option.
Stash and clean
The two commands that quietly destroy work, and how to survive them.
Before you panic
The one command to run first, every time.
Before you do anything
git status && git stash list && git reflog -20
Three questions answered at once: what is uncommitted, what is stashed, and where HEAD has been. Almost every recovery below starts by reading that output, and running it costs nothing.
Nothing committed is really lost for about 90 days. Git keeps
unreachable objects until garbage collection, and reflog is how you find
them. That is the single most useful fact on this page.
Undoing a commit
The choice is between rewriting history and adding to it. On anything
you have pushed and others have pulled, use revert.
# Undo the last commit, KEEP the changes staged
git reset --soft HEAD~1
# Undo the last commit, KEEP the changes but unstage them
git reset --mixed HEAD~1 # --mixed is the default
# Undo the last commit and THROW THE CHANGES AWAY
git reset --hard HEAD~1 # destructive - see reflog below
# Undo a commit by making a new one that reverses it (safe on shared branches)
git revert <sha>
# Change the last commit's message, or add a forgotten file to it
git commit --amend
--hard is the only one of these that touches your working tree.
--soft and --mixed move the branch pointer and leave your files
exactly as they are.
Recovering work you think you have lost
# Everywhere HEAD has been, newest first - including commits no branch points at
git reflog
# Go back to how things were N moves ago
git reset --hard HEAD@{2}
# Recover a single commit onto the current branch
git cherry-pick <sha-from-reflog>
# Recover a deleted branch (find its last sha in the reflog first)
git branch recovered-work <sha>
# Find commits no branch or tag points at any more
git fsck --lost-found
A reset --hard you regret, a branch deleted too early, a rebase that ate
the wrong commits - all of these are in the reflog, and all of them are
recoverable until garbage collection runs.
Committed to the wrong branch
# Work is committed to main and should be on a feature branch
git branch feature-x # point a new branch at the current commit
git reset --hard origin/main # put main back where it was
git switch feature-x # your work is here
# Move just the last commit to another existing branch
git switch target-branch
git cherry-pick main
git switch main
git reset --hard HEAD~1
Create the new branch before resetting. That branch is what keeps the commit reachable, and doing it in the other order is how the reflog gets its next customer.
Getting out of a rebase, merge or conflict
# Stop and go back to before you started
git rebase --abort
git merge --abort
git cherry-pick --abort
# Carry on after fixing the conflicted files
git add <files> && git rebase --continue
# Skip the commit currently causing the conflict
git rebase --skip
# See only the files still in conflict
git diff --name-only --diff-filter=U
--abort is always available and always safe while the operation is in
progress. If it refuses, the operation has already finished and the
reflog is the way back.
Stash and clean, the two that bite
git stash list # what is in there
git stash show -p stash@{0} # what is actually in one
git stash pop # apply and remove
git stash apply # apply and KEEP it in the list
# A dropped stash is still recoverable
git fsck --unreachable | grep commit
# See what clean WOULD delete - always run this first
git clean -nd
git clean -fd # actually delete untracked files
git clean -fd deletes untracked files permanently. They were never
committed, so there is no reflog entry and nothing to recover. The -n
dry run is the whole safety mechanism, and it takes two seconds.
A stash you did not create is not yours to pop - popping replays somebody else's half-finished state into your tree, usually as a conflict in a file you were not working on.
Finding when something broke
# Who last changed each line, and in which commit
git blame -L 40,60 path/to/file
# Every commit that touched a file, following renames
git log --follow -p path/to/file
# Search every commit for when a string appeared or vanished
git log -S "someFunction" --oneline
# Binary search for the commit that introduced a bug
git bisect start && git bisect bad && git bisect good <sha>
git log -S is the one people do not know about. It searches the content
of every diff rather than the messages, so it finds the commit that
introduced a line even when nobody wrote a useful commit message.