Releasing
This project uses a pull request to prepare each release, then a manual Git tag
as the only step that starts publication (GitHub Actions builds installers and
creates a GitHub Release when a v* tag is pushed).
Version sources
Keep these in sync whenever you cut a release:
__version__insrc/dbs_annotator/__init__.py— Hatch reads this as thedbs-annotatordistribution version (PEP 440: stableX.Y.Zor prereleases such asX.Y.Za1,X.Y.Zb2,X.Y.Zrc1).versionunder[tool.briefcase]inpyproject.toml(Briefcase requires a static string)
The automation below updates both, then runs Towncrier to fold Markdown fragments
under newsfragments/ into CHANGELOG.md (see [tool.towncrier] in
pyproject.toml).
Day-to-day: changelog fragments
For each PR that should appear in the release notes, add a fragment in newsfragments/
(usually in the same PR as the change). Types match Types of changes in
Keep a Changelog: added, changed, deprecated, removed, fixed,
security (for example 123.added.md or 123.changed.md for PR number 123).
Create a stub interactively:
uv run towncrier create 123.added.md
CI may require a fragment when certain paths change unless the PR is labeled
skip-changelog or internal-only (see CONTRIBUTING.md).
Option A — Prepare the release PR locally
Ensure
mainis up to date and you have a clean working tree (or pass--allow-dirtyonly if you intend to include other edits — not recommended).Create a branch (do not commit the release bump directly on
main):git checkout main git pull git checkout -b chore/release-prep-X.Y.Z
Run the helper (omit
--commitfirst if you want to inspect diffs only):uv sync --dev uv run python scripts/release_prepare.py 0.4.0 --dry-run uv run python scripts/release_prepare.py 0.4.0rc1 --commit
Instead of typing the next version explicitly, derive it from the current
__version__with--bump:uv run python scripts/release_prepare.py --bump alpha --dry-run uv run python scripts/release_prepare.py --bump beta --dry-run uv run python scripts/release_prepare.py --bump rc --dry-run uv run python scripts/release_prepare.py --bump stable --dry-run uv run python scripts/release_prepare.py --bump patch --dry-run
alpha/beta/rcadvance the prerelease train;stabledrops prerelease labels (0.4.0rc2→0.4.0).patch/minor/majorapply only when there is no prerelease segment (run--bump stablefirst).Use
--date YYYY-MM-DDif the Towncrier release date should not be “today”. Use--skip-towncrieronly in exceptional cases (changelog skipped).Push the branch and open a pull request into
main. Wait for CI to pass, then merge.
Option B — Prepare the release PR from GitHub Actions
In GitHub: Actions → CD - Prepare release PR → Run workflow.
Enter Version (PEP 440 string without a
vprefix, e.g.0.4.0or0.4.0a1). Optionally set Release date (YYYY-MM-DD); otherwise UTC “today” is used.The workflow creates branch
chore/release-prep-X.Y.Z, runs the same steps asscripts/release_prepare.py, pushes it, and opens a pull request.Review and merge the PR when CI is green.
Publish: tag after merge (deliberate final step)
After the release-prep PR is merged into main:
Update your local
mainand identify the merge commit (or use GitHub’s suggested SHA for the PR merge).Create an annotated tag matching the version (
vprefix for the git tag only):git checkout main git pull git tag -a vX.Y.Z -m "Release vX.Y.Z" <merge_commit_sha> git push origin vX.Y.Z
If
originis your fork, usegit push upstream vX.Y.Zto the canonical lab repository (see the next section);git push originalone is not enough to run CD there.That push triggers
.github/workflows/release.yml(tag patternv*), which builds Python wheels and Briefcase artifacts and, when appropriate, publishes a GitHub Release.
Publish when origin is a fork
If you usually clone from your fork and git pull from main on the
canonical repository, your remotes are typically:
origin— your fork (git@github.com:<you>/<fork>.git)upstream— the lab repository where releases andmainlive
In that case, git push origin vX.Y.Z only puts the tag on your fork. GitHub
Actions runs in the repository that receives the tag push. Pushing the tag only
to the fork will not run the org’s CD pipeline or create the release in the
canonical project, even if you have push access to the upstream remote.
After you create the same annotated tag on the merge commit in your local
main (which should match upstream/main or be fast-forwarded to it), push
the tag to the upstream remote:
# Once per machine: add upstream if missing
# git remote add upstream git@github.com:Brain-Modulation-Lab/DBSAnnotator.git
git fetch upstream
git checkout main
git pull upstream main
git tag -a vX.Y.Z -m "Release vX.Y.Z" <merge_commit_sha> # only if you have not already
git push upstream vX.Y.Z
You can also git push origin vX.Y.Z so your fork has the same tag, but the
upstream push is the one that must happen for the official CD release. Verify
remotes with git remote -v if you are unsure.
Do not push a v* tag until the release-prep PR is merged and you are satisfied
with CHANGELOG.md and the version numbers on main.
Manual workflow dispatch on release.yml can still build artifacts without a new
tag; see that workflow’s inputs if you need a one-off build.
Troubleshooting
“Working tree is not clean” — stash or commit unrelated work, or use a fresh clone.
Towncrier fails — ensure there is at least one valid fragment for the release, or confirm
CHANGELOG.mdstill contains the## [Unreleased]heading Towncrier uses asstart_stringinpyproject.toml.Branch already exists — delete the remote branch
chore/release-prep-X.Y.Zor pick a new branch name before re-running the workflow.