Skip to content

Commit 5b65628

Browse files
izaitsevfbpytorchmergebot
authored andcommitted
Workflow to tag trunk commits with trunk/{commit-sha} tags (pytorch#155170)
This PR adds workflow to automate tagging commits on the `main` branch. The workflow includes validation and retry with exponential backoff. The rationale for this is to work around the github limitation on using workflow_dispatch (requires branch or tag). We want to use workflow_dispatch to rerun CI workflows with parameters (trunk, pull, etc). --- ### Testing Tested using almost identical workflow in a personal repo (the difference is in repository_owner check and backoff settings). * successful tag push: https://github.com/izaitsevfb/deleteme/actions/runs/15454729765/job/43504630765 * validation: PR commit (fails) https://github.com/izaitsevfb/deleteme/actions/runs/15454743572/job/43504669720 * tagging of the old commit on main: https://github.com/izaitsevfb/deleteme/actions/runs/15453805748/job/43501885903 * tag already exists: https://github.com/izaitsevfb/deleteme/actions/runs/15454756077/job/43504706980 * invalid sha on workflow dispatch: https://github.com/izaitsevfb/deleteme/actions/runs/15454611077/job/43504286858 * retry with exponential backoff on failure (via tag rule blocklist): https://github.com/izaitsevfb/deleteme/actions/runs/15454768346/job/43504743486 Pull Request resolved: pytorch#155170 Approved by: https://github.com/huydhn
1 parent bee9c70 commit 5b65628

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
name: trunk-tagging
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
inputs:
9+
commit_sha:
10+
description: 'Commit SHA to tag (leave empty for current HEAD)'
11+
required: false
12+
type: string
13+
14+
concurrency:
15+
group: trunk-tagging-${{ github.event.inputs.commit_sha || github.sha }}
16+
cancel-in-progress: false
17+
18+
permissions:
19+
contents: write
20+
21+
jobs:
22+
tag-trunk-commit:
23+
name: Tag trunk commit
24+
runs-on: ubuntu-latest
25+
if: github.repository_owner == 'pytorch'
26+
27+
steps:
28+
- name: Pre-checkout validation
29+
run: |
30+
# For workflow_dispatch, validate SHA format before checkout
31+
if [ -n "${{ github.event.inputs.commit_sha }}" ]; then
32+
COMMIT_SHA="${{ github.event.inputs.commit_sha }}"
33+
34+
# Verify it's a well-formed SHA (40 hex characters)
35+
if ! echo "${COMMIT_SHA}" | grep -qE '^[a-f0-9]{40}$'; then
36+
echo "Error: Invalid commit SHA format. Expected 40 hexadecimal characters, got: ${COMMIT_SHA}"
37+
exit 1
38+
fi
39+
40+
echo "✅ Pre-checkout validation passed for: ${COMMIT_SHA}"
41+
else
42+
echo "✅ Using current commit SHA - no pre-checkout validation needed"
43+
fi
44+
45+
- name: Checkout repository
46+
uses: actions/checkout@v4
47+
with:
48+
# Fetch full history to ensure we have all commits
49+
fetch-depth: 0
50+
# For workflow_dispatch, checkout the specified commit
51+
ref: ${{ github.event.inputs.commit_sha || github.sha }}
52+
53+
- name: Set commit SHA
54+
id: commit
55+
run: |
56+
if [ -n "${{ github.event.inputs.commit_sha }}" ]; then
57+
COMMIT_SHA="${{ github.event.inputs.commit_sha }}"
58+
else
59+
COMMIT_SHA="${{ github.sha }}"
60+
fi
61+
echo "sha=${COMMIT_SHA}" >> "${GITHUB_OUTPUT}"
62+
echo "tag_name=trunk/${COMMIT_SHA}" >> "${GITHUB_OUTPUT}"
63+
64+
- name: Validate commit SHA
65+
run: |
66+
COMMIT_SHA="${{ steps.commit.outputs.sha }}"
67+
68+
# Verify the commit exists and is valid
69+
if ! git cat-file -e "${COMMIT_SHA}"; then
70+
echo "Error: Commit SHA ${COMMIT_SHA} does not exist in repository"
71+
exit 1
72+
fi
73+
74+
# For workflow_dispatch, verify the commit exists on main branch
75+
if [ -n "${{ github.event.inputs.commit_sha }}" ]; then
76+
echo "Manual dispatch detected - validating commit is on main branch..."
77+
78+
# Get all commits reachable from main branch
79+
if ! git merge-base --is-ancestor "${COMMIT_SHA}" origin/main; then
80+
echo "Error: Commit ${COMMIT_SHA} is not reachable from main branch"
81+
echo "Only commits that exist on the main branch can be tagged"
82+
exit 1
83+
fi
84+
85+
echo "✅ Commit ${COMMIT_SHA} is valid and exists on main branch"
86+
else
87+
echo "✅ Commit ${COMMIT_SHA} is valid (automatic push trigger)"
88+
fi
89+
90+
- name: Create and push tag with retry
91+
id: check_tag
92+
env:
93+
TAG_NAME: ${{ steps.commit.outputs.tag_name }}
94+
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
95+
run: |
96+
set -e
97+
98+
# Check if tag already exists
99+
check_tag_exists() {
100+
# Check if tag exists locally
101+
if git tag -l "${TAG_NAME}" | grep -q "${TAG_NAME}"; then
102+
echo "Tag ${TAG_NAME} already exists locally"
103+
return 0
104+
fi
105+
106+
# Check if tag exists on remote
107+
if git ls-remote --tags origin "${TAG_NAME}" | grep -q "${TAG_NAME}"; then
108+
echo "Tag ${TAG_NAME} already exists on remote"
109+
return 0
110+
fi
111+
112+
return 1
113+
}
114+
115+
# Exit early if tag already exists
116+
if check_tag_exists; then
117+
echo "✅ Tag already exists - no action needed"
118+
echo "exists=true" >> "${GITHUB_OUTPUT}"
119+
exit 0
120+
fi
121+
122+
echo "Tag ${TAG_NAME} does not exist, proceeding with creation"
123+
124+
# Retry configuration
125+
MAX_RETRIES=5
126+
BASE_DELAY=2
127+
BACKOFF_MULTIPLIER=4
128+
MAX_DELAY=3600
129+
130+
# Common retry function with exponential backoff
131+
retry_with_backoff() {
132+
local command="${1}"
133+
local description="${2}"
134+
local retry_count=0
135+
136+
while [ "${retry_count}" -le "${MAX_RETRIES}" ]; do
137+
echo "Attempt $((retry_count + 1))/$((MAX_RETRIES + 1)): ${description}"
138+
139+
if eval "${command}"; then
140+
echo "Success on attempt $((retry_count + 1))"
141+
return 0
142+
fi
143+
144+
retry_count=$((retry_count + 1))
145+
146+
if [ "${retry_count}" -le "${MAX_RETRIES}" ]; then
147+
# Calculate delay with exponential backoff
148+
local delay=$((BASE_DELAY * (BACKOFF_MULTIPLIER ** retry_count)))
149+
if [ "${delay}" -gt "${MAX_DELAY}" ]; then
150+
delay="${MAX_DELAY}"
151+
fi
152+
153+
echo "Failed. Retrying in ${delay} seconds..."
154+
sleep "${delay}"
155+
fi
156+
done
157+
158+
echo "All retry attempts exhausted"
159+
return 1
160+
}
161+
162+
# Function to create and push tag
163+
create_and_push_tag() {
164+
# Create the tag
165+
if ! git tag "${TAG_NAME}" "${COMMIT_SHA}"; then
166+
echo "Failed to create local tag"
167+
return 1
168+
fi
169+
170+
# Push the tag
171+
if git push origin "${TAG_NAME}"; then
172+
echo "Successfully created and pushed tag ${TAG_NAME}"
173+
return 0
174+
else
175+
echo "Failed to push tag to remote"
176+
# Clean up local tag for retry
177+
git tag -d "${TAG_NAME}" 2>/dev/null || true
178+
return 1
179+
fi
180+
}
181+
182+
# Function to handle retries with race condition checks
183+
tag_with_retry() {
184+
# Check if tag exists before attempting creation
185+
if check_tag_exists; then
186+
echo "Tag ${TAG_NAME} was created by another process, exiting successfully"
187+
return 0
188+
fi
189+
190+
create_and_push_tag || {
191+
# Fetch latest state for next retry
192+
git fetch origin --tags
193+
return 1
194+
}
195+
}
196+
197+
# Execute with retry
198+
if retry_with_backoff "tag_with_retry" "Creating tag ${TAG_NAME} for commit ${COMMIT_SHA}"; then
199+
echo "exists=false" >> "${GITHUB_OUTPUT}"
200+
exit 0
201+
else
202+
echo "Tag creation failed after all retry attempts"
203+
exit 1
204+
fi
205+
206+
- name: Tag creation summary
207+
if: always()
208+
run: |
209+
if [ "${{ steps.check_tag.outputs.exists }}" = "true" ]; then
210+
echo "✅ Tag ${{ steps.commit.outputs.tag_name }} already existed - no action needed"
211+
elif [ "${{ job.status }}" = "success" ]; then
212+
echo "✅ Successfully created tag ${{ steps.commit.outputs.tag_name }} for commit ${{ steps.commit.outputs.sha }}"
213+
else
214+
echo "❌ Failed to create tag ${{ steps.commit.outputs.tag_name }} for commit ${{ steps.commit.outputs.sha }}"
215+
fi
216+
217+
echo ""
218+
echo "Tag details:"
219+
echo " Name: ${{ steps.commit.outputs.tag_name }}"
220+
echo " Commit: ${{ steps.commit.outputs.sha }}"
221+
echo " Trigger: ${{ github.event_name }}"
222+
if [ -n "${{ github.event.inputs.commit_sha }}" ]; then
223+
echo " Manual commit: ${{ github.event.inputs.commit_sha }}"
224+
fi

0 commit comments

Comments
 (0)