Beginning with 1.8.0, Turnstone separates the stable checkout from next-release development while preserving real Git ancestry between supported lines.
| Branch | Versions | Purpose | Docker aliases |
|---|---|---|---|
main |
final X.Y.Z |
Current stable release and install source | exact, :X.Y, :stable, :latest |
dev |
X.Y.0aN, bN, or rcN |
Next-release integration and GitHub default | exact, :experimental |
stable/X.Y |
final X.Y.Z |
Supported prior minor after main moves on |
exact, :X.Y |
main is the only writable authority for the current stable minor. Do not
maintain a same-minor stable/X.Y branch beside it. Immediately before main
advances to a new minor, preserve its current tip as stable/X.Y; that branch
then becomes the maintenance line for the prior minor.
Archived stable branches and their released artifacts may remain available even after support ends, but they are not active release targets.
- Features, refactors, and fixes needed only by the next release target
dev. - Fixes needed by the current stable release target
main. - Fixes needed by a supported prior minor start on the oldest affected
stable/X.Ybranch.
Move stable fixes forward in order:
stable/X.Y → main → dev
Use a merge commit or a true fast-forward for branch-to-branch synchronization. Do not squash, rebase, or cherry-pick synchronization PRs: the shared ancestry is what lets Git recognize the same fix the next time the branches meet. This restriction applies to synchronization PRs; ordinary focused PRs may follow the repository's normal merge policy.
Release-only metadata belongs to the destination branch. When a forward merge
touches pyproject.toml, turnstone/__init__.py, uv.lock, or the changelog
header, keep the destination line's version and release state while accepting
the source branch's code and documentation changes. The release commit itself
should still become an ancestor of newer branches.
Never merge dev backward into a stable branch outside the deliberate promotion
process.
Turnstone uses PEP 440 versions in one PyPI package:
1.8.1— stable release1.9.0a1— alpha pre-release1.9.0b1— beta pre-release1.9.0rc1— release candidate1.9.0— promoted stable release
scripts/release.sh validates the version and branch, requires a clean worktree,
updates pyproject.toml and turnstone/__init__.py, regenerates uv.lock,
commits, and creates the vX.Y.Z tag. With --push, it pushes the branch and
tag atomically so CI never observes only half of a release.
The helper enforces these release locations:
- pre-releases:
devonly - current stable releases:
main - prior-line patches: the matching
stable/X.Ybranch
git switch dev
git pull --ff-only origin dev
scripts/release.sh 1.9.0rc1 --pushgit switch main
git pull --ff-only origin main
scripts/release.sh 1.8.1 --pushAfter publication, forward-merge the release commit from main into dev,
retaining the development version in the release metadata files.
git switch stable/1.8
git pull --ff-only origin stable/1.8
scripts/release.sh 1.8.2 --pushThen merge stable/1.8 into main and main into dev, retaining each
destination branch's version metadata. This replaces the old repeated
cherry-pick workflow.
For a 1.8 to 1.9 promotion:
- Freeze
devand forward-merge the latestmaininto it. - Finalize the changelog and run the full release gates on
dev. - Create
stable/1.8from the currentmaintip. It becomes the prior-line maintenance branch only whenmainadvances. - Merge
devintomainwith ancestry preserved. - From
main, runscripts/release.sh 1.9.0 --push. - Fast-forward
devto the 1.9.0 release commit, then begin the next pre-release cycle there.
Do not create stable/1.9 at the 1.9.0 release. main already owns that line;
create the maintenance branch only when main is about to advance again.
The initial transition has no existing dev branch:
- Merge the 1.8.0 release preparation into
mainand run the full gates. - From
main, runscripts/release.sh 1.8.0to create the release commit and tag locally. - Create
devfromv1.8.0, then atomically pushmain,dev, and the tag. - Make
devthe GitHub default branch. - Start the 1.9 line on
dev; publish1.9.0a1when that first alpha is ready. - Leave the 1.7 and earlier stable branches frozen.
GitHub's default branch is dev, so normal clones and pull requests begin on
the development line. Stable install paths select main explicitly, and
Renovate is configured to target dev directly.
Pushes and pull requests to main, dev, and stable/* run CI. A v* tag runs
the same gates; only a successful same-repository tag run can start the PyPI,
GitHub Release, and Docker publication jobs.
Pre-release tags produce:
- a PyPI pre-release
- a GitHub pre-release that is never marked latest
- exact and
:experimentalDocker tags
Final tags on the current main minor produce:
- a normal PyPI and GitHub release, marked latest on GitHub
- exact,
:X.Y,:stable, and:latestDocker tags
Final tags on a prior stable/X.Y line produce a normal PyPI/GitHub maintenance
release plus exact and :X.Y Docker tags. They do not move GitHub's latest
release or the global Docker aliases backward.
Renovate targets dev. A dependency or supply-chain fix needed on a stable line
starts on the oldest affected supported branch and follows the same forward-merge
path as any other stable fix.