From bb498c6748c621298f68baefd0ad398f4077450b Mon Sep 17 00:00:00 2001 From: Sabine Maennel <5292683+sabinem@users.noreply.github.com> Date: Tue, 27 Oct 2026 14:57:53 +0100 Subject: [PATCH 1/3] docs(releasing): release branches and how to make a hotfix main is where development happens; each released minor line gets a release/X.Y branch that only takes hotfixes and is released from its own tags. Describes cutting the branch, the hotfix steps, bringing the fix back to main, and how chart versions stay apart. --- CLAUDE.md | 12 +++--- RELEASING.md | 118 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index acd1da91..bdce84dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -165,11 +165,13 @@ These hold across the whole codebase; the skills explain the mechanisms. image tag, so `just version::check` (a CI stage) fails where these disagree. The frontend reads `VERSION` at build time (`vite.config.ts` → `$lib/version` → the footer) and displays it. -- **`main` is the only long-lived branch, and merging to it deploys to dev.** - One focused pull request per change, each carrying its manually added - `CHANGELOG.md` entry. Both clusters install the same chart from the GitOps - setup in `sdsc-ordes/cloud-infra` and differ only in image overrides: **dev** - runs `temporary/*:latest` with `pullPolicy: Always`, so every push to `main` +- **`main` is where development happens, and merging to it deploys to dev.** One + focused pull request per change, each carrying its manually added + `CHANGELOG.md` entry. Each released minor line has a `release/X.Y` branch that + takes hotfixes only, released from its own tags and cherry-picked back to + `main`. Both clusters install the same chart from the GitOps setup in + `sdsc-ordes/cloud-infra` and differ only in image overrides: **dev** runs + `temporary/*:latest` with `pullPolicy: Always`, so every push to `main` reaches it on the next container restart; **prod** takes the chart defaults, `release/*` at `appVersion`, so nothing merged can reach it until someone bumps `appVersion` and installs the new chart version. See `RELEASING.md`. diff --git a/RELEASING.md b/RELEASING.md index 02771f55..b8abc6ee 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -10,17 +10,37 @@ them. ## Branches -`main` is the only long-lived branch. Work happens on a short-lived branch — -`feat/…`, `fix/…`, `docs/…`, `chore/…` — and merges into `main` through a pull -request, with its changelog entry in the same pull request. - -There is no integration branch. One focused pull request per change, reviewed -and merged on its own, is what keeps the history readable and the changelog -honest. +``` +main ──●──●──●──●──●──●──●──●──●──► development; dev follows it + │ + v0.12.1 + its chart bump + │ + └── release/0.12 ──●── v0.12.2 ──●── v0.12.3 hotfixes only; prod runs these tags + ▲ + hotfix/… PRs +``` -A pull request runs the full CI suite: codegen drift, lint, version consistency, -chart lint, build, tests, and an image build. That is the only gate in front of -`main` which matters for the reason below. +`main` is where development happens. Work happens on a short-lived branch — +`feat/…`, `fix/…`, `docs/…`, `chore/…` — and merges into `main` through a pull +request, with its changelog entry in the same pull request. One focused pull +request per change, reviewed and merged on its own, is what keeps the history +readable and the changelog honest. + +`release/X.Y` is one branch per released minor line, created when that line is +released ([Cutting an app release](#cutting-an-app-release)). It never receives +features, only the fixes that prod cannot wait for — see +[Making a hotfix](#making-a-hotfix). Its tags (`v0.12.2`, `v0.12.3`, …) are what +prod runs. Once the next minor line is released, the old branch simply stops +being used. + +So `main` can keep moving during a hackathon while prod stays on the release it +was installed with, and a fix for prod does not have to wait for whatever else +is on `main`. + +A pull request runs the full CI suite, whichever branch it targets: codegen +drift, lint, version consistency, chart lint, build, tests, and an image build. +That is the only gate in front of `main` and `release/*`. A push to `release/*` +itself builds nothing; only its tags do. ## Deployment @@ -74,6 +94,9 @@ that exists because a `v*` tag built it. Nothing merged to `main` can reach prod. It changes only when someone bumps `appVersion`, publishes the chart, and installs that chart version. +While a hackathon is running on prod, install nothing there but a hotfix that +the event needs. + ## Three version numbers Two of them are the app, one is the chart, and they move independently. Read @@ -134,6 +157,17 @@ Pushing the tag builds and pushes `release/backend-service:0.9.0` and `release/frontend-service:0.9.0`. A release image is never overwritten, so a botched tag is fixed by bumping again, not by re-tagging. +Then [make the release deployable](#making-a-release-deployable). Once that +chart pull request is merged, create the release branch **from its merge +commit** — not from the tag, which does not have the chart bump yet. `main` may +have moved on in the meantime, so name the commit: + +```bash +git fetch origin +git switch -c release/0.9 +git push -u origin release/0.9 +``` + ### If you bumped the wrong way `bump` commits and tags locally and pushes nothing, so a release you have not @@ -144,15 +178,59 @@ git reset --hard origin/main git tag -d v0.9.0 ``` -If `main` has moved past something you cannot ship and a released version needs -a fix, branch from the tag rather than from `main`: +## Making a hotfix -```bash -git switch -c hotfix/thing v0.8.0 -``` +A hotfix goes on the release branch prod runs, never through `main`. Take +`release/0.12` and a fix that becomes `v0.12.2`: + +1. **Branch from the release branch**, not from `main`: + + ```bash + git fetch origin + git switch -c hotfix/thing origin/release/0.12 + ``` + +2. **Fix it, in one pull request into `release/0.12`** — set that as the base; + GitHub suggests `main`. The pull request carries: + - the fix itself, + - its line under `[Unreleased]` → `### Fixed` in `CHANGELOG.md`, + - the chart bump for the release it will become: `Chart.yaml` `version` up + one patch, `appVersion: "0.12.2"`. + + The chart bump belongs here, not in a pull request afterwards: CI publishes + charts only for pushes to `main` and for tags, so on a release branch the + chart has to be in the commit that gets tagged. + +3. **Release it from the release branch**, once the pull request is merged: -Open the pull request against `main` if the fix belongs there too. Create such a -branch when a fix actually needs one — not in advance. + ```bash + git switch release/0.12 && git pull + just version::bump patch + git show # read the release commit before it leaves + git push && git push origin v0.12.2 + ``` + + The tag builds `release/*:0.12.2`, then publishes the chart from the tagged + commit. Installing that chart version on prod is what ships the fix. A wrong + bump is undone as [above](#if-you-bumped-the-wrong-way), resetting to + `origin/release/0.12` instead of `origin/main`. + +4. **Bring the fix to `main`**, or the next minor release brings the bug back. + Cherry-pick the fix commit onto a new branch and open a pull request into + `main`: + + ```bash + git switch -c fix/thing origin/main + git cherry-pick --no-commit + git restore --source=origin/main --staged --worktree helm-chart/Chart.yaml + git commit + ``` + + Pick only the fix. The hotfix commit also carries the release's chart bump, + which is why `Chart.yaml` is put back to `main`'s before committing; merging + the release branch into `main` would bring its release commits and changelog + headings as well. `CHANGELOG.md` will conflict: keep the entry, under + `main`'s own `[Unreleased]`. ## Making a release deployable @@ -173,6 +251,12 @@ no images yet. Publishing a chart is not deploying it. Once a real deployment exists, installing that chart version is the step that changes what runs. +`main` and a release branch both publish charts, so they must never pick the +same number. A release line keeps the chart patch versions of its release — +`release/0.12` started at chart `0.8.1` and goes on with `0.8.2`, `0.8.3`. On +`main`, the first chart change after cutting a release branch moves the chart to +the next minor (`0.9.0`). + ## A first release checklist Worth knowing once, then never again: From dfcd5fc41ccd92ec0c0c6fb707204e6d4e6c9464 Mon Sep 17 00:00:00 2001 From: Sabine Maennel <5292683+sabinem@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:21:00 +0100 Subject: [PATCH 2/3] chore: corrections to the release decription --- RELEASING.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index b8abc6ee..7e3ebcb5 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -27,15 +27,14 @@ request per change, reviewed and merged on its own, is what keeps the history readable and the changelog honest. `release/X.Y` is one branch per released minor line, created when that line is -released ([Cutting an app release](#cutting-an-app-release)). It never receives +released ([Making an app release](#making-an-app-release)). It never receives features, only the fixes that prod cannot wait for — see [Making a hotfix](#making-a-hotfix). Its tags (`v0.12.2`, `v0.12.3`, …) are what prod runs. Once the next minor line is released, the old branch simply stops -being used. +being used and can be deleted once the new release has been deployed to production. So `main` can keep moving during a hackathon while prod stays on the release it -was installed with, and a fix for prod does not have to wait for whatever else -is on `main`. +was installed with, and a hotfix for prod can be done on `release/X.Y`. A pull request runs the full CI suite, whichever branch it targets: codegen drift, lint, version consistency, chart lint, build, tests, and an image build. @@ -94,8 +93,8 @@ that exists because a `v*` tag built it. Nothing merged to `main` can reach prod. It changes only when someone bumps `appVersion`, publishes the chart, and installs that chart version. -While a hackathon is running on prod, install nothing there but a hotfix that -the event needs. +While a hackathon is running on prod, install nothing there except hotfixes in case +the event needs them. ## Three version numbers @@ -134,7 +133,7 @@ Concurrent pull requests both appending under `[Unreleased]` will conflict on those lines. The conflict is trivial to resolve — keep both entries — and is the price of a file anyone can read without tooling. -## Cutting an app release +## Making an app release From an up-to-date, clean `main`: @@ -158,8 +157,8 @@ Pushing the tag builds and pushes `release/backend-service:0.9.0` and botched tag is fixed by bumping again, not by re-tagging. Then [make the release deployable](#making-a-release-deployable). Once that -chart pull request is merged, create the release branch **from its merge -commit** — not from the tag, which does not have the chart bump yet. `main` may +chart pull request is merged, create the release branch from its merge +commit — not from the tag, which does not have the chart bump yet. `main` may have moved on in the meantime, so name the commit: ```bash From b92cbb89893fdeefbe5ca6f8403eb859baa805b9 Mon Sep 17 00:00:00 2001 From: Sabine Maennel <5292683+sabinem@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:22:07 +0100 Subject: [PATCH 3/3] chore: corrections to the release decription --- RELEASING.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 7e3ebcb5..eba8cb06 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -31,7 +31,8 @@ released ([Making an app release](#making-an-app-release)). It never receives features, only the fixes that prod cannot wait for — see [Making a hotfix](#making-a-hotfix). Its tags (`v0.12.2`, `v0.12.3`, …) are what prod runs. Once the next minor line is released, the old branch simply stops -being used and can be deleted once the new release has been deployed to production. +being used and can be deleted once the new release has been deployed to +production. So `main` can keep moving during a hackathon while prod stays on the release it was installed with, and a hotfix for prod can be done on `release/X.Y`. @@ -93,8 +94,8 @@ that exists because a `v*` tag built it. Nothing merged to `main` can reach prod. It changes only when someone bumps `appVersion`, publishes the chart, and installs that chart version. -While a hackathon is running on prod, install nothing there except hotfixes in case -the event needs them. +While a hackathon is running on prod, install nothing there except hotfixes in +case the event needs them. ## Three version numbers @@ -157,9 +158,9 @@ Pushing the tag builds and pushes `release/backend-service:0.9.0` and botched tag is fixed by bumping again, not by re-tagging. Then [make the release deployable](#making-a-release-deployable). Once that -chart pull request is merged, create the release branch from its merge -commit — not from the tag, which does not have the chart bump yet. `main` may -have moved on in the meantime, so name the commit: +chart pull request is merged, create the release branch from its merge commit — +not from the tag, which does not have the chart bump yet. `main` may have moved +on in the meantime, so name the commit: ```bash git fetch origin