To undo your last Git commit but keep all the changes staged, run git reset --soft HEAD~1. The commit disappears, your files are untouched, and everything is staged ready to recommit. That single command covers the most common case. Which command you actually want depends on two questions: do you want to keep the changes or throw them away, and have you already pushed the commit.
Here is the short version before the detail:
# Undo the commit, keep changes staged (most common)
git reset --soft HEAD~1
# Undo the commit, keep changes but unstage them
git reset --mixed HEAD~1 # or just: git reset HEAD~1
# Undo the commit AND throw the changes away (destructive)
git reset --hard HEAD~1
# Just fix the last commit's message or add a forgotten file
git commit --amend
# Undo a commit you already pushed (safe for shared branches)
git revert HEAD
If you are still finding your feet with Git, the Git for beginners guide walks through the whole mental model first. This page assumes you have made a commit you want to take back.
The one rule that decides everything: have you pushed?
There are two families of "undo" in Git, and the only thing that decides which family you can safely use is whether the commit has left your machine.
- Not pushed yet (local only): you can rewrite history freely.
resetandamendare fine. They edit the commit graph as if the bad commit never existed. - Already pushed to a shared branch: do not rewrite history. Other people may have pulled that commit. Use
revert, which creates a new commit that undoes the changes, leaving the original in place.
Rewriting a commit that someone else has already pulled is how you end up with the divergent-branches mess and force-push arguments. If the commit is on main (or any branch a teammate tracks) and it has been pushed, reach for revert. Everything else in this article is for commits that are still local.
Keep the changes: git reset --soft HEAD~1
This is the command most people actually want. It removes the last commit from history but leaves your working tree and the staging area exactly as they were. The changes from that commit are now staged, waiting for you to commit again.
git reset --soft HEAD~1HEAD~1 means "one commit before the current HEAD", so this moves the branch pointer back by one commit. Use it when you committed too early, picked a bad commit message, or want to fold a couple of commits together by recommitting. After running it, git status shows your changes staged in green:
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/app.jsRecommit when you are ready:
git commit -m "A better message this time"Keep the changes but unstage them: git reset --mixed HEAD~1
--mixed is the default mode of git reset, so git reset HEAD~1 does the same thing. It removes the last commit and keeps your file changes, but it unstages them. The edits are still in your working tree; they just are not in the staging area anymore.
git reset --mixed HEAD~1
# identical to:
git reset HEAD~1Reach for this when you want to re-stage selectively, splitting one fat commit into several smaller ones, for example. After the reset, git status shows the changes as unstaged ("Changes not staged for commit"), and you re-add the pieces you want with git add. If the difference between staged and unstaged is fuzzy, the Git staging area explained breaks down exactly what git add does and why the index sits between your files and a commit.
Throw the changes away: git reset --hard HEAD~1
--hard is the destructive one. It removes the last commit and discards the changes from your working tree and staging area. After this, the work in that commit is gone from your normal view of the repo.
git reset --hard HEAD~1Only use this when you are certain you want to lose the work, for example a commit full of debugging junk you never want to see again. There is one safety net: a reset moves a branch pointer, and Git keeps a log of every place HEAD has been. If you run --hard and panic, you can usually get the commit back from the reflog for a while (the dangling commit lives until garbage collection). I wrote a full walkthrough of that recovery in recover lost commits with git reflog. For discarding uncommitted edits rather than a whole commit, see how to discard local changes in Git.
If you want to keep the changes but stash them out of the way instead of destroying them, git stash is the gentler option.
Just fix the last commit: git commit --amend
Sometimes you do not want to undo the commit at all. You typed a typo in the message, or you forgot to stage one file. git commit --amend rewrites the most recent commit in place instead of removing it.
Fix the message:
git commit --amend -m "The corrected commit message"Add a file you forgot, keeping the same message:
git add forgotten-file.js
git commit --amend --no-edit--amend replaces the last commit with a new one (it gets a new commit hash, because the content or message changed). That is still a history rewrite, so the pushed-commit rule applies: amending a commit you already pushed means a force-push to a non-fast-forward remote, which you should avoid on shared branches. For getting commit messages right the first time so you amend less often, see my notes on Git commit message best practices.
Already pushed it? Use git revert
If the commit is already on a shared remote, do not rewrite it. git revert creates a brand-new commit that is the inverse of the one you name, so history stays intact and everyone's clones reconcile cleanly on the next pull.
git revert HEADGit opens an editor with a prefilled message like Revert "the original subject"; save and close it (or pass --no-edit to accept the default). You then push the new revert commit like any other commit. The bad change is undone for everyone, and the original commit is still in the log as a record of what happened. For the full comparison of these two approaches and the decision tree, I have a dedicated piece: git reset vs git revert: when to use each.
Which command should I use?
| Situation | Command | Keeps changes? | Rewrites history? |
|---|---|---|---|
| Committed too early, want to recommit | git reset --soft HEAD~1 | Yes, staged | Yes (local only) |
| Want to re-stage selectively | git reset HEAD~1 | Yes, unstaged | Yes (local only) |
| Commit was pure junk, drop it | git reset --hard HEAD~1 | No, discarded | Yes (local only) |
| Just a bad message or a missing file | git commit --amend | Yes | Yes (local only) |
| Commit is already pushed and shared | git revert HEAD | New inverse commit | No, adds a commit |
The pattern is consistent: reset and amend edit local history and are off-limits once a commit is shared; revert adds a new commit and is the only safe choice for anything that has been pushed. When you are working alone on a feature branch, branching gives you room to rewrite local history freely without affecting anyone.
Undoing more than one commit
HEAD~1 walks back one commit; HEAD~2 walks back two, and so on. To undo the last three local commits but keep all their combined changes staged:
git reset --soft HEAD~3For surgical edits across several recent commits (reword, drop, or combine specific ones rather than blanket-resetting them all), interactive rebase is the right tool. The same pushed-commit rule still applies: rewriting commits that are already shared causes pain for everyone tracking the branch.
FAQ
Sources
Authoritative references this article was fact-checked against.
- git-reset: official Git documentationgit-scm.com
- git-revert: official Git documentationgit-scm.com
- Pro Git: Git Basics, Undoing Thingsgit-scm.com
- git-commit: official Git documentationgit-scm.com





