6.7 KiB
Branching and Release Management
This document describes how we branch, release, and merge releases back into develop.
It does not define version naming
(e.g., what constitutes a major-minor, patch, or beta release).
Examples use X.Y for a release line:
X and Y are placeholders, while the trailing x is literal,
e.g., release/X.Y.x is release/3.4.x for the 3.4 line.
Branches
| Branch | Purpose |
|---|---|
develop |
Main development branch. Betas and the first RC of a major-minor release are tagged here. |
staging/X.Y.x |
Where fixes for the X.Y line are developed and RCs are prepared. |
release/X.Y.x |
The last public RC or release of the X.Y line. |
A release line is named X.Y.x
because one branch serves every patch release of that line (X.Y.0, X.Y.1, X.Y.2, ...).
The / groups branches hierarchically,
so tools can filter them and protection rules can target release/* and staging/*.
Principles
- Releases are merged back into
develop, never cherry-picked or rebased onto it. Cherry-picked and rebased commits get new hashes, so Git can't tell thatdevelopalready has them. Future merges then replay them and produce artificial conflicts, and it's hard to verify that every fix actually reacheddevelop. Merging keeps a single history: Git knows exactly which release commitsdevelopcontains, and no fix is left behind. - Branches only move forward.
develop,staging/X.Y.x, andrelease/X.Y.xare never rewritten. - Cherry-picking only goes from
developto a staging branch, for fixes that must get into a release after the code freeze (see Emergency Fixes Fromdevelop). - Security fixes are prepared privately and published with the release that contains them, so vulnerabilities are not disclosed prematurely.
Release Lifecycle
This diagram shows a major-minor release and its first patch release. The staging and release branches are drawn as one line.
develop staging/X.Y.x & release/X.Y.x
│
├── Tag: X.Y.0-b1
├── Tag: X.Y.0-bN
├── Tag: X.Y.0-rc1 ───────────────┐ (branches created)
│ │
│ (development continues) ├── Fixes
│ ├── Tag: X.Y.0-rcN
│ ├── Tag: X.Y.0 (final)
◀──── (merge) ────────────────────┤
│ ├── Fixes
│ ├── Tag: X.Y.1-rcN
│ ├── Tag: X.Y.1 (final)
◀──── (merge) ────────────────────┤
│
▼
Betas, First RC & Branching
Note
This phase applies only to a new major-minor release (e.g.,
X.Y.0). Patch releases work on the existing branches of the line.
- Betas: All beta versions (e.g.,
X.Y.0-b1) are built and tagged directly ondevelop. - First RC: We release the first RC (
X.Y.0-rc1) once everything that should be included in the release has been merged. - Branches:
staging/X.Y.xandrelease/X.Y.xare created fromdevelopat the commit taggedX.Y.0-rc1. The first RC also kicks off the QE process. - Code freeze: No new features or unrelated changes are pulled from
developintostaging/X.Y.xorrelease/X.Y.x. Only critical stabilization fixes go into the line. - No large changes on
develop: UntilX.Y.0is merged back, large changes (e.g., big refactors, moving or renaming many files, mass reformatting) are not merged intodevelop, so that fixes on the line and the merge back don't run into conflicts.
Release Candidates
- Fixes are developed against
staging/X.Y.x. - When ready, a new RC is created on
staging/X.Y.x, andrelease/X.Y.xis fast-forwarded to it.
RCs that contain unpublished security fixes are not published, and don't touch the public branches.
RCs are not merged back into develop:
X.Y.0-rc1 is tagged on develop itself,
and all later changes reach develop with the final release.
Final Release
Security fixes become public as soon as they reach the public repo, so these steps happen only once the release is ready to be published, one right after the other.
- Unpublished security fixes, if any, are merged into
staging/X.Y.x. release/X.Y.xis fast-forwarded tostaging/X.Y.x, and the release is tagged on it.- The release is immediately merged back into
develop. For a major-minor release, this lifts the freeze on large changes indevelop.
Merging Back Into develop
The merge back is a regular PR into develop
whose branch contains a real merge commit of the release tag:
- Create a branch from
develop, rungit merge --no-ff <tag>, and resolve any conflicts. - Once the PR is approved,
developis fast-forwarded to the PR branch, so the merge commit lands as it is. Never squash or rebase it. Never add it to the merge queue either: the queue squashes PRs, which would drop the merge commit. - If
developmoves while the PR is open, redo the merge on top of the newdevelop.
After the merge, git log develop..<tag> must be empty.
Special Cases
Emergency Fixes From develop
If a commit was merged to develop
and needs to be included in a release after the code freeze:
- Create a PR that cherry-picks the commit onto
staging/X.Y.x. - Leave
developas it is, with no reverts. - Follow the release candidates process as usual.
When the release is later merged back into develop,
both sides already contain the same change,
so the merge usually resolves it cleanly or with a trivial conflict.
From Git's perspective, the cherry-picked commit then becomes part of develop too.
Several Supported Lines
When a fix must ship in more than one supported line (e.g., X.Y and X.(Y+1)),
the lines are merged upwards rather than cherry-picked between:
- The fix goes into the oldest line first.
- After that line's release,
its
release/X.Y.xis merged into the staging branch of the next newer line, and so on up to the newest line. - The newest line is merged back into
developas usual.
This way every newer line, and eventually develop, contains the history of the older lines.