skip to content

How do you get a hotfix committed on a release branch back into main, and what breaks if you don't?

level: middleimportance: must knowfreq 70%

answer

  1. a branch is a separate line of the DAG
  2. the next release is cut from somewhere else
  3. merge back or replay the patch
  4. -x leaves a provenance trailer
  5. git cherry marks what is still missing

basics

~20 s

Propagate it deliberately: merge the release branch into main, or replay the commit with git cherry-pick -x. Skip it and the next release cut from main ships without the fix, so the bug returns as a regression nobody expects.

solid answer

~50 s

A release branch is a fork of history, so a commit made on it exists only there. Two Git-level options. **Merge back**: `git switch main && git merge release/1.4` brings the fix plus the merge record, and afterwards Git can prove containment — `git branch --contains <sha>` lists main. **Cherry-pick**: `git switch main && git cherry-pick -x <sha>` replays the change as a new commit with a different id; `-x` appends a `(cherry picked from commit ...)` line so the origin is traceable. Merging is preferable when the release branch holds only fixes you want on main; cherry-picking is right when the branch also carries release-only noise such as version bumps. If nobody propagates, the fix dies on the release line and the next release cut from main reintroduces the bug — the classic regression interviewers listen for. Audit with `git cherry -v main release/1.4`.

code

bash · 9 lines
bash
git switch release/1.4
git commit -m "fix: guard null session id"        # hotfix lands here only
git tag -a v1.4.1 -m "Release 1.4.1"

git switch main
git cherry-pick -x 9fceb02      # replay onto main, record provenance
# or: git merge release/1.4     # bring the whole line back

git cherry -v main release/1.4  # '+' = still missing from main

go deeper

for a junior

Recall the core fact: a commit lives on the branch where it was made. Getting it onto main is a separate, deliberate step — a merge or a cherry-pick — and forgetting it means the bug comes back.

for a middle

Explain both mechanisms and their consequences: merging preserves reachability and advances the merge base; cherry-pick creates a new commit id and needs -x for traceability. Name the regression that results from skipping propagation.

for a senior

Demonstrate the operating discipline — fix on the oldest affected line, propagate forward, and audit with git cherry -v before closing a release rather than trusting anyone's memory.

for a principal

Own the systemic answer: propagation is part of the definition of done, drift between lines is what makes backports expensive, and the audit must be a mechanical check on the graph rather than a checklist item people tick.

## Why the fix does not travel by itself When you cut `release/1.4` from `main`, you create a second line of development in the commit DAG. A commit made on the release branch is a child of a release-branch commit; nothing on `main` points at it, so it is unreachable from `main`. Git has no notion of "this fix logically belongs everywhere" — reachability is the only thing it tracks. The fix reaches `main` when, and only when, some human action makes it reachable there. The failure mode is delayed and embarrassing. The hotfix goes out in 1.4.1, the incident closes, and six weeks later 1.5.0 is cut from `main` — which never received the fix — and ships the same bug to the same customers. Because the code looks "already fixed" in everyone's memory, this regression is usually diagnosed twice. ## Option 1: merge the release branch back `git switch main && git merge release/1.4` creates a merge commit whose second parent is the release tip. Properties worth stating in an interview: - **Containment becomes provable.** After the merge, `git branch --contains <sha>` lists `main`, and `git tag --contains <sha>` lists every release tag that includes it. This is the strongest thing Git can tell you about propagation, because it is graph reachability rather than text similarity. - **Future merges get cheaper.** The merge base between the two lines advances, so the next merge only has to consider work done since. - **Everything on the branch comes along**, including release-only commits such as version-string bumps, changelog edits or a temporary configuration pin. If those do not belong on `main`, the merge drags them in and someone has to revert them — and a revert on `main` will then be inherited by the *next* release branch, which is a whole second class of confusion. ## Option 2: cherry-pick the individual commit `git switch main && git cherry-pick -x <sha>` applies that commit's change on top of `main` as a **new commit with a new id**. The content is equivalent; the identity is not. `-x` appends a `(cherry picked from commit <sha>)` trailer to the message, which is the only durable trace linking the two — use it always for cross-branch propagation, because six months later that line is how someone answers "where did this come from". Cherry-picking is the right tool when the release branch carries material that must not reach `main`, or when only one of several commits on the branch is wanted. The cost is that reachability no longer answers the containment question: `git branch --contains <original-sha>` will not list `main`, even though the fix is there. ## Auditing what has and hasn't crossed Because cherry-picks break reachability, Git offers patch-id-based comparison. `git cherry -v main release/1.4` lists commits on `release/1.4` and marks each `+` (no equivalent change exists on `main`) or `-` (an equivalent change is already there). Equivalence is computed from a *patch id* — a hash of the diff with whitespace and line numbers normalised — so an unmodified cherry-pick is detected, while a cherry-pick that needed conflict resolution may not be. `git log --oneline --cherry-pick --right-only main...release/1.4` gives the same information as a filtered log. Making a `+`-list review part of closing a release is how teams stop losing fixes. ## Which direction, and in what order The habit worth internalising: **fix on the oldest line that has the bug, then propagate forward**. Fixing on `main` first and then backporting is tempting because `main` is where people work, but it means the urgent line is patched last and the merge direction is now backwards. When there is only one release line, either order works as long as somebody actually does the second step; the discipline is what matters. A related conflict trap: if the same fix is applied independently on both lines — hand-written twice, or cherry-picked and then also merged — the subsequent merge will often conflict, because Git sees two different commits touching the same lines with no common ancestor for that change. `rerere.enabled = true` records your resolutions so a repeatedly re-encountered conflict resolves the same way each time. ## Making it hard to forget The organisational answer is that propagation is part of the fix, not a follow-up task: the hotfix is not "done" until it is on `main`, and the release checklist ends with a `git cherry` audit rather than a memory. Server-side hooks can reject a push to a release branch that has no counterpart, but the durable fix is the checklist plus the audit command — mechanisms that report the truth about the graph rather than about intentions.

  • After a cherry-pick, why doesn't `git branch --contains <original-sha>` list main?
    Cherry-pick creates a new commit with a different id and a different parent, so the original commit is still unreachable from main. `--contains` is pure graph reachability. To find the equivalent change you need patch-id comparison — `git cherry -v main release/1.4` or `git log --cherry-pick` — or the `(cherry picked from commit ...)` trailer that `-x` records.
  • When would you merge the release branch into main rather than cherry-pick the single fix?
    When everything on the release branch belongs on main — typically a branch that only ever receives fixes. Merging is cheaper to repeat, advances the merge base so later merges are smaller, and makes containment provable by reachability. Cherry-pick when the branch also carries release-only commits such as version bumps you do not want on main.
  • You cherry-picked a fix to main, and later the release branch is merged into main anyway. What happens?
    Git performs a three-way merge and often flags a conflict on the doubly-applied hunk, since the two commits are unrelated in the graph even though the content matches; where the text is byte-identical it may resolve cleanly. Enabling `rerere.enabled` records your resolution so the same conflict resolves identically on later merges.

saying these in an interview costs you the question

  • Assumes a fix on a release branch reaches main automatically
  • Cherry-picks without -x, losing all provenance
  • Thinks the cherry-picked commit keeps its original hash
  • Treats git branch --contains as proof after a cherry-pick
  • Fixes on main first and hopes someone backports later

context