diff --git a/.github/workflows/BACKPORT_README.md b/.github/workflows/BACKPORT_README.md new file mode 100644 index 0000000000..52bf55197e --- /dev/null +++ b/.github/workflows/BACKPORT_README.md @@ -0,0 +1,106 @@ +# Backport Workflow Documentation + +## Overview + +This repository uses an automated backport workflow to cherry-pick merged pull requests from the `main` branch to release branches. The workflow is powered by the [korthout/backport-action](https://github.com/marketplace/actions/backport-merged-pull-requests-to-selected-branches). + +## How It Works + +### Automatic Backporting via Labels + +1. **Add a backport label** to your pull request before or after merging: + - Label format: `backport/{version}` + - Examples: `backport/v1.0.0`, `backport/v2.3.1`, `backport/1.0` + +2. **When the PR is merged to main**, the workflow automatically: + - Detects all `backport/*` labels + - Transforms them to target `release/{version}` branches + - Creates new pull requests with cherry-picked commits to each target branch + +3. **Target branches** must exist: + - For label `backport/v1.0.0` → targets branch `release/v1.0.0` + - For label `backport/2.0` → targets branch `release/2.0` + - The release branch must already exist in the repository + +### Manual Backporting via Comments + +You can also trigger backports manually by commenting on a merged PR: +- Comment `/backport` on any merged pull request +- The workflow will look for backport labels and create the appropriate PRs + +## Example Workflow + +1. Create a pull request to `main` with your changes +2. Add label: `backport/v1.2.3` +3. Get the PR reviewed and merged +4. The bot automatically: + - Creates a new branch: `backport-{PR-number}-to-release-v1.2.3` + - Cherry-picks the commits from your merged PR + - Opens a new PR from that branch to `release/v1.2.3` + - Adds labels: `backport`, `automated` + - Assigns the original PR author + +## Handling Multiple Backports + +You can backport to multiple release branches by adding multiple labels: +- `backport/v1.0.0` - backports to `release/v1.0.0` +- `backport/v2.0.0` - backports to `release/v2.0.0` +- `backport/v3.0.0` - backports to `release/v3.0.0` + +Each label will create a separate backport PR. + +## Conflict Resolution + +When cherry-pick conflicts occur: +1. The bot creates a **draft PR** with the conflicts committed +2. The draft PR includes instructions on how to resolve the conflicts +3. You need to: + - Check out the backport branch locally + - Resolve the conflicts + - Push the resolved changes + - Mark the PR as ready for review + +## Backport PR Format + +Backport PRs are created with: +- **Title**: `[Backport release/{version}] {original PR title}` +- **Description**: Includes original PR information, author, and description +- **Labels**: `backport`, `automated` +- **Assignee**: Original PR author + +## Prerequisites + +For the workflow to function correctly: +1. Release branches must follow the naming pattern: `release/{version}` +2. The workflow must have write permissions (already configured) +3. Release branches must exist before attempting to backport + +## Troubleshooting + +### Backport not triggering +- Ensure the PR is merged to `main` (not just closed) +- Verify the label format is exactly `backport/{version}` +- Check that the target `release/{version}` branch exists + +### Conflicts in backport +- The bot will create a draft PR with conflicts +- Follow the instructions in the PR to resolve conflicts locally + +### Multiple commits or merge commits +- The workflow is configured to skip merge commits +- Only non-merge commits will be cherry-picked +- If you need merge commits, update the `merge_commits` setting in the workflow + +## Configuration + +The workflow is defined in `.github/workflows/backport.yml`. Key settings: +- **Label pattern**: `backport/{version}` +- **Target branch pattern**: `release/{version}` +- **Conflict strategy**: Create draft PRs with conflicts +- **Merge commits**: Skipped (only cherry-picks non-merge commits) + +## Security + +- The workflow uses `GITHUB_TOKEN` for authentication +- Runs on `pull_request_target` to handle forks securely +- Only triggers on merged PRs to prevent abuse diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 0000000000..15facd5924 --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,136 @@ +name: Backport merged pull request +on: + pull_request_target: + types: [closed] + issue_comment: + types: [created] + +permissions: + contents: write # Required to push branches + pull-requests: write # Required to create pull requests + +jobs: + backport: + name: Backport pull request + runs-on: ubuntu-latest + + # Only run when: + # - A pull request to main is merged (not just closed) + # - OR a comment starting with '/backport' is created by someone other than the bot + if: > + ( + github.event_name == 'pull_request_target' && + github.event.pull_request.merged && + github.event.pull_request.base.ref == 'main' + ) || ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.user.id != 97796249 && + startsWith(github.event.comment.body, '/backport') + ) + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Fetch all history for all branches + fetch-depth: 0 + + - name: Extract backport targets from labels + id: extract-targets + uses: actions/github-script@v7 + with: + script: | + const labels = context.payload.pull_request?.labels || []; + const backportLabels = labels + .map(label => label.name) + .filter(name => name.startsWith('backport/')) + .map(name => { + // Extract version from "backport/{version}" + const version = name.replace('backport/', ''); + // Transform to "release/{version}" + return `release/${version}`; + }); + + if (backportLabels.length > 0) { + console.log(`Found backport targets: ${backportLabels.join(', ')}`); + // Join with spaces as the backport action expects + core.setOutput('targets', backportLabels.join(' ')); + core.setOutput('has-targets', 'true'); + } else { + console.log('No backport labels found'); + core.setOutput('has-targets', 'false'); + } + + // Also output the original labels for the action + const originalLabels = labels + .map(label => label.name) + .filter(name => name.startsWith('backport/')) + .map(name => { + // Transform "backport/{version}" to "backport release/{version}" + const version = name.replace('backport/', ''); + return `backport release/${version}`; + }) + .join(','); + + core.setOutput('transformed-labels', originalLabels); + + - name: Create backport pull requests + if: steps.extract-targets.outputs.has-targets == 'true' + uses: korthout/backport-action@v3 + with: + # GitHub token for authentication + github_token: ${{ secrets.GITHUB_TOKEN }} + + # Directly specify target branches extracted from labels + target_branches: ${{ steps.extract-targets.outputs.targets }} + + # Disable label pattern matching since we're using target_branches + label_pattern: "" + + # Branch name template for the backport branch + # This creates branches like: backport-123-to-release-v1.0.0 + branch_name: "backport-${pull_number}-to-${target_branch}" + + # Title template for the backport PR + pull_title: "[Backport ${target_branch}] ${pull_title}" + + # Description template for the backport PR + pull_description: | + # Backport to ${target_branch} + + This is an automatic backport of pull request #${pull_number} to `${target_branch}`. + + ## Original PR Information + - **Title**: ${pull_title} + - **Author**: @${pull_author} + - **Original PR**: #${pull_number} + + ## Original Description + ${pull_description} + + ## Related Issues + ${issue_refs} + + --- + *This pull request was created automatically by the backport action.* + + # Labels to add to the backport PR + add_labels: "automated" + + # Add original PR author as assignee + add_author_as_assignee: true + + # What to do when there are conflicts + # Options: "fail" or "draft_commit_conflicts" + # Using draft_commit_conflicts so conflicts don't block the process + conflicts_strategy: "draft_commit_conflicts" + + # Cherry-picking strategy + # Options: "auto", "recursive", "ort" + cherry_picking: "auto" + + # How to handle merge commits + # Options: "fail" or "skip" + # Using skip to handle PRs that were kept in sync with base via merge commits + merge_commits: "skip"