Skip to content

Commit

Permalink
feat(issue-17): override backporting pr data (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
lampajr committed Jun 20, 2023
1 parent 1732481 commit 941beda
Show file tree
Hide file tree
Showing 15 changed files with 279 additions and 30 deletions.
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");
});

});

0 comments on commit 941beda

Please sign in to comment.