diff --git a/README.md b/README.md index aa8b900..65891be 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ jobs: runs-on: ubuntu-latest # Only run when pull request is merged - # or when a comment containing `/backport` is created by someone other than the + # or when a comment containing `/backport` is created by someone other than the # https://github.com/backport-action bot user (user id: 97796249). Note that if you use your # own PAT as `github_token`, that you should replace this id with yours. if: > @@ -105,6 +105,13 @@ jobs: The action can be configured with the following optional [inputs](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepswith): +### `copy_assignees` + +Default: `false` (disabled) + +Controls whether to copy the assignees from the original pull request to the backport pull request. +By default, the assignees are not copied. + ### `copy_labels_pattern` Default: `''` (disabled) @@ -113,6 +120,21 @@ Regex pattern to match github labels which will be copied from the original pull Note that labels matching `label_pattern` are excluded. By default, no labels are copied. +### `copy_milestone` + +Default: `false` (disabled) + +Controls whether to copy the milestone from the original pull request to the backport pull request. +By default, the milestone is not copied. + +### `copy_requested_reviewers` + +Default: `false` (disabled) + +Controls whether to copy the requested reviewers from the original pull request to the backport pull request. +Note that this does not request reviews from those users who already reviewed the original pull request. +By default, the requested reviewers are not copied. + ### `github_token` Default: `${{ github.token }}` diff --git a/action.yml b/action.yml index a7dd497..8690342 100644 --- a/action.yml +++ b/action.yml @@ -3,11 +3,27 @@ description: > Fast and flexible action to cherry-pick commits from labeled pull requests author: korthout inputs: + copy_assignees: + description: > + Controls whether to copy the assignees from the original pull request to the backport pull request. + By default, the assignees are not copied. + default: false copy_labels_pattern: description: > Regex pattern to match github labels which will be copied from the original pull request to the backport pull request. Note that labels matching `label_pattern` are excluded. By default, no labels are copied. + copy_milestone: + description: > + Controls whether to copy the milestone from the original pull request to the backport pull request. + By default, the milestone is not copied. + default: false + copy_requested_reviewers: + description: > + Controls whether to copy the requested reviewers from the original pull request to the backport pull request. + Note that this does not request reviews from those users who already reviewed the original pull request. + By default, the requested reviewers are not copied. + default: false github_token: description: > Token to authenticate requests to GitHub. diff --git a/src/backport.ts b/src/backport.ts index 013baa6..ec395cc 100644 --- a/src/backport.ts +++ b/src/backport.ts @@ -25,6 +25,9 @@ export type Config = { commits: { merge_commits: "fail" | "skip"; }; + copy_milestone: boolean; + copy_assignees: boolean; + copy_requested_reviewers: boolean; }; enum Output { @@ -257,6 +260,53 @@ export class Backport { } const new_pr = new_pr_response.data; + if (this.config.copy_milestone == true) { + const milestone = mainpr.milestone?.number; + if (milestone) { + console.info("Setting milestone to " + milestone); + const set_milestone_response = await this.github.setMilestone( + new_pr.number, + milestone, + ); + if (set_milestone_response.status != 200) { + console.error(JSON.stringify(set_milestone_response)); + } + } + } + + if (this.config.copy_assignees == true) { + const assignees = mainpr.assignees.map((label) => label.login); + if (assignees.length > 0) { + console.info("Setting assignees " + assignees); + const set_assignee_response = await this.github.setAssignees( + new_pr.number, + assignees, + ); + if (set_assignee_response.status != 201) { + console.error(JSON.stringify(set_assignee_response)); + } + } + } + + if (this.config.copy_requested_reviewers == true) { + const reviewers = mainpr.requested_reviewers?.map( + (reviewer) => reviewer.login, + ); + if (reviewers?.length > 0) { + console.info("Setting reviewers " + reviewers); + const reviewRequest = { + ...this.github.getRepo(), + pull_number: new_pr.number, + reviewers: reviewers, + }; + const set_reviewers_response = + await this.github.requestReviewers(reviewRequest); + if (set_reviewers_response.status != 201) { + console.error(JSON.stringify(set_reviewers_response)); + } + } + } + if (labelsToCopy.length > 0) { const label_response = await this.github.labelPR( new_pr.number, @@ -377,7 +427,7 @@ export class Backport { private composeMessageForCreatePRFailed( response: CreatePullRequestResponse, ): string { - return dedent`Backport branch created but failed to create PR. + return dedent`Backport branch created but failed to create PR. Request to create PR rejected with status ${response.status}. (see action log for full response)`; diff --git a/src/github.ts b/src/github.ts index 612ee4d..7b54b2d 100644 --- a/src/github.ts +++ b/src/github.ts @@ -20,6 +20,8 @@ export interface GithubApi { createPR(pr: CreatePullRequest): Promise; labelPR(pr: number, labels: string[]): Promise; requestReviewers(request: ReviewRequest): Promise; + setAssignees(pr: number, assignees: string[]): Promise; + setMilestone(pr: number, milestone: number): Promise; } export class Github implements GithubApi { @@ -126,6 +128,24 @@ export class Github implements GithubApi { labels, }); } + + public async setAssignees(pr: number, assignees: string[]) { + console.log(`Set Assignees ${assignees} to #${pr}`); + return this.#octokit.rest.issues.addAssignees({ + ...this.getRepo(), + issue_number: pr, + assignees, + }); + } + + public async setMilestone(pr: number, milestone: number) { + console.log(`Set Milestone ${milestone} to #${pr}`); + return this.#octokit.rest.issues.update({ + ...this.getRepo(), + issue_number: pr, + milestone: milestone, + }); + } } export type PullRequest = { @@ -149,6 +169,18 @@ export type PullRequest = { login: string; }[]; commits: number; + milestone: { + number: number; + id: number; + title: string; + }; + assignees: { + login: string; + id: number; + }[]; + merged_by: { + login: string; + }; }; export type CreatePullRequestResponse = { status: number; @@ -158,6 +190,11 @@ export type CreatePullRequestResponse = { }; }; export type RequestReviewersResponse = CreatePullRequestResponse; + +export type GenericResponse = { + status: number; +}; + export type LabelPullRequestResponse = { status: number; }; diff --git a/src/main.ts b/src/main.ts index b665fd0..618f51d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,9 @@ async function run(): Promise { const copy_labels_pattern = core.getInput("copy_labels_pattern"); const target_branches = core.getInput("target_branches"); const merge_commits = core.getInput("merge_commits"); + const copy_assignees = core.getInput("copy_assignees"); + const copy_milestone = core.getInput("copy_milestone"); + const copy_requested_reviewers = core.getInput("copy_requested_reviewers"); if (merge_commits != "fail" && merge_commits != "skip") { const message = `Expected input 'merge_commits' to be either 'fail' or 'skip', but was '${merge_commits}'`; @@ -36,6 +39,9 @@ async function run(): Promise { copy_labels_pattern === "" ? undefined : new RegExp(copy_labels_pattern), target_branches: target_branches === "" ? undefined : target_branches, commits: { merge_commits }, + copy_assignees: copy_assignees === "true", + copy_milestone: copy_milestone === "true", + copy_requested_reviewers: copy_requested_reviewers === "true", }; const backport = new Backport(github, config, git);