Andrew Mercer
on this page

05 · Undo, Recovery and Troubleshooting

← Overview · Prev: Rebase · Next: Stash →

Pick the right tool by asking: is it committed? is it pushed?

Situation Command
Unstage a file git restore --staged <file> (old: git reset HEAD <file>)
Discard edits to a tracked file git restore <file> (old: git checkout -- <file>)
Discard all uncommitted edits git stash push -u first (safety net), then git restore . — git-adm discard
Delete untracked files git clean -n (dry run), then git clean -fd
Fix the last commit message / add a forgotten file git commit --amend — git-adm amend (force-push needed if already pushed)
Undo last commit, keep changes staged git reset --soft HEAD~1
Undo last commit, keep changes unstaged git reset HEAD~1
Undo a commit that is already pushed/shared git revert <sha> — git-adm revert
Roll back to an earlier state (local only) git reflog → git reset --hard <sha> — git-adm undo
Recover files deleted from the working tree git checkout HEAD -- <file> — git-adm restore
Recover a "lost" commit git reflog, or git fsck --lost-found
Copy one commit onto another branch git cherry-pick <sha>

reset modes

Mode Moves branch pointer Resets index Resets working tree
--soft yes no no
--mixed (default) yes yes no
--hard yes yes yes — destroys uncommitted work

Revert a commit safely (pushed / open PR)

revert adds a new commit that undoes an old one. It does not rewrite history, so no force-push is needed.

git switch -c revert-<sha>
git revert <sha>                 # add -m 1 when reverting a merge commit
git push -u origin HEAD

If it conflicts: fix the files, git add, git revert --continue (or git revert --abort).

Reverting a merge with -m 1 keeps the first parent's side (usually main). If you later want to re-merge the same branch, revert the revert first, otherwise git considers those changes already "handled".

Reset with the reflog (local, unpushed only)

The reflog records every position HEAD (and each branch) has been at, for ~90 days by default.

git reflog
# b4d4682 HEAD@{0}: pull: Fast-forward
# 8613dc7 HEAD@{1}: clone: from <remote-url>
git reset --hard 8613dc7         # back to the known-good state

--hard discards uncommitted work; stash first if unsure. git-adm undo presents the reflog as a numbered picker and offers to stash first.

Recover a deleted branch or dropped commit

git reflog                        # find the last SHA the branch pointed at
git switch -c recovered <sha>

Dropped stashes: git fsck --unreachable | grep commit then git show <sha>.

Remove a file from a commit but keep it on disk

git rm --cached <file>           # add -r for a directory
git commit --amend               # or make a new commit

Recover files deleted from the working tree

Deleted but not committed:

git status                                   # shows "deleted: <file>"
git checkout HEAD -- <file>                  # restore one file (index + working tree)
git ls-files --deleted -z | xargs -0 git checkout HEAD --    # restore all of them

Or, to accept the deletions instead: git add -u (stages deletions and modifications of tracked files), or git ls-files --deleted -z | xargs -0 git rm --.

Reading: https://stackoverflow.com/questions/11956710/git-recover-deleted-file-where-no-commit-was-made-after-the-delete

Common errors

"Your local changes would be overwritten by merge/pull … commit or stash them"

git stash          # keep the changes
git pull
git stash pop
# or, if the changes are junk:  git restore .

"fatal: refusing to merge unrelated histories" — two repos with no common ancestor. If you are sure: git pull --allow-unrelated-histories.

"Updates were rejected because the tip of your current branch is behind" — someone pushed first. git pull --rebase (or git-adm rebase), then push.

"Updates were rejected … non-fast-forward" after your own rebase/amend — expected. git push --force-with-lease.

"You are in 'detached HEAD' state" — you checked out a commit or tag, not a branch. To keep work made here: git switch -c <new-branch>. To leave: git switch -.

"bad index file sha1 signature" / "index file corrupt" (working files are unaffected; staged state is lost)

rm -f .git/index
git reset

git-adm: git-adm fix-index

Committed a secret — rotating the secret is mandatory; rewriting history is secondary. If it was only committed locally: git reset --soft HEAD~1. If pushed, use git filter-repo (or BFG) and force-push, and tell collaborators to re-clone.

Find which commit broke something

git bisect start
git bisect bad                 # current commit is broken
git bisect good <known-good>   # older commit that worked
# git checks out a midpoint; test, then mark: git bisect good | git bisect bad
git bisect reset               # when done
# fully automatic: git bisect run ./test.sh

Reading: https://github.com/k88hudson/git-flight-rules · https://www.atlassian.com/git/articles/titanium-armor-recovering-from-various-disasters