Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/scripts/require-floor-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail

WORKFLOW="scheduled.yml"
CANDIDATE_LIMIT=30

tag_sha=$(git rev-parse HEAD)
tag_name=${GITHUB_REF_NAME:-$tag_sha}

# "The most recent run is green" would not have held 1.8.0 back: the newest floors run at that
# moment WAS green, because it predated the commit that broke the floor. So the run has to have
# tested code that already contains this tag, i.e. the tagged commit is an ancestor of (or equal
# to) the commit the floors ran on.
candidate_shas=$(
gh run list --workflow "$WORKFLOW" --status success --limit "$CANDIDATE_LIMIT" --json headSha --jq '.[].headSha'
)

while read -r candidate_sha; do
[ -n "$candidate_sha" ] || continue
# A run dispatched by hand counts as much as a scheduled one; it verifies the same thing. Its
# commit may not be here yet, and may not be fetchable at all if its branch is gone.
git cat-file -e "${candidate_sha}^{commit}" 2>/dev/null \
|| git fetch --quiet --no-tags origin "$candidate_sha" 2>/dev/null \
|| continue
if git merge-base --is-ancestor "$tag_sha" "$candidate_sha"; then
echo "Dependency floors verified by $WORKFLOW at $candidate_sha, which contains $tag_sha."
exit 0
fi
done <<< "$candidate_shas"

cat >&2 <<MSG
::error::No successful $WORKFLOW run has tested the code in $tag_name, so the declared dependency
floors are unverified. Checked the last $CANDIDATE_LIMIT successful runs. Nothing was published.

Run the floors against this commit, then re-push the tag once they are green:

gh workflow run $WORKFLOW --ref $tag_sha
gh run watch "\$(gh run list --workflow $WORKFLOW --limit 1 --json databaseId --jq '.[0].databaseId')" --exit-status
git push --delete origin $tag_name && git push origin $tag_name
MSG
exit 1
30 changes: 25 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,42 @@ name: Release
# GitHub Release. Replaces the old `on: release: published` publish.yml — that
# trigger is removed so the published Release this workflow creates can't re-fire
# it (double-publish). The tag is the sole entry point; by convention a tag is
# only cut off a green main, so there is no in-workflow CI gate.
# only cut off a green main, so the sole in-workflow gate is floors-gate below,
# covering the one check that never runs on pull requests.
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+' # stable: 2.7.2
- '[0-9]+.[0-9]+.[0-9]+[a-z]+[0-9]+' # pre-release: 2.0.0rc1, 4.0.0a2

# contents: write -> create the GitHub Release; id-token: write -> OIDC for PyPI Trusted Publishing.
permissions:
contents: write
id-token: write
permissions: {}

jobs:
# Gates the tag on the dependency floors, which _checks.yml runs only for scheduled.yml, never
# for pull requests. A floor break can therefore merge with every check green, which is how the
# crash in 1.8.0 reached PyPI (#245). Placed before release so a failure costs nothing.
floors-gate:
runs-on: ubuntu-latest
# contents: read -> the history the ancestor check walks; actions: read -> list workflow runs.
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # the ancestor check needs history, not just the tagged commit
- run: .github/scripts/require-floor-run.sh
env:
GH_TOKEN: ${{ github.token }}

release:
needs: floors-gate
runs-on: ubuntu-latest
environment: pypi # scopes the PyPI Trusted Publisher; hook for approval rules
# contents: write -> create the GitHub Release; id-token: write -> OIDC for PyPI Trusted Publishing.
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
- uses: extractions/setup-just@v4
Expand Down
Loading