From e2ec48f1dbc2b3f2affe24d04a25f66d9e48ac07 Mon Sep 17 00:00:00 2001 From: lyrathorpe Date: Fri, 21 Aug 2026 13:50:31 +0100 Subject: [PATCH] fix: keep the packaged version in step with the release tag (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## The defect `pyproject.toml` carried a static version while releases are derived from conventional commits and tagged by CI. The first release after the packaging change was cut as **v0.4.0** while the file still declared `0.3.0`, so both the Python package metadata and the Nix store path understated the release. ## Why a human cannot fix this by hand The version is computed from commit messages *since the last tag* and is only known inside the release job, after the build. The pull request that causes a release cannot know the number it will produce — especially with more than one PR in flight. Any hand-set value drifts again at the next release. ## The fix The release job now writes the computed version into `pyproject.toml`, commits it as `chore(release): vX.Y.Z`, and tags **that** commit. - Neither push re-triggers the workflow: it listens on `main` only for the image-affecting paths, and `pyproject.toml` is not one of them. - `chore(release)` produces no bump of its own on the next run. - The branch push is ordered before the tag push, so a rejected push (main moved on) cannot leave a tag pointing at a commit that is not on `main`. - `pyproject.toml` is bumped to `0.4.0` here to correct today's state; from the next release onward CI maintains it, and a comment in the file says so. ## Verification - The version-rewrite step was extracted from the workflow and run against a copy of `pyproject.toml`: the line is rewritten, and the step raises if it ever finds no version line rather than silently doing nothing. - `bash -n` on the step, `nix flake check` green at 0.4.0. - `main` has no branch protection rules, so the job's push will be accepted. If you ever add protection, the CI token needs an exemption or this step fails. --------- Co-authored-by: Emma Thorpe Reviewed-on: https://code.emmathe.dev/lyrathorpe/legacy-email-proxy/pulls/17 --- .gitea/workflows/build-and-publish.yaml | 45 +++++++++++++++++++++---- pyproject.toml | 5 ++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/build-and-publish.yaml b/.gitea/workflows/build-and-publish.yaml index 88c3d6f..c40b899 100644 --- a/.gitea/workflows/build-and-publish.yaml +++ b/.gitea/workflows/build-and-publish.yaml @@ -153,15 +153,46 @@ jobs: org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ github.sha }} - # Record the release as an annotated git tag so the next run computes the - # following version from it. This push does not re-trigger the workflow, - # which only listens on the main branch and pull requests. - - name: Tag the release + # Record the release: write the computed version into pyproject.toml, then + # commit and tag it, so the packaging metadata always matches the release + # instead of drifting behind it. The version is derived from commit + # messages and only known here, after the build, so it cannot be set by + # hand in the pull request that causes the release. + # + # Neither push re-triggers this workflow: it listens on main only for the + # image-affecting paths above, and pyproject.toml is not one of them. The + # chore(release) subject also produces no bump of its own on the next run. + - name: Record and tag the release if: steps.version.outputs.release == 'true' + env: + VERSION: ${{ steps.version.outputs.version }} run: | set -euo pipefail - v="v${{ steps.version.outputs.version }}" + + python - "$VERSION" <<'PY' + import pathlib + import re + import sys + + version = sys.argv[1] + path = pathlib.Path("pyproject.toml") + text = path.read_text() + text, count = re.subn( + r'(?m)^version = ".*"$', f'version = "{version}"', text, count=1 + ) + if count != 1: + raise SystemExit("no version line found in pyproject.toml") + path.write_text(text) + PY + git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.${REGISTRY}" - git tag -a "$v" -m "$v" - git push origin "$v" + git add pyproject.toml + git commit -m "chore(release): v${VERSION}" + + # Push the branch before the tag. If main has moved on and this push + # is rejected, the job fails without having left a tag pointing at a + # commit that is not on main. + git push origin "HEAD:${GITHUB_REF_NAME}" + git tag -a "v${VERSION}" -m "v${VERSION}" + git push origin "v${VERSION}" diff --git a/pyproject.toml b/pyproject.toml index db26305..24a82d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,10 @@ build-backend = "setuptools.build_meta" [project] name = "legacy-email-proxy" -version = "0.3.0" +# Written by the release job in .gitea/workflows/build-and-publish.yaml, which +# derives the version from conventional commits and tags the result. Do not +# edit by hand: a hand-set value is overwritten at the next release. +version = "0.4.0" description = "Unauthenticated POP3/SMTP front end proxied to authenticated IMAPS/SMTPS backends" readme = "README.md" requires-python = ">=3.12"