Supporting multiple maintained versions Jump to heading
Services deployed by their own team run one version at a time. Libraries, installed software, mobile apps, on-premise products and anything with long-term support contracts do not: customers run 2.3 while you ship 2.5, and both need security fixes. Supporting several versions is where branching models earn their complexity, and where most teamsβ processes are improvised: which versions get fixes, how fixes reach each one, how long a version is supported. A sustainable setup has four parts: release branches cut from main, a published support window, an upstream-first rule for fixes, and automation that turns βbackport to 2.3 and 2.4β into a label rather than an afternoon. This page builds them, within GitFlow vs GitHub Flow comparison.
When to use this approach Jump to heading
- Customers or users run several released versions of your software at the same time.
- Security or bug fixes must be delivered to versions other than the latest.
- Backports are done by hand, inconsistently, and sometimes forgotten.
- You use any base model β GitHub Flow, GitLab Flow or GitFlow β and need release maintenance on top; see GitLab Flow compared with GitFlow and GitHub Flow.
Step 1 β Cut a release branch per minor version Jump to heading
Each minor version gets a branch cut from main at release time. Patch releases are tags on that branch. New features never go to release branches β only fixes.
git switch -c release/2.5 origin/main
git tag -s v2.5.0 -m "Release 2.5.0"
git push origin release/2.5 v2.5.0 Step 2 β Publish a support window Jump to heading
Supporting every version forever is impossible. Decide and publish how many versions are supported and for how long β for example, the latest two minor versions receive fixes, and the one before that receives security fixes only. Keep it in the repository so tooling can read it.
# SUPPORT.yml β the source of truth for which branches receive fixes
supported:
- { branch: release/2.5, level: full, until: "2027-04-30" }
- { branch: release/2.4, level: full, until: "2026-12-31" }
- { branch: release/2.3, level: security, until: "2026-10-31" } Step 3 β Fix upstream first, then backport Jump to heading
Every fix lands on main first, through the normal review process. Only then is it cherry-picked to supported release branches. That ordering guarantees no future release lacks the fix, and gives every backport a reviewed source.
# After the fix merges to main as abc1234:
for b in release/2.5 release/2.4; do
git switch "$b" && git pull --ff-only
git cherry-pick -x abc1234 && git push
done The details of conflicts on older branches and of recording backports are in resolving cherry-pick conflicts on older branches and recording backports with cherry-pick -x.
Step 4 β Automate backports from the support file Jump to heading
Reading SUPPORT.yml, a workflow can offer backport labels only for supported branches and open backport pull requests when they are applied β the mechanism from automating backport pull requests with labels.
# Keep backport labels in sync with the support window
today=$(date +%F)
yq -r '.supported[] | select(.until >= "'"$today"'") | .branch' SUPPORT.yml | while read -r b; do
gh label create "backport $b" --color FBCA04 --force
done
# Remove labels for branches that left the window
gh label list --json name --jq '.[].name' | grep '^backport ' | while read -r l; do
yq -e '.supported[] | select(.branch == "'"${l#backport }"'" and .until >= "'"$today"'")' SUPPORT.yml >/dev/null || gh label delete "$l" --yes
done Step 5 β Release patches and retire versions on schedule Jump to heading
Tag patch releases on release branches as fixes accumulate, and when a version leaves the support window, stop backporting and mark the branch read-only rather than deleting it.
# Patch release on a maintenance branch
git switch release/2.4 && git pull --ff-only
git tag -s v2.4.3 -m "Release 2.4.3" && git push origin v2.4.3
# Retire a branch: keep it, but forbid new pushes (ruleset: restrict updates)
gh api -X POST "repos/$OWNER/$REPO/rulesets" --input retire-release-2.3.json Keeping retired branches preserves the history needed to reproduce old releases, as described in reproducing a release build from a tag.
Step 6 β Report what each supported version is missing Jump to heading
Even with automation, a backport occasionally fails or is forgotten. A weekly report compares each supported branch with main and lists fixes β by conventional commit type β that have not been ported, so nothing slips silently.
yq -r '.supported[].branch' SUPPORT.yml | while read -r b; do
echo "== $b"
base=$(git merge-base "origin/$b" origin/main)
git cherry -v "origin/$b" origin/main "$base" | awk '$1=="+"' | grep -E ' (fix|security)(\(|:)' || echo " nothing missing"
done Adapted backports show up as missing in patch-ID comparisons; cross-check their -x records before treating them as gaps.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
How many versions should we support? Jump to heading
As few as your users can accept. Each supported branch multiplies the work of every fix. Two minor versions with full support and one with security-only support is a common balance.
Should release branches ever merge back into main? Jump to heading
Not routinely, if every fix goes upstream first β there is nothing on the release branch that main lacks except version bumps. A periodic git cherry check confirms it.
What about fixes that only apply to an old version? Jump to heading
Occasionally a bug exists only in an old branch because main rewrote the code. Fix it directly on the release branch, mark the commit with a Release-Only: trailer explaining why, and skip the upstream step.
Related Jump to heading
- GitFlow vs GitHub Flow Comparison β the parent topic.
- Maintaining Release Branches for Patch Versions β the tagging side of maintenance.
- Finding Unported Commits with git cherry β catching missed backports.
- Choosing a Branching Model for a Mobile Release Train β a common case with several live versions.