# How to Undo the Last Git Commit (Every Case)

URL: https://www.devopsness.com/blog/git-undo-last-commit
Author: Kiril Urbonas
Published: 2026-07-09
Updated: 2026-07-09
Categories: DevOps
Tags: DevOps, Debugging, Operations, Git, Version Control

Summary: You committed too early, wrote a bad message, or included the wrong file. Here's the decision tree for undoing a Git commit safely, by what you actually want to keep.

---
# How to Undo the Last Git Commit (Every Case)

Almost everyone reaches for the same search when they realize the commit they just made was wrong. And almost everyone gets scared off by an answer that shows `git reset --hard` without saying what it deletes. So before any commands: the right undo depends entirely on one question. Do you want to keep the work in that commit, or throw it away? Answer that first and the rest is easy.

Here's the fast map. Read your case, run the command.

## You want to keep the changes, staged and ready

You committed too early. The code is fine, you just want it back out of the commit and sitting in the staging area so you can regroup.

```bash
git reset --soft HEAD~1
```

This moves the branch pointer back one commit and leaves everything staged, exactly as it was the moment before you typed `git commit`. Nothing is lost. Run `git status` right after and you'll see your files under "Changes to be committed." This is the gentlest undo Git has, and it's the one I use most.

## You want to keep the changes, but unstaged

Same as above, except you also want the files pulled out of staging so you can pick and choose what goes into the next commit.

```bash
git reset --mixed HEAD~1
# or just:
git reset HEAD~1
```

`--mixed` is the default, so the bare `git reset HEAD~1` does the same thing. The commit is gone, the changes are back in your working directory as modified files, and staging is clean. You lose nothing but the commit itself. This is the right call when you committed a big blob and want to split it into two.

## You want the changes gone entirely

The commit was garbage and so is the work in it. You want the tree back to how it looked one commit ago, with no trace.

```bash
git reset --hard HEAD~1
```

Read this line twice before running it. `--hard` throws away the commit AND every uncommitted change in your working directory. Not just the last commit's changes, everything that isn't committed. If you had unrelated edits open, they go too. There is no undo prompt. The only safety net is the reflog, and only if the work was committed at some point.

My rule: never run `--hard` on a dirty tree you haven't looked at. Run `git status` first. If there's anything in there you can't afford to lose, stash it before you reset.

## You just need to fix the message or add a forgotten file

Don't reset for this. The commit is fine, you just fumbled it.

```bash
# fix the message
git commit --amend -m "the message you meant to write"

# forgot to stage a file? add it, then fold it in
git add forgotten-file.js
git commit --amend --no-edit
```

`--amend` rewrites the last commit in place. `--no-edit` keeps the existing message while pulling in whatever's now staged. Clean and quick. One catch: amend creates a new commit with a new hash, which matters the moment the old one is already pushed. Hold that thought.

## The commit is already pushed

This is where people get hurt, so here's the rule with no hedging: if the commit is on a shared branch that other people pull, use `revert`, not `reset`.

```bash
git revert HEAD
```

`revert` doesn't erase history. It creates a new commit that undoes the changes of the target commit. The bad change is neutralized, but the timeline stays intact, so nobody else's clone breaks. Push it like a normal commit and you're done. If you want the deeper contrast on why this beats rewriting, we wrote up [revert vs reset](/blog/git-revert-vs-reset) separately.

You can still `reset` a pushed commit and force-push over it:

```bash
git reset --hard HEAD~1
git push --force-with-lease
```

But understand the trade. Anyone who already pulled that commit now has history that disagrees with the remote, and their next pull turns into a mess you'll be untangling in Slack. Use `--force-with-lease` instead of plain `--force` so the push refuses if someone else has pushed in the meantime. On a solo feature branch nobody else touches, a force-push is fine and honestly cleaner. On `main` or anything shared, revert.

## Undoing more than one commit

The `HEAD~1` is just "one commit back." Change the number to reach further:

```bash
git reset --soft HEAD~3    # undo last 3 commits, keep all their changes staged
git reset --hard HEAD~3    # undo last 3 commits and delete their changes
```

For pushed commits, revert a range instead:

```bash
git revert --no-commit HEAD~2..HEAD
git commit -m "revert the last three commits"
```

`--no-commit` stacks the reversals so you get one clean commit instead of three.

## You reset too far and lost a commit

This is the part nobody tells you, and it's the reason `reset` is less scary than it looks. Git keeps a log of everywhere HEAD has been, even after a hard reset.

```bash
git reflog
```

You'll get a list like `a1b2c3d HEAD@{1}: commit: the thing you thought you nuked`. Find the hash of the commit you want back and either check it out or reset to it:

```bash
git reset --hard a1b2c3d
```

The reflog holds entries for about 90 days by default, so a commit you made and then reset away is almost always recoverable. The one thing it can't save is work that was never committed, changes that only ever lived in your working directory before a `--hard`. That's the real reason to commit early and often: a commit is recoverable, an unsaved edit is not.

If you get stuck on something outside this tree, our broader [Git troubleshooting](/blog/git-error-troubleshooting-guide) guide covers the neighboring failures like rejected pushes and detached HEAD.

## The call we'd make

Ask one question before you touch anything: is this commit pushed to a branch other people use? If yes, `git revert` and move on, no exceptions worth the risk. If no, pick your reset by what you want to keep: `--soft` to hold the work staged, default `--mixed` to hold it unstaged, `--hard` only after `git status` confirms there's nothing in the working tree you'll cry over. And keep `git reflog` in your back pocket, because the safety net has been there the whole time.

