diff --git a/BUILD.md b/BUILD.md index 9c4d5558a9..5b4c35b548 100644 --- a/BUILD.md +++ b/BUILD.md @@ -41,15 +41,13 @@ branch. git checkout develop ``` -For a release candidate, choose the relevant release branch, e.g. -`release/3.2.x`. +For a release or release candidate, check out its [tag](https://github.com/XRPLF/rippled/releases), e.g.: ```bash -git checkout release/3.2.x +git checkout 3.4.0 ``` -For a stable release, choose one of the [tagged -releases](https://github.com/XRPLF/rippled/releases). +See [RELEASING.md](./RELEASING.md) for how branches and releases are organized. A build reports `0.0.0-dev` with its short commit hash as build metadata, e.g. `0.0.0-dev+0123abc`. Only builds of a release, from a tag in CI or a versioned diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6fe25d6638..58d5a7d372 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,9 +14,13 @@ The following branches exist in the main project repository: - `develop`: The latest set of unreleased features, and the most common starting point for contributions. -- `release/*` (e.g. `release/3.2.x`): Release branches, one per release line, - holding the latest release candidate, or stable release for that line. - Stable releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases). +- `staging/*` (e.g. `staging/3.4.x`): Staging branches, one per release line, + where fixes for that line are developed. +- `release/*` (e.g. `release/3.4.x`): Release branches, one per release line, + holding the latest public release candidate or release for that line. + Releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases). + +See [RELEASING.md](./RELEASING.md) for how these branches are used. The tip of each branch must be signed. In order for GitHub to sign a squashed commit that it builds from your pull request, GitHub must know @@ -145,8 +149,8 @@ tl;dr In general, pull requests use `develop` as the base branch. -The exceptions are fixes, improvements, and hotfixes for an existing release, -which use that release's branch (e.g. `release/3.2.x`) as the base. +The exceptions are fixes for an existing release line, +which use that line's staging branch (e.g. `staging/3.4.x`) as the base. If your changes are not quite ready, but you want to make it easily available for preliminary examination or review, you can create a "Draft" pull request. @@ -591,15 +595,16 @@ the suggested commit message, or modify it as needed. #### Slightly more complicated pull requests -Some pull requests need to be pushed to `develop` as more than one -commit. A PR author may _request_ to merge as separate commits. They +Some pull requests need to be pushed to their base branch (usually `develop`) +as more than one commit. +A PR author may _request_ to merge as separate commits. They must _justify_ why separate commits are needed, and _specify_ how they would like the commits to be merged. If you disagree with the author, discuss it with them directly. If the process is reasonable, follow it. The simplest option is to do a -fast forward only merge (`--ff-only`) on the command line and push to -`develop`. +fast forward only merge (`--ff-only`) on the command line +and push to the base branch. Some examples of when separate commits are worthwhile are: @@ -612,9 +617,10 @@ Some examples of when separate commits are worthwhile are: Either way, check that: -- The commits are based on the current tip of `develop`. -- The commits are clean: No merge commits (except when reverse - merging), no "[FOLD]" or "fixup!" messages. +- The commits are based on the current tip of the base branch. +- The commits are clean: + No merge commits (except when merging a release, see [RELEASING.md](./RELEASING.md)), + no "[FOLD]" or "fixup!" messages. - All commits are signed. If the commits are not signed by the author, use `git commit --amend -S` to sign them yourself. - At least one (but preferably all) of the commits has the PR number @@ -626,578 +632,8 @@ use them!** ### Releases -All releases, including release candidates and betas, are handled -differently from typical PRs. Most importantly, never use -the Github UI to merge a release. - -Xrpld uses a linear workflow model that can be summarized as: - -1. In between releases, developers work against the `develop` branch. -2. Periodically, a maintainer will build and tag a beta version from - `develop`, which is pushed to `release`. - - Betas are usually released every two to three weeks, though that - schedule can vary depending on progress, availability, and other - factors. -3. When the changes in `develop` are considered stable and mature enough - to be ready to release, a release candidate (RC) is built and tagged - from `develop`, and merged to `release`. - - Further development for that release (primarily fixes) then - continues against `release`, while other development continues on - `develop`. Effectively, `release` is forked from `develop`. Changes - to `release` must be reverse merged to `develop`. -4. When the candidate has passed testing and is ready for release, the - final release is merged to `master`. -5. If any issues are found post-release, a hotfix / point release may be - created, which is merged to `master`, and then reverse merged to - `develop`. - -#### Betas, and the first release candidate - -##### Preparing the `develop` branch - -1. Optimally, the `develop` branch will be ready to go, with all - relevant PRs already merged. -2. If there are any PRs pending, merge them **BEFORE** preparing the beta. - 1. If only one or two PRs need to be merged, merge those PRs [as - normal](#when-and-how-to-merge-pull-requests), updating the second - one, and waiting for CI to finish in between. - 2. If there are several pending PRs, do not use the Github UI, - because the delays waiting for CI in between each merge will be - unnecessarily onerous. (Incidentally, this process can also be - used to merge if the Github UI has issues.) Merge each PR branch - directly to a `release-next` on your local machine and create a single - PR, then push your branch to `develop`. - 1. Squash the changes from each PR, one commit each (unless more - are needed), being sure to sign each commit and update the - commit message to include the PR number. You may be able to use - a fast-forward merge for the first PR. - 2. Push your branch. - 3. Continue to [Making the release](#making-the-release) to update - the version number, etc. - - The workflow may look something like: - -``` -git fetch --multiple upstreams user1 user2 user3 [...] -git checkout -B release-next --no-track upstream/develop - -# Only do an ff-only merge if pr-branch1 is either already -# squashed, or needs to be merged with separate commits, -# and has no merge commits. -# Use -S on the ff-only merge if pr-branch1 isn't signed. -git merge [-S] --ff-only user1/pr-branch1 - -git merge --squash user2/pr-branch2 -git commit -S # Use the commit message provided on the PR - -git merge --squash user3/pr-branch3 -git commit -S # Use the commit message provided on the PR - -[...] - -# Make sure the commits look right -git log --show-signature "upstream/develop..HEAD" - -git push --set-upstream origin - -# Continue to "Making the release" to update the version number, so -# everything can be done in one PR. -``` - -You can also use the [squash-branches] script. - -You may also need to manually close the open PRs after the changes are -merged to `develop`. Be sure to include the commit ID. - -##### Making the release - -This includes, betas, and the first release candidate (RC). - -1. If you didn't create one [preparing the `develop` - branch](#preparing-the-develop-branch), Ensure there is no old - `release-next` branch hanging around. Then make a `release-next` - branch that only changes the version number. e.g. - -``` -git fetch upstreams - -git checkout --no-track -B release-next upstream/develop - -v="A.B.C-bD" -build=$( find -name BuildInfo.cpp ) -sed 's/\(^.*versionString =\).*$/\1 "'${v}'"/' ${build} > version.cpp && mv -vi version.cpp ${build} - -git diff - -git add ${build} - -git commit -S -m "Set version to ${v}" - -# You could use your "origin" repo, but some CI tests work better on upstream. -git push upstream-push -git fetch upstreams -git branch --set-upstream-to=upstream/release-next -``` - -You can also use the [update-version] script. 2. Create a Pull Request for `release-next` with **`develop`** as -the base branch. - -1. Use the title "[TRIVIAL] Set version to X.X.X-bX". -2. Instead of the default description template, use the following: - -``` -## High Level Overview of Change - -This PR only changes the version number. It will be merged as -soon as Github CI actions successfully complete. -``` - -3. Wait for CI to successfully complete, and get someone to approve - the PR. (It is safe to ignore known CI issues.) -4. Push the updated `develop` branch using your `release-next` - branch. **Do not use the Github UI. It's important to preserve - commit IDs.** - -``` -git push upstream-push release-next:develop -``` - -5. In the unlikely event that the push fails because someone has merged - something else in the meantime, rebase your branch onto the updated - `develop` branch, push again, and go back to step 3. -6. Ensure that your PR against `develop` is closed. Github should do it - automatically. -7. Once this is done, forward progress on `develop` can continue - (other PRs may be merged). -8. Now create a Pull Request for `release-next` with **`release`** as - the base branch. Instead of the default template, reuse and update - the message from the previous release. Include the following verbiage - somewhere in the description: - -``` -The base branch is `release`. [All releases (including -betas)](https://github.com/XRPLF/rippled/blob/develop/CONTRIBUTING.md#before-you-start) -go in `release`. This PR branch will be pushed directly to `release` (not -squashed or rebased, and not using the GitHub UI). -``` - -7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur - offline, but at least one approval will be needed on the PR. - - If issues are discovered during testing, simply abandon the - release. It's easy to start a new release, it should be easy to - abandon one. **DO NOT REUSE THE VERSION NUMBER.** e.g. If you - abandon 2.4.0-b1, the next attempt will be 2.4.0-b2. -8. Once everything is ready to go, push to `release`. - -``` -git fetch upstreams - -# Just to be safe, do a dry run first: -git push --dry-run upstream-push release-next:release - -# If everything looks right, push the branch -git push upstream-push release-next:release - -# Check that all of the branches are updated -git fetch upstreams -git log -1 --oneline -# The output should look like: -# 0123456789 (HEAD -> upstream/release-next, upstream/release, -# upstream/develop) Set version to 2.4.0-b1 -# Note that upstream/develop may not be on this commit, but -# upstream/release must be. -# Other branches, including some from upstream-push, may also be -# present. -``` - -9. Tag the release, too. - -``` -git tag -git push upstream-push -``` - -10. Delete the `release-next` branch on the repo. Use the Github UI or: - -``` -git push --delete upstream-push release-next -``` - -11. Finally [create a new release on - Github](https://github.com/XRPLF/rippled/releases). - -#### Release candidates after the first - -Once the first release candidate is [merged into -release](#making-the-release), then `release` and `develop` _are allowed -to diverge_. - -If a bug or issue is discovered in a version that has a release -candidate being tested, any fix and new version will need to be applied -against `release`, then reverse-merged to `develop`. This helps keep git -history as linear as possible. - -A `release-next` branch will be created from `release`, and any further -work for that release must be based on `release-next`. Specifically, -PRs must use `release-next` as the base, and those PRs will be merged -directly to `release-next` when approved. Changes should be restricted -to bug fixes, but other changes may be necessary from time to time. - -1. Open any PRs for the pending release using `release-next` as the base, - so they can be merged directly in to it. Unlike `develop`, though, - `release-next` can be thrown away and recreated if necessary. -2. Once a new release candidate is ready, create a version commit as in - step 1 [above](#making-the-release) on `release-next`. You can use - the [update-version] script for this, too. -3. Jump to step 8 ("Now create a Pull Request for `release-next` with - **`release`** as the base") from the process - [above](#making-the-release) to merge `release-next` into `release`. - -##### Follow up: reverse merge - -Once the RC is merged and tagged, it needs to be reverse merged into -`develop` as soon as possible. - -1. Create a branch, based on `upstream/develop`. - The branch name is not important, but could include "mergeNNNrcN". - E.g. For release A.B.C-rcD, use `mergeABCrcD`. - -``` -git fetch upstreams - -git checkout --no-track -b mergeABCrcD upstream/develop -``` - -2. Merge `release` into your branch. - -``` -# I like the "--edit --log --verbose" parameters, but they are -# not required. -git merge upstream/release -``` - -3. `BuildInfo.cpp` will have a conflict with the version number. - Resolve it with the version from `develop` - the higher version. -4. Push your branch to your repo (or `upstream` if you have permission), - and open a normal PR against `develop`. The "High level overview" can - simply indicate that this is a merge of the RC. The "Context" should - summarize the changes from the RC. Include the following text - prominently: - -``` -This PR must be merged manually using a push. Do not use the Github UI. -``` - -5. Depending on the complexity of the changes, and/or merge conflicts, - the PR may need a thorough review, or just a sign-off that the - merge was done correctly. -6. If `develop` is updated before this PR is merged, do not merge - `develop` back into your branch. Instead rebase preserving merges, - or do the merge again. (See also the `rerere` git config setting.) - -``` -git rebase --rebase-merges upstream/develop -# OR -git reset --hard upstream/develop -git merge upstream/release -``` - -7. When the PR is ready, push it to `develop`. - -``` -git fetch upstreams - -# Make sure the commits look right -git log --show-signature "upstream/develop^..HEAD" - -git push upstream-push mergeABCrcD:develop - -git fetch upstreams -``` - -Development on `develop` can proceed as normal. - -#### Final releases - -A final release is any release that is not a beta or RC, such as 2.2.0. - -Only code that has already been tested and vetted across all three -platforms should be included in a final release. Most of the time, that -means that the commit immediately preceding the commit setting the -version number will be an RC. Occasionally, there may be last-minute bug -fixes included as well. If so, those bug fixes must have been tested -internally as if they were RCs (at minimum, ensuring unit tests pass, -and the app starts, syncs, and stops cleanly across all three -platforms.) - -_If in doubt, make an RC first._ - -The process for building a final release is very similar to [the process -for building a beta](#making-the-release), except the code will be -moving from `release` to `master` instead of from `develop` to -`release`, and both branches will be pushed at the same time. - -1. Ensure there is no old `master-next` branch hanging around. - Then make a `master-next` branch that only changes the version - number. As above, or using the - [update-version] script. -2. Create a Pull Request for `master-next` with **`master`** as - the base branch. Instead of the default template, reuse and update - the message from the previous final release. Include the following verbiage - somewhere in the description: - -``` -The base branch is `master`. This PR branch will be pushed directly to -`release` and `master` (not squashed or rebased, and not using the -GitHub UI). -``` - -7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur - offline, but at least one approval will be needed on the PR. - - If issues are discovered during testing, close the PR, delete - `master-next`, and move development back to `release`, [issuing - more RCs as necessary](#release-candidates-after-the-first) -8. Once everything is ready to go, push to `release` and `master`. - -``` -git fetch upstreams - -# Just to be safe, do dry runs first: -git push --dry-run upstream-push master-next:release -git push --dry-run upstream-push master-next:master - -# If everything looks right, push the branch -git push upstream-push master-next:release -git push upstream-push master-next:master - -# Check that all of the branches are updated -git fetch upstreams -git log -1 --oneline -# The output should look like: -# 0123456789 (HEAD -> upstream/master-next, upstream/master, -# upstream/release) Set version to A.B.0 -# Note that both upstream/release and upstream/master must be on this -# commit. -# Other branches, including some from upstream-push, may also be -# present. -``` - -9. Tag the release, too. - -``` -git tag -git push upstream-push -``` - -10. Delete the `master-next` branch on the repo. Use the Github UI or: - -``` -git push --delete upstream-push master-next -``` - -11. [Create a new release on - Github](https://github.com/XRPLF/rippled/releases). Be sure that - "Set as the latest release" is checked. -12. Open a PR to update the [API-CHANGELOG](API-CHANGELOG.md) and `API-VERSION-[n].md` with the changes for this release (if any are missing). -13. Finally, [reverse merge the release into `develop`](#follow-up-reverse-merge). - -#### Special cases: point releases, hotfixes, etc. - -On occasion, a bug or issue is discovered in a version that already -had a final release. Most of the time, development will have started -on the next version, and will usually have changes in `develop` -and often in `release`. - -Because git history is kept as linear as possible, any fix and new -version will need to be applied against `master`. - -The process for building a hotfix release is very similar to [the -process for building release candidates after the -first](#release-candidates-after-the-first) and [for building a final -release](#final-releases), except the changes will be done against -`master` instead of `release`. - -If there is only a single issue for the hotfix, the work can be done in -any branch. When it's ready to merge, jump to step 3 using your branch -instead of `master-next`. - -1. Create a `master-next` branch from `master`. - -``` -git checkout --no-track -b master-next upstream/master -git push upstream-push -git fetch upstreams -``` - -2. Open any PRs for the pending hotfix using `master-next` as the base, - so they can be merged directly in to it. Unlike `develop`, though, - `master-next` can be thrown away and recreated if necessary. -3. Once the hotfix is ready, create a version commit using the same - steps as above, or use the - [update-version] script. -4. Create a Pull Request for `master-next` with **`master`** as - the base branch. Instead of the default template, reuse and update - the message from the previous final release. Include the following verbiage - somewhere in the description: - -``` -The base branch is `master`. This PR branch will be pushed directly to -`master` (not squashed or rebased, and not using the GitHub UI). -``` - -7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur - offline, but at least one approval will be needed on the PR. - - If issues are discovered during testing, update `master-next` as - needed, but ensure that the changes are properly squashed, and the - version setting commit remains last -8. Once everything is ready to go, push to `master` **only**. - -``` -git fetch upstreams - -# Just to be safe, do a dry run first: -git push --dry-run upstream-push master-next:master - -# If everything looks right, push the branch -git push upstream-push master-next:master - -# Check that all of the branches are updated -git fetch upstreams -git log -1 --oneline -# The output should look like: -# 0123456789 (HEAD -> upstream/master-next, upstream/master) Set version -# to 2.4.1 -# Note that upstream/master must be on this commit. upstream/release and -# upstream/develop should not. -# Other branches, including some from upstream-push, may also be -# present. -``` - -9. Tag the release, too. - -``` -git tag -git push upstream-push -``` - -9. Delete the `master-next` branch on the repo. - -``` -git push --delete upstream-push master-next -``` - -10. [Create a new release on - Github](https://github.com/XRPLF/rippled/releases). Be sure that - "Set as the latest release" is checked. - -Once the hotfix is released, it needs to be reverse merged into -`develop` as soon as possible. It may also need to be merged into -`release` if a release candidate is under development. - -1. Create a branch in your own repo, based on `upstream/develop`. - The branch name is not important, but could include "mergeNNN". - E.g. For release 2.2.3, use `merge223`. - -``` -git fetch upstreams - -git checkout --no-track -b merge223 upstream/develop -``` - -2. Merge master into your branch. - -``` -# I like the "--edit --log --verbose" parameters, but they are -# not required. -git merge upstream/master -``` - -3. `BuildInfo.cpp` will have a conflict with the version number. - Resolve it with the version from `develop` - the higher version. -4. Push your branch to your repo, and open a normal PR against - `develop`. The "High level overview" can simply indicate that this - is a merge of the hotfix version. The "Context" should summarize - the changes from the hotfix. Include the following text - prominently: - -``` -This PR must be merged manually using a --ff-only merge. Do not use the Github UI. -``` - -5. Depending on the complexity of the hotfix, and/or merge conflicts, - the PR may need a thorough review, or just a sign-off that the - merge was done correctly. -6. If `develop` is updated before this PR is merged, do not merge - `develop` back into your branch. Instead rebase preserving merges, - or do the merge again. (See also the `rerere` git config setting.) - -``` -git rebase --rebase-merges upstream/develop -# OR -git reset --hard upstream/develop -git merge upstream/master -``` - -7. When the PR is ready, push it to `develop`. - -``` -git fetch upstreams - -# Make sure the commits look right -git log --show-signature "upstream/develop..HEAD" - -git push upstream-push HEAD:develop -``` - -Development on `develop` can proceed as normal. It is recommended to -create a beta (or RC) immediately to ensure that everything worked as -expected. - -##### An even rarer scenario: A hotfix on an old release - -Historically, once a final release is tagged and packages are released, -versions older than the latest final release are no longer supported. -However, there is a possibility that a very high severity bug may occur -in a non-amendment blocked version that is still being run by -a significant fraction of users, which would necessitate a hotfix / point -release to that version as well as any later versions. - -This scenario would follow the same basic procedure as above, -except that _none_ of `develop`, `release`, or `master` -would be touched during the release process. - -In this example, consider if version 2.1.1 needed to be patched. - -1. Create two branches in the main (`upstream`) repo. - -``` -git fetch upstreams - -# Create a base branch off the tag -git checkout --no-track -b master-2.1.2 2.1.1 -git push upstream-push - -# Create a working branch -git checkout --no-track -b master212-next master-2.1.2 -git push upstream-push - -git fetch upstreams -``` - -2. Work continues as above, except using `master-2.1.2`as - the base branch for any merging, packaging, etc. -3. After the release is tagged and packages are built, you could - potentially delete both branches, e.g. `master-2.1.2` and - `master212-next`. However, it may be useful to keep `master-2.1.2` - around indefinitely for reference. -4. Assuming that a hotfix is also released for the latest - version in parallel with this one, or if the issue is - already fixed in the latest version, do no do any - reverse merges. However, if it is not, it probably makes - sense to reverse merge `master-2.1.2` into `master`, - release a hotfix for _that_ version, then reverse merge - from `master` to `develop`. (Please don't do this unless absolutely - necessary.) +Releases, release branches, and merging releases back into `develop` +are described in [RELEASING.md](./RELEASING.md). [contrib]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects [squash]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits @@ -1205,5 +641,3 @@ git fetch upstreams [xrpld]: https://github.com/XRPLF/rippled [signing]: https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification [setup-upstreams]: ./bin/git/setup-upstreams.sh -[squash-branches]: ./bin/git/squash-branches.sh -[update-version]: ./bin/git/update-version.sh diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000000..08436c3de7 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,152 @@ +# 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 that `develop` already has them. + Future merges then replay them and produce artificial conflicts, + and it's hard to verify that every fix actually reached `develop`. + Merging keeps a single history: + Git knows exactly which release commits `develop` contains, + and no fix is left behind. +- **Branches only move forward.** + `develop`, `staging/X.Y.x`, and `release/X.Y.x` are never rewritten. +- **Cherry-picking only goes from `develop` to a staging branch**, + for fixes that must get into a release after the code freeze + (see [Emergency Fixes From `develop`](#emergency-fixes-from-develop)). +- **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. + +```text +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. + +1. **Betas:** All beta versions (e.g., `X.Y.0-b1`) are built and tagged directly on `develop`. +2. **First RC:** We release the first RC (`X.Y.0-rc1`) + once everything that should be included in the release has been merged. +3. **Branches:** `staging/X.Y.x` and `release/X.Y.x` are created from `develop` + at the commit tagged `X.Y.0-rc1`. + The first RC also kicks off the QE process. +4. **Code freeze:** No new features or unrelated changes are pulled from `develop` + into `staging/X.Y.x` or `release/X.Y.x`. + Only critical stabilization fixes go into the line. +5. **No large changes on `develop`:** Until `X.Y.0` is [merged back](#merging-back-into-develop), + large changes (e.g., big refactors, moving or renaming many files, mass reformatting) + are not merged into `develop`, + so that fixes on the line and the merge back don't run into conflicts. + +### Release Candidates + +1. Fixes are developed against `staging/X.Y.x`. +2. When ready, a new RC is created on `staging/X.Y.x`, + and `release/X.Y.x` is 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). + +### 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. + +1. Unpublished security fixes, if any, are merged into `staging/X.Y.x`. +2. `release/X.Y.x` is fast-forwarded to `staging/X.Y.x`, + and the release is tagged on it. +3. The release is immediately [merged back into `develop`](#merging-back-into-develop). + For a major-minor release, this lifts the freeze on large changes in `develop`. + +### Merging Back Into `develop` + +The merge back is a regular PR into `develop` +whose branch contains a real merge commit of the release tag: + +1. Create a branch from `develop`, run `git merge --no-ff `, and resolve any conflicts. +2. Once the PR is approved, `develop` is 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. +3. If `develop` moves while the PR is open, redo the merge on top of the new `develop`. + +After the merge, `git log develop..` 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: + +1. Create a PR that cherry-picks the commit onto `staging/X.Y.x`. +2. Leave `develop` as it is, with no reverts. +3. Follow the [release candidates](#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: + +1. The fix goes into the oldest line first. +2. After that line's release, + its `release/X.Y.x` is merged into the staging branch of the next newer line, + and so on up to the newest line. +3. The newest line is merged back into `develop` as usual. + +This way every newer line, and eventually `develop`, contains the history of the older lines.