Undo the Last Commit: git reset vs git revert
The two main ways to undo a commit — one rewrites history, one adds to it. Which to use depends on whether anyone else has already pulled the commit.
You made a commit and immediately want to undo it. How you do that depends on one question: has anyone else pulled that commit?
git reset — rewrite history
git reset moves the branch pointer backward to an earlier commit. The commit you are undoing disappears from the history as if it never happened.
Keep your changes in the working directory:
git reset HEAD~1
HEAD~1 means "one commit before HEAD." After this, the changes from the last commit are unstaged in your working directory. You can edit them, re-stage them, and commit differently. The commit is gone; the code is not.
Keep your changes staged:
git reset --soft HEAD~1
Same as above, but the changes land in the index (staging area) rather than the working directory. Useful when you want to re-commit immediately with a different message or with additional changes combined.
Discard the changes entirely:
git reset --hard HEAD~1
The commit is gone and the code changes are gone. There is no recovery unless you act within the next few weeks — git reflog keeps a record of where HEAD has been, so you can recover a lost commit for a while:
git reflog
git reset --hard HEAD@{1} # return to where you were before the reset
git revert — add an undo commit
git revert does not touch history. It creates a new commit that inverts the changes from the target commit:
git revert HEAD
Your history grows by one commit: the original commit, then a "Revert: ..." commit that undoes it. The code returns to the state before the original commit; the history record that it happened (and was undone) remains.
To revert without immediately committing — review the result first:
git revert --no-commit HEAD
The inverted changes land in the index. Inspect them, adjust if necessary, then git commit.
Which one to use
Use git reset when the commit has not been pushed to a shared remote, or when you are on a private branch that no one else has pulled. Rewriting history on your own feature branch is fine.
Use git revert when the commit is already on a shared branch (main, develop, or any branch a teammate has pulled). Rewriting shared history with reset forces everyone else into a painful force-pull or causes diverging histories. A revert keeps everyone's history intact.
Undo a commit that is not the most recent
git reset only moves HEAD backward cleanly when you are undoing the latest commit or a sequence of recent commits. For a specific commit buried in history, use revert:
git log --oneline # find the commit hash
git revert a1b2c3d
Git figures out what that commit changed and inverts exactly that, leaving everything before and after it untouched. If the code around that commit has changed since, there may be conflicts to resolve.
Undo multiple recent commits
With reset:
git reset HEAD~3 # undo the last 3 commits, keep changes staged
With revert, you need one revert per commit, oldest last (to avoid cascading conflicts):
git revert HEAD~2..HEAD # reverts commits in reverse order automatically
Quick reference
| Scenario | Command |
|---|---|
| Undo last commit, keep changes unstaged | git reset HEAD~1 |
| Undo last commit, keep changes staged | git reset --soft HEAD~1 |
| Undo last commit, discard changes | git reset --hard HEAD~1 |
| Undo a pushed commit safely | git revert HEAD |
| Undo a specific old commit | git revert <hash> |
| Recover a lost commit after hard reset | git reflog then git reset --hard HEAD@{n} |
SysEmperor