Decide two things first
What do you want back — the message, the file, the last commit, the last five? And has anyone else got it? If the commit has been pushed and someone may have pulled it, the only safe undos are the ones that add a new commit.
| Situation | Command | Safe after pushing? |
|---|---|---|
| Bad message, or forgot a file | git commit --amend | no — new id |
| Unstage a file | git restore --staged <file> | yes — nothing committed |
| Throw away an edit | git restore <file> | yes, and unrecoverable |
| Undo the commit, keep the changes staged | git reset --soft HEAD~1 | no |
| Undo the commit, keep the files | git reset HEAD~1 | no |
| Undo the commit and the files | git reset --hard HEAD~1 | no |
| Undo a commit publicly | git revert <id> | yes |
amend: the last commit, rewritten
Amend replaces the last commit. Anyone who already has the old one now has a commit that no longer exists in your history, and their next pull is a mess. On your own branch, before pushing: use it constantly.
git commit --amend -m "Fix the total when a line is discounted"
git add forgotten-file.md
git commit --amend --no-edit # same message, now with the file in it
reset: move the branch pointer
--soft is how you combine the last three commits into one: reset back three, then commit once. --mixed is the default and is how you un-stage everything. --hard is the one to be slow with — it is the only reset that touches your files.
git reset --soft HEAD~1 # branch moves back. index and files untouched
git reset HEAD~1 # branch moves back. index reset. files untouched (--mixed)
git reset --hard HEAD~1 # branch moves back. index reset. FILES OVERWRITTEN
git reflog lists every position HEAD has held — including the commits a --hard "lost". git reset --hard HEAD@{1} puts it back. Entries live about ninety days, so almost nothing is truly gone.
revert: undo in public
The bad commit stays in history and a new one reverses it. Nobody's clone breaks, the record of what happened is honest, and the fix is deployable in a minute — which is why this is what you do on main.
git revert 832dbe3 # makes a commit undoing that one
git revert HEAD # undo what you just shipped
git revert -m 1 <merge> # undo a merge, keeping the first parent's side
The situations, and the answer
- Committed on the wrong branch —
git reset --soft HEAD~1, switch, commit again. - Committed a secret — remove it, rotate the key, and rewrite history only if it has not been pushed; if it has, the key is compromised no matter what you do.
- Deleted a branch by accident —
git reflog, find the id,git branch <name> <id>. - A merge went wrong, nothing pushed —
git merge --abortduring the conflict, orgit reset --hard ORIG_HEADafter it. - Everything is a mess and nothing is committed —
git stashputs it aside safely; it is not gone.