Skip to content

Commit d3e0496

Browse files
authored
Switch to push from pull_request_target in backport.yml (#20093)
Signed-off-by: Mohamed Hamza <mhamza@fastmail.com>
1 parent 4a0691d commit d3e0496

1 file changed

Lines changed: 88 additions & 34 deletions

File tree

.github/workflows/backport.yml

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ name: Backport and Forwardport PRs
33
permissions: read-all
44

55
on:
6-
pull_request_target:
7-
types: [closed]
6+
push:
87
branches:
98
- main
109
workflow_dispatch:
@@ -25,7 +24,7 @@ jobs:
2524
permissions:
2625
pull-requests: write
2726
runs-on: ubuntu-latest
28-
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
27+
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push'
2928
steps:
3029
- name: Harden the runner (Audit all outbound calls)
3130
uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
@@ -40,40 +39,91 @@ jobs:
4039

4140
- name: Get GitHub App User ID
4241
id: get-user-id
43-
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
4442
env:
43+
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
4544
GH_TOKEN: ${{ steps.app-token.outputs.token }}
45+
run: |
46+
USER_ID=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
47+
echo "user-id=${USER_ID}" >> "$GITHUB_OUTPUT"
4648
4749
- name: Configure Git
50+
env:
51+
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
52+
APP_USER_ID: ${{ steps.get-user-id.outputs.user-id }}
4853
run: |
49-
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
50-
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
54+
git config --global user.name "${APP_SLUG}[bot]"
55+
git config --global user.email "${APP_USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com"
5156
git config --global merge.conflictStyle zdiff3
5257
5358
- name: Checkout repository
5459
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5560
with:
5661
fetch-depth: 0
62+
persist-credentials: false
5763
token: ${{ steps.app-token.outputs.token }}
5864

5965
- name: Perform backport/forwardport
6066
env:
67+
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
68+
APP_USER_ID: ${{ steps.get-user-id.outputs.user-id }}
69+
EVENT_NAME: ${{ github.event_name }}
6170
GH_TOKEN: ${{ steps.app-token.outputs.token }}
71+
INPUT_DRY_RUN: ${{ inputs.dry_run || 'false' }}
72+
INPUT_PR_NUMBER: ${{ inputs.pr_number || '' }}
6273
run: |
63-
# Determine PR number and dry-run mode based on trigger type
64-
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
65-
PR_NUMBER="${{ inputs.pr_number }}"
66-
DRY_RUN="${{ inputs.dry_run }}"
74+
GIT_AUTHOR="${APP_SLUG}[bot] <${APP_USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com>"
75+
GIT_AUTH_HEADER="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')"
76+
77+
# Determine PR numbers and dry-run mode based on trigger type.
78+
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
79+
PR_NUMBERS=("$INPUT_PR_NUMBER")
80+
DRY_RUN="$INPUT_DRY_RUN"
6781
else
68-
PR_NUMBER="${{ github.event.pull_request.number }}"
82+
# Find the merged main PRs represented by this push so each one can be
83+
# backported or forwardported according to its labels.
84+
PR_NUMBERS_FILE=$(mktemp)
85+
if ! (
86+
set -o pipefail
87+
88+
jq -r '.commits[].id' "$GITHUB_EVENT_PATH" |
89+
while IFS= read -r COMMIT_SHA; do
90+
if [[ ! "$COMMIT_SHA" =~ ^[0-9a-f]{40}$ ]]; then
91+
echo "Skipping unexpected commit SHA: $COMMIT_SHA" >&2
92+
continue
93+
fi
94+
95+
if ! gh api \
96+
-H "Accept: application/vnd.github+json" \
97+
"repos/${GITHUB_REPOSITORY}/commits/${COMMIT_SHA}/pulls" \
98+
--jq '.[] | select(.merged_at != null and .base.ref == "main") | .number'; then
99+
echo "Error: failed to look up pull requests for commit $COMMIT_SHA" >&2
100+
exit 1
101+
fi
102+
done |
103+
sort -n -u > "$PR_NUMBERS_FILE"
104+
); then
105+
rm -f "$PR_NUMBERS_FILE"
106+
exit 1
107+
fi
108+
109+
mapfile -t PR_NUMBERS < "$PR_NUMBERS_FILE"
110+
rm -f "$PR_NUMBERS_FILE"
111+
69112
DRY_RUN="false"
70113
fi
71114
115+
if [ "${#PR_NUMBERS[@]}" -eq 0 ]; then
116+
echo "No merged pull requests found for this push"
117+
exit 0
118+
fi
119+
72120
if [ "$DRY_RUN" = "true" ]; then
73121
echo "🔍 DRY RUN MODE ENABLED - No changes will be made"
74122
echo "=================================================="
75123
fi
76124
125+
for PR_NUMBER in "${PR_NUMBERS[@]}"; do
126+
77127
# Fetch PR details from API
78128
PR_DATA=$(gh pr view "$PR_NUMBER" --json number,title,author,mergeCommit,state)
79129
@@ -97,8 +147,10 @@ jobs:
97147
LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name')
98148
99149
# Extract backport branches
100-
BACKPORT_BRANCHES=$(echo "$LABELS" | grep "^Backport to: " | sed 's/^Backport to: //' || true)
101-
FORWARDPORT_BRANCHES=$(echo "$LABELS" | grep "^Forwardport to: " | sed 's/^Forwardport to: //' || true)
150+
BACKPORT_BRANCHES=$(echo "$LABELS" | grep "^Backport to: " || true)
151+
BACKPORT_BRANCHES=${BACKPORT_BRANCHES//Backport to: /}
152+
FORWARDPORT_BRANCHES=$(echo "$LABELS" | grep "^Forwardport to: " || true)
153+
FORWARDPORT_BRANCHES=${FORWARDPORT_BRANCHES//Forwardport to: /}
102154
103155
# Extract other labels (excluding backport/forwardport labels)
104156
OTHER_LABELS=$(echo "$LABELS" | grep -v "^Backport to: " | grep -v "^Forwardport to: " | jq -R -s -c 'split("\n") | map(select(length > 0))' || echo '[]')
@@ -150,7 +202,7 @@ jobs:
150202
git add .
151203
152204
# Commit with conflict message, setting bot as author
153-
git commit --author='${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' -m "Cherry-pick $MERGE_COMMIT_SHA with conflicts" || {
205+
git commit --author="$GIT_AUTHOR" -m "Cherry-pick $MERGE_COMMIT_SHA with conflicts" || {
154206
echo "Error: Failed to commit conflicts"
155207
git checkout main
156208
git branch -D "$NEW_BRANCH" 2>/dev/null || true
@@ -179,7 +231,7 @@ jobs:
179231
echo " Command: git push -f origin $NEW_BRANCH"
180232
NEW_PR_NUMBER="<dry-run>"
181233
else
182-
git push -f origin "$NEW_BRANCH" || {
234+
git -c http.https://github.com/.extraheader="$GIT_AUTH_HEADER" push -f origin "$NEW_BRANCH" || {
183235
echo "Error: Failed to push branch $NEW_BRANCH"
184236
git checkout main
185237
git branch -D "$NEW_BRANCH" 2>/dev/null || true
@@ -207,15 +259,13 @@ jobs:
207259
208260
echo " Creating pull request..."
209261
# Create the pull request with labels (returns URL like https://github.com/owner/repo/pull/123)
210-
PR_URL=$(gh pr create \
262+
if ! PR_URL=$(gh pr create \
211263
--title "[$BRANCH] $PR_TITLE (#$PR_NUMBER)" \
212264
--body "$PR_BODY" \
213265
--base "$BRANCH" \
214266
--head "$NEW_BRANCH" \
215267
--label "$LABELS_LIST" \
216-
$DRAFT_FLAG 2>&1)
217-
218-
if [ $? -ne 0 ] || [ -z "$PR_URL" ]; then
268+
$DRAFT_FLAG 2>&1) || [ -z "$PR_URL" ]; then
219269
echo "Error: Failed to create PR for branch $NEW_BRANCH"
220270
echo "$PR_URL"
221271
git checkout main
@@ -250,7 +300,7 @@ jobs:
250300
else
251301
echo " Draft: false"
252302
fi
253-
echo " Repository: ${{ github.repository }}"
303+
echo " Repository: $GITHUB_REPOSITORY"
254304
echo ""
255305
echo " [DRY RUN] Would add labels:"
256306
echo "$LABELS_JSON" | jq -r '.[]' | while read -r label; do
@@ -262,7 +312,7 @@ jobs:
262312
if [ "$CONFLICT" = true ]; then
263313
CONFLICT_COMMENT="Hello @${PR_AUTHOR}, there are conflicts in this ${PORT_TYPE}."$'\n\n'
264314
CONFLICT_COMMENT+="Please address them in order to merge this Pull Request. You can execute the snippet below to reset your branch and resolve the conflict manually."$'\n\n'
265-
CONFLICT_COMMENT+="Make sure you replace \`origin\` by the name of the ${{ github.repository_owner }}/${{ github.event.repository.name }} remote"$'\n'
315+
CONFLICT_COMMENT+="Make sure you replace \`origin\` by the name of the ${GITHUB_REPOSITORY} remote"$'\n'
266316
CONFLICT_COMMENT+="\`\`\`"$'\n'
267317
CONFLICT_COMMENT+="git fetch --all"$'\n'
268318
CONFLICT_COMMENT+="gh pr checkout ${NEW_PR_NUMBER}"$'\n'
@@ -274,7 +324,9 @@ jobs:
274324
echo ""
275325
echo " [DRY RUN] Would add conflict resolution comment:"
276326
echo " ------------------------------------------------"
277-
echo "$CONFLICT_COMMENT" | sed 's/^/ /'
327+
while IFS= read -r line; do
328+
echo " $line"
329+
done <<< "$CONFLICT_COMMENT"
278330
else
279331
if ! OUTPUT=$(gh pr comment "$NEW_PR_NUMBER" --body "$CONFLICT_COMMENT" 2>&1); then
280332
echo "Warning: Could not add conflict resolution comment"
@@ -284,7 +336,7 @@ jobs:
284336
fi
285337
286338
# Get reviewers from original PR and build JSON array
287-
REVIEWERS_JSON=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/requested_reviewers" \
339+
REVIEWERS_JSON=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/requested_reviewers" \
288340
--jq '[.users[].login, .teams[].slug] + ["'"$PR_AUTHOR"'"]' 2>/dev/null || echo '["'"$PR_AUTHOR"'"]')
289341
290342
if [ "$DRY_RUN" = "true" ]; then
@@ -346,7 +398,7 @@ jobs:
346398
git add .
347399
348400
# Commit with conflict message, setting bot as author
349-
git commit --author='${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' -m "Cherry-pick $MERGE_COMMIT_SHA with conflicts" || {
401+
git commit --author="$GIT_AUTHOR" -m "Cherry-pick $MERGE_COMMIT_SHA with conflicts" || {
350402
echo "Error: Failed to commit conflicts"
351403
git checkout main
352404
git branch -D "$NEW_BRANCH" 2>/dev/null || true
@@ -375,7 +427,7 @@ jobs:
375427
echo " Command: git push -f origin $NEW_BRANCH"
376428
NEW_PR_NUMBER="<dry-run>"
377429
else
378-
git push -f origin "$NEW_BRANCH" || {
430+
git -c http.https://github.com/.extraheader="$GIT_AUTH_HEADER" push -f origin "$NEW_BRANCH" || {
379431
echo "Error: Failed to push branch $NEW_BRANCH"
380432
git checkout main
381433
git branch -D "$NEW_BRANCH" 2>/dev/null || true
@@ -403,15 +455,13 @@ jobs:
403455
404456
echo " Creating pull request..."
405457
# Create the pull request with labels (returns URL like https://github.com/owner/repo/pull/123)
406-
PR_URL=$(gh pr create \
458+
if ! PR_URL=$(gh pr create \
407459
--title "[$BRANCH] $PR_TITLE (#$PR_NUMBER)" \
408460
--body "$PR_BODY" \
409461
--base "$BRANCH" \
410462
--head "$NEW_BRANCH" \
411463
--label "$LABELS_LIST" \
412-
$DRAFT_FLAG 2>&1)
413-
414-
if [ $? -ne 0 ] || [ -z "$PR_URL" ]; then
464+
$DRAFT_FLAG 2>&1) || [ -z "$PR_URL" ]; then
415465
echo "Error: Failed to create PR for branch $NEW_BRANCH"
416466
echo "$PR_URL"
417467
git checkout main
@@ -446,7 +496,7 @@ jobs:
446496
else
447497
echo " Draft: false"
448498
fi
449-
echo " Repository: ${{ github.repository }}"
499+
echo " Repository: $GITHUB_REPOSITORY"
450500
echo ""
451501
echo " [DRY RUN] Would add labels:"
452502
echo "$LABELS_JSON" | jq -r '.[]' | while read -r label; do
@@ -458,10 +508,10 @@ jobs:
458508
if [ "$CONFLICT" = true ]; then
459509
CONFLICT_COMMENT="Hello @${PR_AUTHOR}, there are conflicts in this ${PORT_TYPE}."$'\n\n'
460510
CONFLICT_COMMENT+="Please address them in order to merge this Pull Request. You can execute the snippet below to reset your branch and resolve the conflict manually."$'\n\n'
461-
CONFLICT_COMMENT+="Make sure you replace \`origin\` by the name of the ${{ github.repository_owner }}/${{ github.event.repository.name }} remote"$'\n'
511+
CONFLICT_COMMENT+="Make sure you replace \`origin\` by the name of the ${GITHUB_REPOSITORY} remote"$'\n'
462512
CONFLICT_COMMENT+="\`\`\`"$'\n'
463513
CONFLICT_COMMENT+="git fetch --all"$'\n'
464-
CONFLICT_COMMENT+="gh pr checkout ${NEW_PR_NUMBER} -R ${{ github.repository }}"$'\n'
514+
CONFLICT_COMMENT+="gh pr checkout ${NEW_PR_NUMBER} -R ${GITHUB_REPOSITORY}"$'\n'
465515
CONFLICT_COMMENT+="git reset --hard origin/${BRANCH}"$'\n'
466516
CONFLICT_COMMENT+="git cherry-pick -m 1 ${MERGE_COMMIT_SHA}"$'\n'
467517
CONFLICT_COMMENT+="\`\`\`"
@@ -470,7 +520,9 @@ jobs:
470520
echo ""
471521
echo " [DRY RUN] Would add conflict resolution comment:"
472522
echo " ------------------------------------------------"
473-
echo "$CONFLICT_COMMENT" | sed 's/^/ /'
523+
while IFS= read -r line; do
524+
echo " $line"
525+
done <<< "$CONFLICT_COMMENT"
474526
else
475527
if ! OUTPUT=$(gh pr comment "$NEW_PR_NUMBER" --body "$CONFLICT_COMMENT" 2>&1); then
476528
echo "Warning: Could not add conflict resolution comment"
@@ -480,7 +532,7 @@ jobs:
480532
fi
481533
482534
# Get reviewers from original PR and build JSON array
483-
REVIEWERS_JSON=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/requested_reviewers" \
535+
REVIEWERS_JSON=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/requested_reviewers" \
484536
--jq '[.users[].login, .teams[].slug] + ["'"$PR_AUTHOR"'"]' 2>/dev/null || echo '["'"$PR_AUTHOR"'"]')
485537
486538
if [ "$DRY_RUN" = "true" ]; then
@@ -501,3 +553,5 @@ jobs:
501553
# Return to main branch for next iteration
502554
git checkout main
503555
done <<< "$FORWARDPORT_BRANCHES"
556+
557+
done

0 commit comments

Comments
 (0)