fix: add manual backport workflow (#128) (#131)

(cherry picked from commit c57fe6af02)

Signed-off-by: matttrach <matt.trachier@suse.com>
Co-authored-by: Matt Trachier <matt.trachier@suse.com>
This commit is contained in:
github-actions[bot] 2025-08-29 16:46:50 -05:00 committed by GitHub
parent a1e577a99c
commit 7300e97fd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 124 additions and 1 deletions

123
.github/workflows/backport-pr-manual.yml vendored Normal file
View File

@ -0,0 +1,123 @@
name: 'Auto Cherry-Pick to Release Branches'
on:
workflow_dispatch:
inputs:
merge_commit_sha:
description: 'The sha of the merge commit from the main PR.'
required: true
jobs:
create-cherry-pick-prs:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
actions: write
steps:
- name: 'Wait for merge to settle'
run: sleep 10
- name: 'Checkout Repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 https://github.com/actions/checkout
with:
fetch-depth: 0
- name: 'Find Issues and Create Cherry-Pick PRs'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 https://github.com/actions/github-script
env:
MERGE_COMMIT_SHA: ${{ inputs.merge_commit_sha }}
with:
script: |
const execSync = require('child_process').execSync;
const owner = github.repository_owner;
const repo = github.repository;
const mergeCommitSha = process.env.MERGE_COMMIT_SHA;
const assignees = ['matttrach', 'jiaqiluo', 'HarrisonWAffel'];
const { data: associatedPrs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: mergeCommitSha
});
const pr = associatedPrs.find(p => p.base.ref === 'main' && p.merged_at);
if (!pr) {
core.info(`No merged PR found for commit ${mergeCommitSha}. This may have been a direct push. Exiting.`);
return;
}
core.info(`Found associated PR: #${pr.number}`);
// https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests
core.info(`Searching for 'internal/main' issue linked to PR #${pr.number}`);
const { data: searchResults } = await github.request('GET /search/issues', {
q: `is:issue state:open label:"internal/main" repo:${owner}/${repo} in:body #${pr.number}`,
advanced_search: true,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
});
if (searchResults.total_count === 0) {
core.info(`No 'internal/main' issue found for PR #${pr.number}. Exiting.`);
return;
}
const mainIssue = searchResults.items[0];
core.info(`Found main issue: #${mainIssue.number}`);
// https://docs.github.com/en/rest/issues/sub-issues?apiVersion=2022-11-28#add-sub-issue
core.info(`Fetching sub-issues for main issue #${mainIssue.number}`);
const { data: subIssues } = await github.request('GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues', {
owner: owner,
repo: repo,
issue_number: mainIssue.number,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
});
if (subIssues.length === 0) {
core.info(`No sub-issues found for issue #${mainIssue.number}. Exiting.`);
return;
}
core.info(`Found ${subIssues.length} sub-issues.`);
for (const subIssue of subIssues) {
const subIssueNumber = subIssue.number;
// Find the release label directly on the sub-issue object
const releaseLabel = subIssue.labels.find(label => label.name.startsWith('release/v'));
if (!releaseLabel) {
core.warning(`Sub-issue #${subIssueNumber} has no 'release/v...' label. Skipping.`);
continue;
}
const targetBranch = releaseLabel.name
core.info(`Processing sub-issue #${subIssueNumber} for target branch: ${targetBranch}`);
const newBranchName = `backport-${pr.number}-${targetBranch.replace(/\//g, '-')}`;
execSync(`git config user.name "github-actions[bot]"`);
execSync(`git config user.email "github-actions[bot]@users.noreply.github.com"`);
execSync(`git fetch origin ${targetBranch}`);
execSync(`git checkout -b ${newBranchName} origin/${targetBranch}`);
execSync(`git cherry-pick -x ${mergeCommitSha} -X theirs`);
execSync(`git push origin ${newBranchName}`);
core.info(`Creating pull request for branch ${newBranchName} targeting ${targetBranch}...`);
const { data: newPr } = await github.rest.pulls.create({
owner,
repo,
title: pr.title,
head: newBranchName,
base: targetBranch,
body: [
`This pull request cherry-picks the changes from #${pr.number} into ${targetBranch}`,
`Addresses #${subIssueNumber} for #${mainIssue.number}`,
`**WARNING!**: to avoid having to resolve merge conflicts this PR is generated with 'git cherry-pick -X theirs'.`,
`Please make sure to carefully inspect this PR so that you don't accidentally revert anything!`,
`Please add the proper milestone to this PR`,
`Copied from main PR:`,
`${pr.body}`
].join("\n\n")
});
const prNumber = newPr.number
await github.rest.issues.addAssignees({
owner,
repo,
issue_number: prNumber,
assignees: assignees
});
}

View File

@ -61,7 +61,7 @@ jobs:
`Backport #${prNumber} to ${labelName} for #${parentIssueNumber}`,
`Copied from PR:`,
`${pr.body}`
].join("\n\n")
].join("\n\n"),
labels: [labelName],
assignees: assignees
});