Undoing a git commit is one of the most Googled Git questions, yet the answer is usually different depending on one key fact: has the commit been pushed to a shared branch? The local vs. remote distinction determines whether you can rewrite history or must add a new commit that reverses the change.
What changed in 2026
git revert is the team-safe standard — nothing has changed here; it has always been the right choice for shared branches.
git restore replaced git checkout -- file for unstaging and discarding file changes — cleaner API, same effect.
- Branch protection rules on GitHub/GitLab now default to blocking force-push on main — if you need to undo something on main, revert is usually your only option.
- AI-assisted commit messages mean commits are more descriptive, making
git log --oneline easier to navigate when looking for the commit to undo.
The decision tree
Did you push the commit to a shared branch?
├─ YES → git revert <sha> (safe, adds a new commit)
└─ NO
├─ Keep the changes, just undo the commit?
│ └─ git reset --soft HEAD~1
├─ Unstage the changes too (keep in working tree)?
│ └─ git reset --mixed HEAD~1 (default)
└─ Discard everything?
└─ git reset --hard HEAD~1
git revert — safe for shared branches
# Undo a specific commit by SHA
git revert abc1234
# Undo the last commit
git revert HEAD
# Undo multiple commits (creates one revert commit per target)
git revert HEAD~3..HEAD
# Revert but do not auto-commit (stage the changes for review first)
git revert --no-commit abc1234
git diff --cached # review
git commit -m "Revert feature X"
git revert is a forward operation — it adds a new commit that applies the inverse of the target commit. History is preserved; no force-push needed.
git reset — local branches only
# --soft: move HEAD back, keep changes staged
git reset --soft HEAD~1
# Use this to re-commit with a different message or split into two commits
# --mixed (default): move HEAD back, unstage changes, keep in working tree
git reset HEAD~1
# Use this to re-stage selectively or discard specific files
# --hard: move HEAD back, discard all changes in index and working tree
git reset --hard HEAD~1
# DESTRUCTIVE — make sure you have no unsaved work
| Flag |
HEAD |
Index (staged) |
Working tree |
| --soft |
moved back |
unchanged |
unchanged |
| --mixed |
moved back |
reset |
unchanged |
| --hard |
moved back |
reset |
reset (discarded) |
Amend the last commit (before pushing)
# Fix the commit message only
git commit --amend -m "Better commit message"
# Add a forgotten file to the last commit
git add forgotten-file.ts
git commit --amend --no-edit
--amend rewrites the last commit. Use it only on local commits — amending a pushed commit requires a force-push.
Recover with git reflog
Every movement of HEAD is recorded in the reflog for ~90 days. If you ran git reset --hard and lost commits:
git reflog
# HEAD@{0}: reset: moving to HEAD~1
# HEAD@{1}: commit: add feature X <-- this is the lost commit
# HEAD@{2}: commit: fix bug Y
# Recover
git checkout -b recovery-branch HEAD@{1}
# or
git reset --hard HEAD@{1}
The reflog is local — it does not sync to the remote. If your teammate pushed the commit but you have not pulled it, git fetch and look at the remote ref.
Undo a pushed commit (force-push on your own branch)
# Only acceptable on branches you own and no one else is working on
git reset --hard HEAD~1
git push --force-with-lease origin feature/my-branch
--force-with-lease is safer than --force — it fails if someone else pushed to the branch since your last fetch, preventing you from overwriting their work.
Never force-push to main, master, or any shared branch.
Common mistakes
git reset --hard with uncommitted changes in the working tree. The reset discards both the commit and any unstaged changes. Check git status first and stash if needed.
Reverting a merge commit without -m. Merge commits have two parents. git revert <merge-sha> requires -m 1 (or -m 2) to specify which parent to revert to.
git push --force instead of --force-with-lease. --force overwrites unconditionally; --force-with-lease is a safety check. Always prefer --force-with-lease.
Amending a commit that has already been pushed. It creates a diverged history that requires a force-push and breaks colleagues' local branches.
What to skip
git checkout HEAD~1 to "undo" a commit — this puts you in detached HEAD state, not a branch. Use reset or revert.
- Deleting the
.git folder to "start fresh" — you lose all history. If the repo is broken, ask for help.
- Force-pushing to main — on most platforms this is blocked by default for good reason; use
git revert.
FAQ
What is the difference between revert and reset?
revert adds a new commit that undoes the change — history is preserved and the operation is safe for shared branches. reset moves the branch pointer backward, rewriting history — only safe for local, unpushed commits.
Can I undo a git revert?
Yes — git revert <revert-sha> reverts the revert, restoring the original change.
How do I undo all commits since I branched from main?
git reset --soft $(git merge-base HEAD main) moves HEAD back to the branch point and keeps all changes staged, ready to re-commit.
Is it safe to amend a commit that has been pushed to a PR branch that only I use?
Technically yes, with --force-with-lease. Be explicit with your team that you rewrote history, because their local branch will diverge.
Where to go next