Accidentally pushed a buggy commit? Need to roll back a feature that wasn't ready? Undoing pushed commits is a common Git task, but it carries risks — especially when working with a team. This guide covers the three main strategies — git revert, git reset with force push, and branching — with concrete examples and safety tips.

Why Undoing Pushed Commits Is Tricky
Local undo is easy: git reset --hard HEAD~1 removes the last commit. But once you've pushed, the commit exists on the remote. Other developers may have pulled it, built on top of it, or based their work on it. Rewriting history can cause chaos. The key is to choose a strategy that matches your situation: revert (safe for public branches), reset + force push (only for private branches), or branch (for long rollbacks).
Strategy 1: git revert — Safe for Public Branches
git revert creates a new commit that undoes the changes from a previous commit. It does not delete history, so it's safe for shared branches like main or develop.
How It Works
# Revert a specific commit (by hash)
git revert <commit-hash>
# Revert the last two commits
git revert HEAD~2..HEAD
Git will open an editor for the revert commit message. Save and close, then push:
git push origin <branch>
Worked Example
Suppose you have three commits on main:
a1b2c3— Add login feature (BUGGY)d4e5f6— Refactor APIg7h8i9— Initial commit
You want to undo a1b2c3. Run:
git revert a1b2c3
This creates a new commit j0k1l2 that reverses the changes of a1b2c3. Push:
git push origin main
The remote history now shows all four commits, but the bug is gone. Other developers can pull without conflict.
When to Use Revert
| Scenario | Recommended? |
|---|---|
| Public branch (main, develop) | ✅ Yes |
| Private feature branch | ❌ No, use reset |
| Undo many commits at once | ✅ Yes (with range) |
| Need to keep full history | ✅ Yes |
Strategy 2: git reset + Force Push — Only for Private Branches
git reset moves the branch pointer backward, removing commits from history. This is destructive — anyone who has pulled those commits will have divergent history. Only use on branches that you own (e.g., personal feature branches).
How It Works
# Reset to a specific commit, discarding all changes
git reset --hard <commit-hash>
# Force push to overwrite remote
git push --force-with-lease origin <branch>
--force-with-lease is safer than --force because it checks that your remote-tracking branch is up to date, preventing accidental overwrites of others' work.
Worked Example
Same scenario: you want to remove commit a1b2c3 from your feature branch feat/login.
git checkout feat/login
git log --oneline
# a1b2c3 Add login feature (BUGGY)
# d4e5f6 Refactor API
# g7h8i9 Initial commit
git reset --hard d4e5f6
git push --force-with-lease origin feat/login
Now the remote branch has only d4e5f6 and g7h8i9. The buggy commit is gone.
Reset Modes
| Mode | Working Directory | Staging Area |
|---|---|---|
--soft |
Unchanged | Unchanged |
--mixed (default) |
Unchanged | Cleared |
--hard |
Overwritten | Overwritten |
--keep |
Unchanged (untracked) | Overwritten |
Use --hard when you want to completely discard the commits and their changes.
Strategy 3: Create a New Branch — For Long Rollbacks
If you need to roll back dozens or hundreds of commits, creating a new branch from the target commit is cleaner than repeated reverts or resets.
How It Works
# Create a new branch at the desired commit
git checkout -b <new-branch-name> <commit-hash>
# Push the new branch
git push origin <new-branch-name>
You can then delete the old branch (if appropriate) or keep it for reference.
Worked Example
Your main branch has 100 commits, and you want to start fresh from commit abc123. Create a branch main-clean:
git checkout -b main-clean abc123
git push origin main-clean
Now main-clean has only the commits up to abc123. You can later merge or rebase as needed.
Common Pitfalls
- Force pushing to a protected branch (e.g.,
main) — most remotes block it. Use revert instead. - Using
--forceinstead of--force-with-lease— can overwrite others' pushes. Prefer--force-with-lease. - Resetting without communicating — if others have pulled the commits, they will have merge conflicts. Always coordinate.
- Reverting a merge commit —
git revert -m 1 <merge-commit>is needed to specify which parent to follow. - Losing uncommitted work with
--hard— stash or commit before resetting.
FAQ
What is the difference between git revert and git reset?
git revert creates a new commit that undoes changes; it preserves history and is safe for public branches. git reset moves the branch pointer backward, removing commits; it rewrites history and should only be used on private branches.
How do I undo a pushed merge commit?
Use git revert -m 1 <merge-commit-hash>. The -m 1 tells Git to revert to the first parent (the branch you were on when merging).
Can I undo a force push?
If you force-pushed and lost commits, you can recover them if you have the commit hashes (e.g., from git reflog). On the remote, check if the commits are still in the reflog (some hosts keep them temporarily). Otherwise, ask teammates who may have pulled them.
What is --force-with-lease?
It's a safer version of --force that aborts if the remote branch has commits you haven't seen. This prevents accidentally overwriting others' work.
Should I use git reset --hard on a shared branch?
No. It will cause divergence for everyone who has pulled those commits. Use git revert instead.
Try the companion tool Git Memo to visualize your branch history and plan undo operations.