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

feat(issue-17): override backporting pr data #29

Merged
merged 1 commit into from
Jun 20, 2023
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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,13 @@ This toold comes with some inputs that allow users to override the default behav
| Pull Request | -pr, --pull-request | Y | Original pull request url, the one that must be backported, e.g., https://github.com/lampajr/backporting/pull/1 | |
| Auth | -a, --auth | N | `GITHUB_TOKEN` or a `repo` scoped [Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) | "" |
| Folder | -f, --folder | N | Local folder where the repo will be checked out, e.g., /tmp/folder | {cwd}/bp |
| Title | --title | N | Backporting pull request title | "{original-pr-title}" |
| Body | --body | N | Backporting pull request body | "{original-pr-body}" |
| Body Prefix | --body-prefix | N | Prefix to the backporting pull request body | "Backport: {original-pr-link}" |
| Backport Branch Name | --bp-branch-name | N | Name of the backporting pull request branch | bp-{target-branch}-{sha} |
| Dry Run | -d, --dry-run | N | If enabled the tool does not push nor create anything remotely, use this to skip PR creation | false |


## GitHub Action

This action can be used in any GitHub workflow, below you can find a simple example of manually triggered workflow backporting a specific pull request (provided as input).
Expand Down Expand Up @@ -135,13 +140,11 @@ For a complete description of all inputs see [Inputs section](#inputs).

**BPer** is in development mode, this means that it has many limitations right now. I'll try to summarize the most importan ones:

- No way to override backporting pull request fields like body, reviewers and so on.
- You can backport pull requests only.
- It only works for [GitHub](https://github.com/).
- Integrated in GitHub Actions CI/CD only.

Based on these limitations, the next **Future Works** could be the following:
- Give users the possibility to override/customize the backporting pull request.
- Provide a way to backport single commit too (or a set of them), even if no original pull request is present.
- Integrate this tool with other git management services (like GitLab and Bitbucket) to make it as generic as possible.
- Provide some reusable GitHub workflows.
Expand Down
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ inputs:
target-branch:
description: "Branch where the pull request must be backported to."
required: true
title:
description: "Backporting PR title. Default is the original PR title prefixed by the target branch."
required: false
body:
description: "Backporting PR body. Default is the original PR body prefixed by `backport: <original-pr-link>`."
required: false
body-prefix:
description: "Backporting PR body prefix. Default is `backport: <original-pr-link>`"
required: false
bp-branch-name:
description: "Backporting PR branch name. Default is auto-generated from commit."
required: false

runs:
using: node16
Expand Down
27 changes: 19 additions & 8 deletions dist/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ class CLIArgsParser {
.requiredOption("-pr, --pull-request <pr url>", "pull request url, e.g., https://github.com/lampajr/backporting/pull/1.")
.option("-d, --dry-run", "if enabled the tool does not create any pull request nor push anything remotely", false)
.option("-a, --auth <auth>", "git service authentication string, e.g., github token.", "")
.option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder.", undefined);
.option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder.", undefined)
.option("--title <folder>", "backport pr title, default original pr title prefixed by target branch.", undefined)
.option("--body <folder>", "backport pr title, default original pr body prefixed by bodyPrefix.", undefined)
.option("--body-prefix <folder>", "backport pr body prefix, default `backport <original-pr-link>`.", undefined)
.option("--bp-branch-name <folder>", "backport pr branch name, default auto-generated by the commit.", undefined);
}
parse() {
const opts = this.getCommand()
Expand All @@ -50,7 +54,11 @@ class CLIArgsParser {
auth: opts.auth,
pullRequest: opts.pullRequest,
targetBranch: opts.targetBranch,
folder: opts.folder
folder: opts.folder,
title: opts.title,
body: opts.body,
bodyPrefix: opts.bodyPrefix,
bpBranchName: opts.bpBranchName,
};
}
}
Expand Down Expand Up @@ -122,32 +130,35 @@ class PullRequestConfigsParser extends configs_parser_1.default {
folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`,
targetBranch: args.targetBranch,
originalPullRequest: pr,
backportPullRequest: this.getDefaultBackportPullRequest(pr, args.targetBranch)
backportPullRequest: this.getDefaultBackportPullRequest(pr, args)
};
}
getDefaultFolder() {
return "bp";
}
/**
* Create a default backport pull request starting from the target branch and
* Create a backport pull request starting from the target branch and
* the original pr to be backported
* @param originalPullRequest original pull request
* @param targetBranch target branch where the backport should be applied
* @returns {GitPullRequest}
*/
getDefaultBackportPullRequest(originalPullRequest, targetBranch) {
getDefaultBackportPullRequest(originalPullRequest, args) {
const reviewers = [];
reviewers.push(originalPullRequest.author);
if (originalPullRequest.mergedBy) {
reviewers.push(originalPullRequest.mergedBy);
}
const bodyPrefix = args.bodyPrefix ?? `**Backport:** ${originalPullRequest.htmlUrl}\r\n\r\n`;
const body = args.body ?? `${originalPullRequest.body}\r\n\r\nPowered by [BPer](https://github.com/lampajr/backporting).`;
return {
author: originalPullRequest.author,
title: `[${targetBranch}] ${originalPullRequest.title}`,
body: `**Backport:** ${originalPullRequest.htmlUrl}\r\n\r\n${originalPullRequest.body}\r\n\r\nPowered by [BPer](https://github.com/lampajr/backporting).`,
title: args.title ?? `[${args.targetBranch}] ${originalPullRequest.title}`,
body: `${bodyPrefix}${body}`,
reviewers: [...new Set(reviewers)],
targetRepo: originalPullRequest.targetRepo,
sourceRepo: originalPullRequest.targetRepo,
branchName: args.bpBranchName,
nCommits: 0,
commits: [] // TODO needed?
};
Expand Down Expand Up @@ -649,7 +660,7 @@ class Runner {
// 4. clone the repository
await git.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, configs.targetBranch);
// 5. create new branch from target one and checkout
const backportBranch = `bp-${configs.targetBranch}-${originalPR.commits.join("-")}`;
const backportBranch = backportPR.branchName ?? `bp-${configs.targetBranch}-${originalPR.commits.join("-")}`;
await git.createLocalBranch(configs.folder, backportBranch);
// 6. fetch pull request remote if source owner != target owner or pull request still open
if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner ||
Expand Down
21 changes: 14 additions & 7 deletions dist/gha/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ class GHAArgsParser {
auth: (0, core_1.getInput)("auth") ? (0, core_1.getInput)("auth") : "",
pullRequest: (0, core_1.getInput)("pull-request"),
targetBranch: (0, core_1.getInput)("target-branch"),
folder: (0, core_1.getInput)("folder") !== "" ? (0, core_1.getInput)("folder") : undefined
folder: (0, core_1.getInput)("folder") !== "" ? (0, core_1.getInput)("folder") : undefined,
title: (0, core_1.getInput)("title"),
body: (0, core_1.getInput)("body"),
bodyPrefix: (0, core_1.getInput)("body-prefix"),
bpBranchName: (0, core_1.getInput)("bp-branch-name"),
};
}
}
Expand Down Expand Up @@ -108,32 +112,35 @@ class PullRequestConfigsParser extends configs_parser_1.default {
folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`,
targetBranch: args.targetBranch,
originalPullRequest: pr,
backportPullRequest: this.getDefaultBackportPullRequest(pr, args.targetBranch)
backportPullRequest: this.getDefaultBackportPullRequest(pr, args)
};
}
getDefaultFolder() {
return "bp";
}
/**
* Create a default backport pull request starting from the target branch and
* Create a backport pull request starting from the target branch and
* the original pr to be backported
* @param originalPullRequest original pull request
* @param targetBranch target branch where the backport should be applied
* @returns {GitPullRequest}
*/
getDefaultBackportPullRequest(originalPullRequest, targetBranch) {
getDefaultBackportPullRequest(originalPullRequest, args) {
const reviewers = [];
reviewers.push(originalPullRequest.author);
if (originalPullRequest.mergedBy) {
reviewers.push(originalPullRequest.mergedBy);
}
const bodyPrefix = args.bodyPrefix ?? `**Backport:** ${originalPullRequest.htmlUrl}\r\n\r\n`;
const body = args.body ?? `${originalPullRequest.body}\r\n\r\nPowered by [BPer](https://github.com/lampajr/backporting).`;
return {
author: originalPullRequest.author,
title: `[${targetBranch}] ${originalPullRequest.title}`,
body: `**Backport:** ${originalPullRequest.htmlUrl}\r\n\r\n${originalPullRequest.body}\r\n\r\nPowered by [BPer](https://github.com/lampajr/backporting).`,
title: args.title ?? `[${args.targetBranch}] ${originalPullRequest.title}`,
body: `${bodyPrefix}${body}`,
reviewers: [...new Set(reviewers)],
targetRepo: originalPullRequest.targetRepo,
sourceRepo: originalPullRequest.targetRepo,
branchName: args.bpBranchName,
nCommits: 0,
commits: [] // TODO needed?
};
Expand Down Expand Up @@ -635,7 +642,7 @@ class Runner {
// 4. clone the repository
await git.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, configs.targetBranch);
// 5. create new branch from target one and checkout
const backportBranch = `bp-${configs.targetBranch}-${originalPR.commits.join("-")}`;
const backportBranch = backportPR.branchName ?? `bp-${configs.targetBranch}-${originalPR.commits.join("-")}`;
await git.createLocalBranch(configs.folder, backportBranch);
// 6. fetch pull request remote if source owner != target owner or pull request still open
if (configs.originalPullRequest.sourceRepo.owner !== configs.originalPullRequest.targetRepo.owner ||
Expand Down
4 changes: 4 additions & 0 deletions src/service/args/args.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ export interface Args {
pullRequest: string, // url of the pull request to backport
folder?: string, // local folder where the repositories should be cloned
author?: string, // backport pr author, default taken from pr
title?: string, // backport pr title, default original pr title prefixed by target branch
body?: string, // backport pr title, default original pr body prefixed by bodyPrefix
bodyPrefix?: string, // backport pr body prefix, default `backport <original-pr-link>`
bpBranchName?: string, // backport pr branch name, default computed from commit
}
12 changes: 10 additions & 2 deletions src/service/args/cli/cli-args-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export default class CLIArgsParser implements ArgsParser {
.requiredOption("-pr, --pull-request <pr url>", "pull request url, e.g., https://github.com/lampajr/backporting/pull/1.")
.option("-d, --dry-run", "if enabled the tool does not create any pull request nor push anything remotely", false)
.option("-a, --auth <auth>", "git service authentication string, e.g., github token.", "")
.option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder.", undefined);
.option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder.", undefined)
.option("--title <folder>", "backport pr title, default original pr title prefixed by target branch.", undefined)
.option("--body <folder>", "backport pr title, default original pr body prefixed by bodyPrefix.", undefined)
.option("--body-prefix <folder>", "backport pr body prefix, default `backport <original-pr-link>`.", undefined)
.option("--bp-branch-name <folder>", "backport pr branch name, default auto-generated by the commit.", undefined);
}

parse(): Args {
Expand All @@ -27,7 +31,11 @@ export default class CLIArgsParser implements ArgsParser {
auth: opts.auth,
pullRequest: opts.pullRequest,
targetBranch: opts.targetBranch,
folder: opts.folder
folder: opts.folder,
title: opts.title,
body: opts.body,
bodyPrefix: opts.bodyPrefix,
bpBranchName: opts.bpBranchName,
};
}

Expand Down
6 changes: 5 additions & 1 deletion src/service/args/gha/gha-args-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export default class GHAArgsParser implements ArgsParser {
auth: getInput("auth") ? getInput("auth") : "",
pullRequest: getInput("pull-request"),
targetBranch: getInput("target-branch"),
folder: getInput("folder") !== "" ? getInput("folder") : undefined
folder: getInput("folder") !== "" ? getInput("folder") : undefined,
title: getInput("title"),
body: getInput("body"),
bodyPrefix: getInput("body-prefix"),
bpBranchName: getInput("bp-branch-name"),
};
}

Expand Down
14 changes: 9 additions & 5 deletions src/service/configs/pullrequest/pr-configs-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class PullRequestConfigsParser extends ConfigsParser {
folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`,
targetBranch: args.targetBranch,
originalPullRequest: pr,
backportPullRequest: this.getDefaultBackportPullRequest(pr, args.targetBranch)
backportPullRequest: this.getDefaultBackportPullRequest(pr, args)
};
}

Expand All @@ -34,26 +34,30 @@ export default class PullRequestConfigsParser extends ConfigsParser {
}

/**
* Create a default backport pull request starting from the target branch and
* Create a backport pull request starting from the target branch and
* the original pr to be backported
* @param originalPullRequest original pull request
* @param targetBranch target branch where the backport should be applied
* @returns {GitPullRequest}
*/
private getDefaultBackportPullRequest(originalPullRequest: GitPullRequest, targetBranch: string): GitPullRequest {
private getDefaultBackportPullRequest(originalPullRequest: GitPullRequest, args: Args): GitPullRequest {
const reviewers = [];
reviewers.push(originalPullRequest.author);
if (originalPullRequest.mergedBy) {
reviewers.push(originalPullRequest.mergedBy);
}

const bodyPrefix = args.bodyPrefix ?? `**Backport:** ${originalPullRequest.htmlUrl}\r\n\r\n`;
const body = args.body ?? `${originalPullRequest.body}\r\n\r\nPowered by [BPer](https://github.com/lampajr/backporting).`;

return {
author: originalPullRequest.author,
title: `[${targetBranch}] ${originalPullRequest.title}`,
body: `**Backport:** ${originalPullRequest.htmlUrl}\r\n\r\n${originalPullRequest.body}\r\n\r\nPowered by [BPer](https://github.com/lampajr/backporting).`,
title: args.title ?? `[${args.targetBranch}] ${originalPullRequest.title}`,
body: `${bodyPrefix}${body}`,
reviewers: [...new Set(reviewers)],
targetRepo: originalPullRequest.targetRepo,
sourceRepo: originalPullRequest.targetRepo,
branchName: args.bpBranchName,
nCommits: 0, // TODO: needed?
commits: [] // TODO needed?
};
Expand Down
6 changes: 4 additions & 2 deletions src/service/git/git.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export interface GitPullRequest {
targetRepo: GitRepository,
sourceRepo: GitRepository,
nCommits: number, // number of commits in the pr
commits: string[] // merge commit or last one
commits: string[], // merge commit or last one
branchName?: string,
}

export interface GitRepository {
Expand All @@ -28,7 +29,8 @@ export interface BackportPullRequest {
base: string, // name of the target branch
title: string, // pr title
body: string, // pr body
reviewers: string[] // pr list of reviewers
reviewers: string[], // pr list of reviewers
branchName?: string,
}

export enum GitServiceType {
Expand Down
2 changes: 1 addition & 1 deletion src/service/runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default class Runner {
await git.clone(configs.originalPullRequest.targetRepo.cloneUrl, configs.folder, configs.targetBranch);

// 5. create new branch from target one and checkout
const backportBranch = `bp-${configs.targetBranch}-${originalPR.commits.join("-")}`;
const backportBranch = backportPR.branchName ?? `bp-${configs.targetBranch}-${originalPR.commits.join("-")}`;
await git.createLocalBranch(configs.folder, backportBranch);

// 6. fetch pull request remote if source owner != target owner or pull request still open
Expand Down
26 changes: 25 additions & 1 deletion test/service/args/cli/cli-args-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ describe("cli args parser", () => {
expect(args.folder).toEqual(undefined);
expect(args.targetBranch).toEqual("target");
expect(args.pullRequest).toEqual("https://localhost/whatever/pulls/1");
expect(args.title).toEqual(undefined);
expect(args.body).toEqual(undefined);
expect(args.bodyPrefix).toEqual(undefined);
expect(args.bpBranchName).toEqual(undefined);
});

test("valid execution [default, long]", () => {
Expand All @@ -45,6 +49,10 @@ describe("cli args parser", () => {
expect(args.folder).toEqual(undefined);
expect(args.targetBranch).toEqual("target");
expect(args.pullRequest).toEqual("https://localhost/whatever/pulls/1");
expect(args.title).toEqual(undefined);
expect(args.body).toEqual(undefined);
expect(args.bodyPrefix).toEqual(undefined);
expect(args.bpBranchName).toEqual(undefined);
});

test("valid execution [override, short]", () => {
Expand All @@ -65,6 +73,10 @@ describe("cli args parser", () => {
expect(args.folder).toEqual(undefined);
expect(args.targetBranch).toEqual("target");
expect(args.pullRequest).toEqual("https://localhost/whatever/pulls/1");
expect(args.title).toEqual(undefined);
expect(args.body).toEqual(undefined);
expect(args.bodyPrefix).toEqual(undefined);
expect(args.bpBranchName).toEqual(undefined);
});

test("valid execution [override, long]", () => {
Expand All @@ -75,7 +87,15 @@ describe("cli args parser", () => {
"--target-branch",
"target",
"--pull-request",
"https://localhost/whatever/pulls/1"
"https://localhost/whatever/pulls/1",
"--title",
"New Title",
"--body",
"New Body",
"--body-prefix",
"New Body Prefix",
"--bp-branch-name",
"bp_branch_name",
]);

const args: Args = parser.parse();
Expand All @@ -85,6 +105,10 @@ describe("cli args parser", () => {
expect(args.folder).toEqual(undefined);
expect(args.targetBranch).toEqual("target");
expect(args.pullRequest).toEqual("https://localhost/whatever/pulls/1");
expect(args.title).toEqual("New Title");
expect(args.body).toEqual("New Body");
expect(args.bodyPrefix).toEqual("New Body Prefix");
expect(args.bpBranchName).toEqual("bp_branch_name");
});

});
Loading