Resolving cherry-pick conflicts on older branches Jump to heading

A fix that applies cleanly to main often conflicts on a release branch cut six months ago. The conflict is rarely about the fix itself. It is about everything that happened on main between the release cut and the fix: a function was renamed, a module was split, a dependency was upgraded. The fix was written against that newer code. Resolving the conflict means understanding what the fix depends on and either bringing those dependencies along or rewriting the fix for the old code. Doing it carelessly produces a backport that compiles and quietly does not fix the bug. This page walks through the analysis, within cherry-pick and backporting.

When to use this approach Jump to heading

Step 1 — Cherry-pick with the context you will need Jump to heading

Use -x so the backport records its origin, and make sure conflict markers include the base. In a cherry-pick, the base is the fix commit’s parent on main, which is exactly the “before” the fix was written against.

git switch release/2.3
git config merge.conflictStyle zdiff3
git cherry-pick -x 4e7a91c
# CONFLICT (content): Merge conflict in src/billing/invoice.py
Why a backport conflictsThe fix F was written on main after a refactor R changed the code around it. The release branch was cut before R, so the code F expects is not there. Cherry-picking F onto the release branch compares F's parent, which includes R, with code that lacks it.the fix assumes the refactormaincutRFrelease/2.3cutr1r2F'?the conflict is the gap between what F assumed and what the release branch has Why a backport conflictsThe fix F was written on main after a refactor R changed the code around it. The release branch was cut before R, so the code F expects is not there. Cherry-picking F onto the release branch compares F's parent, which includes R, with code that lacks it.the fix assumes the refactormaincutRFrelease/2.3cutr1r2F'?the conflict is the gap between what F assumed and what the release branch has

Step 2 — Find what the fix depends on Jump to heading

Look at the commits that changed the conflicted lines on main between the release cut and the fix. Those are the candidates for missing prerequisites.

cut=$(git merge-base release/2.3 main)
fix=4e7a91c
# Commits on main, after the cut and before the fix, touching the conflicted file
git log --oneline "$cut..$fix^" -- src/billing/invoice.py
# Narrow to the specific lines the fix changes
git log --oneline -L '/def apply_discount/,+30:src/billing/invoice.py' "$cut..$fix^"

The -L form traces the history of a function or line range, which is usually far more precise than file-level history. Each commit it shows changed the lines the fix touches.

# Verification: compare the function as main had it before the fix with the release branch's version
git show "$fix^:src/billing/invoice.py" > /tmp/main-before-fix.py
git show release/2.3:src/billing/invoice.py > /tmp/release.py
diff -u /tmp/release.py /tmp/main-before-fix.py | head -40

Step 3 — Decide: bring prerequisites, or adapt the fix Jump to heading

Two ways forward, and the right one depends on the prerequisite.

Prerequisite or adaptation?If the prerequisite is small and safe — a rename or a helper function — cherry-pick it first so the fix applies as written. If it is large or risky — a refactor or dependency upgrade — rewrite the fix against the old code instead. If it is itself a bug fix, it probably needed backporting anyway.What kind of change is the prerequisite?small rename or helperPick it firstthen the fix applieslarge refactorAdapt the fixwrite it for old codeanother bug fixBackport bothin original ordernever backport a refactor just to make a fix apply — that is new risk on a stable branch Prerequisite or adaptation?If the prerequisite is small and safe — a rename or a helper function — cherry-pick it first so the fix applies as written. If it is large or risky — a refactor or dependency upgrade — rewrite the fix against the old code instead. If it is itself a bug fix, it probably needed backporting anyway.What kind of change is the prerequisite?small rename or helperPick it firstthen the fix applieslarge refactorAdapt the fixwrite it for old codeanother bug fixBackport bothin original ordernever backport a refactor just to make a fix apply — that is new risk on a stable branch
# Option A: bring a small prerequisite first, in order
git cherry-pick --abort
git cherry-pick -x 91b03ee 4e7a91c     # helper, then fix

# Option B: adapt — resolve by applying the fix's intent to the old code
git diff "$fix^" "$fix" -- src/billing/invoice.py      # read what the fix actually does
# then edit the conflicted region so the old code gains the same behaviour

Step 4 — Resolve by re-applying intent, not by merging text Jump to heading

When adapting, do not treat the conflict as “choose between two versions”. The release branch’s version is correct for the release branch; the fix’s version is correct for main. What you want is the release branch’s code with the fix’s behavioural change applied. Read the fix’s diff, then make the equivalent change by hand.

<<<<<<< HEAD
    total = sum(line.amount for line in lines)
    if customer.discount:
        total -= total * customer.discount
||||||| parent of 4e7a91c (Fix double discount on credit notes)
    total = calculate_total(lines, customer)
=======
    total = calculate_total(lines, customer, skip_discount=invoice.is_credit_note)
>>>>>>> 4e7a91c (Fix double discount on credit notes)

The fix adds “skip the discount for credit notes”. The release branch inlines the discount logic, so the adapted version guards the inline code:

    total = sum(line.amount for line in lines)
    if customer.discount and not invoice.is_credit_note:
        total -= total * customer.discount
git add src/billing/invoice.py
git cherry-pick --continue      # -x appends "(cherry picked from commit 4e7a91c…)"

Add a sentence to the commit message saying the fix was adapted, so the next person comparing branches knows the code differs on purpose.

Step 5 — Test the backport on the old code, including the original bug Jump to heading

A backport is new code on that branch. Run the release branch’s test suite, and port the fix’s regression test too — adapting it if needed — so you have evidence the bug is fixed on this version, not just that nothing else broke.

git show "$fix" --stat | grep -i test      # the fix's own test, if any
git checkout "$fix" -- tests/billing/test_credit_notes.py   # bring the test, then adapt
pytest tests/billing -q
A careful backport, end to endCherry-pick with -x and ancestor markers, trace the conflicted lines back to their prerequisites, choose between bringing a small prerequisite or adapting the fix, resolve by intent, and prove the fix with the ported regression test.cherry-pick -xzdiff3 markersTrace lineslog -L on mainDecidepick prereq / adaptResolveapply intentProveported regression testthe regression test is what tells you the backport fixes the bug, not just compiles A careful backport, end to endCherry-pick with -x and ancestor markers, trace the conflicted lines back to their prerequisites, choose between bringing a small prerequisite or adapting the fix, resolve by intent, and prove the fix with the ported regression test.cherry-pick -xzdiff3 markersTrace lineslog -L on mainDecidepick prereq / adaptResolveapply intentProveported regression testthe regression test is what tells you the backport fixes the bug, not just compiles

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Should I just backport the refactor too, to keep branches similar? Jump to heading

Rarely. A release branch exists to change as little as possible. A refactor brings risk unrelated to the bug, and if it introduces a problem, it does so in the version customers are running. Adapt the fix instead.

How do I backport to several older branches at once? Jump to heading

Start with the newest release branch and work backwards, using each adapted backport as the source for the next older one. Each step crosses less history, so conflicts stay smaller.

What if the bug does not exist on the old branch? Jump to heading

Check before backporting: reproduce it with the regression test on the release branch first. Code that never had the bug does not need the fix, and applying it anyway adds risk for nothing.