Skip to content

Commit 5fe64d8

Browse files
authored
chore(release): automate backport processing on PR merge, and have add-backports accept hashtag and url refs (#3897)
Currently, backports must be manually added to the release tracking issue. This change automates the process by allowing maintainers to comment `/backport` on a PR to add it to the active release's backport checklist, and automatically triggering the backport processing when the PR is merged. To support this, the following changes were made: - Added `add-backports` subcommand to the release tool to append PRs to the tracking issue. - Added `on-pr-merged` subcommand to verify `/backport` comment, find the tracking issue, and process backports. - Created `release_add_backports.yaml` reusable workflow to run the `add-backports` subcommand. - Updated `on_comment.yaml` to parse `/backport` comments on PRs and trigger the addition. - Created `on_pr_closed.yaml` to detect merged PRs and run the `on-pr-merged` subcommand. - Added unit tests for the new subcommands and URL resolution.
1 parent f1db067 commit 5fe64d8

21 files changed

Lines changed: 1046 additions & 142 deletions

.github/workflows/on_comment.yaml

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
name: "On Comment"
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
permissions:
8+
contents: write
9+
issues: write
10+
pull-requests: write
11+
12+
jobs:
13+
# This job always runs to prevent GHA from marking the run as failed when
14+
# all other jobs are skipped.
15+
noop:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- run: echo "No-op"
19+
20+
parse_comment:
21+
runs-on: ubuntu-latest
22+
if: |
23+
github.event.comment.author_association == 'OWNER' ||
24+
github.event.comment.author_association == 'MEMBER' ||
25+
github.event.comment.author_association == 'COLLABORATOR'
26+
outputs:
27+
command: ${{ steps.parse.outputs.command }}
28+
issue_number: ${{ steps.parse.outputs.issue_number }}
29+
pr_number: ${{ steps.parse.outputs.pr_number }}
30+
backports: ${{ steps.parse.outputs.backports }}
31+
steps:
32+
- name: Parse comment
33+
id: parse
34+
env:
35+
COMMENT_BODY: ${{ github.event.comment.body }}
36+
IS_PR: "${{ github.event.issue.pull_request != null }}"
37+
EVENT_NUMBER: "${{ github.event.issue.number }}"
38+
HAS_RELEASE_LABEL: "${{ contains(github.event.issue.labels.*.name, 'type: release') }}"
39+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40+
run: |
41+
if [ "$IS_PR" = "false" ] && [ "$HAS_RELEASE_LABEL" = "true" ]; then
42+
issue_number=$EVENT_NUMBER
43+
if echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/create-rc([[:space:]]|$)'; then
44+
echo "command=create-rc" >> "$GITHUB_OUTPUT"
45+
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
46+
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/prepare([[:space:]]|$)'; then
47+
echo "command=prepare" >> "$GITHUB_OUTPUT"
48+
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
49+
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/process-backports([[:space:]]|$)'; then
50+
echo "command=process-backports" >> "$GITHUB_OUTPUT"
51+
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
52+
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/add-backports([[:space:]]|$)'; then
53+
args=$(echo "$COMMENT_BODY" | grep -E '^[[:space:]]*/add-backports([[:space:]]|$)' | sed -E 's/^[[:space:]]*\/add-backports[[:space:]]*//')
54+
args=$(echo "$args" | sed -e 's/^[[:space:],]*//' -e 's/[[:space:],]*$//')
55+
csv=$(echo "$args" | sed -E 's/[[:space:],]+/ /g' | tr ' ' ',')
56+
if [ -n "$csv" ]; then
57+
echo "command=add-backports" >> "$GITHUB_OUTPUT"
58+
echo "backports=$csv" >> "$GITHUB_OUTPUT"
59+
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
60+
else
61+
echo "command=none" >> "$GITHUB_OUTPUT"
62+
echo "Error: No PRs specified for add-backports." >&2
63+
gh api \
64+
--method POST \
65+
-H "Accept: application/vnd.github+json" \
66+
-H "X-GitHub-Api-Version: 2022-11-28" \
67+
/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
68+
-f "content=-1"
69+
fi
70+
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/promote([[:space:]]|$)'; then
71+
echo "command=promote" >> "$GITHUB_OUTPUT"
72+
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
73+
else
74+
echo "command=none" >> "$GITHUB_OUTPUT"
75+
fi
76+
elif [ "$IS_PR" = "true" ]; then
77+
pr_number=$EVENT_NUMBER
78+
if echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/backport([[:space:]]|$)'; then
79+
echo "command=pr-backport" >> "$GITHUB_OUTPUT"
80+
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
81+
else
82+
echo "command=none" >> "$GITHUB_OUTPUT"
83+
fi
84+
else
85+
echo "command=none" >> "$GITHUB_OUTPUT"
86+
fi
87+
88+
call_create_rc:
89+
needs: parse_comment
90+
if: needs.parse_comment.outputs.command == 'create-rc'
91+
uses: ./.github/workflows/release_create_rc.yaml
92+
with:
93+
issue: ${{ needs.parse_comment.outputs.issue_number }}
94+
comment_id: "${{ github.event.comment.id }}"
95+
secrets: inherit
96+
97+
call_prepare:
98+
needs: parse_comment
99+
if: needs.parse_comment.outputs.command == 'prepare'
100+
uses: ./.github/workflows/release_prepare.yaml
101+
with:
102+
issue: ${{ needs.parse_comment.outputs.issue_number }}
103+
secrets: inherit
104+
105+
call_add_backports:
106+
needs: parse_comment
107+
if: |
108+
needs.parse_comment.outputs.command == 'add-backports' ||
109+
needs.parse_comment.outputs.command == 'pr-backport'
110+
uses: ./.github/workflows/release_add_backports.yaml
111+
with:
112+
prs: ${{ needs.parse_comment.outputs.command == 'pr-backport' && needs.parse_comment.outputs.pr_number || needs.parse_comment.outputs.backports }}
113+
issue: ${{ needs.parse_comment.outputs.issue_number }}
114+
secrets: inherit
115+
116+
call_process_backports_after_add:
117+
needs: [parse_comment, call_add_backports]
118+
if: needs.parse_comment.outputs.command == 'add-backports'
119+
uses: ./.github/workflows/release_process_backports.yaml
120+
with:
121+
issue: ${{ needs.parse_comment.outputs.issue_number }}
122+
comment_id: "${{ github.event.comment.id }}"
123+
secrets: inherit
124+
125+
call_process_backports_only:
126+
needs: parse_comment
127+
if: needs.parse_comment.outputs.command == 'process-backports'
128+
uses: ./.github/workflows/release_process_backports.yaml
129+
with:
130+
issue: ${{ needs.parse_comment.outputs.issue_number }}
131+
comment_id: "${{ github.event.comment.id }}"
132+
secrets: inherit
133+
134+
call_promote:
135+
needs: parse_comment
136+
if: needs.parse_comment.outputs.command == 'promote'
137+
uses: ./.github/workflows/release_promote_rc.yaml
138+
with:
139+
issue: ${{ needs.parse_comment.outputs.issue_number }}
140+
secrets: inherit

.github/workflows/on_issue_comment.yaml

Lines changed: 0 additions & 105 deletions
This file was deleted.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: "On PR Closed"
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
7+
permissions:
8+
contents: read
9+
issues: read
10+
pull-requests: read
11+
12+
jobs:
13+
# This job always runs to prevent GHA from marking the run as failed when
14+
# all other jobs are skipped.
15+
noop:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- run: echo "No-op"
19+
20+
check_if_backport:
21+
runs-on: ubuntu-latest
22+
if: github.event.pull_request.merged == true
23+
outputs:
24+
should_process: ${{ steps.check.outputs.should_process }}
25+
steps:
26+
- name: Check if PR is a backport candidate
27+
id: check
28+
env:
29+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
PR_NUMBER: ${{ github.event.pull_request.number }}
31+
run: |
32+
# Check if there is any active release issue
33+
ACTIVE_ISSUES=$(gh issue list --repo ${{ github.repository }} --label "type: release" --state open --json number)
34+
if [ "$ACTIVE_ISSUES" = "[]" ] || [ -z "$ACTIVE_ISSUES" ]; then
35+
echo "No active release tracking issue found. Skipping."
36+
echo "should_process=false" >> "$GITHUB_OUTPUT"
37+
exit 0
38+
fi
39+
40+
# Check if PR has "/backport" in comments (only comments, not body)
41+
PR_DATA=$(gh pr view "$PR_NUMBER" --repo ${{ github.repository }} --json comments)
42+
43+
if echo "$PR_DATA" | jq -r '.comments[].body' | grep -qE '^[[:space:]]*/backport([[:space:]]|$)'; then
44+
echo "Found /backport comment. Proceeding."
45+
echo "should_process=true" >> "$GITHUB_OUTPUT"
46+
else
47+
echo "No /backport comment found. Skipping."
48+
echo "should_process=false" >> "$GITHUB_OUTPUT"
49+
fi
50+
51+
process_backports:
52+
needs: check_if_backport
53+
if: needs.check_if_backport.outputs.should_process == 'true'
54+
runs-on: ubuntu-latest
55+
permissions:
56+
contents: write
57+
issues: write
58+
pull-requests: read
59+
steps:
60+
- name: Checkout repository
61+
uses: actions/checkout@v7
62+
with:
63+
fetch-depth: 0
64+
65+
- name: Setup Bazel
66+
uses: bazel-contrib/setup-bazel@0.19.0
67+
with:
68+
bazelisk-version: 1.20.0
69+
70+
- name: Configure Git Identity
71+
run: |
72+
git config --global user.name "github-actions[bot]"
73+
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
74+
75+
- name: Process Backports
76+
env:
77+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78+
PR_NUMBER: ${{ github.event.pull_request.number }}
79+
run: |
80+
bazel run //tools/private/release -- on-pr-merged \
81+
"$PR_NUMBER" \
82+
--remote origin \
83+
--no-dry-run
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: "Release: Add Backports"
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
prs:
7+
description: 'CSV list of PR numbers to add (e.g., 123,456)'
8+
required: true
9+
type: string
10+
issue:
11+
description: 'The Release Tracking Issue Number (optional)'
12+
required: false
13+
type: string
14+
workflow_call:
15+
inputs:
16+
prs:
17+
description: 'CSV list of PR numbers to add (e.g., 123,456)'
18+
required: true
19+
type: string
20+
issue:
21+
description: 'The Release Tracking Issue Number (optional)'
22+
required: false
23+
type: string
24+
25+
permissions:
26+
issues: write
27+
28+
jobs:
29+
add_backports:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- name: Checkout repository
33+
uses: actions/checkout@v7
34+
with:
35+
fetch-depth: 0
36+
37+
- name: Setup Bazel
38+
uses: bazel-contrib/setup-bazel@0.19.0
39+
with:
40+
bazelisk-version: 1.20.0
41+
42+
- name: Add Backports to Tracking Issue
43+
env:
44+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
PRS: ${{ inputs.prs }}
46+
ISSUE: ${{ inputs.issue }}
47+
run: |
48+
ARGS=()
49+
if [ -n "$ISSUE" ]; then
50+
ARGS+=("--issue=$ISSUE")
51+
fi
52+
53+
# Convert CSV to array
54+
IFS=',' read -r -a pr_array <<< "$PRS"
55+
56+
bazel run //tools/private/release -- add-backports \
57+
"${pr_array[@]}" \
58+
"${ARGS[@]}"

0 commit comments

Comments
 (0)