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(dev-infra): add script for merging pull requests [patch version] #37184

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
63 changes: 62 additions & 1 deletion .ng-dev-config.ts
@@ -1,3 +1,7 @@
import {exec} from 'shelljs';

import {MergeConfig} from './dev-infra/pr/merge/config';

// The configuration for `ng-dev commit-message` commands.
const commitMessage = {
'maxLength': 120,
Expand Down Expand Up @@ -72,15 +76,72 @@ const format = {
'buildifier': true
};

// Github metadata information for `ng-dev` commands.
/** Github metadata information for `ng-dev` commands. */
const github = {
owner: 'angular',
name: 'angular',
};

/**
* Gets the name of the current patch branch. The patch branch is determined by
* looking for upstream branches that follow the format of `{major}.{minor}.x`.
*/
const getPatchBranchName = (): string => {
const branches =
exec(
`git ls-remote --heads https://github.com/${github.owner}/${github.name}.git`,
{silent: true})
.trim()
.split('\n');

for (let i = branches.length - 1; i >= 0; i--) {
const branchName = branches[i];
const matches = branchName.match(/refs\/heads\/([0-9]+\.[0-9]+\.x)/);
if (matches !== null) {
return matches[1];
}
}

throw Error('Could not determine patch branch name.');
};

// Configuration for the `ng-dev pr merge` command. The command can be used
// for merging upstream pull requests into branches based on a PR target label.
const merge = () => {
const patchBranch = getPatchBranchName();
const config: MergeConfig = {
githubApiMerge: false,
claSignedLabel: 'cla: yes',
mergeReadyLabel: /^PR action: merge(-assistance)?/,
commitMessageFixupLabel: 'commit message fixup',
labels: [
{
pattern: 'PR target: master-only',
branches: ['master'],
},
{
pattern: 'PR target: patch-only',
branches: [patchBranch],
},
{
pattern: 'PR target: master & patch',
branches: ['master', patchBranch],
},
],
requiredBaseCommits: {
// PRs that target either `master` or the patch branch, need to be rebased
// on top of the latest commit message validation fix.
'master': '4341743b4a6d7e23c6f944aa9e34166b701369a1',
[patchBranch]: '2a53f471592f424538802907aca1f60f1177a86d'
},
};
return config;
};

// Export function to build ng-dev configuration object.
module.exports = {
commitMessage,
format,
github,
merge,
};
14 changes: 3 additions & 11 deletions dev-infra/pr/BUILD.bazel
Expand Up @@ -2,20 +2,12 @@ load("@npm_bazel_typescript//:index.bzl", "ts_library")

ts_library(
name = "pr",
srcs = glob([
"*.ts",
]),
srcs = ["cli.ts"],
module_name = "@angular/dev-infra-private/pr",
visibility = ["//dev-infra:__subpackages__"],
deps = [
"//dev-infra/utils",
"@npm//@types/cli-progress",
"@npm//@types/node",
"@npm//@types/shelljs",
"//dev-infra/pr/discover-new-conflicts",
"//dev-infra/pr/merge",
"@npm//@types/yargs",
"@npm//cli-progress",
"@npm//shelljs",
"@npm//typed-graphqlify",
"@npm//yargs",
],
)
41 changes: 11 additions & 30 deletions dev-infra/pr/cli.ts
Expand Up @@ -7,39 +7,20 @@
*/

import * as yargs from 'yargs';
import {discoverNewConflictsForPr} from './discover-new-conflicts';

/** A Date object 30 days ago. */
const THIRTY_DAYS_AGO = (() => {
const date = new Date();
// Set the hours, minutes and seconds to 0 to only consider date.
date.setHours(0, 0, 0, 0);
// Set the date to 30 days in the past.
date.setDate(date.getDate() - 30);
return date;
})();
import {buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand} from './discover-new-conflicts/cli';
import {buildMergeCommand, handleMergeCommand} from './merge/cli';

/** Build the parser for the pr commands. */
/** Build the parser for pull request commands. */
export function buildPrParser(localYargs: yargs.Argv) {
return localYargs.help().strict().demandCommand().command(
'discover-new-conflicts <pr>',
'Check if a pending PR causes new conflicts for other pending PRs',
args => {
return args.option('date', {
description: 'Only consider PRs updated since provided date',
defaultDescription: '30 days ago',
coerce: Date.parse,
default: THIRTY_DAYS_AGO,
});
},
({pr, date}) => {
// If a provided date is not able to be parsed, yargs provides it as NaN.
if (isNaN(date)) {
console.error('Unable to parse the value provided via --date flag');
process.exit(1);
}
discoverNewConflictsForPr(pr, date);
});
return localYargs.help()
.strict()
.demandCommand()
.command('merge <pr-number>', 'Merge pull requests', buildMergeCommand, handleMergeCommand)
.command(
'discover-new-conflicts <pr-number>',
'Check if a pending PR causes new conflicts for other pending PRs',
buildDiscoverNewConflictsCommand, handleDiscoverNewConflictsCommand)
}

if (require.main === module) {
Expand Down
19 changes: 19 additions & 0 deletions dev-infra/pr/discover-new-conflicts/BUILD.bazel
@@ -0,0 +1,19 @@
load("@npm_bazel_typescript//:index.bzl", "ts_library")

ts_library(
name = "discover-new-conflicts",
srcs = [
"cli.ts",
"index.ts",
],
module_name = "@angular/dev-infra-private/pr/discover-new-conflicts",
visibility = ["//dev-infra:__subpackages__"],
deps = [
"//dev-infra/utils",
"@npm//@types/cli-progress",
"@npm//@types/node",
"@npm//@types/shelljs",
"@npm//@types/yargs",
"@npm//typed-graphqlify",
],
)
33 changes: 33 additions & 0 deletions dev-infra/pr/discover-new-conflicts/cli.ts
@@ -0,0 +1,33 @@
import {Arguments, Argv} from 'yargs';

import {discoverNewConflictsForPr} from './index';

/** Builds the discover-new-conflicts pull request command. */
export function buildDiscoverNewConflictsCommand(yargs: Argv) {
return yargs.option('date', {
description: 'Only consider PRs updated since provided date',
defaultDescription: '30 days ago',
coerce: Date.parse,
default: getThirtyDaysAgoDate,
});
}

/** Handles the discover-new-conflicts pull request command. */
export async function handleDiscoverNewConflictsCommand({prNumber, date}: Arguments) {
// If a provided date is not able to be parsed, yargs provides it as NaN.
if (isNaN(date)) {
console.error('Unable to parse the value provided via --date flag');
process.exit(1);
}
await discoverNewConflictsForPr(prNumber, date);
}

/** Gets a date object 30 days ago from today. */
function getThirtyDaysAgoDate(): Date {
const date = new Date();
// Set the hours, minutes and seconds to 0 to only consider date.
date.setHours(0, 0, 0, 0);
// Set the date to 30 days in the past.
date.setDate(date.getDate() - 30);
return date;
}
Expand Up @@ -9,10 +9,10 @@
import {Bar} from 'cli-progress';
import {types as graphQLTypes} from 'typed-graphqlify';

import {getConfig, NgDevConfig} from '../utils/config';
import {getCurrentBranch, hasLocalChanges} from '../utils/git';
import {getPendingPrs} from '../utils/github';
import {exec} from '../utils/shelljs';
import {getConfig, NgDevConfig} from '../../utils/config';
import {getCurrentBranch, hasLocalChanges} from '../../utils/git';
import {getPendingPrs} from '../../utils/github';
import {exec} from '../../utils/shelljs';


/* GraphQL schema for the response body for each pending PR. */
Expand Down Expand Up @@ -67,8 +67,6 @@ export async function discoverNewConflictsForPr(
const progressBar = new Bar({format: `[{bar}] ETA: {eta}s | {value}/{total}`});
/* PRs which were found to be conflicting. */
const conflicts: Array<PullRequest> = [];
/* String version of the updatedAfter value, for logging. */
const updatedAfterString = new Date(updatedAfter).toLocaleDateString();

console.info(`Requesting pending PRs from Github`);
/** List of PRs from github currently known as mergable. */
Expand Down
17 changes: 17 additions & 0 deletions dev-infra/pr/merge/BUILD.bazel
@@ -0,0 +1,17 @@
load("@npm_bazel_typescript//:index.bzl", "ts_library")

ts_library(
name = "merge",
srcs = glob(["**/*.ts"]),
module_name = "@angular/dev-infra-private/pr/merge",
visibility = ["//dev-infra:__subpackages__"],
deps = [
"//dev-infra/commit-message",
"//dev-infra/utils",
"@npm//@octokit/rest",
"@npm//@types/inquirer",
"@npm//@types/node",
"@npm//@types/yargs",
"@npm//chalk",
],
)
33 changes: 33 additions & 0 deletions dev-infra/pr/merge/cli.ts
@@ -0,0 +1,33 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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
*/

import chalk from 'chalk';
import {Arguments, Argv} from 'yargs';
import {GITHUB_TOKEN_GENERATE_URL, mergePullRequest} from './index';

/** Builds the options for the merge command. */
export function buildMergeCommand(yargs: Argv) {
return yargs.help().strict().option('github-token', {
type: 'string',
description: 'Github token. If not set, token is retrieved from the environment variables.'
})
}

/** Handles the merge command. i.e. performs the merge of a specified pull request. */
export async function handleMergeCommand(args: Arguments) {
const githubToken = args.githubToken || process.env.GITHUB_TOKEN || process.env.TOKEN;
if (!githubToken) {
console.error(
chalk.red('No Github token set. Please set the `GITHUB_TOKEN` environment variable.'));
console.error(chalk.red('Alternatively, pass the `--github-token` command line flag.'));
console.error(chalk.yellow(`You can generate a token here: ${GITHUB_TOKEN_GENERATE_URL}`));
process.exit(1);
}

await mergePullRequest(args.prNumber, githubToken);
}