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. add_labels: no-changelog # Copy milestone from original PR to backport PR. copy_milestone: true # 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"}'