Scenario
Section titled “Scenario”A feature branch was merged into main. Ten minutes later the feature turns out to be broken.
The merge has to come out.
There are two correct answers, and which one applies depends on a single question: has anyone else fetched the merge? Get it right and it is one command. Get it wrong and you either rewrite history under your colleagues, or you leave the bug in place while believing you removed it.
Objective
Section titled “Objective”Undo the same bad merge both ways, understand when each is appropriate, and see for yourself why
git revert of a merge leaves a trap for the next person who tries to merge that branch.
Prerequisites
Section titled “Prerequisites”- Git installed
- Merge commits — what a two-parent commit is
- Reset, revert and restore is the companion lab; either order works
Starting state
Section titled “Starting state”mkdir -p /tmp/lab-badmerge && cd /tmp/lab-badmergegit init -q -b main .git config user.email "lab@example.com"git config user.name "Lab User"
echo "# Project" > README.mdgit add . && git commit -q -m "Initial commit"
git switch -qc feature/brokenecho "bug" > broken.txtgit add . && git commit -q -m "Add feature (contains a bug)"
git switch -q mainecho "ok" > other.txtgit add . && git commit -q -m "Unrelated work on main"Two branches that have diverged from the initial commit, so a merge will produce a real merge commit rather than a fast-forward.
-
Merge the broken branch:
Terminal window git merge feature/broken --no-editgit log --oneline --graphNote the merge commit at the top and its two parents.
-
Undo it as if nobody has seen it. The merge exists only on your machine. Use the reference Git set aside for exactly this:
Terminal window git reset --hard ORIG_HEADgit log --oneline --graphThe merge commit is gone. What is
ORIG_HEAD? -
Redo the merge, then undo it as if it has been pushed. Now imagine you pushed the merge an hour ago and three people have pulled. Resetting would rewrite history under them. Instead:
Terminal window git merge feature/broken --no-edit -qgit revert -m 1 HEAD --no-editgit log --oneline --graphlsbroken.txtis gone from the working tree. The merge commit is still in history. Why-m 1? -
Hit the trap. The feature is fixed and you want to merge it again:
Terminal window git merge feature/brokenRead the output. Then run:
Terminal window git log --oneline main..feature/brokenWhat does the empty result tell you?
Validation
Section titled “Validation”After step 2 the graph is linear with two commits. After step 3 it has a merge commit and a
revert commit above it, and ls shows README.md and other.txt only. Step 4 prints
Already up to date. — that is the trap, not a success.
Step 2. ORIG_HEAD is set by operations that move HEAD substantially — merge, rebase, reset —
to where HEAD was before. It is the “undo” reference for the last such operation.
Step 3. A merge commit has two parents. Reverting it means “return to one of them”. -m 1
says mainline is parent 1 — the branch you were on when you merged. Without -m, Git refuses
because it cannot guess.
Step 4. From Git’s point of view, feature/broken’s commits are already in main’s history —
the merge commit is still there. The revert only undid the changes. Merging again brings in
nothing, because there is nothing new to bring.
Solution
Section titled “Solution”Step 1:
Merge made by the 'ort' strategy. broken.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 broken.txt* 2e42fdf Merge branch 'feature/broken'|\| * a707dde Add feature (contains a bug)* | addee73 Unrelated work on main|/* f5813f4 Initial commitStep 2 — reset, because the merge was never shared:
HEAD is now at addee73 Unrelated work on main* addee73 Unrelated work on main* f5813f4 Initial commitStep 3 — revert, because it was:
[main bbc4060] Revert "Merge branch 'feature/broken'" 1 file changed, 1 deletion(-) delete mode 100644 broken.txt* bbc4060 Revert "Merge branch 'feature/broken'"* 2e42fdf Merge branch 'feature/broken'|\| * a707dde Add feature (contains a bug)* | addee73 Unrelated work on main|/* f5813f4 Initial commitStep 4 — the trap:
Already up to date.git log main..feature/broken prints nothing: every commit on the feature branch is already an
ancestor of main.
Getting out of the trap. Once the feature is genuinely fixed, either revert the revert
(git revert bbc4060), which reinstates the original changes, and then merge the fix commits; or
rebase the feature branch so its commits are new and Git sees them as unmerged. The first is
simpler to reason about.
Explanation
Section titled “Explanation”Reset moves the branch pointer. git reset --hard ORIG_HEAD sets main back to where it was,
and the merge commit becomes unreachable. Correct when the merge has not left your machine; wrong
the moment someone else has it, because their main now contains a commit yours does not.
Revert adds a commit that undoes the changes. History is untouched and grows by one commit. Anyone who pulls gets the revert cleanly. This is the only safe option for shared history.
A reverted merge is still a merge. The graph still says feature/broken was merged, so Git
will not merge it again. This surprises everyone the first time — the branch’s changes are gone
but its commits are not. The lesson on reverting covers the
recovery options.
Troubleshooting
Section titled “Troubleshooting”git revert HEAD says “is a merge but no -m option was given”. Expected. Add -m 1.
Step 1 fast-forwarded instead of creating a merge commit. main had no commits of its own
since the branch point. Check the starting state committed other.txt on main.
ORIG_HEAD points somewhere unexpected. It reflects the most recent merge, rebase or reset.
If you ran another such command after the merge, it moved.
Clean up
Section titled “Clean up”cd /tmp && rm -rf lab-badmergeRelated lessons
Section titled “Related lessons”Next lab
Section titled “Next lab”Reset, revert and restore — five situations, three commands, and the one that destroys work.