Git Undo Last Commit: Keep Changes, Stay Safe
Git remove or undo the last commit and keep the changes: git reset --soft HEAD~1. Unstage with --mixed, fix a pushed commit with revert. Every verb, one page.
Remove, delete, cancel or undo — whatever verb you searched, the command that keeps the changes is the same one. Those three commands cover almost every "I committed too early" moment. The rest of this guide walks through each case with copy-paste commands, explains the difference between --soft, --mixed and --hard, and shows how to recover if something goes wrong. Every command below works on any recent Git install on Linux, macOS or Windows.
The 10-second decision
How do I undo the last commit and keep the changes?
The most common situation: you committed, then spotted a typo, a missing file, or realised the change belongs in a different commit. Nothing is pushed yet. Undo the commit and put everything back where it was:
# Commit stays in history nowhere — changes go back to the staging area
git reset --soft HEAD~1
# Changes go back to the working tree (unstaged) instead
git reset HEAD~1
HEAD~1 means "one commit before where HEAD points now". After either command your files are untouched on disk — only the branch pointer moved. Check it with git status: with --soft the changes are staged, ready to re-commit with fixes folded in; without a flag they are unstaged, so you can edit freely first.
verify: git status → changes staged (--soft) or unstaged (default) · git log --oneline -1 → the commit before
A safer habit when you just want to add files to the last commit is not to undo it at all:
git add forgotten-file.txt
git commit --amend --no-edit
--amend replaces the last commit in place (no commit message change here). Note that amending a pushed commit rewrites history — more on that below.
What is the difference between --soft, --mixed and --hard?
This is the part worth memorising, because the flag decides where your changes end up — and whether they can be lost:
Whichever mess is yours, the consequence sits in the last column.
| Flag | Commit undone? | Changes on disk | Changes staged? | Typical use |
|---|---|---|---|---|
--soft |
Yes | Kept | Yes | Re-commit with small fixes |
--mixed (default) |
Yes | Kept | No | Re-group changes, re-stage selectively |
--hard |
Yes | Deleted | — | Throw the work away entirely |
git revert |
No (new commit) | Kept | — | Undo a commit that was already pushed |
--hard is the only dangerous one: it discards the commit and the changes. Before any reset --hard, stash or branch what you have:
git branch backup-before-reset # cheap insurance
git reset --hard HEAD~1 # commit + changes gone
If you already ran the dangerous version, all is not lost — git reflog remembers where HEAD has been:
git reflog # find the commit hash you lost
git reset --hard HEAD@{1} # or: git reset --hard <hash>
The reflog keeps dangling commits for around 90 days by default, so "I hard-reset by mistake" is almost always recoverable if you act before garbage collection.
verify: git reflog → the dropped commit sits at HEAD@{1}
What if I already pushed the commit?
Shared history gets a new commit, never a rewrite.
If the commit is on a shared branch (anything other than your own feature branch), do not rewrite history. Use git revert, which computes the opposite change and commits it:
git revert HEAD
git push
Everyone who pulls simply gets a new commit that removes the old one's changes. No force-push, no broken colleagues. If you need to undo a run of commits, revert a range: git revert --no-commit HEAD~3..HEAD && git commit.
Why --force-with-lease, never --force
The alternative — git reset --hard HEAD~1 && git push --force-with-lease — is only acceptable on a branch nobody else builds on. --force-with-lease refuses the push if the remote moved since your last fetch; plain --force overwrites whatever appeared. Force-pushing shared branches is how teams lose commits and CI logs mysteriously stop matching anyone's checkout.
verify: git log --oneline -2 → Revert "…" sits on top of the undone commit
How do I undo the last commit but keep it around for later?
Sometimes the commit is good work at the wrong address — on the wrong branch, or too early. Instead of undoing it, move it:
git branch stash-commit # park the commit on a new branch
git reset --hard HEAD~1 # then clean your current branch
Or take just the commit to another branch without touching your current one:
git cherry-pick <hash> # while on the target branch
Several commits in one go is the same move with a range — cherry pick multiple commits from another branch.
Between reset --soft, cherry-pick and revert, there is no commit you cannot relocate or nullify — the trick is choosing "move" versus "undo" before reaching for a flag.
Undo, revert, reset, remove, cancel — the word is the command
Half the confusion is vocabulary: six verbs people type into the search box, three commands that actually exist. Match the word in your head to the flag that does it:
Every spelling on that list now has a one-stop answer page, too — the undo, cancel, revert FAQ collects the whole keep-changes family from our search board on one tab.
| The word you typed | What you want | The command | Changes kept? |
|---|---|---|---|
| undo / uncommit | commit gone, work back | git reset --soft HEAD~1 | yes — staged |
| undo, unstaged | commit gone, work back loose | git reset HEAD~1 | yes — mixed |
| remove / drop | commit and edits gone | git reset --hard HEAD~1 | no — reflog ~90 days |
| revert / cancel, pushed | public undo, history intact | git revert HEAD | yes — new commit |
The verbs are not synonyms in Git: revert never touches the past, reset rewrites it. Say the wrong word to a teammate and you get the wrong command.
Which undo should I use? A quick decision guide
- Not pushed, want to fix and re-commit →
git reset --soft HEAD~1 - Not pushed, want to re-stage selectively →
git reset HEAD~1(mixed) - Want the changes gone completely →
git reset --hard HEAD~1(reflog knows, if you regret it) - Already pushed to a shared branch →
git revert HEAD - Commit belongs on another branch →
cherry-pick, don't undo
Want the decision made for you? The git undo generator asks these three questions and prints the command.
And when Git refuses to run at all — fatal: not a git repository — that is a different problem from undoing anything: the 60-second fix for the error itself.
One last operational tip: if a bad commit did make it to a server — a botched deploy hook, a CI runner misbehaving after a force-push — the next place to look is the machine's logs, not Git. On any systemd box, journalctl -u <service> -n 100 shows you exactly what ran and when; our journalctl cheat sheet has the copy-paste patterns. And if your workflow includes local LLM tooling to review diffs, we compared the two main options in Ollama vs LM Studio. And the whole undo cluster — reset, revert, cherry-pick, clean — lives on the git-undo hub.