How to undo the last Git commit (reset, revert, amend, restore — pick the right one)

· Category: Git

Short answer

The right command depends on two questions:

  1. Has the commit been pushed to a shared branch?
  2. Do you want to keep, edit, or discard the changes that were in it?
Pushed? Keep changes? Command
No Keep, in index git reset --soft HEAD~1
No Keep, in working tree only git reset --mixed HEAD~1 (the default)
No Discard everything git reset --hard HEAD~1
No Just edit the message or contents git commit --amend
Yes Anything git revert HEAD

Pushed commits never get rewritten with reset or amend. Use git revert to add a new commit that undoes the old one. This is the rule that prevents the "I broke history for the whole team" disaster.

What each git reset mode does

git reset moves the branch pointer to a different commit. The flag controls what happens to the index and the working tree:

                    branch ptr   index    working tree
git reset --soft         move    keep     keep
git reset --mixed        move    reset    keep      (the default)
git reset --hard         move    reset    reset

Concrete example. Say HEAD is at commit C with two staged files and one unstaged edit:

git log --oneline
# C  staged: a.js, b.js  unstaged: c.js
# B
# A

git reset --soft HEAD~1
# HEAD now at B; a.js and b.js still staged; c.js still has the unstaged edit.
git status
#   modified:   a.js  (staged)
#   modified:   b.js  (staged)
#   modified:   c.js  (not staged)

git reset --mixed HEAD~1
# HEAD at B; nothing staged; a.js, b.js, c.js all unstaged but kept.

git reset --hard HEAD~1
# HEAD at B; a.js, b.js, c.js wiped to B's content. Unrecoverable except via reflog.

Reference: git-reset(1).

Steps for each scenario

"I want to redo my last commit with a better message or extra files"

# Just the message:
git commit --amend -m "Add login form validation"

# Extra files into the same commit:
git add src/utils/validate.js
git commit --amend --no-edit       # keep the existing message

--amend rewrites the previous commit, producing a new SHA. Don't amend after pushing unless you also force-push (and your team is OK with that).

"I want my last commit's changes back as uncommitted edits"

Most common case — you committed too early, or to the wrong branch.

git reset --soft HEAD~1
# Files are still staged; just `git commit` again with whatever you want.

# Or to move them off this branch:
git stash                           # stash the now-uncommitted work
git switch correct-branch
git stash pop
git commit -m "..."

"I want to throw away the last commit completely"

git reset --hard HEAD~1

This is destructive — the working tree is reset to the previous commit. If you change your mind within 90 days, git reflog can recover it; see the recovery section below.

"The commit is already pushed; I need to undo it cleanly"

git revert HEAD               # creates a new commit that inverts HEAD
git push origin main

git revert doesn't rewrite history. It adds a new commit on top whose changes cancel the previous one out. Reference: git-revert(1).

To revert a merge commit, you need -m:

git revert -m 1 abc1234       # 1 = keep the first parent (mainline)

-m 1 means "treat the first parent (the branch you merged into) as the mainline" — that's what you almost always want.

Recovering from a bad git reset --hard

git reflog records every move of HEAD for ~90 days, including the commits you reset away from. Recover with:

git reflog
# d4f9b2a HEAD@{0}: reset: moving to HEAD~1
# 1a2b3c4 HEAD@{1}: commit: the commit you wish you hadn't reset

git reset --hard HEAD@{1}    # bring HEAD back to where it was before the reset

Don't run git gc --prune=now if you're trying to recover — it can collect the unreferenced commits permanently. See how to use git reflog to recover lost commits for the full recovery workflow.

When --amend and revert are both wrong

Two situations need a different tool:

  • You want to undo a commit that's not the most recent one. Use git rebase -i HEAD~N to reach back N commits and drop or edit the offending one. See how to squash commits in Git for the rebase-edit workflow.
  • You only want to undo a single file's changes from the last commit, leaving everything else. Use git restore --source=HEAD~1 -- path/to/file to bring the file back to its previous content; commit the result. Reference: how to revert a specific file to a previous version in Git.

Common issues

git reset --hard blew away an uncommitted file I cared about. Uncommitted changes that were never staged are unrecoverable — Git never recorded them. Staged changes that were never committed are also gone unless you ran git stash first. The lesson: git stash before git reset --hard if there's any uncertainty.

I git commit --amend'ed and now git push is rejected. --amend rewrote a commit you'd already pushed. Either force-push (git push --force-with-lease origin main) — only OK on a branch that's solely yours — or, if it's a shared branch, use git revert HEAD instead and live with the slightly noisier history.

git revert says "could not revert: nothing to commit". The changes you tried to revert have already been undone elsewhere — no inverse to apply. git log --oneline -p -1 on the SHA you wanted to revert shows the original change so you can verify.

git reset --soft HEAD~1 followed by changes — my old commit is "gone". It is, from the branch's perspective. git reflog show HEAD lists every previous position; git reset --hard HEAD@{1} puts you back to before the soft reset. Reflog covers you for accidents like this.

I want to undo a git revert. git revert <revert-commit> reverts the revert. It works even on pushed history because it's just another normal commit on top. Don't try to undo a revert by reverting --no-commit and editing — you'll diverge and confuse the next person who needs to read the history.

Tips

  • git push --force-with-lease is safer than --force: it refuses to overwrite remote work if anyone pushed since you last fetched. Make it your default for anything that needs force-push.
  • After git reset --soft HEAD~1, run git status before doing anything else. Confirming what's staged vs unstaged once saves a lot of "wait, why did that file get committed" later.
  • git config --global advice.detachedHead false if you reset onto a SHA and want to silence the warning — but only after you understand what detached HEAD means; see how to recover from detached HEAD in Git.
  • For commits in the middle of a branch, learn git rebase -i once and the "rewrite history" surface area becomes much smaller. See how to rebase in Git.