Skip to content

Commit

Permalink
Merge pull request #340 from korthout/292-target-branches-input
Browse files Browse the repository at this point in the history
Use `target_branches` to select target branches
  • Loading branch information
korthout authored May 29, 2023
2 parents 2ac35cd + 254871b commit e319d32
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 104 deletions.
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ The manual labor of cherry-picking the individual commits can be automated using

## How it works

The backport action looks for labels matching the `label_pattern` input (e.g. `backport release-3.4`) on your merged pull request.
For each of those labels:
1. fetch and checkout a new branch from the target branch (e.g. `release-3.4`)
You can select the branches to backport merged pull requests in two ways:
- using labels on the merged pull request.
The action looks for labels on your merged pull request matching the [`label_pattern`](#label_pattern) input
- using the [`target_branches`](#target_branches) input

For each selected branch, the backport action takes the following steps:
1. fetch and checkout a new branch from the target branch
2. cherry-pick the merged pull request's commits
3. create a pull request to merge the new branch into the target branch
4. comment on the original pull request about its success
Expand Down Expand Up @@ -50,8 +54,6 @@ jobs:
> **Note**
> This workflow runs on [`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) so that `GITHUB_TOKEN` has write access to the repo when the merged pull request comes from a forked repository.
> This write access is necessary for the action to push the commits it cherry-picked.
> The backport action can be run on [`pull_request`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) instead, by checking out the repository using a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) (PAT) with write access to the repo.
> See [actions/checkout#usage](https://github.com/actions/checkout#usage) (`token`).

### Trigger using a comment

Expand Down Expand Up @@ -103,6 +105,14 @@ 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_labels_pattern`

Default: `''` (disabled)

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.

### `github_token`

Default: `${{ github.token }}`
Expand All @@ -124,6 +134,7 @@ Default: `^backport ([^ ]+)$` (e.g. matches `backport release-3.4`)

Regex pattern to match the backport labels on the merged pull request.
Must contain a capture group for the target branch.
Label matching can be disabled entirely using an empty string `''` as pattern.

The action will backport the pull request to each matched target branch.
See [How it works](#how-it-works).
Expand Down Expand Up @@ -152,13 +163,15 @@ Placeholders can be used to define variable values.
These are indicated by a dollar sign and curly braces (`${placeholder}`).
Please refer to this action's README for all available [placeholders](#placeholders).

### `copy_labels_pattern`
### `target_branches`

Default: `''` (disabled)

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.
The action will backport the pull request to each specified target branch (space-delimited).
See [How it works](#how-it-works).

Can be used in addition to backport labels.
By default, only backport labels are used to specify the target branches.

## Placeholders
In the `pull_description` and `pull_title` inputs, placeholders can be used to define variable values.
Expand Down
13 changes: 9 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ description: >
Fast and flexible action to cherry-pick commits from labeled pull requests
author: korthout
inputs:
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.
github_token:
description: >
Token to authenticate requests to GitHub.
Expand Down Expand Up @@ -35,11 +40,11 @@ inputs:
Please refer to this action's README for all available placeholders.
default: >-
[Backport ${target_branch}] ${pull_title}
copy_labels_pattern:
target_branches:
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.
The action will backport the pull request to each specified target branch (space-delimited).
Can be used in addition to backport labels.
By default, only backport labels are used to specify the target branches.
outputs:
was_successful:
description: >
Expand Down
94 changes: 54 additions & 40 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Backport = void 0;
exports.findTargetBranches = exports.Backport = void 0;
const core = __importStar(__nccwpck_require__(2186));
const dedent_1 = __importDefault(__nccwpck_require__(5281));
const git = __importStar(__nccwpck_require__(3374));
Expand All @@ -58,14 +58,16 @@ class Backport {
this.config = config;
}
run() {
var _a, _b;
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
try {
const payload = this.github.getPayload();
const owner = this.github.getRepo().owner;
const repo = (_b = (_a = payload.repository) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : this.github.getRepo().repo;
const pull_number = this.github.getPullNumber();
const mainpr = yield this.github.getPullRequest(pull_number);
const headref = mainpr.head.sha;
const baseref = mainpr.base.sha;
if (!(yield this.github.isMerged(mainpr))) {
const message = "Only merged pull requests can be backported.";
this.github.createComment({
Expand All @@ -76,12 +78,9 @@ class Backport {
});
return;
}
const headref = mainpr.head.sha;
const baseref = mainpr.base.sha;
const labels = mainpr.labels;
console.log(`Detected labels on PR: ${labels.map((label) => label.name)}`);
if (!someLabelIn(labels).matches(this.config.labels.pattern)) {
console.log(`Nothing to backport: none of the labels match the backport pattern '${this.config.labels.pattern.source}'`);
const target_branches = this.findTargetBranches(mainpr, this.config);
if (target_branches.length === 0) {
console.log(`Nothing to backport: no 'target_branches' specified and none of the labels match the backport pattern '${(_c = this.config.labels.pattern) === null || _c === void 0 ? void 0 : _c.source}'`);
return; // nothing left to do here
}
console.log(`Fetching all the commits from the pull request: ${mainpr.commits + 1}`);
Expand All @@ -93,31 +92,16 @@ class Backport {
let labelsToCopy = [];
if (typeof this.config.copy_labels_pattern !== "undefined") {
let copyLabelsPattern = this.config.copy_labels_pattern;
labelsToCopy = labels
labelsToCopy = mainpr.labels
.map((label) => label.name)
.filter((label) => label.match(copyLabelsPattern) &&
!label.match(this.config.labels.pattern));
(this.config.labels.pattern === undefined ||
!label.match(this.config.labels.pattern)));
}
console.log(`Will copy labels matching ${this.config.copy_labels_pattern}. Found matching labels: ${labelsToCopy}`);
const successByTarget = new Map();
for (const label of labels) {
console.log(`Working on label ${label.name}`);
// we are looking for labels like "backport stable/0.24"
const match = this.config.labels.pattern.exec(label.name);
if (!match) {
console.log("Doesn't match expected prefix");
continue;
}
if (match.length < 2) {
console.error((0, dedent_1.default) `\`label_pattern\` '${this.config.labels.pattern.source}' \
matched "${label.name}", but did not capture any branchname. \
Please make sure to provide a regex with a capture group as \
\`label_pattern\`.`);
continue;
}
//extract the target branch (e.g. "stable/0.24")
const target = match[1];
console.log(`Found target in label: ${target}`);
for (const target of target_branches) {
console.log(`Backporting to target branch '${target}...'`);
try {
yield git.fetch(target, this.config.pwd, 1);
}
Expand Down Expand Up @@ -255,6 +239,10 @@ class Backport {
}
});
}
findTargetBranches(mainpr, config) {
const labels = mainpr.labels.map((label) => label.name);
return findTargetBranches(config, labels);
}
composePRContent(target, main) {
const title = utils.replacePlaceholders(this.config.pull.title, main, target);
const body = utils.replacePlaceholders(this.config.pull.description, main, target);
Expand Down Expand Up @@ -313,16 +301,40 @@ class Backport {
}
}
exports.Backport = Backport;
/**
* Helper method for label arrays to check that it matches a particular pattern
*
* @param labels an array of labels
* @returns a 'curried' function to easily test for a matching a label
*/
function someLabelIn(labels) {
return {
matches: (pattern) => labels.some((l) => pattern.test(l.name)),
};
function findTargetBranches(config, labels) {
var _a, _b;
console.log("Determining target branches...");
console.log(`Detected labels on PR: ${labels}`);
const targetBranchesFromLabels = findTargetBranchesFromLabels(labels, config);
const configuredTargetBranches = (_b = (_a = config.target_branches) === null || _a === void 0 ? void 0 : _a.split(" ").map((t) => t.trim()).filter((t) => t !== "")) !== null && _b !== void 0 ? _b : [];
console.log(`Found target branches in labels: ${targetBranchesFromLabels}`);
console.log(`Found target branches in \`target_branches\` input: ${configuredTargetBranches}`);
return [
...new Set([...targetBranchesFromLabels, ...configuredTargetBranches]),
];
}
exports.findTargetBranches = findTargetBranches;
function findTargetBranchesFromLabels(labels, config) {
const pattern = config.labels.pattern;
if (pattern === undefined) {
return [];
}
return labels
.map((label) => {
return { label: label, match: pattern.exec(label) };
})
.filter((result) => {
if (!result.match) {
console.log(`label '${result.label}' doesn't match \`label_pattern\` '${pattern.source}'`);
}
else if (result.match.length < 2) {
console.error((0, dedent_1.default) `label '${result.label}' matches \`label_pattern\` '${pattern.source}', \
but no branchname could be captured. Please make sure to provide a regex with a capture group as \
\`label_pattern\`.`);
}
return !!result.match && result.match.length === 2;
})
.map((result) => result.match[1]);
}


Expand Down Expand Up @@ -626,16 +638,18 @@ function run() {
return __awaiter(this, void 0, void 0, function* () {
const token = core.getInput("github_token", { required: true });
const pwd = core.getInput("github_workspace", { required: true });
const pattern = new RegExp(core.getInput("label_pattern"));
const pattern = core.getInput("label_pattern");
const description = core.getInput("pull_description");
const title = core.getInput("pull_title");
const copy_labels_pattern = core.getInput("copy_labels_pattern");
const target_branches = core.getInput("target_branches");
const github = new github_1.Github(token);
const backport = new backport_1.Backport(github, {
pwd,
labels: { pattern },
labels: { pattern: pattern === "" ? undefined : new RegExp(pattern) },
pull: { description, title },
copy_labels_pattern: copy_labels_pattern === "" ? undefined : new RegExp(copy_labels_pattern),
target_branches: target_branches === "" ? undefined : target_branches,
});
return backport.run();
});
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

Loading

0 comments on commit e319d32

Please sign in to comment.