Skip to main content

Git: Delete Last Commit

How to undo a Git commit using git reset –soft, –mixed, or –hard for unpushed commits, and git revert for commits already pushed to a shared remote.

Everyone who’s used Git for long enough has committed something they didn’t mean to โ€” a debug print left in, a config file with the wrong values, a commit message that’s just “wip.” The fix depends entirely on one question: has that commit been pushed anywhere yet? This post walks through both cases. It’s the twelfth post in this site’s Git command guide, following Git Log.

If You Haven’t Pushed: git reset

If the bad commit only exists locally, git reset can remove it cleanly. Say you have three commits:

$ git log --oneline -3
c3d8f29 Add debug logging (oops, not meant to commit this)
7a1e6b4 Fix pagination bug
9f4c2d1 Add user search endpoint

To undo c3d8f29 but keep its changes available to re-stage or edit, use --soft:

$ git reset --soft HEAD~1
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   src/main/java/com/ankurm/api/UserController.java

HEAD~1 means “one commit before HEAD” โ€” your last commit. --soft moves the branch pointer back but leaves every changed file staged, exactly as if you’d just run git add on them. This is what I reach for when the content of the commit was fine but I want to fold it into the next commit, split it up, or fix the message.

If you want the changes gone entirely โ€” not staged, not in your working directory, just gone โ€” use --hard instead:

$ git reset --hard HEAD~1
HEAD is now at 7a1e6b4 Fix pagination bug

This is the one to be careful with. There’s no staging step, no working-copy step โ€” the commit’s changes are discarded immediately. If you run this and realize you needed something from that commit, your best recovery option is git reflog, which tracks where HEAD has pointed recently and can usually get you back to the discarded commit if you act before garbage collection runs.

You’re not limited to HEAD~1 โ€” you can reset to any commit by its hash, which discards everything after it:

$ git reset --soft 9f4c2d1

Comparison: Soft, Mixed, and Hard Reset

ModeMoves branch pointerStaging areaWorking directory
--softYesUnchanged โ€” changes stay stagedUnchanged โ€” files untouched
--mixed (default)YesReset โ€” changes become unstagedUnchanged โ€” files keep their edits
--hardYesResetReset โ€” all changes discarded

--mixed is the default if you omit the flag entirely โ€” git reset HEAD~1 unstages the commit’s changes but leaves them sitting, modified, in your working directory. I use it less often than --soft or --hard because I rarely want changes half-staged, but it’s worth knowing it’s there.

If You’ve Already Pushed: git revert

git reset rewrites history by moving the branch pointer backward, which is fine on a commit only you have ever seen. Once you’ve pushed and someone else might have pulled that commit, rewriting history under them causes exactly the divergent-history mess covered in Git Pull. For pushed commits, use git revert instead โ€” it creates a new commit that undoes the changes, leaving history intact and forward-only:

$ git revert HEAD
[main 4e8a1f2] Revert "Add debug logging (oops, not meant to commit this)"
 1 file changed, 3 deletions(-)

git log --oneline afterward shows both commits โ€” the original mistake and the revert that undoes it:

$ git log --oneline -3
4e8a1f2 Revert "Add debug logging (oops, not meant to commit this)"
c3d8f29 Add debug logging (oops, not meant to commit this)
7a1e6b4 Fix pagination bug

Then push the revert commit like any other:

$ git push origin main

This is the part that surprised me the first time I used revert: it doesn’t remove the bad commit from history โ€” it adds a new one on top. Anyone reading the log later sees both the mistake and the fix, which is honestly a more honest record than rewriting things to look like the mistake never happened.

FAQs

I ran git reset –hard and need that commit back. Is it gone forever?

Probably not, if you act quickly. Run git reflog to see recent HEAD positions, find the commit hash from before the reset, and recover it with git checkout <hash> or git reset --hard <hash>. Git doesn’t garbage-collect unreachable commits immediately โ€” usually a 30-day grace window by default โ€” but don’t rely on that window being long.

Can I undo a commit from the middle of my history, not just the last one?

For an unpushed commit, an interactive rebase (git rebase -i) lets you drop a specific commit anywhere in the range. For a pushed commit, git revert <commit-hash> works on any commit, not just the most recent โ€” it doesn’t have to be HEAD.

What’s the difference between git reset and git revert in one sentence?

reset moves your branch backward and rewrites history; revert adds a new commit that cancels out an earlier one without rewriting anything.

See Also: This Site’s Git Command Guide

This post is part of a 13-part Git command guide on this site:

Conclusion

The rule I actually follow: if git status on the remote-tracking branch shows my commit isn’t pushed yet, reset is fair game. The moment it’s pushed, I treat history as fixed and reach for revert instead, no exceptions, even on branches nobody else seems to be using โ€” “nobody else is using it” has been wrong for me before. Last stop in this guide is a smaller but genuinely useful one: Git โ€“ Autocorrect Spelling.

No Comments yet!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.