Skip to content

Allow configuring RegEx patterns for copySourcePRLabels #529

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 6 additions & 2 deletions docs/config-file-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,11 +490,15 @@ Assign the same reviewers to the target pull request that were assigned to the o

#### `copySourcePRLabels`

Copies all labels from the original (source) pull request to the backport (target) pull request.
Copies labels from the original (source) pull request to the backport (target) pull request.
Can be either a boolean or an array of strings. When set to `true`, _all_ labels from the source PR are copied to the target PR.
When an array of strings is configured, each string is used as a RegEx pattern and will be compared to each label of the source PR.
If the label on the source PR matches at least one configured string, it will be copied to the target PR.

Default: `false`
```json
{
"copySourcePRLabels": false
"copySourcePRLabels": ["^Team:.*", "^:.*", "^>.*"]
}
```

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Commit } from '../../entrypoint.api';
import { ValidConfigOptions } from '../../options/options';
import { getLabelsToCopy } from './copySourcePullRequestLabels';

describe('getLabelsToCopy', () => {
it('should return an empty array when no commits have sourcePullRequest', () => {
const commits = [{ sourcePullRequest: null }] as unknown as Commit[];
const options = { copySourcePRLabels: true } as ValidConfigOptions;
const result = getLabelsToCopy(commits, options);
expect(result).toEqual([]);
});

it('copies all labels except backport labels when copySourcePRLabels is boolean', () => {
const commits = [
{
sourcePullRequest: {
title: 'My pr title',
labels: ['a', 'b', 'my-backport-label'],
number: 1,
},
targetPullRequestStates: [{ label: 'my-backport-label' } as any],
} as unknown as Commit,
];

const options = {
copySourcePRLabels: true,
} as unknown as ValidConfigOptions;

const result = getLabelsToCopy(commits, options);
expect(result).toEqual(['a', 'b']);
});

it('copies labels using regex patterns when copySourcePRLabels is string array', () => {
const commits = [
{
sourcePullRequest: {
title: 'PR',
labels: ['feat:new', 'chore', 'important-bug'],
number: 2,
},
targetPullRequestStates: [],
} as unknown as Commit,
];

const options = {
copySourcePRLabels: ['^feat', 'bug$'],
} as unknown as ValidConfigOptions;

const result = getLabelsToCopy(commits, options);
expect(result).toEqual(['feat:new', 'important-bug']);
});

it('handles multiple commits and flattens results', () => {
const commits = [
{
sourcePullRequest: { title: 'PR1', labels: ['x'], number: 3 },
targetPullRequestStates: [],
} as unknown as Commit,
{
sourcePullRequest: { title: 'PR2', labels: ['y'], number: 4 },
targetPullRequestStates: [],
} as unknown as Commit,
];

const options = {
copySourcePRLabels: true,
} as unknown as ValidConfigOptions;

const result = getLabelsToCopy(commits, options);
expect(result).toEqual(['x', 'y']);
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isArray } from 'lodash';
import { ValidConfigOptions } from '../../options/options';
import { addLabelsToPullRequest } from '../github/v3/addLabelsToPullRequest';
import { Commit } from '../sourceCommit/parseSourceCommit';
Expand All @@ -7,22 +8,41 @@ export async function copySourcePullRequestLabelsToTargetPullRequest(
commits: Commit[],
pullNumber: number,
) {
const labels = getNonBackportLabels(commits);
const labels = getLabelsToCopy(commits, options);
if (labels.length > 0) {
await addLabelsToPullRequest({ ...options, pullNumber, labels });
}
}

function getNonBackportLabels(commits: Commit[]) {
export function getLabelsToCopy(
commits: Commit[],
options: ValidConfigOptions,
) {
return commits.flatMap((commit) => {
if (!commit.sourcePullRequest) {
return [];
}

const backportLabels = commit.targetPullRequestStates.map((pr) => pr.label);
const labels = commit.sourcePullRequest.labels.filter(
(label) => !backportLabels.includes(label),
);
const labels = commit.sourcePullRequest.labels.filter((label) => {
// If `copySourcePRLabels` is a boolean, it must be true to reach this method.
// Therefore, we simply copy all labels from the source PR that aren't already on the target PR.
const copySourcePRLabels = options.copySourcePRLabels;
if (copySourcePRLabels === true) {
const isBackportLabel = backportLabels.includes(label);
return !isBackportLabel;
}

// Otherwise, it's an array of regex patterns.
if (isArray(copySourcePRLabels)) {
return copySourcePRLabels.some((sourceLabel) =>
label.match(new RegExp(sourceLabel)),
);
}
throw new Error(
`Unexpected type of copySourcePRLabels: ${JSON.stringify(copySourcePRLabels)}, must be boolean or array`,
);
});

return labels;
});
Expand Down
2 changes: 1 addition & 1 deletion src/options/ConfigOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type Options = Partial<{
cherrypickRef: boolean;
commitConflicts: boolean;
commitPaths: string[];
copySourcePRLabels: boolean;
copySourcePRLabels: boolean | string[];
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to figure out a way to make this change in cliArgs.ts. We could also define a new option instead of allowing two types in the existing option, if that's preferred. Let me know what you think.

copySourcePRReviewers: boolean;
details: boolean;
dir: string;
Expand Down
30 changes: 30 additions & 0 deletions src/options/cliArgs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,36 @@ describe('getOptionsFromCliArgs', () => {
});
});

describe('copySourcePRReviewers', () => {
it('should be undefined when not provided', () => {
const res = getOptionsFromCliArgs([]);
expect(res.copySourcePRReviewers).toBe(undefined);
});

it('should be true when option is provided with no value given', () => {
const res = getOptionsFromCliArgs(['--copy-source-pr-reviewers']);
expect(res.copySourcePRReviewers).toBe(true);
});

it('should set to false', () => {
const argv = ['--copy-source-pr-reviewers', 'false'];
const res = getOptionsFromCliArgs(argv);
expect(res.copySourcePRReviewers).toBe(false);
});

it('should set to true', () => {
const argv = ['--copy-source-pr-reviewers', 'true'];
const res = getOptionsFromCliArgs(argv);
expect(res.copySourcePRReviewers).toBe(true);
});

it('should accept a list of regexes', () => {
const argv = ['--copy-source-pr-reviewers', '^foo, bar$, baz*'];
const res = getOptionsFromCliArgs(argv);
expect(res.copySourcePRReviewers).toEqual(['^foo', 'bar$', 'baz*']);
});
});

describe('repo', () => {
it('splits into repoOwner and repoName', () => {
const argv = ['--repo', 'elastic/kibana'];
Expand Down
8 changes: 7 additions & 1 deletion src/options/cliArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,13 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) {
.option('copySourcePRReviewers', {
description: 'Copy reviewers from the source PR to the target PR',
alias: ['copySourcePrReviewers', 'addOriginalReviewers'],
type: 'boolean',
type: 'string',
coerce: (val: string) => {
if (val === 'true' || val === '') return true;
if (val === 'false') return false;

return val.split(',').map((s) => s.trim());
},
})

.option('targetBranch', {
Expand Down
2 changes: 1 addition & 1 deletion src/test/e2e/cli/entrypoint.cli.private.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Options:
[boolean]
--copySourcePRReviewers, Copy reviewers from the source PR to the
--copySourcePrReviewers, target PR
--addOriginalReviewers [boolean]
--addOriginalReviewers [string]
-b, --targetBranch, --branch Branch(es) to backport to [array]
--targetBranchChoice List branches to backport to [array]
-l, --targetPRLabel, --label Add labels to the target (backport) PR [array]
Expand Down