Most Git trouble is one of about eighteen situations, and almost all of it is recoverable.
Find your symptom below. Each answer gives you the diagnosis — what Git is actually doing — the commands, and the specific thing to be careful of, because in recovery the dangerous step is usually the one that feels like tidying up.
Undoing a commit
I want to undo my last commit but keep the changesHas it been pushed? → No
The commit exists locally only. Moving the branch pointer back one commit leaves the changes in your working tree.
git reset --soft HEAD~1Careful: `--soft` keeps changes staged, `--mixed` (the default) unstages them, `--hard` discards them entirely. Only `--hard` loses work.
I want to undo a commit that is already pushedHas it been pushed? → Yes
Others may already have this commit. Rewriting history would break their clones, so add a commit that reverses it instead.
git revert <commit-sha>
git pushCareful: Do not use `reset --hard` + force push on a shared branch. `revert` is the safe operation because it adds history rather than rewriting it.
I need to fix the message on my last commit
Amending replaces the most recent commit with a new one carrying the new message.
git commit --amendCareful: The amended commit has a different SHA. If it was already pushed you will need `--force-with-lease`, and anyone who pulled it will have to reconcile.
I committed to the wrong branch
Undo the commit where it is, carry the changes across, and commit them where they belong.
git reset --soft HEAD~1
git stash
git switch correct-branch
git stash pop
git commit -m "..."I don’t know whether to use reset or revert
`reset` moves the branch pointer and rewrites history. `revert` adds a new commit that undoes an old one. The deciding question is whether anyone else has the commit.
# private, not pushed:
git reset --soft HEAD~1
# shared, already pushed:
git revert <sha>Careful: Using `reset` on a shared branch and force-pushing is the standard way to lose a colleague’s work.
Recovering lost work
I deleted a branch that had work on it
Deleting a branch removes a pointer, not the commits. The reflog still records where that pointer was.
git reflog
git switch -c <branch-name> <sha>Careful: Do not run `git gc --prune=now` first — that removes exactly the unreachable objects you are trying to recover.
I ran git reset --hard and lost commits
Committed work is still in the object database and still recorded in the reflog. Only uncommitted changes are gone.
git reflog
git reset --hard <sha-from-before-the-reset>Careful: This recovers *commits*. Changes that were never committed are not stored anywhere in Git and cannot be recovered.
Git says I am in "detached HEAD" state
HEAD points directly at a commit rather than at a branch. Commits made here belong to no branch and will be unreferenced when you leave.
# to keep work made here:
git switch -c new-branch-name
# to discard it and go back:
git switch mainCareful: Leaving detached HEAD without creating a branch leaves any commits you made unreferenced. They stay in the reflog for a while, then are collected.
I dropped a stash I still needed
A dropped stash becomes an unreachable commit, which fsck can still find.
git fsck --unreachable | grep commit
git show <sha>
git stash apply <sha>Careful: Only works before garbage collection removes the object.
Conflicts and rebase
I have a merge conflict
Both sides changed the same region. Git wrote both versions into the file and stopped rather than guessing.
git status # which files
# edit files, remove <<<<<<< ======= >>>>>>> markers
git add <file>
git commit # or: git merge --continueCareful: Resolving by deleting the conflicting block removes one side’s change silently. Read the whole region, not just the marked lines.
I want to abandon a merge I started
While a merge is unresolved, Git can restore the exact pre-merge state.
git merge --abortCareful: Only works while the merge is unresolved. Once committed, use `git revert -m 1 <merge-sha>` instead.
My rebase went wrong and I want out
An unfinished rebase can be abandoned outright. A finished one is undone by resetting to the pre-rebase commit from the reflog.
# still in progress:
git rebase --abort
# already finished:
git reflog
git reset --hard <sha-before-rebase>Careful: Never resolve rebase confusion by force-pushing. Get back to a known state locally first.
git rebase --continue says there are no changes
Your resolution matched what was already there, so the commit being applied would be empty.
git rebase --skip # drop this commit
# or check you actually saved and staged the file:
git add <file> && git rebase --continuePushing and pulling
My push was rejected (non-fast-forward)
The remote has commits you do not. Git refuses to overwrite them, which is the correct behaviour.
git pull --rebase
# resolve any conflicts, then:
git pushCareful: Do not reach for `--force`. That is what discards the commits the rejection was protecting.
I force-pushed and overwrote someone else’s commits
The commits still exist on the machine that had them, and on the remote for a while.
# on the machine that had the work:
git reflog
git switch -c recovered <sha>
git push origin recoveredCareful: Use `--force-with-lease` in future. It refuses when the remote has commits you have not seen — exactly this case.
Files and branches
I want to discard my uncommitted changes
Restore the file from the index or from the last commit.
git restore <file> # one file
git restore . # everything trackedCareful: This is genuinely irreversible. Uncommitted changes are not stored anywhere in Git, so there is nothing to recover them from. Commit or stash first if unsure.
I deleted a file and want it back
If it was committed, it is in history. Restore it from the last commit that had it.
# still in the last commit:
git restore <path>
# deleted in an earlier commit:
git log --oneline -- <path>
git restore --source=<sha>^ -- <path>I committed files that should not be tracked
Remove them from tracking, ignore them, and — if they were secret — treat it as an exposure.
git rm --cached <path>
echo "<path>" >> .gitignore
git commit -m "Stop tracking <path>"Careful: If the file contained a credential, **revoke it now**. Removing it from the tip does not remove it from history, and anyone who cloned already has it.
Nothing matches that. Try fewer words, or browse theGit Command Atlas if you know which command you need.
The rule underneath all of this
Section titled “The rule underneath all of this”Committed work is almost always recoverable. Uncommitted work usually is not.
Git records every movement of HEAD in the reflog, including movements away from commits that no
branch points to any more. That is the safety net, and it is why “I deleted the branch” is a
two-minute fix.
It does not cover changes you never committed. git restore, git reset --hard and git clean
discard those permanently, because they were never in Git’s object database to begin with.
Which makes the first move in any confusing situation the same: commit or stash, then think.