feat(dev-infra): support user-failures when computing branches for target label (#38223)

The merge tool provides a way for configurations to determine the branches
for a label lazily. This is supported because it allows labels to respect
the currently selected base branch through the Github UI. e.g. if `target: label`
is applied on a PR and the PR is based on the patch branch, then the change
could only go into the selected target branch, while if it would be based on
`master`, the change would be cherry-picked to `master` too. This allows for
convenient back-porting of changes if they did not apply cleanly to the primary
development branch (`master`).

We want to expand this function so that it is possible to report failures if an
invalid target label is appplied (e.g. `target: major` not allowed in
some situations), or if the Github base branch is not valid for the given target
label (e.g. if `target: lts` is used, but it's not based on a LTS branch).

PR Close #38223
This commit is contained in:
Paul Gschwendtner 2020-07-24 18:05:51 +02:00 committed by Andrew Kushnir
parent 576e329f33
commit 6f0f0d3ea2
3 changed files with 45 additions and 6 deletions

View File

@ -31,6 +31,9 @@ export interface TargetLabel {
* List of branches a pull request with this target label should be merged into. * List of branches a pull request with this target label should be merged into.
* Can also be wrapped in a function that accepts the target branch specified in the * Can also be wrapped in a function that accepts the target branch specified in the
* Github Web UI. This is useful for supporting labels like `target: development-branch`. * Github Web UI. This is useful for supporting labels like `target: development-branch`.
*
* @throws {InvalidTargetLabelError} Invalid label has been applied to pull request.
* @throws {InvalidTargetBranchError} Invalid Github target branch has been selected.
*/ */
branches: TargetLabelBranchResult|((githubTargetBranch: string) => TargetLabelBranchResult); branches: TargetLabelBranchResult|((githubTargetBranch: string) => TargetLabelBranchResult);
} }

View File

@ -12,7 +12,7 @@ import {GitClient} from '../../utils/git';
import {PullRequestFailure} from './failures'; import {PullRequestFailure} from './failures';
import {matchesPattern} from './string-pattern'; import {matchesPattern} from './string-pattern';
import {getBranchesFromTargetLabel, getTargetLabelFromPullRequest} from './target-label'; import {getBranchesFromTargetLabel, getTargetLabelFromPullRequest, InvalidTargetBranchError, InvalidTargetLabelError} from './target-label';
import {PullRequestMergeTask} from './task'; import {PullRequestMergeTask} from './task';
/** Interface that describes a pull request. */ /** Interface that describes a pull request. */
@ -83,6 +83,20 @@ export async function loadAndValidatePullRequest(
labels.some(name => matchesPattern(name, config.commitMessageFixupLabel)); labels.some(name => matchesPattern(name, config.commitMessageFixupLabel));
const hasCaretakerNote = !!config.caretakerNoteLabel && const hasCaretakerNote = !!config.caretakerNoteLabel &&
labels.some(name => matchesPattern(name, config.caretakerNoteLabel!)); labels.some(name => matchesPattern(name, config.caretakerNoteLabel!));
let targetBranches: string[];
// If branches are determined for a given target label, capture errors that are
// thrown as part of branch computation. This is expected because a merge configuration
// can lazily compute branches for a target label and throw. e.g. if an invalid target
// label is applied, we want to exit the script gracefully with an error message.
try {
targetBranches = await getBranchesFromTargetLabel(targetLabel, githubTargetBranch);
} catch (error) {
if (error instanceof InvalidTargetBranchError || error instanceof InvalidTargetLabelError) {
return new PullRequestFailure(error.failureMessage);
}
throw error;
}
return { return {
url: prData.html_url, url: prData.html_url,
@ -92,8 +106,8 @@ export async function loadAndValidatePullRequest(
githubTargetBranch, githubTargetBranch,
needsCommitMessageFixup, needsCommitMessageFixup,
hasCaretakerNote, hasCaretakerNote,
targetBranches,
title: prData.title, title: prData.title,
targetBranches: getBranchesFromTargetLabel(targetLabel, githubTargetBranch),
commitCount: prData.commits, commitCount: prData.commits,
}; };
} }

View File

@ -9,6 +9,22 @@
import {MergeConfig, TargetLabel} from './config'; import {MergeConfig, TargetLabel} from './config';
import {matchesPattern} from './string-pattern'; import {matchesPattern} from './string-pattern';
/**
* Unique error that can be thrown in the merge configuration if an
* invalid branch is targeted.
*/
export class InvalidTargetBranchError {
constructor(public failureMessage: string) {}
}
/**
* Unique error that can be thrown in the merge configuration if an
* invalid label has been applied to a pull request.
*/
export class InvalidTargetLabelError {
constructor(public failureMessage: string) {}
}
/** Gets the target label from the specified pull request labels. */ /** Gets the target label from the specified pull request labels. */
export function getTargetLabelFromPullRequest(config: MergeConfig, labels: string[]): TargetLabel| export function getTargetLabelFromPullRequest(config: MergeConfig, labels: string[]): TargetLabel|
null { null {
@ -21,8 +37,14 @@ export function getTargetLabelFromPullRequest(config: MergeConfig, labels: strin
return null; return null;
} }
/** Gets the branches from the specified target label. */ /**
export function getBranchesFromTargetLabel( * Gets the branches from the specified target label.
label: TargetLabel, githubTargetBranch: string): string[] { *
return typeof label.branches === 'function' ? label.branches(githubTargetBranch) : label.branches; * @throws {InvalidTargetLabelError} Invalid label has been applied to pull request.
* @throws {InvalidTargetBranchError} Invalid Github target branch has been selected.
*/
export async function getBranchesFromTargetLabel(
label: TargetLabel, githubTargetBranch: string): Promise<string[]> {
return typeof label.branches === 'function' ? await label.branches(githubTargetBranch) :
await label.branches;
} }