With release/1.4, release/1.5 and main all supported, how should a security fix reach all three?
answer
- start where the bug is oldest
- one commit, two merges, not three commits
- reachability is provable, similarity is not
- duplicate patches collide at merge time
- rerere remembers repeated resolutions
basics
~20 sCommit the fix on the oldest affected line, release/1.4, then merge forward: 1.4 into 1.5, 1.5 into main. Each merge makes the fix reachable, so git branch --contains proves coverage instead of you having to trust three independent cherry-picks.
solid answer
~50 sFix once, on the **oldest** supported line that has the bug, then propagate **forward** through the ordered chain: `git switch release/1.5 && git merge release/1.4`, then `git switch main && git merge release/1.5`. Two payoffs. First, containment is provable by graph reachability — `git branch --contains <sha>` and `git tag --contains <sha>` list every line and every release that has it, which no cherry-pick fan-out can give you. Second, the merge base advances, so the next merge-up is smaller and later conflicts do not re-litigate the same hunks. Fanning cherry-picks out to each line instead creates three unrelated commits with the same content; the eventual merge between lines then conflicts on the doubly-applied hunk. Where lines have diverged too far to merge, cherry-pick with `-x` and audit with `git cherry -v`. Enable `rerere.enabled` so repeated conflicts resolve consistently.
code
bash · 9 linesgit switch release/1.4
git commit -m "fix: reject oversized auth header"
git tag -a v1.4.3 -m "Release 1.4.3"
git switch release/1.5 && git merge release/1.4
git switch main && git merge release/1.5
git branch --contains 9fceb02 # release/1.4, release/1.5, main
git tag --contains 9fceb02 # every release that shipped itgo deeper
Know that a fix needed on several release lines does not spread by itself, and that the safe order is oldest line first, then forward — not fixing each branch independently.
Explain the two topologies and their merge consequences: merging forward advances the merge base and preserves reachability, while duplicate commits with identical content still conflict because they share no ancestry.
Show you audit rather than assume — git cherry -v before cutting a release, git tag --contains to answer which shipped versions are affected, and rerere for the conflicts you meet repeatedly.
Own the drift budget: every unnecessary change on a maintenance line raises the cost of every future backport, so the policy that keeps those lines boring is worth more than any tooling.
## The shape of the problem Three live lines, one vulnerability present in all of them. There are two topologies you can produce, and they behave very differently over the following year. **Fan-out.** Write or cherry-pick the fix independently onto `release/1.4`, `release/1.5` and `main`. Three commits, three ids, no ancestry between them. **Merge-up.** Commit the fix once on `release/1.4`, then merge `release/1.4` into `release/1.5`, then `release/1.5` into `main`. One commit, made reachable from all three tips by two merges. ## Why merge-up wins **Containment becomes a query, not a belief.** After merge-up, `git branch --contains <sha>` lists all three branches and `git tag --contains <sha>` lists every release tag that includes the fix. That is graph reachability — Git can prove it. After fan-out, `--contains` lists exactly one branch, and answering "is 1.5 patched?" requires patch-id comparison (`git cherry -v release/1.5 release/1.4`) or reading `(cherry picked from commit ...)` trailers, both of which are weaker: patch-id equivalence is defeated by any conflict resolution that altered the diff. **Conflicts are paid once.** With merge-up, the merge base between `release/1.4` and `release/1.5` advances past the fix, so the fix is settled history for every future merge. With fan-out, the two lines contain unrelated commits touching the same lines; if anyone later merges those lines, the three-way merge sees changes on both sides with no common ancestor for either, and conflicts on the already-fixed hunk. Teams that fan out and then merge occasionally spend the rest of the release line re-resolving the same conflict. **Drift is visible.** Merge-up forces you to confront divergence at the moment it costs something. If `release/1.4` cannot be merged into `release/1.5` without a mess, that is real information about how far the lines have separated — information fan-out hides until much later. ## When merge-up is not available Sometimes the lines have diverged so far that merging the whole branch is wrong: the release branch has version bumps, changelog edits or vendored artifacts that must not travel forward, or the code was restructured on `main` and only a hand-adapted version of the fix applies. Then cherry-pick, and do it with discipline: - Always `git cherry-pick -x` so the trailer records the source commit; it is the only durable link once the ids diverge. - Adapt rather than force: if the pick conflicts, resolve it as a real port, and record it consciously — the resulting diff no longer matches by patch id, so automated audits will not see it as already applied. - Audit at release time: `git cherry -v main release/1.4` marks with `+` every commit on the release line with no equivalent upstream. A `+`-list review before cutting the next release is the mechanical check that catches the fix nobody propagated. - `git log --oneline --cherry-pick --right-only main...release/1.4` gives the same audit as a log you can filter and diff against last time. ## Ordering and direction The rule is: **fix at the oldest affected point, move forward, never backwards.** Fixing on `main` first and backporting downwards means the most urgent line — the one customers are running — is patched last and by a second, riskier operation. It also inverts the merge direction, so any later merge-up conflicts with what was already backported. For a security fix there is an extra wrinkle: the fix often must land on all lines simultaneously at disclosure time. That is a coordination problem, not a Git one — prepare the fix and the merges privately, tag each line (`git tag -a v1.4.3 …`), and publish together. The Git-level requirement is that each line ends up with an annotated tag naming the exact commit that was audited and shipped, so `git tag --contains <sha>` afterwards answers, per release, whether it is affected. ## Tooling that reduces the pain `rerere.enabled = true` records how you resolved a conflicted hunk and replays that resolution when Git meets the same conflict again — invaluable when the same merge-up happens every week and hits the same divergent file. Keeping maintenance branches boring is the bigger lever: no refactors, no dependency upgrades, no formatting sweeps on a maintenance line, because each one increases the textual distance that every future backport must cross. ## What the interviewer wants to hear That you chose a topology on purpose; that you can name what Git can and cannot prove about propagation (`--contains` versus patch-id equivalence); that you audit rather than trust; and that you understand drift on maintenance lines is the thing that makes backporting expensive later.
- Why does fanning the same fix out to three branches make later merges between them conflict?The three commits are unrelated in the graph. When two of those lines are merged, the three-way merge finds changes on both sides of the merge base touching the same lines, with no ancestor containing either, so it reports a conflict even though the content matches. Merging forward instead advances the merge base past the fix so it is settled history.
- How do you verify afterwards which releases actually contain the fix?`git tag --contains <sha>` lists every tag whose commit can reach the fix, and `git branch --contains <sha>` does the same for branches — both are pure reachability, so they are only meaningful when the fix propagated by merge. If it was cherry-picked, fall back to `git cherry -v` patch-id comparison or the `-x` trailer.
- What makes backporting to an old maintenance line expensive over time, and what limits it?Textual drift. Every refactor, dependency bump or formatting sweep that lands on one line and not another increases the distance a patch must cross, until picks stop applying and must be hand-ported. Keeping maintenance lines boring — fixes only, no restructuring — and merging forward frequently keeps the distance small.
saying these in an interview costs you the question
- Applies the fix independently to every branch and calls it done
- Backports downwards from main to the release lines
- Trusts git branch --contains after a cherry-pick
- Believes identical content means Git will merge cleanly
- Refactors freely on a long-lived maintenance branch