Git, CI/CD & GitOpsXIV · RevertMergeReverts
Reverting a merge commit — `git revert -m 1 <merge>` and the mainline parent
What you'll learn
- Explain what a merge commit is and why it has two parents
- Identify why `git revert <merge>` requires `-m <parent-number>` to know which parent is the mainline
- Use `git revert -m 1 <merge>` to revert a feature merge on `main`
- Recognise that reverting a merge does not delete the merged branch; it only undoes the changes at HEAD
- Plan a re-revert (`git revert <revert>`) when the original branch is later merged again
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
A merge commit has two parents. That is what makes it a merge
commit and not an ordinary commit: when you merge a feature
branch into main, the resulting merge commit has main’s tip
as its first parent and the feature branch’s tip as its second
parent. The history is a DAG, not a line, and a merge commit is
the node that joins two branches.
git revert <merge> cannot work without knowing which of those
two parents is the mainline — the line of history the revert
should pretend never diverged. The -m <parent-number> flag
specifies that. git revert -m 1 <merge> says “parent 1 is the
mainline; compute the inverse of the merge as if the merge had
not happened from the perspective of parent 1.” For a feature
merge into main, -m 1 is almost always the right choice,
because parent 1 is the main tip at the moment of the merge.
What a merge commit looks like
After a --no-ff merge from a feature branch, the history has a
node with two parents. git log shows the parents in order, and
git cat-file -p <merge> shows them in the commit object:
gitGraph
commit id: "main tip before merge"
branch feature
checkout feature
commit id: "feat A"
commit id: "feat B"
checkout main
merge feature id: "merge commit (parents: main, feature)" tag: "HEAD"
The merge commit’s OID is merge. Its first parent is main tip before merge. Its second parent is feat B (the feature branch
tip at the time of the merge). The commit message defaults to
the merge summary; the diff is the combined effect of feat A
plus feat B against the merge base.
git cat-file -p HEAD
# tree <tree-sha>
# parent <main-tip-sha> <-- parent 1
# parent <feature-tip-sha> <-- parent 2
# author ...
# committer ...
#
# Merge branch 'feature'
#
# feat A description
#
# feat B description
The order of parents matters for revert. Parent 1 is the branch
you were on when you ran git merge; parent 2 is the branch you
merged in. For a feature merge into main, parent 1 is the
mainline and parent 2 is the side branch.
Why -m is required
A non-merge commit has one parent, and git revert <commit> can
unambiguously compute the inverse by doing a three-way merge
between the parent and the current HEAD. A merge commit has two
parents, and Git does not know which parent is “the history that
should have continued.” Without -m, git revert <merge> fails:
COMMIT=merge_sha
git revert $COMMIT
# error: commit $COMMIT is a merge but no -m option was given.
# fatal: revert failed
-m <parent-number> selects the mainline. The merge base is then
computed as the merge base of the mainline parent and the current
HEAD (which is a descendant of the mainline parent, since you are
on the same branch). The inverse of the merge is the diff from
the merge base to the side parent, negated:
COMMIT=merge_sha
git revert -m 1 $COMMIT
# produces a new commit that undoes the changes from the side branch
# (parent 2), as seen from parent 1 (the mainline)
For a feature merge into main, -m 1 is correct because
parent 1 is the main tip at the time of the merge. -m 2
would treat the feature branch as the mainline, which is rarely
what you want when you are reverting on main.
What the revert commit contains
The resulting revert commit’s diff is the inverse of the merge
as seen from the mainline. If the merge introduced features A
and B (changes to service.yaml, iam.tf, and a new file
policies/feature.rego), the revert removes exactly those
changes from the tree at HEAD:
COMMIT=merge_sha
git revert -m 1 $COMMIT
git show HEAD --stat
# modified: service.yaml
# modified: iam.tf
# deleted: policies/feature.rego
The original feature branch is not deleted. The feature branch
still exists, still points at its tip, and still has its history.
The revert commit does not touch it. If the feature branch is
later merged again — for example, after the feature is fixed and
ready to ship — Git will see the revert commit on main and
require a re-revert to keep the changes in the tree.
Re-reverting a reverted merge
When the feature is fixed and ready to ship again, the team has two options:
# Option A: revert the revert, then merge the fixed feature branch
REVERT=revert_sha
git revert $REVERT
git merge feature-fixed
# Option B: merge the fixed feature branch with a new merge commit,
# accepting that the merge will see the revert commit on main and
# require manual resolution
git merge --no-ff feature-fixed
# resolve conflicts against the revert
Option A is cleaner: the revert-of-revert explicitly reintroduces
the changes, and the subsequent merge is a normal merge. Option
B is also valid but requires more conflict resolution, because
Git sees the changes as “present on the feature branch but
absent on main due to the revert” and treats the divergence
as a merge conflict.
UnderTheHood: the merge base for a merge revert
The merge base for git revert -m 1 <merge> is the merge base
of <merge>^1 (the mainline parent) and HEAD. That merge base
is necessarily an ancestor of HEAD (HEAD is on the mainline),
and is the same merge base that the original git merge used
to compute the merge commit’s tree. The three-way merge then
proceeds with the mainline parent as one head, HEAD as the
other, and the merge base as the common ancestor. The inverse
of the merge is “the diff from the merge base to the side
parent, applied to HEAD with reversed polarity.”
Production discipline
The production discipline for merge reverts has four rules:
- Always pass
-m 1for a feature merge intomain. Parent 1 is the mainline; the inverse of the merge is computed against the mainline.-m 2is almost never the right choice when reverting onmain. - Use
--no-editwhen the default message is good enough. The default message names the merge and states it has been reverted; rewriting it loses the audit trail. Use--editonly when adding material context (CVE link, incident reference, rationale). - Open a follow-up issue when the feature will be re-shipped. The revert must be reverted before the re-merge, or the re-merge will conflict against the revert commit. Document this so the next engineer is not surprised.
- Never delete the merged branch as part of the revert. The branch is still part of history; deleting it removes the evidence of where the changes came from. Delete the branch only when it is genuinely abandoned and the team has decided not to re-ship it.
Cross-course references
- Git, CI/CD & GitOps — Part IX (Three-way merges) — the
merge base computation that powers
git mergeis the same one that powersgit revert -m 1. - Git, CI/CD & GitOps — Part X (Conflicts) — the conflict
resolution flow for a merge revert is the same flow as a
regular merge conflict: edit, stage,
--continue. - Git, CI/CD & GitOps — Part XIII (Cherry-pick) —
git revertis a cherry-pick of the inverse;-m 1is the cherry-pick’s way of saying “treat parent 1 as the source.” - Terraform for Production Sysadmins — Part IX-XII (State) —
reverting a Terraform-bearing merge does not revert the state
file; the rollback plan must include a state-aware
terraform applyof the reverted tree, not just agit revert.
Quiz
Knowledge check · 4 questions
Q1. A team merged a feature branch into `main` as merge commit `m3r9e5d`. The merge introduced a broken IAM policy that has been applied to production. What does `git revert -m 1 m3r9e5d` do?
Q2. `git revert <merge>` works without `-m` if the merge commit has only one parent that is reachable from `main`.
Q3. Name the flag that selects the mainline parent when reverting a merge commit, and state which parent number corresponds to the mainline for a feature merge into `main`.
Q4. Plan the rollback and the eventual re-ship of a feature that was merged to `main`, reverted, and then fixed on the feature branch.
Feature branch `feature/iam-v2` was merged into `main` as commit `m3r9e5d`, applied to production, and rolled back with `git revert -m 1 m3r9e5d` after a CVE was found in the IAM policy. The team has fixed the IAM policy on `feature/iam-v2` and wants to re-ship. The team lead asks for the rollback-and-re-ship plan.
Passing score: 75%. Answers are checked in this browser.