Skip to content

Commit 726a2fb

Browse files
feat: add manual dispatch to backport workflow (#5651)
Enables manual backport triggering for scenarios where labels are added after PR merge. Adds workflow_dispatch trigger to the backport workflow with support for: - Specifying PR number to backport post-merge - Force rerun option to override duplicate detection - Proper handling of multi-version backport scenarios Solves the issue where adding version labels (e.g., 1.27) after a PR is already merged and backported (e.g., to 1.26) would not trigger additional backports. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5651-feat-add-manual-dispatch-to-backport-workflow-2736d73d365081b6ba00c7a43c9ba06b) by [Unito](https://www.unito.io)
1 parent 553b5aa commit 726a2fb

File tree

1 file changed

+96
-13
lines changed

1 file changed

+96
-13
lines changed

.github/workflows/backport.yaml

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,61 @@ on:
44
pull_request_target:
55
types: [closed, labeled]
66
branches: [main]
7+
workflow_dispatch:
8+
inputs:
9+
pr_number:
10+
description: 'PR number to backport'
11+
required: true
12+
type: string
13+
force_rerun:
14+
description: 'Force rerun even if backports exist'
15+
required: false
16+
type: boolean
17+
default: false
718

819
jobs:
920
backport:
10-
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'needs-backport')
21+
if: >
22+
(github.event_name == 'pull_request_target' &&
23+
github.event.pull_request.merged == true &&
24+
contains(github.event.pull_request.labels.*.name, 'needs-backport')) ||
25+
github.event_name == 'workflow_dispatch'
1126
runs-on: ubuntu-latest
1227
permissions:
1328
contents: write
1429
pull-requests: write
1530
issues: write
1631

1732
steps:
33+
- name: Validate inputs for manual triggers
34+
if: github.event_name == 'workflow_dispatch'
35+
run: |
36+
# Validate PR number format
37+
if ! [[ "${{ inputs.pr_number }}" =~ ^[0-9]+$ ]]; then
38+
echo "::error::Invalid PR number format. Must be a positive integer."
39+
exit 1
40+
fi
41+
42+
# Validate PR exists and is merged
43+
if ! gh pr view "${{ inputs.pr_number }}" --json merged >/dev/null 2>&1; then
44+
echo "::error::PR #${{ inputs.pr_number }} not found or inaccessible."
45+
exit 1
46+
fi
47+
48+
MERGED=$(gh pr view "${{ inputs.pr_number }}" --json merged --jq '.merged')
49+
if [ "$MERGED" != "true" ]; then
50+
echo "::error::PR #${{ inputs.pr_number }} is not merged. Only merged PRs can be backported."
51+
exit 1
52+
fi
53+
54+
# Validate PR has needs-backport label
55+
if ! gh pr view "${{ inputs.pr_number }}" --json labels --jq '.labels[].name' | grep -q "needs-backport"; then
56+
echo "::error::PR #${{ inputs.pr_number }} does not have 'needs-backport' label."
57+
exit 1
58+
fi
59+
env:
60+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61+
1862
- name: Checkout repository
1963
uses: actions/checkout@v4
2064
with:
@@ -29,7 +73,7 @@ jobs:
2973
id: check-existing
3074
env:
3175
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32-
PR_NUMBER: ${{ github.event.pull_request.number }}
76+
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
3377
run: |
3478
# Check for existing backport PRs for this PR number
3579
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
@@ -39,6 +83,13 @@ jobs:
3983
exit 0
4084
fi
4185
86+
# For manual triggers with force_rerun, proceed anyway
87+
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force_rerun }}" = "true" ]; then
88+
echo "skip=false" >> $GITHUB_OUTPUT
89+
echo "::warning::Force rerun requested - existing backports will be updated"
90+
exit 0
91+
fi
92+
4293
echo "Found existing backport PRs:"
4394
echo "$EXISTING_BACKPORTS"
4495
echo "skip=true" >> $GITHUB_OUTPUT
@@ -50,8 +101,17 @@ jobs:
50101
run: |
51102
# Extract version labels (e.g., "1.24", "1.22")
52103
VERSIONS=""
53-
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
54-
for label in $(echo "$LABELS" | jq -r '.[].name'); do
104+
105+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
106+
# For manual triggers, get labels from the PR
107+
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
108+
else
109+
# For automatic triggers, extract from PR event
110+
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
111+
LABELS=$(echo "$LABELS" | jq -r '.[].name')
112+
fi
113+
114+
for label in $LABELS; do
55115
# Match version labels like "1.24" (major.minor only)
56116
if [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
57117
# Validate the branch exists before adding to list
@@ -75,12 +135,20 @@ jobs:
75135
if: steps.check-existing.outputs.skip != 'true'
76136
id: backport
77137
env:
78-
PR_NUMBER: ${{ github.event.pull_request.number }}
79-
PR_TITLE: ${{ github.event.pull_request.title }}
80-
MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }}
138+
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
81139
run: |
82140
FAILED=""
83141
SUCCESS=""
142+
143+
# Get PR data for manual triggers
144+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
145+
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,mergeCommit)
146+
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
147+
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
148+
else
149+
PR_TITLE="${{ github.event.pull_request.title }}"
150+
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
151+
fi
84152
85153
for version in ${{ steps.versions.outputs.versions }}; do
86154
echo "::group::Backporting to core/${version}"
@@ -133,10 +201,18 @@ jobs:
133201
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success
134202
env:
135203
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
136-
PR_TITLE: ${{ github.event.pull_request.title }}
137-
PR_NUMBER: ${{ github.event.pull_request.number }}
138-
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
204+
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
139205
run: |
206+
# Get PR data for manual triggers
207+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
208+
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json title,author)
209+
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
210+
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
211+
else
212+
PR_TITLE="${{ github.event.pull_request.title }}"
213+
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
214+
fi
215+
140216
for backport in ${{ steps.backport.outputs.success }}; do
141217
IFS=':' read -r version branch <<< "${backport}"
142218
@@ -165,9 +241,16 @@ jobs:
165241
env:
166242
GH_TOKEN: ${{ github.token }}
167243
run: |
168-
PR_NUMBER="${{ github.event.pull_request.number }}"
169-
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
170-
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
244+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
245+
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --json author,mergeCommit)
246+
PR_NUMBER="${{ inputs.pr_number }}"
247+
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
248+
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
249+
else
250+
PR_NUMBER="${{ github.event.pull_request.number }}"
251+
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
252+
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
253+
fi
171254
172255
for failure in ${{ steps.backport.outputs.failed }}; do
173256
IFS=':' read -r version reason conflicts <<< "${failure}"

0 commit comments

Comments
 (0)