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.
This commit is contained in:
1 file changed
+121
@@ -0,0 +1,121 @@
|
|||||||
|
name: Backport
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed, labeled]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
issues: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
backport:
|
||||||
|
name: Backport PR
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Only run on merged PRs with backport labels.
|
||||||
|
# Labels must match pattern: backport-v* (e.g., backport-v0.20.x-branch).
|
||||||
|
# This excludes labels like "backport candidate" or "backport-candidate".
|
||||||
|
if: |
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
contains(join(github.event.pull_request.labels.*.name, ','), 'backport-v')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ github.event.pull_request.base.ref }}
|
||||||
|
|
||||||
|
- name: Validate target branches exist
|
||||||
|
id: validate
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Extract all backport labels
|
||||||
|
labels='${{ toJSON(github.event.pull_request.labels.*.name) }}'
|
||||||
|
echo "All labels: $labels"
|
||||||
|
|
||||||
|
# Parse labels and extract branch names
|
||||||
|
# Only match labels starting with "backport-v" to exclude labels like
|
||||||
|
# "backport candidate" or "backport-candidate"
|
||||||
|
backport_labels=$(echo "$labels" | jq -r '.[] | select(startswith("backport-v"))')
|
||||||
|
|
||||||
|
if [ -z "$backport_labels" ]; then
|
||||||
|
echo "::error::No valid backport labels found (must start with 'backport-v')"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found backport labels:"
|
||||||
|
echo "$backport_labels"
|
||||||
|
|
||||||
|
# Check each target branch exists
|
||||||
|
missing_branches=()
|
||||||
|
valid_branches=()
|
||||||
|
while IFS= read -r label; do
|
||||||
|
# Extract branch name (everything after "backport-")
|
||||||
|
branch_name="${label#backport-}"
|
||||||
|
echo "Checking if branch exists: $branch_name"
|
||||||
|
|
||||||
|
# Check if branch exists in remote
|
||||||
|
if ! git ls-remote --heads origin "$branch_name" | grep -q "$branch_name"; then
|
||||||
|
echo "::warning::Target branch '$branch_name' does not exist (from label '$label')"
|
||||||
|
missing_branches+=("$branch_name")
|
||||||
|
else
|
||||||
|
echo "✓ Branch '$branch_name' exists"
|
||||||
|
valid_branches+=("$branch_name")
|
||||||
|
fi
|
||||||
|
done <<< "$backport_labels"
|
||||||
|
|
||||||
|
# Report validation results
|
||||||
|
if [ ${#missing_branches[@]} -gt 0 ]; then
|
||||||
|
echo "::warning::The following target branches do not exist and will be skipped: ${missing_branches[*]}"
|
||||||
|
echo "::warning::Please check the branch names or create the branches before retrying"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only fail if ALL branches are invalid
|
||||||
|
if [ ${#valid_branches[@]} -eq 0 ]; then
|
||||||
|
echo "::error::No valid target branches found. All backport labels reference non-existent branches."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Found ${#valid_branches[@]} valid target branch(es): ${valid_branches[*]}"
|
||||||
|
if [ ${#missing_branches[@]} -gt 0 ]; then
|
||||||
|
echo "⚠ Skipping ${#missing_branches[@]} invalid branch(es): ${missing_branches[*]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create backport PRs
|
||||||
|
# Uses version v3.4, we pin to a hash here. For more details to
|
||||||
|
# available versions, see:
|
||||||
|
# https://github.com/korthout/backport-action/releases.
|
||||||
|
uses: korthout/backport-action@d07416681cab29bf2661702f925f020aaa962997
|
||||||
|
with:
|
||||||
|
# Automatically detect target branches from labels.
|
||||||
|
# Labels must be in format: backport-v0.20.x-branch (must start
|
||||||
|
# with "backport-v"). This excludes labels like "backport candidate"
|
||||||
|
# or "backport-candidate". The pattern extracts everything after
|
||||||
|
# "backport-" as the branch name.
|
||||||
|
label_pattern: '^backport-(v.+)$'
|
||||||
|
|
||||||
|
# GitHub token for creating PRs.
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# PR title format - shows it's a backport with original PR number.
|
||||||
|
pull_title: '[${target_branch}] Backport #${pull_number}: ${pull_title}'
|
||||||
|
|
||||||
|
# PR description template - links back to original PR.
|
||||||
|
pull_description: |-
|
||||||
|
Backport of #${pull_number}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
${pull_description}
|
||||||
|
|
||||||
|
# Automatically add labels to backport PRs.
|
||||||
|
# The 'no-changelog' label skips the release notes check in CI.
|
||||||
|
labels: no-changelog
|
||||||
|
|
||||||
|
# Merge strategy - skip merge commits, use cherry-pick only.
|
||||||
|
merge_commits: skip
|
||||||
|
|
||||||
|
# If conflicts occur, create a draft PR with conflict markers.
|
||||||
|
experimental: '{"conflict_resolution": "draft_commit_conflicts"}'
|
||||||
Reference in new issue
Block a user