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
Release branches cut from mainMain continues with new development. At each minor release, a release branch is cut: release/2.4 and later release/2.5. Fixes land on main first and are cherry-picked to each supported release branch, where patch tags such as v2.4.3 and v2.5.1 are created.main moves on; release branches receive only fixesmain2.4 cut2.5 cutfixnextrelease/2.5v2.5.0fix'v2.5.1release/2.4v2.4.2fix''v2.4.3each supported branch gets the fix as its own cherry-pick, tagged as a patch Release branches cut from mainMain continues with new development. At each minor release, a release branch is cut: release/2.4 and later release/2.5. Fixes land on main first and are cherry-picked to each supported release branch, where patch tags such as v2.4.3 and v2.5.1 are created.main moves on; release branches receive only fixesmain2.4 cut2.5 cutfixnextrelease/2.5v2.5.0fix'v2.5.1release/2.4v2.4.2fix''v2.4.3each supported branch gets the fix as its own cherry-pick, tagged as a patch

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" }
Support levels by versionThe newest minor version and the one before it receive all bug and security fixes. The version before that receives security fixes only until its end date. Older versions receive nothing, and the published window tells users when to upgrade.Bug fixesSecurity fixesrelease/2.5 (latest)yesyesrelease/2.4yesyesrelease/2.3nountil Oct 2026release/2.2 and oldernonoa published window turns 'can you backport this?' into a lookup Support levels by versionThe newest minor version and the one before it receive all bug and security fixes. The version before that receives security fixes only until its end date. Older versions receive nothing, and the published window tells users when to upgrade.Bug fixesSecurity fixesrelease/2.5 (latest)yesyesrelease/2.4yesyesrelease/2.3nountil Oct 2026release/2.2 and oldernonoa published window turns 'can you backport this?' into a lookup

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.

A security fix reaching every supported versionThe fix is reviewed and merged to main. The support file lists the branches still in their window. Backport labels on the original pull request open one backport per supported branch, each tested on that branch, then tagged as a patch release.Fix on mainreviewed PRSUPPORT.ymlwhich branchesBackport PRsone per branchBranch CIold code, new fixPatch tagsv2.5.1, v2.4.3 …the support file decides which branches; automation does the rest A security fix reaching every supported versionThe fix is reviewed and merged to main. The support file lists the branches still in their window. Backport labels on the original pull request open one backport per supported branch, each tested on that branch, then tagged as a patch release.Fix on mainreviewed PRSUPPORT.ymlwhich branchesBackport PRsone per branchBranch CIold code, new fixPatch tagsv2.5.1, v2.4.3 …the support file decides which branches; automation does the rest

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.