Undoing a bad merge on a shared branch Jump to heading
A pull request merged to main breaks production, and it needs to come out now. On a private branch you would reset and force-push. On a shared branch that is the wrong move: everyone who fetched since the merge now has history the server no longer agrees with, and the next person to push reintroduces it. The safe tool is git revert -m 1, which adds a new commit undoing the merge’s changes while leaving history intact. The catch comes later, when the fixed version of the work is ready to merge again — Git believes those changes are already in main, and they silently fail to reappear. This page covers the revert and, just as importantly, how to bring the work back correctly, within history rewriting and recovery.
When to use this approach Jump to heading
- A merge commit on a shared branch introduced a problem that must be undone quickly.
- Other people have already fetched or built on top of the merge.
- The work will be fixed and merged again later.
- For a squash-merged pull request, an ordinary
git revert <sha>is enough; the reintroduction concerns below still apply. Choosing between revert and reset in general is covered in when to use git revert vs git reset.
Step 1 — Revert the merge relative to its first parent Jump to heading
A merge commit has two parents; the revert must undo the changes relative to main’s side. That is parent 1 when the merge was made on main.
git switch main && git pull --ff-only
git log -1 --format='%h %s%nparents: %p' 5a9e3c1 # confirm it is the merge commit
git revert -m 1 5a9e3c1
git push origin main The revert commit’s message names the merge it undoes. Add one line saying why — the incident or bug — so the history explains itself.
# Verification: main's tree no longer contains the merged changes
git diff 5a9e3c1^1 HEAD --stat # expect empty, or only commits made after the merge Step 2 — Understand why re-merging the same branch does nothing Jump to heading
After the revert, the feature’s commits are still ancestors of main. When the feature branch gets a fix and is merged again, the merge base is the old tip of the feature branch, so Git only considers the new fix commit as the feature’s change. The original work — undone by the revert — stays undone.
# Demonstrate: what would a direct re-merge bring in?
git diff main...feature/search --stat # only the new fix, not the original work Step 3 — Bring the work back by reverting the revert Jump to heading
When the fixed version is ready, first revert the revert on a branch, then merge the fix on top, and review both together.
git switch -c reland/search main
git revert <sha-of-revert-commit> # brings the original changes back
git merge --no-ff feature/search # adds the fix made since
git push -u origin reland/search
gh pr create --base main --title "Re-land search (reverts revert of #812, adds fix)" --fill Reviewers should look at the combined diff against main, which is exactly what will land. It should be the original feature plus the fix.
# Verification: the re-land contains the original work and the fix
git diff main...reland/search --stat Step 4 — Alternative: rebuild the branch with new commits Jump to heading
If the feature branch will be reworked substantially, a clean alternative is to recreate its commits so they are not already ancestors of main. Rebasing the branch onto main with --no-ff style replay — or simply rebasing it onto the revert — produces new commit hashes, and a normal merge then brings everything in.
git switch feature/search
git rebase --force-rebase main # replay every commit as a new one, even if unchanged
git log --oneline main..HEAD # now the full feature appears as new commits ⚠️ SAFETY WARNING:
--force-rebaserewrites every commit on the feature branch, so the branch must be force-pushed and anyone else working on it must reset to the new version. Usegit push --force-with-lease. If the rewrite goes wrong,git reset --hard ORIG_HEADimmediately afterwards restores the previous branch tip.
Step 5 — Make the sequence visible to the team Jump to heading
Reverts and re-lands are easy to misread in history. Use a consistent message convention, and link the incident, the revert and the re-land together.
Revert "Add search index rebuild (#812)"
This reverts commit 5a9e3c1, reversing changes made to 2d4b6f8.
Reason: INC-4471, index rebuild locked the orders table.
Re-land "Add search index rebuild (#812)" with batched rebuild
Reverts the revert in 8c71a02 and adds batching (#839). Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Why not just reset main and force-push? Jump to heading
Because everyone who fetched since the merge now has commits the server has dropped, and their next push or pull reintroduces them or causes confusing divergence. A revert is an ordinary commit that every clone can fast-forward to.
What if the merge was squash-merged? Jump to heading
There is no merge commit, so git revert <squash-sha> without -m undoes it. Re-landing is simpler: the squash commit is not an ancestor of the feature branch, so merging a new squash of the fixed branch works — but the revert must still be reverted, or its change will conflict with the new squash.
Can a revert itself conflict? Jump to heading
Yes, if later commits on main changed the same lines the merge introduced. Resolve it like any conflict, keeping the later commits’ intent while removing the merged work.
Related Jump to heading
- History Rewriting & Recovery — the parent topic.
- Finding the Merge Base and Why It Matters — the mechanics behind step 2.
- Rolling Back a Deployment with Git — the deployment side of the same incident.
- Cherry-Picking a Merge Commit with --mainline — the same parent-number logic in a cherry-pick.