Skip to content

Commit a66eeda

Browse files
Add github workflows to automate IFU (#2688)
Fixes #ISSUE_NUMBER --------- Co-authored-by: Jithun Nair <[email protected]>
1 parent d6ae9ac commit a66eeda

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: Create git tags for IFU PRs
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
7+
permissions:
8+
contents: write # create/push tags
9+
pull-requests: write # edit PR body
10+
11+
jobs:
12+
tag-ifu:
13+
# Only proceed if: merged AND title has both markers
14+
if: >
15+
github.event.pull_request.merged == true &&
16+
contains(github.event.pull_request.title, '[AUTOGENERATED]') &&
17+
contains(github.event.pull_request.title, 'IFU')
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- name: Checkout base repo (full history)
22+
uses: actions/checkout@v4
23+
with:
24+
ref: ${{ github.event.pull_request.base.ref }}
25+
fetch-depth: 0
26+
27+
- name: Configure Git user
28+
run: |
29+
git config user.name "github-actions[bot]"
30+
git config user.email "github-actions[bot]@users.noreply.github.com"
31+
32+
- name: Derive key SHAs (rocm base, upstream main, merge)
33+
id: shas
34+
shell: bash
35+
run: |
36+
set -euo pipefail
37+
38+
PR_NUM="${{ github.event.pull_request.number }}"
39+
BASE_REF="${{ github.event.pull_request.base.ref }}"
40+
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
41+
MERGE_SHA="${{ github.event.pull_request.merge_commit_sha }}"
42+
43+
# The ROCm base commit is the first parent of the merge commit that landed the PR
44+
# (i.e., the base branch tip BEFORE this PR merged).
45+
ROCM_BASE_SHA=$(git rev-parse "${MERGE_SHA}^1")
46+
47+
# Add and fetch upstream to identify the upstream/main commit that HEAD integrated.
48+
git remote add upstream "https://github.com/pytorch/pytorch.git"
49+
git fetch upstream "$BASE_REF"
50+
51+
# Heuristic: the upstream commit integrated by the PR's head is the merge-base
52+
# between the PR head commit and upstream/main as fetched now.
53+
# This gives you the exact upstream commit (or the best common ancestor) that HEAD included.
54+
UPSTREAM_MAIN_SHA=$(git merge-base "${HEAD_SHA}" "upstream/$BASE_REF")
55+
echo "PR_NUM=$PR_NUM"
56+
echo "BASE_REF=$BASE_REF"
57+
echo "HEAD_SHA=$HEAD_SHA"
58+
echo "MERGE_SHA=$MERGE_SHA"
59+
echo "ROCM_BASE_SHA=$ROCM_BASE_SHA"
60+
echo "UPSTREAM_MAIN_SHA=$UPSTREAM_MAIN_SHA"
61+
62+
63+
echo "PR_NUM=$PR_NUM" >> "$GITHUB_OUTPUT"
64+
echo "BASE_REF=$BASE_REF" >> "$GITHUB_OUTPUT"
65+
echo "HEAD_SHA=$HEAD_SHA" >> "$GITHUB_OUTPUT"
66+
echo "MERGE_SHA=$MERGE_SHA" >> "$GITHUB_OUTPUT"
67+
echo "ROCM_BASE_SHA=$ROCM_BASE_SHA" >> "$GITHUB_OUTPUT"
68+
echo "UPSTREAM_MAIN_SHA=$UPSTREAM_MAIN_SHA" >> "$GITHUB_OUTPUT"
69+
70+
- name: Extract tag base from PR title
71+
id: tagname
72+
run: |
73+
TITLE="${{ github.event.pull_request.title }}"
74+
# Remove everything up to and including "[AUTOGENERATED]"
75+
BASE_TAG=$(echo "$TITLE" | sed -E 's/^\[AUTOGENERATED\][[:space:]]*//')
76+
77+
echo "BASE_TAG=$BASE_TAG"
78+
echo "PRE_TAG=${BASE_TAG}_pre"
79+
echo "POST_TAG=${BASE_TAG}_post"
80+
81+
echo "BASE_TAG=$BASE_TAG" >> $GITHUB_OUTPUT
82+
echo "PRE_TAG=${BASE_TAG}_pre" >> $GITHUB_OUTPUT
83+
echo "POST_TAG=${BASE_TAG}_post" >> $GITHUB_OUTPUT
84+
85+
- name: Create pre/post tags
86+
shell: bash
87+
run: |
88+
set -euo pipefail
89+
echo "Tagging:"
90+
echo " ${{ steps.tagname.outputs.PRE_TAG }} @ ${{ steps.shas.outputs.ROCM_BASE_SHA }}"
91+
echo " ${{ steps.tagname.outputs.POST_TAG }} @ ${{ steps.shas.outputs.MERGE_SHA }}"
92+
93+
git tag -a "${{ steps.tagname.outputs.PRE_TAG }}" -m "IFU pre (PR #${{ steps.shas.outputs.PR_NUM }})" "${{ steps.shas.outputs.ROCM_BASE_SHA }}"
94+
git tag -a "${{ steps.tagname.outputs.POST_TAG }}" -m "IFU post (PR #${{ steps.shas.outputs.PR_NUM }})" "${{ steps.shas.outputs.MERGE_SHA }}"
95+
96+
#Force pushing is safe. If we land a new PR, we'd wanna retag a commit if we have to.
97+
git push origin "refs/tags/${{ steps.tagname.outputs.PRE_TAG }}" -f
98+
git push origin "refs/tags/${{ steps.tagname.outputs.POST_TAG }}" -f
99+
100+
- name: Append rocm_base & upstream_main to PR body
101+
env:
102+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
103+
shell: bash
104+
run: |
105+
set -euo pipefail
106+
# Read current body
107+
PR="${{ steps.shas.outputs.PR_NUM }}"
108+
CURR=$(gh api repos/${{ github.repository }}/pulls/$PR --jq .body)
109+
APPEND=$'\n'"rocm_base: ${{ steps.shas.outputs.ROCM_BASE_SHA }}"$'\n'"upstream_main: ${{ steps.shas.outputs.UPSTREAM_MAIN_SHA }}"$'\n'
110+
NEW_BODY="${CURR}${APPEND}"
111+
112+
# Write to a temp file and update PR body
113+
printf '%s' "$NEW_BODY" > body.txt
114+
gh api --method PATCH -H "Accept: application/vnd.github+json" \
115+
repos/${{ github.repository }}/pulls/$PR -F [email protected]
116+

.github/workflows/pytorch_ifu.yml

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
name: PyTorch IFU (Sync with upstream)
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
ifu_target_repo:
7+
description: "Target repo for IFU"
8+
required: false
9+
default: "ROCm/pytorch"
10+
type: string
11+
ifu_target_branch:
12+
description: "Target branch for IFU"
13+
required: true
14+
default: "rocm7.1_internal_testing"
15+
type: string
16+
ifu_source_repo:
17+
description: "Source repo for IFU"
18+
required: false
19+
default: "pytorch/pytorch"
20+
type: string
21+
ifu_source_branch:
22+
description: "Source branch for IFU"
23+
required: false
24+
default: "main"
25+
type: string
26+
# schedule:
27+
# # Runs every 14 days at 09:00 AM UTC/ 04:00 AM CST
28+
# - cron: "0 9 */14 * *"
29+
30+
permissions:
31+
contents: write # push branches/tags
32+
pull-requests: write # create PRs
33+
34+
concurrency:
35+
group: ifu
36+
# If two jobs are running simultaneously, we will queue them (not cancel the one running)
37+
cancel-in-progress: false
38+
39+
jobs:
40+
ifu:
41+
runs-on: ubuntu-latest
42+
env:
43+
UPSTREAM_REMOTE: upstream # IFU source remote name
44+
UPSTREAM_REPO: ${{ inputs.ifu_source_repo }} # source repo for IFU
45+
UPSTREAM_BRANCH: ${{ inputs.ifu_source_branch }} # source branch for IFU
46+
DOWNSTREAM_REMOTE: origin # IFU target remote name
47+
DOWNSTREAM_REPO: ${{ inputs.ifu_target_repo }} # target repo for IFU (fork); actions/checkout sets this to origin
48+
DOWNSTREAM_BRANCH: ${{ inputs.ifu_target_branch }} # target branch for IFU
49+
GH_TOKEN: ${{ secrets.PG_GITHUB_TOKEN }} # used by gh; provided by Action @TODO remove PG before merging
50+
steps:
51+
- name: Checkout repository (${{ env.DOWNSTREAM_REPO }}) (full history)
52+
uses: actions/checkout@v4
53+
with:
54+
repository: ${{ env.DOWNSTREAM_REPO }}
55+
path: ${{ env.DOWNSTREAM_REPO }}
56+
ref: ${{ env.DOWNSTREAM_BRANCH }}
57+
token: ${{ env.GH_TOKEN }}
58+
fetch-depth: 0 # need full history for merges/tags
59+
submodules: recursive
60+
61+
- name: Add upstream remote (${{ env.UPSTREAM_REPO }})
62+
working-directory: ${{ env.DOWNSTREAM_REPO }}
63+
run: |
64+
if ! git remote get-url ${UPSTREAM_REMOTE} >/dev/null 2>&1; then
65+
git remote add ${UPSTREAM_REMOTE} https://github.com/${UPSTREAM_REPO}.git
66+
fi
67+
# Confirm remotes
68+
git remote -v
69+
70+
- name: Configure Git user
71+
working-directory: ${{ env.DOWNSTREAM_REPO }}
72+
run: |
73+
git config user.name "github-actions[bot]"
74+
git config user.email "github-actions[bot]@users.noreply.github.com"
75+
76+
- name: Fetch upstream and local branch
77+
working-directory: ${{ env.DOWNSTREAM_REPO }}
78+
run: |
79+
git fetch ${UPSTREAM_REMOTE} ${UPSTREAM_BRANCH}
80+
git fetch ${DOWNSTREAM_REMOTE} ${DOWNSTREAM_BRANCH}
81+
82+
- name: Compute date tag and create working branch
83+
working-directory: ${{ env.DOWNSTREAM_REPO }}
84+
id: tag
85+
shell: bash
86+
run: |
87+
DATE="$(date +"%Y-%m-%d")"
88+
TAG="${DOWNSTREAM_BRANCH}_IFU_${DATE}"
89+
echo "TAG=${TAG}" >> $GITHUB_OUTPUT
90+
# Start from rocm branch
91+
git checkout -b "$TAG" "${DOWNSTREAM_REMOTE}/${DOWNSTREAM_BRANCH}"
92+
93+
- name: Save ROCm base commit
94+
working-directory: ${{ env.DOWNSTREAM_REPO }}
95+
id: rocm_base
96+
run: |
97+
base_commit=`git rev-parse --short HEAD`
98+
echo "ROCM_BASE_COMMIT=$base_commit" >> $GITHUB_OUTPUT
99+
100+
- name: Merge upstream into working branch (non-interactive)
101+
working-directory: ${{ env.DOWNSTREAM_REPO }}
102+
id: merge
103+
run: |
104+
if git merge "${UPSTREAM_REMOTE}/${UPSTREAM_BRANCH}" --no-edit; then
105+
echo "merge_status=clean" >> $GITHUB_OUTPUT
106+
else
107+
echo "Merge conflicts detected. Committing current resolution snapshot."
108+
git submodule sync
109+
git submodule update --init --recursive
110+
git add -A
111+
git status
112+
git commit --no-edit
113+
echo "merge_status=conflict" >> $GITHUB_OUTPUT
114+
fi
115+
116+
- name: Push branch & tag to fork
117+
working-directory: ${{ env.DOWNSTREAM_REPO }}
118+
run: |
119+
git push ${DOWNSTREAM_REMOTE} "${{ steps.tag.outputs.TAG }}"
120+
121+
- name: Authenticate gh (non-interactive)
122+
working-directory: ${{ env.DOWNSTREAM_REPO }}
123+
run: |
124+
# The GitHub-hosted runner has gh preinstalled.
125+
gh auth status || echo "$GH_TOKEN" | gh auth login --with-token
126+
gh repo set-default "${{ env.DOWNSTREAM_REPO }}"
127+
128+
- name: Create Pull Request with gh
129+
working-directory: ${{ env.DOWNSTREAM_REPO }}
130+
run: |
131+
BASE="${DOWNSTREAM_BRANCH}"
132+
HEAD="${{ steps.tag.outputs.TAG }}"
133+
TITLE="[AUTOGENERATED] $HEAD"
134+
BODY="rocm_base: ${{ steps.rocm_base.outputs.ROCM_BASE_COMMIT }}"
135+
136+
# If a PR for this head already exists, skip creating a new one
137+
if gh pr list --head "$HEAD" --base "$BASE" --state all --json number | grep -q '[0-9]'; then
138+
echo "PR already exists for $HEAD -> $BASE. Skipping creation."
139+
else
140+
gh pr create --base "$BASE" --head "$HEAD" --title "$TITLE" --body "$BODY"
141+
fi
142+
143+
- name: Summarize
144+
run: |
145+
echo "::notice title=IFU Completed::Branch ${{ steps.tag.outputs.TAG }} pushed. PR created (or already existed). Merge status: ${{ steps.merge.outputs.merge_status }}"

0 commit comments

Comments
 (0)