Skip to content

Commit 65cea34

Browse files
committed
ci(release): require root-owned tags
Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
1 parent 52b1a08 commit 65cea34

1 file changed

Lines changed: 38 additions & 12 deletions

File tree

.github/workflows/release.yml

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
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 }}
@@ -49,6 +52,7 @@ jobs:
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

Comments
 (0)