“Undo the last commit” means different things depending on what you want to keep and whether you’ve already pushed. Pick the situation that matches yours.
Quick decision table
| Situation | Command |
|---|---|
| Fix the message or add a forgotten file (not pushed) | git commit --amend |
| Undo commit, keep changes staged | git reset --soft HEAD~1 |
| Undo commit, keep changes in working files | git reset HEAD~1 |
| Undo commit and throw away the changes | git reset --hard HEAD~1 |
| Commit is already pushed/shared | git revert HEAD |
| Committed to the wrong branch | branch + reset (below) |
| “I reset too far!” | git reflog |
HEAD~1 means “the commit before the current one”. HEAD~3 goes back three.
Change the last commit: amend
Wrong message:
git commit --amend -m "Correct message"
Forgot a file:
git add forgotten-file.txt
git commit --amend --no-edit
Amend creates a new commit replacing the old one. Fine before pushing; after pushing, you’d have to force-push (see below).
Undo the commit, keep the work
git reset --soft HEAD~1
The commit disappears; its changes remain staged, ready to recommit — e.g. after splitting it differently or fixing something.
git reset HEAD~1 # same as --mixed
The changes remain in your files but unstaged. Use this when you want to re-pick what goes into the commit (git add -p).
Undo the commit and discard the changes
git reset --hard HEAD~1
The commit and its changes are gone from your working tree. Any uncommitted work is discarded too — check git status first. (The commit itself can still be recovered via reflog for a while.)
The commit is already pushed
Rewriting shared history causes problems for everyone who pulled it. Use revert, which adds a new commit that reverses the old one:
git revert HEAD
git push
To revert an older commit: git revert a1b2c3d. For a merge commit: git revert -m 1 <merge-sha>.
If you really must rewrite pushed history
Only on branches nobody else uses (your own feature branch):
git reset --hard HEAD~1 # or --soft, then recommit
git push --force-with-lease
--force-with-lease refuses to overwrite commits someone else pushed in the meantime. Never force-push to shared branches like main without agreement — many platforms protect them anyway.
Pushed a secret (password, API key)? Reverting doesn’t remove it from history. Rotate the secret immediately — consider it compromised — then clean history with git filter-repo if needed.
Committed to the wrong branch
You meant to start feature/x but committed on main:
git branch feature/x # new branch pointing at your commit(s)
git reset --hard origin/main # move main back to the remote state
git switch feature/x
Or move just one commit to another existing branch:
git switch other-branch
git cherry-pick a1b2c3d
git switch main
git reset --hard HEAD~1
Undo several commits
git reset --soft HEAD~3 # combine the last 3 commits into one new commit
git commit -m "One clean commit"
Or use interactive rebase to squash, reorder or drop: git rebase -i HEAD~3.
Recover from a mistake with reflog
Git keeps a log of where HEAD has been — including commits you “deleted”:
git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~1
9f8e7d6 HEAD@{1}: commit: Add payment form
Restore:
git reset --hard 9f8e7d6
# or keep current state and create a branch at the lost commit:
git branch rescue 9f8e7d6
Reflog entries expire after a while (90 days by default for reachable entries, 30 for unreachable), and they’re local — they don’t exist on other clones.
Undo changes that aren’t committed yet
git restore file.txt # discard changes in a file
git restore --staged file.txt # unstage, keep changes
git stash # set everything aside temporarily
Summary
- Not pushed →
amendorreset. - Pushed →
revert. - Panic →
reflog.
More in the Git commands cheat sheet.