Files
giant/.gitea/workflows/ci.yml
T
ci 8971121167
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: restructure release pipeline into a single atomic release commit
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
2026-09-03 18:29:38 +02:00

194 lines
7.2 KiB
YAML

name: CI
"on":
push:
branches: ["master"]
tags: ["**"]
pull_request: {}
env:
UV_CACHE_DIR: /uv-cache
jobs:
ruff-check:
name: Lint (ruff check)
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
volumes:
- /srv/act-runner-cache/uv:/uv-cache
steps:
- uses: actions/checkout@v4
- uses: ./.gitea/actions/setup
- run: uv run ruff check .
ruff-format:
name: Format (ruff format)
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
volumes:
- /srv/act-runner-cache/uv:/uv-cache
steps:
- uses: actions/checkout@v4
- uses: ./.gitea/actions/setup
- run: uv run ruff format --check .
type-check:
name: Type check (ty)
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
volumes:
- /srv/act-runner-cache/uv:/uv-cache
steps:
- uses: actions/checkout@v4
- uses: ./.gitea/actions/setup
- run: uv run ty check .
test:
name: Tests
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
needs: [ruff-check, ruff-format, type-check]
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
volumes:
- /srv/act-runner-cache/uv:/uv-cache
steps:
- uses: actions/checkout@v4
- uses: ./.gitea/actions/setup
- run: uv run pytest --cov --cov-report=term-missing --cov-report=xml
- uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
release:
name: Release (bump, changelog, badges, tag) on merge to master
needs: [ruff-check, ruff-format, type-check, test]
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
volumes:
- /srv/act-runner-cache/uv:/uv-cache
steps:
# CI_TOKEN needs write:repository scope (not just read) — this job
# pushes a commit and a tag to master, unlike ruff-check/ruff-format/
# type-check/test above, which only need to check out the repo.
- uses: actions/checkout@v4
with:
token: ${{ secrets.CI_TOKEN }}
fetch-depth: 0
- name: Check whether this push is a merge commit
id: merge_check
run: |
PARENTS=$(git rev-parse HEAD^@ | wc -l)
echo "HEAD has $PARENTS parent(s)"
if [ "$PARENTS" -ge 2 ]; then
echo "is_merge=true" >> "$GITHUB_OUTPUT"
else
echo "is_merge=false" >> "$GITHUB_OUTPUT"
fi
- uses: ./.gitea/actions/setup
if: steps.merge_check.outputs.is_merge == 'true'
- name: Configure git identity
if: steps.merge_check.outputs.is_merge == 'true'
run: |
git config user.name "gitea-actions"
git config user.email "actions@git.larsbogner.de"
- name: Build the release commit
if: steps.merge_check.outputs.is_merge == 'true'
run: |
OLD_VERSION=$(git show "${{ github.event.before }}:pyproject.toml" 2>/dev/null | grep -m1 '^version = ' | sed -E 's/version = "(.*)"/\1/')
CURRENT_VERSION=$(uv version --short)
if [ -z "$OLD_VERSION" ]; then
echo "Could not read pyproject.toml at github.event.before; falling back to HEAD^1"
OLD_VERSION=$(git show "HEAD^1:pyproject.toml" | grep -m1 '^version = ' | sed -E 's/version = "(.*)"/\1/')
fi
if [ "$OLD_VERSION" = "$CURRENT_VERSION" ]; then
echo "Version unchanged by this merge ($CURRENT_VERSION); bumping patch"
.gitea/scripts/release-commit.sh
else
echo "Branch already bumped the version ($OLD_VERSION -> $CURRENT_VERSION); building release commit without bumping"
.gitea/scripts/release-commit.sh --no-bump
fi
- name: Push the release commit and its tag together
if: steps.merge_check.outputs.is_merge == 'true'
run: |
VERSION=$(uv version --short)
TAG="v$VERSION"
if git rev-parse "$TAG" >/dev/null 2>&1 && [ "$(git rev-parse "$TAG")" = "$(git rev-parse HEAD)" ]; then
git push --atomic origin HEAD:master "refs/tags/$TAG"
else
echo "No new release commit/tag to push (already released, or nothing changed)"
git push origin HEAD:master
fi
sync-version-on-tag:
name: Sync project version with tag (hand-pushed tags only)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
volumes:
- /srv/act-runner-cache/uv:/uv-cache
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.CI_TOKEN }}
fetch-depth: 0
- name: Require the tagged commit to already be on master
run: |
git fetch origin master
if ! git merge-base --is-ancestor "${{ github.sha }}" origin/master; then
echo "::error::Tag ${GITHUB_REF_NAME} points at a commit not on master; refusing to publish an unreviewed tree. Push the commit to master first, or delete and re-push the tag once it is."
exit 1
fi
- uses: ./.gitea/actions/setup
- name: Check tag against project version, build a release commit if they differ
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
CURRENT_VERSION=$(uv version --short)
if [ "$TAG_VERSION" = "$CURRENT_VERSION" ]; then
echo "Tag version matches project version ($CURRENT_VERSION)"
else
echo "Tag version ($TAG_VERSION) != project version ($CURRENT_VERSION); building a release commit"
git config user.name "gitea-actions"
git config user.email "actions@git.larsbogner.de"
.gitea/scripts/release-commit.sh "$TAG_VERSION"
git push origin HEAD:master
git push origin ":refs/tags/${GITHUB_REF_NAME}"
git tag -f "${GITHUB_REF_NAME}" HEAD
git push origin "refs/tags/${GITHUB_REF_NAME}"
fi
publish-package:
name: Publish package to Gitea package registry
needs: [sync-version-on-tag]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
volumes:
- /srv/act-runner-cache/uv:/uv-cache
steps:
# Check out by tag name (not the triggering SHA) since sync-version-on-tag
# may have force-moved the tag to a version-corrected commit.
- uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
- uses: ./.gitea/actions/setup
- run: uv build
# CI_TOKEN needs write:package scope (in addition to write:repository,
# used elsewhere) for this upload to authenticate.
- run: |
uv publish \
--publish-url "https://git.larsbogner.de/api/packages/lars/pypi" \
--username gitea-actions \
--password "${{ secrets.CI_TOKEN }}"