Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[infra] Improve cherry-pick workflow #234

Merged
merged 4 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions .github/workflows/prs_create-cherry-pick-pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Cherry pick onto target branch
name: Cherry pick onto target branches

on:
pull_request_target:
Expand All @@ -11,17 +11,18 @@ on:
permissions: {}

jobs:
detect_cherry_pick_target:
detect_cherry_pick_targets:
runs-on: ubuntu-latest
name: Detect cherry-pick target branch
name: Detect cherry-pick targets
permissions:
issues: write
pull-requests: write
contents: write
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs cherry-pick') && github.event.pull_request.merged == true }}
outputs:
targetBranch: ${{ steps.detect.outputs.TARGET_BRANCH }}
transferLabels: ${{ steps.detect.outputs.TRANSFER_LABELS }}
targetBranches: ${{ steps.detect.outputs.TARGET_BRANCHES }}
reviewers: ${{ steps.detect.outputs.REVIEWERS }}
labels: ${{ steps.detect.outputs.LABELS }}
steps:
- name: Check out mui-public repo
id: checkout
Expand All @@ -40,12 +41,15 @@ jobs:
open_cherry_pick_pr:
runs-on: ubuntu-latest
name: Open cherry-pick PR with target branch
if: needs.detect_cherry_pick_targets.outputs.targetBranches != ''
strategy:
matrix:
branch: ${{ needs.detect_cherry_pick_targets.outputs.targetBranches }}
permissions:
issues: write
pull-requests: write
contents: write
needs: detect_cherry_pick_target
if: needs.detect_cherry_pick_target.outputs.targetBranch != ''
needs: detect_cherry_pick_targets
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand All @@ -56,8 +60,13 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: carloscastrojumo/github-cherry-pick-action@503773289f4a459069c832dc628826685b75b4b3 # v1.0.10
with:
branch: ${{ needs.detect_cherry_pick_target.outputs.targetBranch }}
# the action will run for each value in matrix.branch
branch: ${{ matrix.branch }}
body: 'Cherry-pick of #{old_pull_request_id}'
cherry-pick-branch: ${{ format('cherry-pick-{0}', github.event.number) }}
title: '{old_title} (@${{ github.event.pull_request.user.login }})'
labels: ${{ needs.detect_cherry_pick_target.outputs.transferLabels }}
# assigning the original reviewers to the new PR
reviewers: ${{ needs.detect_cherry_pick_targets.outputs.reviewers }}
# instead of inheriting labels (including target branch label, etc.), we filter and set the labels explicitly
inherit_labels: false
labels: ${{ needs.detect_cherry_pick_targets.outputs.labels }}
86 changes: 22 additions & 64 deletions .github/workflows/scripts/prs/detectTargetBranch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-check
const vBranchRegex = /^v\d{1,3}\.x$/;
const transferLabels = ['cherry-pick'];
const targetBranches = [];

/**
* @param {Object} params
Expand All @@ -22,90 +22,48 @@ module.exports = async ({ core, context, github }) => {

core.info(`>>> PR fetched: ${pr.number}`);

const targetLabels = pr.labels
?.map((label) => label.name)
.filter((label) => vBranchRegex.test(label));
const prLabels = pr.labels.map((label) => label.name);

// filter the target labels from the original PR
const targetLabels = prLabels.filter((label) => vBranchRegex.test(label));
const otherLabels = prLabels.filter(
(label) => label !== 'needs cherry-pick' && !vBranchRegex.test(label),
);

if (targetLabels.length === 0) {
// there was no target branch present
core.info('>>> No target branch label found');
core.info('>>> No target label found');

if (vBranchRegex.test(pr.head_ref)) {
// the branch this is coming from is a version branch, so the cherry-pick target should be master
core.info('>>> Head Ref is a version branch, setting `master` as target');
core.setOutput('TARGET_BRANCH', 'master');
core.setOutput('TRANSFER_LABELS', transferLabels.join(','));
core.setOutput('TARGET_BRANCHES', 'master');
return;
}

core.setOutput('TARGET_BRANCH', '');
core.setOutput('TRANSFER_LABELS', transferLabels.join(','));
// the PR is not coming from a version branch
core.setOutput('TARGET_BRANCHES', '');
return;
}

core.info(`>>> Target labels found: ${targetLabels.join(', ')}`);
let target = '';

// there was a target branch label present
// filter the highest available target number and remove the others from the PR when present
if (targetLabels.length > 1) {
core.info(`>>> Multiple target labels found.`);
targetLabels.sort((a, b) => {
const aNum = parseInt(a.match(/\d+/)[0], 10);
const bNum = parseInt(b.match(/\d+/)[0], 10);
return bNum - aNum;
});

target = targetLabels.shift();

core.info(`>>> Sorting and setting the highest as 'TARGET_BRANCH' output.`);
core.setOutput('TARGET_BRANCH', target);

// since we have multiple targets we need to add the "needs cherry-pick" label
// this makes this workflow de-facto recursive
transferLabels.push('needs cherry-pick');

// add the other targets to the transfer labels
transferLabels.push(...targetLabels);
core.setOutput('TRANSFER_LABELS', transferLabels.join(','));

// the others will be removed from the PR
core.info(`>>> Removing the other target labels from the PR`);
for (const label of targetLabels) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pullNumber,
name: label,
});
}

core.info(`>>> Creating explanatory comment on PR`);
await github.rest.issues.createComment({
owner,
repo,
issue_number: pullNumber,
body: [
`The target branch for the cherry-pick PR has been set to \`${target}\`.`,
`Branches that will be created after merging are: ${targetLabels.join(', ')}`,
`Thank you!`,
].join('\n\n'),
});
return;
}
// get a list of the original reviewers
const reviewers = pr.requested_reviewers.map((reviewer) => reviewer.login);
core.info(`>>> Reviewers from original PR: ${reviewers.join(', ')}`);

core.info(`>>> Removing the "needs cherry-pick" label from the PR`);
await github.rest.issues.removeLabel({
core.info(`>>> Creating explanatory comment on PR`);
await github.rest.issues.createComment({
owner,
repo,
issue_number: pullNumber,
name: 'needs cherry-pick',
body: `Cherry-pick PRs will be created targeting branches: ${targetLabels.join(', ')}`,
});

target = targetLabels[0];
core.info(`>>> Setting found 'TARGET_BRANCH' output.`);
core.setOutput('TARGET_BRANCH', target);
core.setOutput('TRANSFER_LABELS', transferLabels.join(','));
// set the target branches as output to be used as an input for the next step
core.setOutput('TARGET_BRANCHES', targetLabels.join(','));
core.setOutput('LABELS', ['cherry-pick', ...otherLabels].join(','));
JCQuintas marked this conversation as resolved.
Show resolved Hide resolved
core.setOutput('REVIEWERS', reviewers.join(','));
} catch (error) {
core.error(`>>> Workflow failed with: ${error.message}`);
core.setFailed(error.message);
Expand Down
Loading