Skip to content

Commit 9ccc543

Browse files
committed
ci: add automated backport workflow
This commit introduces an automated GitHub Actions workflow to streamline the backporting process for merged PRs from master to release branches. Key features: - Triggers on merged PRs with labels matching 'backport-v*' pattern (e.g., backport-v0.20.x-branch) - Validates that target branches exist before attempting backport - Creates separate backport PRs for each target branch - Automatically adds 'no-changelog' label to backport PRs - Handles merge conflicts by creating draft PRs with conflict markers - Supports multiple simultaneous backports via multiple labels Workflow steps: 1. Checkout repository with full git history 2. Validate all target branches exist in the remote repository 3. For each valid backport label: - Create a new branch (backport-<pr-num>-to-<target-branch>) - Cherry-pick commits from the master PR - Create a new PR targeting the release branch - Link back to the original PR 4. If conflicts occur, create a draft PR for manual resolution Label format: - Valid: backport-v0.20.x-branch, backport-v0.19.x-branch - Invalid: backport candidate, backport-candidate, backport-needed This automation reduces manual work and ensures consistency in the backporting process while maintaining full visibility and control for maintainers.
1 parent cb3991e commit 9ccc543

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

.github/workflows/backport.yml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: Backport
2+
3+
on:
4+
pull_request_target:
5+
types: [closed, labeled]
6+
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
issues: read
11+
12+
jobs:
13+
backport:
14+
name: Backport PR
15+
runs-on: ubuntu-latest
16+
# Only run on merged PRs with backport labels.
17+
# Labels must match pattern: backport-v* (e.g., backport-v0.20.x-branch).
18+
# This excludes labels like "backport candidate" or "backport-candidate".
19+
if: |
20+
github.event.pull_request.merged == true &&
21+
contains(join(github.event.pull_request.labels.*.name, ','), 'backport-v')
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v5
26+
with:
27+
fetch-depth: 0
28+
ref: ${{ github.event.pull_request.base.ref }}
29+
30+
- name: Validate target branches exist
31+
id: validate
32+
shell: bash
33+
run: |
34+
# Extract all backport labels
35+
labels='${{ toJSON(github.event.pull_request.labels.*.name) }}'
36+
echo "All labels: $labels"
37+
38+
# Parse labels and extract branch names
39+
# Only match labels starting with "backport-v" to exclude labels like
40+
# "backport candidate" or "backport-candidate"
41+
backport_labels=$(echo "$labels" | jq -r '.[] | select(startswith("backport-v"))')
42+
43+
if [ -z "$backport_labels" ]; then
44+
echo "::error::No valid backport labels found (must start with 'backport-v')"
45+
exit 1
46+
fi
47+
48+
echo "Found backport labels:"
49+
echo "$backport_labels"
50+
51+
# Check each target branch exists
52+
missing_branches=()
53+
valid_branches=()
54+
while IFS= read -r label; do
55+
# Extract branch name (everything after "backport-")
56+
branch_name="${label#backport-}"
57+
echo "Checking if branch exists: $branch_name"
58+
59+
# Check if branch exists in remote
60+
if ! git ls-remote --heads origin "$branch_name" | grep -q "$branch_name"; then
61+
echo "::warning::Target branch '$branch_name' does not exist (from label '$label')"
62+
missing_branches+=("$branch_name")
63+
else
64+
echo "✓ Branch '$branch_name' exists"
65+
valid_branches+=("$branch_name")
66+
fi
67+
done <<< "$backport_labels"
68+
69+
# Report validation results
70+
if [ ${#missing_branches[@]} -gt 0 ]; then
71+
echo "::warning::The following target branches do not exist and will be skipped: ${missing_branches[*]}"
72+
echo "::warning::Please check the branch names or create the branches before retrying"
73+
fi
74+
75+
# Only fail if ALL branches are invalid
76+
if [ ${#valid_branches[@]} -eq 0 ]; then
77+
echo "::error::No valid target branches found. All backport labels reference non-existent branches."
78+
exit 1
79+
fi
80+
81+
echo "✓ Found ${#valid_branches[@]} valid target branch(es): ${valid_branches[*]}"
82+
if [ ${#missing_branches[@]} -gt 0 ]; then
83+
echo "⚠ Skipping ${#missing_branches[@]} invalid branch(es): ${missing_branches[*]}"
84+
fi
85+
86+
- name: Create backport PRs
87+
uses: korthout/backport-action@v3
88+
with:
89+
# Automatically detect target branches from labels.
90+
# Labels must be in format: backport-v0.20.x-branch (must start
91+
# with "backport-v"). This excludes labels like "backport candidate"
92+
# or "backport-candidate". The pattern extracts everything after
93+
# "backport-" as the branch name.
94+
label_pattern: '^backport-(v.+)$'
95+
96+
# GitHub token for creating PRs.
97+
github_token: ${{ secrets.GITHUB_TOKEN }}
98+
99+
# PR title format - shows it's a backport with original PR number.
100+
pull_title: '[${target_branch}] Backport #${pull_number}: ${pull_title}'
101+
102+
# PR description template - links back to original PR.
103+
pull_description: |-
104+
Backport of #${pull_number}
105+
106+
---
107+
108+
${pull_description}
109+
110+
# Automatically add labels to backport PRs.
111+
# The 'no-changelog' label skips the release notes check in CI.
112+
labels: no-changelog
113+
114+
# Merge strategy - skip merge commits, use cherry-pick only.
115+
merge_commits: skip
116+
117+
# If conflicts occur, create a draft PR with conflict markers.
118+
experimental: '{"conflict_resolution": "draft_commit_conflicts"}'

0 commit comments

Comments
 (0)