ci: restructure release pipeline into a single atomic release commit
CI / Sync project version with tag (hand-pushed tags only) (pull_request) Has been skipped
CI / Publish package to Gitea package registry (pull_request) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 19s
CI / Type check (ty) (pull_request) Successful in 16s
CI / Format (ruff format) (pull_request) Successful in 24s
CI / Tests (pull_request) Successful in 2m27s
CI / Release (bump, changelog, badges, tag) on merge to master (pull_request) Has been skipped
CI / Sync project version with tag (hand-pushed tags only) (pull_request) Has been skipped
CI / Publish package to Gitea package registry (pull_request) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 19s
CI / Type check (ty) (pull_request) Successful in 16s
CI / Format (ruff format) (pull_request) Successful in 24s
CI / Tests (pull_request) Successful in 2m27s
CI / Release (bump, changelog, badges, tag) on merge to master (pull_request) Has been skipped
Every merge to master previously produced three separate commits (bump version -> update changelog, tagged here -> update README badges), so the published tag never carried the current release's own README badges, and the badge commit leaked into the next release's changelog since no cliff.toml parser skipped it. - Extract the bump/changelog/badge assembly into .gitea/scripts/release-commit.sh, used by both the merge-to-master path and the hand-pushed-tag sync path, so every tag now points at one complete "chore: release vX.Y.Z" commit. Push the commit and its tag atomically. - sync-version-on-tag now refuses to touch a tag whose commit isn't reachable from master (rather than silently rewriting an unreviewed tree), and builds a proper release commit via the same script when it does need to correct a hand-pushed tag's version. - publish-package now depends only on sync-version-on-tag: since a tag can only pass that guard if its commit is already on master, and master is always fully checked, re-running the lint/type/test matrix on tag pushes was redundant. - Factor the repeated checkout/setup-uv/env/sync steps into a local composite action (.gitea/actions/setup), fix `test`'s `needs` to include ruff-format, and bump actions/upload-artifact to v4. - cliff.toml: skip "chore: release ..." commits from the changelog. Verified by dry-running release-commit.sh against a scratch worktree for all three code paths (patch bump, --no-bump, explicit VERSION sync), confirming idempotency and that the resulting commit carries pyproject.toml, .bumpversion.toml, uv.lock, CHANGELOG.md and README.md together. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnotyatakKNS4NLjDbfYw1
This commit is contained in:
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build one complete release commit: version bump (or sync to a given
|
||||
# version, or no bump at all), changelog entry, and refreshed README
|
||||
# badges — all in a single commit, tagged at the end. Used by both the
|
||||
# merge-to-master release job and the hand-pushed-tag sync job, so "the
|
||||
# tag == a complete release commit" holds either way a release gets made.
|
||||
#
|
||||
# Usage: release-commit.sh [--no-bump|VERSION]
|
||||
# (no argument) bump the current patch version (normal merge path).
|
||||
# --no-bump don't touch the version — the branch already bumped it
|
||||
# (e.g. a manual minor/major bump); just build the
|
||||
# changelog/badge commit for whatever version is current.
|
||||
# VERSION sync the project to this exact version (tag-sync path).
|
||||
#
|
||||
# Preconditions: repo is checked out with full history (fetch-depth: 0),
|
||||
# `uv` is available and synced, and git user.name/user.email are configured.
|
||||
# Idempotent: if the resulting tag already exists, this is a no-op.
|
||||
set -euo pipefail
|
||||
|
||||
VERSION_ARG="${1:-}"
|
||||
|
||||
if [ "$VERSION_ARG" = "--no-bump" ]; then
|
||||
echo "Version already bumped by this branch; using current version as-is"
|
||||
elif [ -n "$VERSION_ARG" ]; then
|
||||
echo "Syncing project version to $VERSION_ARG"
|
||||
uv version "$VERSION_ARG" --no-sync
|
||||
uv lock
|
||||
else
|
||||
CURRENT_VERSION=$(uv version --short)
|
||||
echo "Bumping patch version from $CURRENT_VERSION"
|
||||
uv run bump-my-version bump patch --current-version "$CURRENT_VERSION" --no-commit --no-tag
|
||||
# bump-my-version's pre_commit_hooks (uv lock + git add uv.lock) only run
|
||||
# on its own commit path, which we skipped with --no-commit — so do it here.
|
||||
uv lock
|
||||
fi
|
||||
|
||||
VERSION=$(uv version --short)
|
||||
TAG="v$VERSION"
|
||||
|
||||
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
echo "Tag $TAG already exists; nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
uv run git-cliff --tag "$TAG" --unreleased --prepend CHANGELOG.md
|
||||
|
||||
TEST_COUNT=$(uv run pytest --collect-only -q 2>/dev/null | grep -oE '^[0-9]+ tests? collected' | grep -oE '^[0-9]+')
|
||||
sed -i -E "s|badge/version-[^-]+-informational|badge/version-${VERSION}-informational|" README.md
|
||||
sed -i -E "s|badge/tests-[0-9]+%20passing-brightgreen|badge/tests-${TEST_COUNT}%20passing-brightgreen|" README.md
|
||||
|
||||
# .bumpversion.toml stores its own current_version, which bump-my-version
|
||||
# rewrites even with --no-commit — must be staged or the next run sees a
|
||||
# dirty tree and bump-my-version refuses (allow_dirty = false).
|
||||
git add pyproject.toml .bumpversion.toml uv.lock CHANGELOG.md README.md
|
||||
if git diff --cached --quiet; then
|
||||
echo "Nothing changed; skipping release commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "chore: release $TAG"
|
||||
git tag -a "$TAG" -m "$TAG"
|
||||
|
||||
echo "Created release commit and tag $TAG"
|
||||
Reference in New Issue
Block a user