Skip to content

Commit

Permalink
feat(gh75): extract target branched from pr labels (#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
lampajr authored Mar 30, 2024
1 parent b2e2e27 commit 53cc505
Show file tree
Hide file tree
Showing 20 changed files with 523 additions and 83 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ This tool comes with some inputs that allow users to override the default behavi
| Version | -V, --version | - | Current version of the tool | |
| Help | -h, --help | - | Display the help message | |
| Target Branches | -tb, --target-branch | N | Comma separated list of branches where the changes must be backported to | |
| Target Branches Pattern | -tbp, --target-branch-pattern | N | Regular expression pattern to extract target branch(es) from pr labels. The branches will be extracted from the pattern's required `target` named capturing group, e.g., `^backport (?<target>([^ ]+))$` | |
| Pull Request | -pr, --pull-request | N | Original pull request url, the one that must be backported, e.g., https://github.com/kiegroup/git-backporting/pull/1 | |
| Configuration File | -cf, --config-file | N | Configuration file, in JSON format, containing all options to be overridded, note that if provided all other CLI options will be ignored | |
| Auth | -a, --auth | N | Git access/authorization token, if provided all token env variables will be ignored. See [auth token](#authorization-token) section for more details | "" |
Expand All @@ -126,7 +127,7 @@ This tool comes with some inputs that allow users to override the default behavi
| Additional comments | --comments | N | Semicolon separated list of additional comments to be posted to the backported pull request | [] |
| Dry Run | -d, --dry-run | N | If enabled the tool does not push nor create anything remotely, use this to skip PR creation | false |

> **NOTE**: `pull request` and `target branch` are *mandatory*, they must be provided as CLI options or as part of the configuration file (if used).
> **NOTE**: `pull request` and (`target branch` or `target branch pattern`) are *mandatory*, they must be provided as CLI options or as part of the configuration file (if used).
#### Authorization token

Expand Down
72 changes: 50 additions & 22 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,77 +1,105 @@
name: "Backporting GitHub Action"
description: "GitHub action providing an automated way to backport pull requests from one branch to another"
description: GitHub action providing an automated way to backport pull requests from one branch to another
inputs:
pull-request:
description: "URL of the pull request to backport, e.g., https://github.com/kiegroup/git-backporting/pull/1"
description: >
URL of the pull request to backport, e.g., "https://github.com/kiegroup/git-backporting/pull/1"
required: false
target-branch:
description: "Comma separated list of branches where the pull request must be backported to"
description: >
Comma separated list of branches where the pull request must be backported to
required: false
target-branch-pattern:
description: >
Regular expression pattern to extract target branch(es) from pr labels.
The branches will be extracted from the pattern's required `target` named capturing group,
for instance "^backport (?<target>([^ ]+))$"
required: false
config-file:
description: "Path to a file containing the json configuration for this tool, the object must match the Args interface"
description: >
Path to a file containing the json configuration for this tool,
the object must match the Args interface
required: false
dry-run:
description: "If enabled the tool does not create any pull request nor push anything remotely"
description: >
If enabled the tool does not create any pull request nor push anything remotely
required: false
default: "false"
auth:
description: "GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT), if not provided will look for existing env variables like GITHUB_TOKEN"
description: >
GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT),
if not provided will look for existing env variables like GITHUB_TOKEN
default: ${{ github.token }}
required: false
git-client:
description: "Git client type <github|gitlab|codeberg>, if not set it is infered from pull-request"
description: >
Git client type <github|gitlab|codeberg>, if not set it is infered from pull-request
required: false
git-user:
description: "Local git user name"
description: Local git user name
default: "GitHub"
required: false
git-email:
description: "Local git user email"
description: Local git user email
default: "noreply@github.com"
required: false
title:
description: "Backporting PR title. Default is the original PR title prefixed by the target branch"
description: >
Backporting PR title. Default is the original PR title prefixed by the target branch
required: false
body-prefix:
description: "Backporting PR body prefix. Default is `Backport: <original-pr-link>`"
description: >
Backporting PR body prefix. Default is `Backport: <original-pr-link>`
required: false
body:
description: "Backporting PR body. Default is the original PR body"
description: >
Backporting PR body. Default is the original PR body
required: false
bp-branch-name:
description: "Comma separated list of backporting PR branch names. Default is auto-generated from commit and target branches"
description: >
Comma separated list of backporting PR branch names.
Default is auto-generated from commit and target branches
required: false
reviewers:
description: "Comma separated list of reviewers for the backporting pull request"
description: >
Comma separated list of reviewers for the backporting pull request
required: false
assignees:
description: "Comma separated list of reviewers for the backporting pull request"
description: >
Comma separated list of reviewers for the backporting pull request
required: false
no-inherit-reviewers:
description: "Considered only if reviewers is empty, if true keep reviewers as empty list, otherwise inherit from original pull request"
description: >
Considered only if reviewers is empty, if true keep reviewers as empty list,
otherwise inherit from original pull request
required: false
default: "false"
labels:
description: "Comma separated list of labels to be assigned to the backported pull request"
description: >
Comma separated list of labels to be assigned to the backported pull request
required: false
inherit-labels:
description: "If true the backported pull request will inherit labels from the original one"
description: >
If true the backported pull request will inherit labels from the original one
required: false
default: "false"
no-squash:
description: "If set to true the tool will backport all commits as part of the pull request instead of the suqashed one"
description: >
If set to true the tool will backport all commits as part of the pull request
instead of the suqashed one
required: false
default: "false"
strategy:
description: "Cherry-pick merge strategy"
description: Cherry-pick merge strategy
required: false
default: "recursive"
strategy-option:
description: "Cherry-pick merge strategy option"
description: Cherry-pick merge strategy option
required: false
default: "theirs"
comments:
description: "Semicolon separated list of additional comments to be posted to the backported pull request"
description: >
Semicolon separated list of additional comments to be posted to the backported pull request
required: false

runs:
Expand Down
48 changes: 43 additions & 5 deletions dist/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ class ArgsParser {
}
parse() {
const args = this.readArgs();
if (!args.pullRequest) {
throw new Error("Missing option: pull request must be provided");
}
// validate and fill with defaults
if (!args.pullRequest || !args.targetBranch || args.targetBranch.trim().length == 0) {
throw new Error("Missing option: pull request and target branches must be provided");
if ((!args.targetBranch || args.targetBranch.trim().length == 0) && !args.targetBranchPattern) {
throw new Error("Missing option: target branch(es) or target regular expression must be provided");
}
return {
pullRequest: args.pullRequest,
targetBranch: args.targetBranch,
targetBranchPattern: args.targetBranchPattern,
dryRun: this.getOrDefault(args.dryRun, false),
auth: this.getOrDefault(args.auth),
folder: this.getOrDefault(args.folder),
Expand Down Expand Up @@ -181,6 +185,7 @@ class CLIArgsParser extends args_parser_1.default {
.version(package_json_1.version)
.description(package_json_1.description)
.option("-tb, --target-branch <branches>", "comma separated list of branches where changes must be backported to")
.option("-tbp, --target-branch-pattern <pattern>", "regular expression pattern to extract target branch(es) from pr labels, the branches will be extracted from the pattern's required `target` named capturing group")
.option("-pr, --pull-request <pr-url>", "pull request url, e.g., https://github.com/kiegroup/git-backporting/pull/1")
.option("-d, --dry-run", "if enabled the tool does not create any pull request nor push anything remotely")
.option("-a, --auth <auth>", "git authentication string, if not provided fallback by looking for existing env variables like GITHUB_TOKEN")
Expand Down Expand Up @@ -218,6 +223,7 @@ class CLIArgsParser extends args_parser_1.default {
auth: opts.auth,
pullRequest: opts.pullRequest,
targetBranch: opts.targetBranch,
targetBranchPattern: opts.targetBranchPattern,
folder: opts.folder,
gitClient: opts.gitClient,
gitUser: opts.gitUser,
Expand Down Expand Up @@ -331,7 +337,18 @@ class PullRequestConfigsParser extends configs_parser_1.default {
throw error;
}
const folder = args.folder ?? this.getDefaultFolder();
const targetBranches = [...new Set((0, args_utils_1.getAsCommaSeparatedList)(args.targetBranch))];
let targetBranches = [];
if (args.targetBranchPattern) {
// parse labels to extract target branch(es)
targetBranches = this.getTargetBranchesFromLabels(args.targetBranchPattern, pr.labels);
if (targetBranches.length === 0) {
throw new Error(`Unable to extract target branches with regular expression "${args.targetBranchPattern}"`);
}
}
else {
// target branch must be provided if targetRegExp is missing
targetBranches = [...new Set((0, args_utils_1.getAsCommaSeparatedList)(args.targetBranch))];
}
const bpBranchNames = [...new Set(args.bpBranchName ? ((0, args_utils_1.getAsCleanedCommaSeparatedList)(args.bpBranchName) ?? []) : [])];
if (bpBranchNames.length > 1 && bpBranchNames.length != targetBranches.length) {
throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`);
Expand All @@ -353,6 +370,28 @@ class PullRequestConfigsParser extends configs_parser_1.default {
getDefaultFolder() {
return "bp";
}
/**
* Parse the provided labels and return a list of target branches
* obtained by applying the provided pattern as regular expression extractor
* @param pattern reg exp pattern to extract target branch from label name
* @param labels list of labels to check
* @returns list of target branches
*/
getTargetBranchesFromLabels(pattern, labels) {
this.logger.debug(`Extracting branches from [${labels}] using ${pattern}`);
const regExp = new RegExp(pattern);
const branches = [];
for (const l of labels) {
const result = regExp.exec(l);
if (result?.groups) {
const { target } = result.groups;
if (target) {
branches.push(target);
}
}
}
return [...new Set(branches)];
}
/**
* Create a backport pull request starting from the target branch and
* the original pr to be backported
Expand Down Expand Up @@ -5891,7 +5930,6 @@ var preservedUrlFields = [
"protocol",
"query",
"search",
"hash",
];

// Create handlers that pass events from native requests
Expand Down Expand Up @@ -6325,7 +6363,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
redirectUrl.protocol !== "https:" ||
redirectUrl.host !== currentHost &&
!isSubdomain(redirectUrl.host, currentHost)) {
removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);
removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
}

// Evaluate the beforeRedirect callback
Expand Down
49 changes: 43 additions & 6 deletions dist/gha/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@ class ArgsParser {
}
parse() {
const args = this.readArgs();
if (!args.pullRequest) {
throw new Error("Missing option: pull request must be provided");
}
// validate and fill with defaults
if (!args.pullRequest || !args.targetBranch || args.targetBranch.trim().length == 0) {
throw new Error("Missing option: pull request and target branches must be provided");
if ((!args.targetBranch || args.targetBranch.trim().length == 0) && !args.targetBranchPattern) {
throw new Error("Missing option: target branch(es) or target regular expression must be provided");
}
return {
pullRequest: args.pullRequest,
targetBranch: args.targetBranch,
targetBranchPattern: args.targetBranchPattern,
dryRun: this.getOrDefault(args.dryRun, false),
auth: this.getOrDefault(args.auth),
folder: this.getOrDefault(args.folder),
Expand Down Expand Up @@ -186,7 +190,8 @@ class GHAArgsParser extends args_parser_1.default {
dryRun: (0, args_utils_1.getAsBooleanOrDefault)((0, core_1.getInput)("dry-run")),
auth: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("auth")),
pullRequest: (0, core_1.getInput)("pull-request"),
targetBranch: (0, core_1.getInput)("target-branch"),
targetBranch: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("target-branch")),
targetBranchPattern: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("target-reg-exp")),
folder: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("folder")),
gitClient: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("git-client")),
gitUser: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("git-user")),
Expand Down Expand Up @@ -300,7 +305,18 @@ class PullRequestConfigsParser extends configs_parser_1.default {
throw error;
}
const folder = args.folder ?? this.getDefaultFolder();
const targetBranches = [...new Set((0, args_utils_1.getAsCommaSeparatedList)(args.targetBranch))];
let targetBranches = [];
if (args.targetBranchPattern) {
// parse labels to extract target branch(es)
targetBranches = this.getTargetBranchesFromLabels(args.targetBranchPattern, pr.labels);
if (targetBranches.length === 0) {
throw new Error(`Unable to extract target branches with regular expression "${args.targetBranchPattern}"`);
}
}
else {
// target branch must be provided if targetRegExp is missing
targetBranches = [...new Set((0, args_utils_1.getAsCommaSeparatedList)(args.targetBranch))];
}
const bpBranchNames = [...new Set(args.bpBranchName ? ((0, args_utils_1.getAsCleanedCommaSeparatedList)(args.bpBranchName) ?? []) : [])];
if (bpBranchNames.length > 1 && bpBranchNames.length != targetBranches.length) {
throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`);
Expand All @@ -322,6 +338,28 @@ class PullRequestConfigsParser extends configs_parser_1.default {
getDefaultFolder() {
return "bp";
}
/**
* Parse the provided labels and return a list of target branches
* obtained by applying the provided pattern as regular expression extractor
* @param pattern reg exp pattern to extract target branch from label name
* @param labels list of labels to check
* @returns list of target branches
*/
getTargetBranchesFromLabels(pattern, labels) {
this.logger.debug(`Extracting branches from [${labels}] using ${pattern}`);
const regExp = new RegExp(pattern);
const branches = [];
for (const l of labels) {
const result = regExp.exec(l);
if (result?.groups) {
const { target } = result.groups;
if (target) {
branches.push(target);
}
}
}
return [...new Set(branches)];
}
/**
* Create a backport pull request starting from the target branch and
* the original pr to be backported
Expand Down Expand Up @@ -7621,7 +7659,6 @@ var preservedUrlFields = [
"protocol",
"query",
"search",
"hash",
];

// Create handlers that pass events from native requests
Expand Down Expand Up @@ -8055,7 +8092,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
redirectUrl.protocol !== "https:" ||
redirectUrl.host !== currentHost &&
!isSubdomain(redirectUrl.host, currentHost)) {
removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);
removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
}

// Evaluate the beforeRedirect callback
Expand Down
8 changes: 6 additions & 2 deletions src/service/args/args-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ export default abstract class ArgsParser {
public parse(): Args {
const args = this.readArgs();

if (!args.pullRequest) {
throw new Error("Missing option: pull request must be provided");
}
// validate and fill with defaults
if (!args.pullRequest || !args.targetBranch || args.targetBranch.trim().length == 0) {
throw new Error("Missing option: pull request and target branches must be provided");
if ((!args.targetBranch || args.targetBranch.trim().length == 0) && !args.targetBranchPattern) {
throw new Error("Missing option: target branch(es) or target regular expression must be provided");
}

return {
pullRequest: args.pullRequest,
targetBranch: args.targetBranch,
targetBranchPattern: args.targetBranchPattern,
dryRun: this.getOrDefault(args.dryRun, false),
auth: this.getOrDefault(args.auth),
folder: this.getOrDefault(args.folder),
Expand Down
Loading

0 comments on commit 53cc505

Please sign in to comment.