mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
chore(actions): Automated backport to release branches (#65)
* chore(actions): automated backport to release branches * nit
This commit is contained in:
parent
74b165a362
commit
58a72d4087
2 changed files with 242 additions and 0 deletions
106
.github/workflows/BACKPORT_README.md
vendored
Normal file
106
.github/workflows/BACKPORT_README.md
vendored
Normal file
|
|
@ -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
|
||||||
136
.github/workflows/backport.yml
vendored
Normal file
136
.github/workflows/backport.yml
vendored
Normal file
|
|
@ -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"
|
||||||
Loading…
Reference in a new issue