feat(dev-infra): expose script for determining merge branches (#37217)
The components repo and framework repository follow the same patch branch concept. We should be able to share a script for determining these merge branches. Additonally the logic has been improved compared to the old merge script because we no longer consult `git ls-remote` unless really needed. Currently, `git ls-remote` is always consulted, even though not necessarily needed. This can slow down the merge script and the caretaker process when a couple of PRs are merged (personally saw around ~4 seconds per merge). Additionally, the new logic is more strict and will ensure (in most cases) that no wrong patch/minor branch is determined. Previously, the script just used the lexicographically greatest patch branch. This _could_ be wrong when a new patch branch has been created too early, or by accident. PR Close #37217
This commit is contained in:
parent
3e5fa56956
commit
cdb5d07606
|
@ -1,6 +1,5 @@
|
||||||
import {exec} from 'shelljs';
|
|
||||||
|
|
||||||
import {MergeConfig} from './dev-infra/pr/merge/config';
|
import {MergeConfig} from './dev-infra/pr/merge/config';
|
||||||
|
import {determineMergeBranches} from './dev-infra/pr/merge/determine-merge-branches';
|
||||||
|
|
||||||
// The configuration for `ng-dev commit-message` commands.
|
// The configuration for `ng-dev commit-message` commands.
|
||||||
const commitMessage = {
|
const commitMessage = {
|
||||||
|
@ -82,33 +81,10 @@ const github = {
|
||||||
name: '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
|
// 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.
|
// for merging upstream pull requests into branches based on a PR target label.
|
||||||
const merge = () => {
|
const merge = () => {
|
||||||
const patchBranch = getPatchBranchName();
|
const {patch} = determineMergeBranches(require('./package.json').version, '@angular/core');
|
||||||
const config: MergeConfig = {
|
const config: MergeConfig = {
|
||||||
githubApiMerge: false,
|
githubApiMerge: false,
|
||||||
claSignedLabel: 'cla: yes',
|
claSignedLabel: 'cla: yes',
|
||||||
|
@ -121,18 +97,18 @@ const merge = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: 'PR target: patch-only',
|
pattern: 'PR target: patch-only',
|
||||||
branches: [patchBranch],
|
branches: [patch],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: 'PR target: master & patch',
|
pattern: 'PR target: master & patch',
|
||||||
branches: ['master', patchBranch],
|
branches: ['master', patch],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
requiredBaseCommits: {
|
requiredBaseCommits: {
|
||||||
// PRs that target either `master` or the patch branch, need to be rebased
|
// PRs that target either `master` or the patch branch, need to be rebased
|
||||||
// on top of the latest commit message validation fix.
|
// on top of the latest commit message validation fix.
|
||||||
'master': '4341743b4a6d7e23c6f944aa9e34166b701369a1',
|
'master': '4341743b4a6d7e23c6f944aa9e34166b701369a1',
|
||||||
[patchBranch]: '2a53f471592f424538802907aca1f60f1177a86d'
|
[patch]: '2a53f471592f424538802907aca1f60f1177a86d'
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return config;
|
return config;
|
||||||
|
|
|
@ -11,6 +11,7 @@ ts_library(
|
||||||
"@npm//@octokit/rest",
|
"@npm//@octokit/rest",
|
||||||
"@npm//@types/inquirer",
|
"@npm//@types/inquirer",
|
||||||
"@npm//@types/node",
|
"@npm//@types/node",
|
||||||
|
"@npm//@types/semver",
|
||||||
"@npm//@types/yargs",
|
"@npm//@types/yargs",
|
||||||
"@npm//chalk",
|
"@npm//chalk",
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* @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 * as semver from 'semver';
|
||||||
|
import {exec} from '../../utils/shelljs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that can be used to determine merge branches based on a given
|
||||||
|
* project version. The function determines merge branches primarily through the
|
||||||
|
* specified version, but falls back to consulting the NPM registry when needed.
|
||||||
|
*
|
||||||
|
* Consulting the NPM registry for determining the patch branch may slow down merging,
|
||||||
|
* so whenever possible, the branches are determined statically based on the current
|
||||||
|
* version. In some cases, consulting the NPM registry is inevitable because for major
|
||||||
|
* pre-releases, we cannot determine the latest stable minor version from the current
|
||||||
|
* pre-release version.
|
||||||
|
*/
|
||||||
|
export function determineMergeBranches(
|
||||||
|
currentVersion: string, npmPackageName: string): {minor: string, patch: string} {
|
||||||
|
const projectVersion = semver.parse(currentVersion);
|
||||||
|
if (projectVersion === null) {
|
||||||
|
throw Error('Cannot parse version set in project "package.json" file.');
|
||||||
|
}
|
||||||
|
const {major, minor, patch, prerelease} = projectVersion;
|
||||||
|
const isMajor = minor === 0 && patch === 0;
|
||||||
|
const isMinor = minor !== 0 && patch === 0;
|
||||||
|
|
||||||
|
// If there is no prerelease, then we compute patch and minor branches based
|
||||||
|
// on the current version major and minor.
|
||||||
|
if (prerelease.length === 0) {
|
||||||
|
return {minor: `${major}.x`, patch: `${major}.${minor}.x`};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If current version is set to a minor prerelease, we can compute the merge branches
|
||||||
|
// statically. e.g. if we are set to `9.3.0-next.0`, then our merge branches should
|
||||||
|
// be set to `9.x` and `9.2.x`.
|
||||||
|
if (isMinor) {
|
||||||
|
return {minor: `${major}.x`, patch: `${major}.${minor - 1}.x`};
|
||||||
|
} else if (!isMajor) {
|
||||||
|
throw Error('Unexpected version. Cannot have prerelease for patch version.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are set to a major prerelease, we cannot statically determine the stable patch
|
||||||
|
// branch (as the latest minor segment is unknown). We determine it by looking in the NPM
|
||||||
|
// registry for the latest stable release that will tell us about the current minor segment.
|
||||||
|
// e.g. if the current major is `v10.0.0-next.0`, then we need to look for the latest release.
|
||||||
|
// Let's say this is `v9.2.6`. Our patch branch will then be called `9.2.x`.
|
||||||
|
const latestVersion = exec(`yarn -s info ${npmPackageName} dist-tags.latest`).trim();
|
||||||
|
if (!latestVersion) {
|
||||||
|
throw Error('Could not determine version of latest release.');
|
||||||
|
}
|
||||||
|
const expectedMajor = major - 1;
|
||||||
|
const parsedLatestVersion = semver.parse(latestVersion);
|
||||||
|
if (parsedLatestVersion === null) {
|
||||||
|
throw Error(`Could not parse latest version from NPM registry: ${latestVersion}`);
|
||||||
|
} else if (parsedLatestVersion.major !== expectedMajor) {
|
||||||
|
throw Error(
|
||||||
|
`Expected latest release to have major version: v${expectedMajor}, ` +
|
||||||
|
`but got: v${latestVersion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {patch: `${expectedMajor}.${parsedLatestVersion.minor}.x`, minor: `${expectedMajor}.x`};
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
"inquirer": "<from-root>",
|
"inquirer": "<from-root>",
|
||||||
"minimatch": "<from-root>",
|
"minimatch": "<from-root>",
|
||||||
"multimatch": "<from-root>",
|
"multimatch": "<from-root>",
|
||||||
|
"semver": "<from-root>",
|
||||||
"shelljs": "<from-root>",
|
"shelljs": "<from-root>",
|
||||||
"typed-graphqlify": "<from-root>",
|
"typed-graphqlify": "<from-root>",
|
||||||
"yaml": "<from-root>",
|
"yaml": "<from-root>",
|
||||||
|
|
Loading…
Reference in New Issue