From 2980d85b8cd8ec7d0dda657c2ade640fc7f5ff0f Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Fri, 22 Jan 2021 14:05:28 -0800 Subject: [PATCH] feat(dev-infra): support --no-branch-prompt to suppress the merge tool branch target confirmation (#40528) Add support for a `--no-branch-prompt` flag for the `ng-dev pr merge` tool. This flag enables suppression of the confirmation prompt for which branches the specified PR will merge into. PR Close #40528 --- dev-infra/ng-dev.js | 147 +++++++++++++++++++++--------------- dev-infra/pr/cli.ts | 4 +- dev-infra/pr/merge/cli.ts | 39 +++++++--- dev-infra/pr/merge/index.ts | 18 ++--- dev-infra/pr/merge/task.ts | 20 ++++- 5 files changed, 141 insertions(+), 87 deletions(-) diff --git a/dev-infra/ng-dev.js b/dev-infra/ng-dev.js index 25bcc895fa0..7fc25d5c19b 100755 --- a/dev-infra/ng-dev.js +++ b/dev-infra/ng-dev.js @@ -3970,15 +3970,20 @@ var AutosquashMergeStrategy = /** @class */ (function (_super) { * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +var defaultPullRequestMergeTaskFlags = { + branchPrompt: true, +}; /** * Class that accepts a merge script configuration and Github token. It provides * a programmatic interface for merging multiple pull requests based on their * labels that have been resolved through the merge script configuration. */ var PullRequestMergeTask = /** @class */ (function () { - function PullRequestMergeTask(config, git) { + function PullRequestMergeTask(config, git, flags) { this.config = config; this.git = git; + // Update flags property with the provided flags values as patches to the default flag values. + this.flags = tslib.__assign(tslib.__assign({}, defaultPullRequestMergeTaskFlags), flags); } /** * Merges the given pull request and pushes it upstream. @@ -3988,10 +3993,10 @@ var PullRequestMergeTask = /** @class */ (function () { PullRequestMergeTask.prototype.merge = function (prNumber, force) { if (force === void 0) { force = false; } return tslib.__awaiter(this, void 0, void 0, function () { - var hasOauthScopes, pullRequest, _a, strategy, previousBranchOrRevision, failure, e_1; + var hasOauthScopes, pullRequest, _a, _b, strategy, previousBranchOrRevision, failure, e_1; var _this = this; - return tslib.__generator(this, function (_b) { - switch (_b.label) { + return tslib.__generator(this, function (_c) { + switch (_c.label) { case 0: return [4 /*yield*/, this.git.hasOauthScopes(function (scopes, missing) { if (!scopes.includes('repo')) { if (_this.config.remote.private) { @@ -4003,7 +4008,7 @@ var PullRequestMergeTask = /** @class */ (function () { } })]; case 1: - hasOauthScopes = _b.sent(); + hasOauthScopes = _c.sent(); if (hasOauthScopes !== true) { return [2 /*return*/, { status: 5 /* GITHUB_ERROR */, @@ -4015,43 +4020,48 @@ var PullRequestMergeTask = /** @class */ (function () { } return [4 /*yield*/, loadAndValidatePullRequest(this, prNumber, force)]; case 2: - pullRequest = _b.sent(); + pullRequest = _c.sent(); if (!isPullRequest(pullRequest)) { return [2 /*return*/, { status: 3 /* FAILED */, failure: pullRequest }]; } + _a = this.flags.branchPrompt; + if (!_a) return [3 /*break*/, 4]; return [4 /*yield*/, promptConfirm(getTargettedBranchesConfirmationPromptMessage(pullRequest))]; case 3: - if (!(_b.sent())) { + _a = !(_c.sent()); + _c.label = 4; + case 4: + if (_a) { return [2 /*return*/, { status: 4 /* USER_ABORTED */ }]; } - _a = pullRequest.hasCaretakerNote; - if (!_a) return [3 /*break*/, 5]; + _b = pullRequest.hasCaretakerNote; + if (!_b) return [3 /*break*/, 6]; return [4 /*yield*/, promptConfirm(getCaretakerNotePromptMessage(pullRequest))]; - case 4: - _a = !(_b.sent()); - _b.label = 5; case 5: + _b = !(_c.sent()); + _c.label = 6; + case 6: // If the pull request has a caretaker note applied, raise awareness by prompting // the caretaker. The caretaker can then decide to proceed or abort the merge. - if (_a) { + if (_b) { return [2 /*return*/, { status: 4 /* USER_ABORTED */ }]; } strategy = this.config.githubApiMerge ? new GithubApiMergeStrategy(this.git, this.config.githubApiMerge) : new AutosquashMergeStrategy(this.git); previousBranchOrRevision = null; - _b.label = 6; - case 6: - _b.trys.push([6, 10, 11, 12]); + _c.label = 7; + case 7: + _c.trys.push([7, 11, 12, 13]); previousBranchOrRevision = this.git.getCurrentBranchOrRevision(); // Run preparations for the merge (e.g. fetching branches). return [4 /*yield*/, strategy.prepare(pullRequest)]; - case 7: - // Run preparations for the merge (e.g. fetching branches). - _b.sent(); - return [4 /*yield*/, strategy.merge(pullRequest)]; case 8: - failure = _b.sent(); + // Run preparations for the merge (e.g. fetching branches). + _c.sent(); + return [4 /*yield*/, strategy.merge(pullRequest)]; + case 9: + failure = _c.sent(); if (failure !== null) { return [2 /*return*/, { status: 3 /* FAILED */, failure: failure }]; } @@ -4059,26 +4069,26 @@ var PullRequestMergeTask = /** @class */ (function () { // branches because we cannot delete branches which are currently checked out. this.git.run(['checkout', '-f', previousBranchOrRevision]); return [4 /*yield*/, strategy.cleanup(pullRequest)]; - case 9: - _b.sent(); + case 10: + _c.sent(); // Return a successful merge status. return [2 /*return*/, { status: 2 /* SUCCESS */ }]; - case 10: - e_1 = _b.sent(); + case 11: + e_1 = _c.sent(); // Catch all git command errors and return a merge result w/ git error status code. // Other unknown errors which aren't caused by a git command are re-thrown. if (e_1 instanceof GitCommandError) { return [2 /*return*/, { status: 0 /* UNKNOWN_GIT_ERROR */ }]; } throw e_1; - case 11: + case 12: // Always try to restore the branch if possible. We don't want to leave // the repository in a different state than before. if (previousBranchOrRevision !== null) { this.git.runGraceful(['checkout', '-f', previousBranchOrRevision]); } return [7 /*endfinally*/]; - case 12: return [2 /*return*/]; + case 13: return [2 /*return*/]; } }); }); @@ -4106,8 +4116,7 @@ var PullRequestMergeTask = /** @class */ (function () { * @param projectRoot Path to the local Git project that is used for merging. * @param config Configuration for merging pull requests. */ -function mergePullRequest(prNumber, githubToken, projectRoot, config) { - if (projectRoot === void 0) { projectRoot = getRepoBaseDir(); } +function mergePullRequest(prNumber, githubToken, flags) { return tslib.__awaiter(this, void 0, void 0, function () { /** Performs the merge and returns whether it was successful or not. */ function performMerge(ignoreFatalErrors) { @@ -4223,7 +4232,7 @@ function mergePullRequest(prNumber, githubToken, projectRoot, config) { // Set the environment variable to skip all git commit hooks triggered by husky. We are unable to // rely on `--no-verify` as some hooks still run, notably the `prepare-commit-msg` hook. process.env['HUSKY'] = '0'; - return [4 /*yield*/, createPullRequestMergeTask(githubToken, projectRoot, config)]; + return [4 /*yield*/, createPullRequestMergeTask(githubToken, flags)]; case 1: api = _a.sent(); return [4 /*yield*/, performMerge(false)]; @@ -4244,16 +4253,13 @@ function mergePullRequest(prNumber, githubToken, projectRoot, config) { * and optional explicit configuration. An explicit configuration can be specified * when the merge script is used outside of a `ng-dev` configured repository. */ -function createPullRequestMergeTask(githubToken, projectRoot, explicitConfig) { +function createPullRequestMergeTask(githubToken, flags) { return tslib.__awaiter(this, void 0, void 0, function () { - var git_1, devInfraConfig, git, _a, config, errors; + var projectRoot, devInfraConfig, git, _a, config, errors; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: - if (explicitConfig !== undefined) { - git_1 = new GitClient(githubToken, { github: explicitConfig.remote }, projectRoot); - return [2 /*return*/, new PullRequestMergeTask(explicitConfig, git_1)]; - } + projectRoot = getRepoBaseDir(); devInfraConfig = getConfig(); git = new GitClient(githubToken, devInfraConfig, projectRoot); return [4 /*yield*/, loadAndValidateConfig(devInfraConfig, git.github)]; @@ -4269,7 +4275,7 @@ function createPullRequestMergeTask(githubToken, projectRoot, explicitConfig) { config.remote = devInfraConfig.github; // We can cast this to a merge config with remote because we always set the // remote above. - return [2 /*return*/, new PullRequestMergeTask(config, git)]; + return [2 /*return*/, new PullRequestMergeTask(config, git, flags)]; } }); }); @@ -4282,17 +4288,29 @@ function createPullRequestMergeTask(githubToken, projectRoot, explicitConfig) { * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -/** Builds the options for the merge command. */ -function buildMergeCommand(yargs) { - return addGithubTokenOption(yargs).help().strict().positional('pr-number', { demandOption: true, type: 'number' }); +/** Builds the command. */ +function builder$7(yargs) { + return addGithubTokenOption(yargs) + .help() + .strict() + .positional('pr', { + demandOption: true, + type: 'number', + description: 'The PR to be merged.', + }) + .option('branch-prompt', { + type: 'boolean', + default: true, + description: 'Whether to prompt to confirm the branches a PR will merge into.', + }); } -/** Handles the merge command. i.e. performs the merge of a specified pull request. */ -function handleMergeCommand(_a) { - var pr = _a["pr-number"], githubToken = _a.githubToken; +/** Handles the command. */ +function handler$7(_a) { + var pr = _a.pr, githubToken = _a.githubToken, branchPrompt = _a.branchPrompt; return tslib.__awaiter(this, void 0, void 0, function () { return tslib.__generator(this, function (_b) { switch (_b.label) { - case 0: return [4 /*yield*/, mergePullRequest(pr, githubToken)]; + case 0: return [4 /*yield*/, mergePullRequest(pr, githubToken, { branchPrompt: branchPrompt })]; case 1: _b.sent(); return [2 /*return*/]; @@ -4300,6 +4318,13 @@ function handleMergeCommand(_a) { }); }); } +/** yargs command module describing the command. */ +var MergeCommandModule = { + handler: handler$7, + builder: builder$7, + command: 'merge ', + describe: 'Merge a PR into its targeted branches.', +}; /** * @license @@ -4459,9 +4484,9 @@ function buildPrParser(localYargs) { return localYargs.help() .strict() .demandCommand() - .command('merge ', 'Merge pull requests', buildMergeCommand, handleMergeCommand) .command('discover-new-conflicts ', 'Check if a pending PR causes new conflicts for other pending PRs', buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand) .command('rebase ', 'Rebase a pending PR and push the rebased commits back to Github', buildRebaseCommand, handleRebaseCommand) + .command(MergeCommandModule) .command(CheckoutCommandModule) .command(CheckTargetBranchesModule); } @@ -4930,7 +4955,7 @@ function buildReleaseOutput() { * found in the LICENSE file at https://angular.io/license */ /** Yargs command builder for configuring the `ng-dev release build` command. */ -function builder$7(argv) { +function builder$8(argv) { return argv.option('json', { type: 'boolean', description: 'Whether the built packages should be printed to stdout as JSON.', @@ -4938,7 +4963,7 @@ function builder$7(argv) { }); } /** Yargs command handler for building a release. */ -function handler$7(args) { +function handler$8(args) { return tslib.__awaiter(this, void 0, void 0, function* () { const { npmPackages } = getReleaseConfig(); let builtPackages = yield buildReleaseOutput(); @@ -4973,8 +4998,8 @@ function handler$7(args) { } /** CLI command module for building release output. */ const ReleaseBuildCommandModule = { - builder: builder$7, - handler: handler$7, + builder: builder$8, + handler: handler$8, command: 'build', describe: 'Builds the release output for the current branch.', }; @@ -6450,11 +6475,11 @@ class ReleaseTool { * found in the LICENSE file at https://angular.io/license */ /** Yargs command builder for configuring the `ng-dev release publish` command. */ -function builder$8(argv) { +function builder$9(argv) { return addGithubTokenOption(argv); } /** Yargs command handler for staging a release. */ -function handler$8(args) { +function handler$9(args) { return tslib.__awaiter(this, void 0, void 0, function* () { const config = getConfig(); const releaseConfig = getReleaseConfig(config); @@ -6477,8 +6502,8 @@ function handler$8(args) { } /** CLI command module for publishing a release. */ const ReleasePublishCommandModule = { - builder: builder$8, - handler: handler$8, + builder: builder$9, + handler: handler$9, command: 'publish', describe: 'Publish new releases and configure version branches.', }; @@ -6490,7 +6515,7 @@ const ReleasePublishCommandModule = { * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -function builder$9(args) { +function builder$a(args) { return args .positional('tagName', { type: 'string', @@ -6504,7 +6529,7 @@ function builder$9(args) { }); } /** Yargs command handler for building a release. */ -function handler$9(args) { +function handler$a(args) { return tslib.__awaiter(this, void 0, void 0, function* () { const { targetVersion: rawVersion, tagName } = args; const { npmPackages, publishRegistry } = getReleaseConfig(); @@ -6536,8 +6561,8 @@ function handler$9(args) { } /** CLI command module for setting an NPM dist tag. */ const ReleaseSetDistTagCommand = { - builder: builder$9, - handler: handler$9, + builder: builder$a, + handler: handler$a, command: 'set-dist-tag ', describe: 'Sets a given NPM dist tag for all release packages.', }; @@ -6616,22 +6641,22 @@ function getCurrentGitUser() { * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -function builder$a(args) { +function builder$b(args) { return args.option('mode', { demandOption: true, description: 'Whether the env-stamp should be built for a snapshot or release', choices: ['snapshot', 'release'] }); } -function handler$a({ mode }) { +function handler$b({ mode }) { return tslib.__awaiter(this, void 0, void 0, function* () { buildEnvStamp(mode); }); } /** CLI command module for building the environment stamp. */ const BuildEnvStampCommand = { - builder: builder$a, - handler: handler$a, + builder: builder$b, + handler: handler$b, command: 'build-env-stamp', describe: 'Build the environment stamping information', }; diff --git a/dev-infra/pr/cli.ts b/dev-infra/pr/cli.ts index c9e6ccfbca0..df57551dfab 100644 --- a/dev-infra/pr/cli.ts +++ b/dev-infra/pr/cli.ts @@ -11,7 +11,7 @@ import * as yargs from 'yargs'; import {CheckTargetBranchesModule} from './check-target-branches/cli'; import {CheckoutCommandModule} from './checkout/cli'; import {buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand} from './discover-new-conflicts/cli'; -import {buildMergeCommand, handleMergeCommand} from './merge/cli'; +import {MergeCommandModule} from './merge/cli'; import {buildRebaseCommand, handleRebaseCommand} from './rebase/cli'; /** Build the parser for pull request commands. */ @@ -19,7 +19,6 @@ export function buildPrParser(localYargs: yargs.Argv) { return localYargs.help() .strict() .demandCommand() - .command('merge ', 'Merge pull requests', buildMergeCommand, handleMergeCommand) .command( 'discover-new-conflicts ', 'Check if a pending PR causes new conflicts for other pending PRs', @@ -27,6 +26,7 @@ export function buildPrParser(localYargs: yargs.Argv) { .command( 'rebase ', 'Rebase a pending PR and push the rebased commits back to Github', buildRebaseCommand, handleRebaseCommand) + .command(MergeCommandModule) .command(CheckoutCommandModule) .command(CheckTargetBranchesModule); } diff --git a/dev-infra/pr/merge/cli.ts b/dev-infra/pr/merge/cli.ts index 0c2600351ae..a71b142a2f9 100644 --- a/dev-infra/pr/merge/cli.ts +++ b/dev-infra/pr/merge/cli.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Arguments, Argv} from 'yargs'; +import {Arguments, Argv, CommandModule} from 'yargs'; import {addGithubTokenOption} from '../../utils/git/github-yargs'; @@ -15,17 +15,36 @@ import {mergePullRequest} from './index'; /** The options available to the merge command via CLI. */ export interface MergeCommandOptions { githubToken: string; - 'pr-number': number; + pr: number; + branchPrompt: boolean; } -/** Builds the options for the merge command. */ -export function buildMergeCommand(yargs: Argv): Argv { - return addGithubTokenOption(yargs).help().strict().positional( - 'pr-number', {demandOption: true, type: 'number'}); +/** Builds the command. */ +function builder(yargs: Argv) { + return addGithubTokenOption(yargs) + .help() + .strict() + .positional('pr', { + demandOption: true, + type: 'number', + description: 'The PR to be merged.', + }) + .option('branch-prompt' as 'branchPrompt', { + type: 'boolean', + default: true, + description: 'Whether to prompt to confirm the branches a PR will merge into.', + }); } -/** Handles the merge command. i.e. performs the merge of a specified pull request. */ -export async function handleMergeCommand( - {'pr-number': pr, githubToken}: Arguments) { - await mergePullRequest(pr, githubToken); +/** Handles the command. */ +async function handler({pr, githubToken, branchPrompt}: Arguments) { + await mergePullRequest(pr, githubToken, {branchPrompt}); } + +/** yargs command module describing the command. */ +export const MergeCommandModule: CommandModule<{}, MergeCommandOptions> = { + handler, + builder, + command: 'merge ', + describe: 'Merge a PR into its targeted branches.', +}; diff --git a/dev-infra/pr/merge/index.ts b/dev-infra/pr/merge/index.ts index cddc947394f..c2f5e1a193e 100644 --- a/dev-infra/pr/merge/index.ts +++ b/dev-infra/pr/merge/index.ts @@ -14,7 +14,7 @@ import {GITHUB_TOKEN_GENERATE_URL} from '../../utils/git/github-urls'; import {GitClient} from '../../utils/git/index'; import {loadAndValidateConfig, MergeConfigWithRemote} from './config'; -import {MergeResult, MergeStatus, PullRequestMergeTask} from './task'; +import {MergeResult, MergeStatus, PullRequestMergeTask, PullRequestMergeTaskFlags} from './task'; /** * Merges a given pull request based on labels configured in the given merge configuration. @@ -30,13 +30,12 @@ import {MergeResult, MergeStatus, PullRequestMergeTask} from './task'; * @param config Configuration for merging pull requests. */ export async function mergePullRequest( - prNumber: number, githubToken: string, projectRoot: string = getRepoBaseDir(), - config?: MergeConfigWithRemote) { + prNumber: number, githubToken: string, flags: PullRequestMergeTaskFlags) { // Set the environment variable to skip all git commit hooks triggered by husky. We are unable to // rely on `--no-verify` as some hooks still run, notably the `prepare-commit-msg` hook. process.env['HUSKY'] = '0'; - const api = await createPullRequestMergeTask(githubToken, projectRoot, config); + const api = await createPullRequestMergeTask(githubToken, flags); // Perform the merge. Force mode can be activated through a command line flag. // Alternatively, if the merge fails with non-fatal failures, the script @@ -128,13 +127,8 @@ export async function mergePullRequest( * and optional explicit configuration. An explicit configuration can be specified * when the merge script is used outside of a `ng-dev` configured repository. */ -async function createPullRequestMergeTask( - githubToken: string, projectRoot: string, explicitConfig?: MergeConfigWithRemote) { - if (explicitConfig !== undefined) { - const git = new GitClient(githubToken, {github: explicitConfig.remote}, projectRoot); - return new PullRequestMergeTask(explicitConfig, git); - } - +async function createPullRequestMergeTask(githubToken: string, flags: PullRequestMergeTaskFlags) { + const projectRoot = getRepoBaseDir(); const devInfraConfig = getConfig(); const git = new GitClient(githubToken, devInfraConfig, projectRoot); const {config, errors} = await loadAndValidateConfig(devInfraConfig, git.github); @@ -150,5 +144,5 @@ async function createPullRequestMergeTask( config!.remote = devInfraConfig.github; // We can cast this to a merge config with remote because we always set the // remote above. - return new PullRequestMergeTask(config! as MergeConfigWithRemote, git); + return new PullRequestMergeTask(config! as MergeConfigWithRemote, git, flags); } diff --git a/dev-infra/pr/merge/task.ts b/dev-infra/pr/merge/task.ts index b42f532b864..19e2fd7dd92 100644 --- a/dev-infra/pr/merge/task.ts +++ b/dev-infra/pr/merge/task.ts @@ -34,13 +34,28 @@ export interface MergeResult { failure?: PullRequestFailure; } +export interface PullRequestMergeTaskFlags { + branchPrompt: boolean; +} + +const defaultPullRequestMergeTaskFlags: PullRequestMergeTaskFlags = { + branchPrompt: true, +}; + /** * Class that accepts a merge script configuration and Github token. It provides * a programmatic interface for merging multiple pull requests based on their * labels that have been resolved through the merge script configuration. */ export class PullRequestMergeTask { - constructor(public config: MergeConfigWithRemote, public git: GitClient) {} + private flags: PullRequestMergeTaskFlags; + + constructor( + public config: MergeConfigWithRemote, public git: GitClient, + flags: Partial) { + // Update flags property with the provided flags values as patches to the default flag values. + this.flags = {...defaultPullRequestMergeTaskFlags, ...flags}; + } /** * Merges the given pull request and pushes it upstream. @@ -79,7 +94,8 @@ export class PullRequestMergeTask { } - if (!await promptConfirm(getTargettedBranchesConfirmationPromptMessage(pullRequest))) { + if (this.flags.branchPrompt && + !await promptConfirm(getTargettedBranchesConfirmationPromptMessage(pullRequest))) { return {status: MergeStatus.USER_ABORTED}; }