TL;DR: to undo the last commit but keep the changes, run git reset --soft HEAD~1 (changes stay staged) or git reset HEAD~1 (changes stay in your working tree). If the commit is already pushed, run git revert HEAD instead — it creates a new commit that undoes it without rewriting history. 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.
How do I undo the last commit but 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.
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:
| 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.
What if I already pushed the commit?
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.
The alternative — git reset --hard HEAD~1 && git push --force-with-lease — is only acceptable on a branch nobody else builds on, and --force-with-lease (never plain --force) is the only safe form because it refuses if someone pushed in the meantime. Force-pushing shared branches is how teams lose commits and CI logs mysteriously stop matching anyone’s checkout.
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 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.
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
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.
— mrsaynothing