Releasing & Versioning
How to cut a release of any Aetos repository. The rule lives in the reference page Versioning & Changelog; this is the runbook you follow.
Every repository is one of two Drone shapes — a PyPI package or a Docker deployment (Drone Pipelines). Both release the same way: the version bump and changelog promotion go in the merge request, and the tag is created on the merged commit.
🧾 Before you start
- You have an open merge request holding the change, and
mainis green (the Drone quality gate passed on the last run). - You know whether this is a breaking, feature, or fix release.
- The version file exists:
- package →
versioninpyproject.toml - service →
VERSIONat the repo root
🚦 Pick the next version
The version file and the tag must be identical. For 0.y.z, 0.y is the major
line (see the reference page); once at 1.0.0, normal SemVer applies.
| Change | 0.y.z → |
1.0.0+ → |
|---|---|---|
| Breaking | 0.(y+1).0 |
(x+1).0.0 |
| Feature | 0.y.(z+1) |
x.(y+1).0 |
| Fix | 0.y.(z+1) |
x.y.(z+1) |
🌿 Release in the MR
The version is known while the change is still a merge request, so the bump rides along with it. Do all edits in the MR:
-
Reconcile the changelog first.
[Unreleased]is a draft, not the source of truth: diff the change against the last tag (git log <last-tag>..HEAD,git diff --stat <last-tag>..HEAD) and add an entry for every user-facing change that is missing. Always show the proposed additions to the user and wait for approval before promoting — ask rather than guess when an entry is ambiguous. -
Promote the changelog in
CHANGELOG.md: move the (now reconciled)[Unreleased]content under a new## [x.y.z] - YYYY-MM-DDheading, leave a fresh empty[Unreleased]on top, and add the compare links — naming the version/tag, never a commit id:
- Bump the version file to the intended release version:
version = "x.y.z"inpyproject.toml(packages) or theVERSIONfile (services).
🏷️ Tag the merged commit
- Merge or squash the MR into
main. - Create the tag on the resulting commit:
git tag x.y.z(lightweight). - Push the tag (
git push origin x.y.z) — Drone builds the release.
🤖 Agent-assisted tagging
The agent does not author or merge the MR and never tags before the merge. After you confirm the MR is merged/squashed and tell it to proceed, the agent:
- Waits for that explicit go-ahead.
- Finds the commit that introduced the bump:
- package:
git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml - service:
git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION - Verifies that commit's version file equals
x.y.z, then tags only that commit:git tag x.y.z <sha>. - Does not push — it asks you to run
git push origin x.y.z.
🤖 What Drone does
- Type A (package): asserts
tag == pyproject version, then skips if that version already exists on the index, runs the quality gate, builds, and uploads the wheel + sdist. - Type B (service): asserts
tag == VERSION, resolves the tag, skips if the image already exists, runs the quality gate, builds, pushes, and restarts the container.
If the guard fails, nothing is published — fix the version file, amend the commit, and move the tag only if it was never pushed.
🛟 Backfill / recovery
If an older tag never published (a past version-file mismatch, an index outage):
- Package: run the matching
backfill-*-from-tagspipeline (custom event) —backfill-pypi-from-tagsfor Python,backfill-npm-from-tagsfor npm. It checks out every tag, verifiestag ==the version file at that commit, and uploads the ones missing from the index. Already-fixed history is never rewritten. - Service: rebuild the image for the missing tag with a fresh commit and a new patch version; do not move a published tag.
🔁 If a release is wrong
- Never reuse or force-move a pushed tag.
- Fix forward: bump to the next patch, promote the changelog, tag again.