How do tags, release branches and cherry-picks work together for a hotfix, and how do you check that a fix reached every branch that needs it?
Predict first
A fix on main is cherry-picked with -x onto a release branch cut from a tag. What do describe, cherry and contains show?
// === run.sh ===
export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@x GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@x GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
git init -q -b main r && cd r
for i in 1 2 3; do echo $i > f$i; git add .; git commit -qm "c$i"; done
git tag -a v1.0.0 -m release && git branch release/1.0 v1.0.0
echo fix > fix; git add .; git commit -qm "fix crash"; FIX=$(git rev-parse HEAD)
echo more > more; git add .; git commit -qm "feature work"
git checkout -q release/1.0 && git cherry-pick -x $FIX >/dev/null
echo "message trailer: $(git log -1 --format=%B | grep 'cherry picked' | sed 's/[0-9a-f]\{40\}/<sha>/')"
echo "describe: $(git describe --tags | sed 's/g[0-9a-f]*$/g<sha>/')"
echo "git cherry (- already on release, + missing):"
git cherry release/1.0 main | sed 's/[0-9a-f]\{40\}/<sha>/'
echo "tags containing the original fix commit: $(git tag --contains $FIX | wc -l | tr -d ' ')"
echo "branches containing the original fix: $(git branch --contains $FIX --format='%(refname:short)' | tr '\n' ' ')"
- The message has a cherry-picked line, describe is v1.0.0-1-g<sha>, cherry shows - for the fix and + for the feature, and no tag contains the original fix
- The cherry-pick keeps the same hash
- git cherry marks both commits with +
Mental model. A hotfix is a photocopy of the repair page inserted into the old edition of the manual. git cherry checks that the photocopy says the same as the original, even though it has a new page number.
Cut a tag on the exact commit you shipped (annotated, such as v1.0.0) and keep a release branch from it if you may need to patch that version. A hotfix is then made once, on the main line, and cherry-picked onto the release branch with git cherry-pick -x, whose -x appends “(cherry picked from commit …)” so the link survives. The cherry-pick is a new commit with a different hash, which is why the check finds that no tag contains the original fix commit, and why git describe on the release branch reads v1.0.0-1-g… (one commit after the tag). Rebuilding the release branch gives build N+1 of version 1.0.1 or 1.0.0, according to your versioning policy (Q481).
To verify, do not compare hashes; compare patches. git cherry release/1.0 main lists the commits on main with - when an equivalent patch is already on the release branch and + when it is not, so in the check the fix shows - and the unrelated feature +. Add that to the release checklist as a gate (“every + is intended”) and use git branch --contains and git tag --contains to see where an original commit lives. Process points: fix forward on main first so the fix cannot be lost in the next release, take the smallest change that fixes the problem, resolve conflicts on the release branch with extra care (the code differs), test the release branch build, and for a monorepo or several apps record which apps each fix touches (Q487). The check used a local scratch repository; hosted-provider features such as automated backports were not exercised.
The copy is a different commit with an equivalent patch; git cherry compares patches.
Common traps
- Fixing the release branch only. The next release from main reintroduces the bug.
- Checking
git branch --containsfor a cherry-picked fix. The copy has a different hash, so it reports nothing; usegit cherryor search for the “cherry picked from” line.
They might follow up with
- When merge instead of cherry-pick? When the release branch should take everything from main up to the point, which is rare for hotfixes.
- What about versions after the hotfix? Bump the patch version, keep the build rising, and tag the new release commit.
Related questions
- 25.3.3 What can and cannot be rolled back after release, and how do you respond to a bad release: pause, flags…
- 25.4.2 How do you run a release train for many apps: cadence, branch cuts, ownership, go/no-go criteria, hotfix…
- 25.2.2 How do marketing versions and build numbers work, how should CI generate them, and which rules does App Store…
- 25.2.3 How do you design a release pipeline: hermetic builds, secrets, caching, artefact promotion and the order of…
- 25.8.2 Can you use the git commit count as the build number, and what breaks it: shallow clones, squashes, rebases…
← Can you use the git commit count as the build number, and what breaks… How do you decide whether a phased release is healthy: why is a… →