Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXIV · RevertMergeReverts

Reverting a merge commit — `git revert -m 1 <merge>` and the mainline parent

Advanced⏱ ~22 mingit

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

Not yet marked complete on this device.

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 &lt;commit&gt; 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 &lt;merge&gt; fails:

COMMIT=merge_sha
git revert $COMMIT
# error: commit $COMMIT is a merge but no -m option was given.
# fatal: revert failed

-m &lt;parent-number&gt; 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 &lt;merge&gt; is the merge base of &lt;merge&gt;^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:

  1. Always pass -m 1 for a feature merge into main. Parent 1 is the mainline; the inverse of the merge is computed against the mainline. -m 2 is almost never the right choice when reverting on main.
  2. Use --no-edit when 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 --edit only when adding material context (CVE link, incident reference, rationale).
  3. 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.
  4. 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 merge is the same one that powers git 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 revert is a cherry-pick of the inverse; -m 1 is 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 apply of the reverted tree, not just a git revert.

Quiz

Knowledge check · 4 questions

  1. 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?

  2. Q2. `git revert &lt;merge&gt;` works without `-m` if the merge commit has only one parent that is reachable from `main`.

  3. 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`.

  4. 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.