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(gh75): extract target branched from pr labels #112

Merged
merged 1 commit into from
Mar 30, 2024
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
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: "[email protected]"
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
Loading