open atlas
↑ Back to track
Git, from zero to senior GIT · 10 · 02

Incident: a bad merge

A bad merge lands on main and CI goes red. Worked under pressure: diagnose with log --graph and show, then revert the merge with -m 1 (never reset shared history), and recover force-pushed commits via reflog from a clone or the server. Revert vs reset, decided under fire.

GIT Senior ◷ 19 min
Level
FoundationsJuniorMiddleSenior

It is 16:40. Someone merged a half-finished feature branch into main, CI is red, and the deploy pipeline is blocked for the whole team. The pressure is real and the temptation is to git reset --hard it away. That instinct is the dangerous one: main is shared history, and rewriting shared history under pressure turns one incident into two.

By the end of this lesson you will be able to run a bad-merge incident the way a senior does: diagnose the offending merge, decide revert-versus-reset by whether the history is shared, revert a merge commit correctly with -m 1, and recover commits a panicked force-push erased — using git reflog.

Goal

After this lesson you can respond to a bad merge on a shared branch: locate the merge commit with git log --graph and git show, undo it with git revert -m 1 instead of reset, recover force-pushed commits from a colleague’s reflog or the server, and explain the “never rewrite shared history” rule that governs the whole decision.

1

First diagnose: find the exact commit that broke main. Do not guess — read the graph. The merge commit is the one with two parents sitting where main went red:

git fetch origin
git log --graph --oneline --decorate origin/main | head -30
git show <merge-sha>          # see what was merged and the parent line

git show on a merge prints the combined diff and, crucially, the parent list (Merge: a1b2c3 d4e5f6). The first parent is the branch you were on when the merge happened — main — and the second is the feature branch that got pulled in. That parent order is what the revert in step 3 depends on.

2

Decide the tool by one question: is this history pushed and shared? This is the whole decision tree, and it is binary:

  • Shared / already pushed (the bad merge is on origin/main): you must append a correction, never rewrite. Use git revert.
  • Local-only (the merge exists only on your machine, never pushed): you may rewrite freely. git reset --hard <before-merge> is fine.

On a team incident the answer is almost always “shared,” because the merge is on origin/main and teammates have already fetched it. Rewriting it with reset + force-push would erase commits other people now build on and desync every clone. Revert keeps the bad merge in history and lands a new commit that undoes its effect.

3

Revert the merge with -m 1 to name the mainline parent. A merge has two parents, so git revert cannot know which side is “the change to undo” — you must tell it with -m (mainline). -m 1 means “keep the first parent (main) as the baseline and undo everything the second parent brought in”:

git revert -m 1 <merge-sha>
git push origin main          # ordinary push: revert is a new commit, no force

This appends one new commit that subtracts the feature’s changes. main is green again, the full history is preserved, and nobody’s clone is invalidated. Picking the wrong mainline (-m 2) would undo main’s own work instead — so confirm the parent order from step 1 first.

Edge cases

Reverting a merge has a sharp follow-up: git now records that the feature’s changes were removed, so later trying to merge that same branch again does nothing — git thinks it is already integrated. To re-land the fixed feature you must first revert the revert (git revert <revert-sha>), or rebase the feature onto the new main so it arrives as fresh commits. This is the classic “revert of a revert” and it trips up teams who do not expect it.

4

If a force-push erased commits, recover them with reflog — the safety net for shared disasters. Suppose someone “fixed” the incident by force-pushing and wiped good commits off main. Those commits are not gone; they are unreferenced, and the reflog still names them on any machine that had them:

git reflog                    # local: every position HEAD has held, with shas
git reflog show origin/main   # what the remote-tracking ref pointed to over time
git branch rescue <good-sha>  # re-anchor the lost commits onto a real branch

The reflog is per-clone and local, so the recovery often happens on a colleague’s clone (or the server’s own reflog / git fsck --lost-found) that still holds the pre-force-push tip. Find the last good SHA there, branch it, push it, and the lost work is back. Reflog is why “I force-pushed over good work” is recoverable rather than fatal.

5

Communicate, then re-land the fix cleanly. Technical recovery is half the job. Post in the incident channel what happened, what you reverted, and that main is green. Then bring the feature back the right way: branch off the fresh main, revert-the-revert or rebase the original work onto it, fix the actual defect, run CI to green, and merge through a normal reviewed PR. The incident ends not when main builds again but when the feature lands correctly and the team knows the sequence.

Worked example

16:40, main is red — the full response.

You fetch and run git log --graph --oneline origin/main. A merge commit 9f3a1c sits at the top: “Merge branch ‘feat/new-billing’.” git show 9f3a1c confirms it pulled in unfinished billing code and prints Merge: 4c2d10 8e77ab — parent 1 (4c2d10) is the old main, parent 2 is the feature.

Is it shared? Yes — it is on origin/main and three people already fetched. So no reset. You run git revert -m 1 9f3a1c, which creates a07bb2 “Revert ‘Merge branch feat/new-billing’”, and git push origin main. CI goes green in eight minutes. You post in #incidents: “Reverted the premature billing merge (9f3a1c) via a07bb2; main is green; re-landing once billing is finished.”

Two days later billing is ready. Because the merge was reverted, simply re-merging the branch would be a no-op, so you git revert a07bb2 on a fresh branch to restore the code, fix the actual bug on top, and open a normal PR. Clean re-land, no rewritten shared history, full audit trail of what happened and why.

Common mistake

The fatal mistake is reaching for git reset --hard plus git push --force on main during the panic. It looks like it cleans up the mess — the bad merge vanishes — but it rewrites shared history: every teammate’s clone now disagrees with origin/main, their next pull creates phantom merges or “lost” commits, and anyone who branched off the reverted range is stranded. On shared branches the rule is absolute: undo by appending a revert, never by rewriting. Reset is for local-only history only.

Check yourself
Quiz

A broken feature branch was merged into origin/main and CI is red. Teammates have already fetched it. How do you undo it correctly?

Recap

A bad merge on main is an incident you run by rule, not by panic. Diagnose the merge with git log --graph and git show to confirm its two parents. Decide by one question — is the history shared? If it is pushed, you must append, not rewrite. Revert the merge with -m 1 to name the mainline parent, then push normally; the bad merge stays in history and a new commit undoes its effect. If a force-push already erased good commits, recover them via git reflog on a clone or the server that still holds the tip. Reset is for local-only history; on shared branches you never rewrite. Next you will distill the whole track into the mental models a senior interview tests.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.