77 workflow_dispatch :
88 inputs :
99 version :
10- description : " Version to release from VERSION (numeric SemVer X.Y.Z). Creates the tag when dispatching manually ."
10+ description : " Numeric SemVer X.Y.Z; manual dispatch only verifies an existing tag ."
1111 required : true
1212 type : string
1313
@@ -34,13 +34,16 @@ jobs:
3434 egress-policy : audit
3535 - name : Checkout
3636 uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
37+ with :
38+ fetch-depth : 0
39+ persist-credentials : false
3740 - name : Set up Python
3841 uses : actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
3942 with :
4043 python-version : " 3.13"
4144 - name : Install validation dependencies
4245 run : python3 -m pip install --user "pytest==9.1.1" "pytest-cov==7.1.0"
43- - name : Resolve release version
46+ - name : Resolve existing release tag
4447 env :
4548 INPUT_VERSION : ${{ inputs.version }}
4649 EVENT_NAME : ${{ github.event_name }}
4952 set -euo pipefail
5053 if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
5154 version="$INPUT_VERSION"
55+ echo "manual dispatch only verifies an existing tag"
5256 else
5357 version="$REF_NAME"
5458 fi
@@ -61,19 +65,41 @@ jobs:
6165 echo "release version '$version' does not match VERSION '$file_version'" >&2
6266 exit 1
6367 fi
68+
69+ remote_ref="refs/tags/${version}"
70+ remote_match="$(git ls-remote --refs origin "$remote_ref")"
71+ remote_count="$(printf '%s\n' "$remote_match" | awk 'NF { count++ } END { print count + 0 }')"
72+ if [ "$remote_count" -ne 1 ]; then
73+ echo "Expected exactly one existing remote tag $remote_ref" >&2
74+ exit 1
75+ fi
76+ read -r remote_oid remote_name <<< "$remote_match"
77+ if [ "$remote_name" != "$remote_ref" ]; then
78+ echo "Remote returned unexpected ref $remote_name" >&2
79+ exit 1
80+ fi
81+ git fetch --no-tags --force origin "$remote_ref:$remote_ref"
82+ local_oid="$(git rev-parse --verify "$remote_ref")"
83+ if [ "$local_oid" != "$remote_oid" ]; then
84+ echo "Fetched tag object does not match $remote_ref on origin" >&2
85+ exit 1
86+ fi
87+ tag_commit="$(git rev-parse --verify "${remote_ref}^{commit}")"
88+
89+ git fetch --no-tags --force origin main:refs/remotes/origin/main
90+ if [ "$EVENT_NAME" = "push" ]; then
91+ git merge-base --is-ancestor "$GITHUB_SHA" origin/main
92+ if [ "$tag_commit" != "$GITHUB_SHA" ]; then
93+ echo "Pushed tag peels to $tag_commit, not event commit $GITHUB_SHA" >&2
94+ exit 1
95+ fi
96+ else
97+ git merge-base --is-ancestor "$tag_commit" origin/main
98+ fi
99+ git checkout --detach "$tag_commit"
64100 printf 'RELEASE_VERSION=%s\n' "$version" >> "$GITHUB_ENV"
65101 - name : Validate release
66102 run : scripts/validate_release.sh
67- - name : Create release tag (workflow_dispatch only)
68- if : github.event_name == 'workflow_dispatch'
69- run : |
70- set -euo pipefail
71- if git rev-parse -q --verify "refs/tags/${RELEASE_VERSION}" >/dev/null; then
72- echo "Tag ${RELEASE_VERSION} already exists locally"
73- else
74- git tag "${RELEASE_VERSION}"
75- git push origin "refs/tags/${RELEASE_VERSION}"
76- fi
77103 - name : Build source archive
78104 run : |
79105 set -euo pipefail
0 commit comments