To undo the last commit and keep all its changes staged, run:
git reset --soft HEAD~1
The commit disappears from history, but your files stay exactly as they were, ready to commit again. If the commit is already pushed to a shared branch, use git revert HEAD instead. Both are explained below, with the real output from a test repo.
Pick the right command
| You want to… | Command | Your changes |
|---|---|---|
| Undo commit, keep changes staged | git reset --soft HEAD~1 | Kept, staged |
| Undo commit, keep changes unstaged | git reset HEAD~1 | Kept, unstaged |
| Undo commit and throw changes away | git reset --hard HEAD~1 | Deleted |
| Fix the message or add a forgotten file | git commit --amend | Kept |
| Undo a commit that’s already pushed | git revert HEAD | Reversed by a new commit |
I tested each one on macOS with the Git that ships with Xcode Command Line Tools:
git version 2.39.5 (Apple Git-154)
My test repo had two commits. The second one changed app.txt and added a new file, notes.txt:
a0e5a0f Add notes and line two
6104188 First commit
Undo the last commit but keep changes (—soft and —mixed)
git reset --soft HEAD~1
git reset --soft HEAD~1
git status --short
git log --oneline
M app.txt
A notes.txt
6104188 First commit
The commit is gone. Both changes are still staged (the letter is in the first column). Use this when you committed too early, or want to split or reword the commit. Just run git commit again.
git reset HEAD~1 (the default, —mixed)
With no flag, git reset uses --mixed. It moves the branch back and also unstages everything:
Unstaged changes after reset:
M app.txt
M app.txt
?? notes.txt
Look at notes.txt. It shows ??, which means untracked. A file that was new in the undone commit doesn’t vanish, but Git forgets it was ever added. If you then run git commit -am "...", the -a flag only picks up tracked files, so notes.txt gets left out. Run git add on new files again before you recommit.
Undo the last commit and discard changes (—hard)
git reset --hard HEAD~1
HEAD is now at 6104188 First commit
After this, ls showed only app.txt. The commit and its changes are gone from the working folder.
I also tested what else --hard touches. Before resetting, I added an unsaved edit to app.txt and created a brand-new file that was never added:
M app.txt
?? untracked.log
After git reset --hard HEAD~1:
?? untracked.log
The edit to app.txt was wiped. That uncommitted edit is the one thing reflog can’t bring back, because it was never in a commit. The untracked file survived. So --hard resets tracked files only, and it does not clean up random files. Run git stash first if you have uncommitted work you might want.
Recover a commit after git reset —hard (reflog)
A hard reset only moves the branch pointer. The commit object stays in the repo for a while, and git reflog lists every place HEAD has been:
git reflog
6104188 HEAD@{0}: reset: moving to HEAD~1
a0e5a0f HEAD@{1}: commit: Add notes and line two
6104188 HEAD@{2}: reset: moving to HEAD~1
a0e5a0f HEAD@{3}: commit: Add notes and line two
HEAD@{1} is where I was just before the reset. Jump back to it:
git reset --hard HEAD@{1}
HEAD is now at a0e5a0f Add notes and line two
Both commits were back, and notes.txt was back on disk. Reflog is local to your machine. It isn’t pushed, and Git prunes old entries eventually, so recover sooner rather than later.
Zsh gotcha: HEAD^ says “no matches found”
Many answers write HEAD^ instead of HEAD~1. They mean the same commit. But in zsh, ^ is a glob character when the extendedglob option is on, which some zsh setups and plugin frameworks enable. I reproduced it:
zsh -f -c 'setopt extendedglob; git log --oneline -1 HEAD^'
zsh:1: no matches found: HEAD^
In a plain zsh (zsh -f) the same command worked. Either use HEAD~1, or quote it: 'HEAD^'. HEAD@{1} worked unquoted in zsh during my tests.
Fix the last commit instead: git commit —amend
If the commit is fine but the message is wrong, you don’t need to undo it:
git commit --amend -m "Add notes file and second line"
[main aecfc8c] Add notes file and second line
Date: Mon Nov 2 17:31:56 2026 +0900
2 files changed, 2 insertions(+)
create mode 100644 notes.txt
Forgot a file? Stage it and amend without changing the message:
git add forgot.txt
git commit --amend --no-edit
[main 18b059b] Add notes file and second line
Date: Mon Nov 2 17:31:56 2026 +0900
3 files changed, 3 insertions(+)
create mode 100644 forgot.txt
create mode 100644 notes.txt
Note the hash changed twice: a0e5a0f, then aecfc8c, then 18b059b. Amend doesn’t edit a commit. It replaces it with a new one. That matters as soon as the commit is pushed.
Undo a commit you already pushed
To test this safely, I created a local bare repo as origin, pushed a commit called “Introduce bug”, and then tried the reset approach:
git reset --hard HEAD~1
git push
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '../remote.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
Git refuses, and for good reason. The commit is on the remote, so your branch now looks “behind”. Following the hint and running git pull would just bring the bad commit back.
On a shared branch like main, revert it. Revert adds a new commit that does the opposite, so nobody’s history is rewritten:
git revert --no-edit HEAD
git push
[main 4d6e495] Revert "Introduce bug"
Date: Mon Nov 2 17:32:29 2026 +0900
1 file changed, 1 deletion(-)
To ../remote.git
cce377d..4d6e495 main -> main
The log keeps both commits, and app.txt was back to its old content:
4d6e495 Revert "Introduce bug"
cce377d Introduce bug
18b059b Add notes file and second line
If the branch is yours alone (a feature branch nobody else pulls), you can rewrite it instead. I reset a pushed “WIP” commit on a feature branch and force-pushed:
git reset --soft HEAD~1
git push --force-with-lease
To ../remote.git
+ bbb21a3...4d6e495 feature -> feature (forced update)
--force-with-lease is safer than --force. It refuses to overwrite the remote if someone else pushed in the meantime. Never force-push a shared main.
Common questions
How do I undo the very first commit in a repo?
HEAD~1 doesn’t exist yet, so reset fails. I got this in a one-commit repo:
fatal: ambiguous argument 'HEAD~1': unknown revision or path not in the working tree.
Delete the branch ref instead. Your files stay staged:
git update-ref -d HEAD
After that, git status --short showed A a.txt and git log said the branch had no commits yet.
How do I undo the last 3 commits?
Use HEAD~3 with the reset mode you want, for example git reset --soft HEAD~3. All three commits’ changes end up staged together, which is a quick way to squash them.
Does git reset delete my files?
Only --hard changes files on disk, and only tracked ones. --soft and --mixed never touch your working folder. If you’re not sure, run git stash or copy the folder first.
Revert or reset: which should I use?
Reset if the commit exists only on your Mac. Revert if anyone else could have pulled it. When in doubt, revert. It is always safe to push.
My repo got huge after a bad commit. How do I find what’s taking space?
A large file committed by mistake stays in history until it’s removed and garbage-collected. To find big files on disk first, see how to find large files on Mac from Terminal. Also check for hidden .git folders inside your project with show hidden files on Mac.