angular-cn/tools/utils/git-get-changeset-refs.js
Joey Perrott d1ea1f4c7f build: update license headers to reference Google LLC ()
Update the license headers throughout the repository to reference Google LLC
rather than Google Inc, for the required license headers.

PR Close 
2020-05-26 14:26:58 -04:00

182 lines
5.2 KiB
JavaScript

/**
* @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
*/
const {execSync} = require('child_process');
/** A regex to select a ref that matches our semver refs. */
const semverRegex = /^(\d+)\.(\d+)\.x$/;
/**
* Synchronously executes the command.
*
* Return the trimmed stdout as a string, with an added attribute of the exit code.
*/
function exec(command, allowStderr = true) {
let output = new String();
output.code = 0;
try {
output += execSync(command, {stdio: ['pipe', 'pipe', 'pipe']}).toString().trim();
} catch (err) {
allowStderr && console.error(err.stderr.toString());
output.code = err.status;
}
return output;
}
/**
* Sort a list of fullpath refs into a list and then provide the first entry.
*
* The sort order will first find master ref, and then any semver ref, followed
* by the rest of the refs in the order provided.
*
* Branches are sorted in this order as work is primarily done on master, and
* otherwise on a semver branch. If neither of those were to match, the most
* likely correct branch will be the first one encountered in the list.
*/
function getRefFromBranchList(gitOutput, remote) {
const branches = gitOutput.split('\n').map(b => b.split('/').slice(1).join('').trim());
return branches.sort((a, b) => {
if (a === 'master') {
return -1;
}
if (b === 'master') {
return 1;
}
const aIsSemver = semverRegex.test(a);
const bIsSemver = semverRegex.test(b);
if (aIsSemver && bIsSemver) {
const [, aMajor, aMinor] = a.match(semverRegex);
const [, bMajor, bMinor] = b.match(semverRegex);
return parseInt(bMajor, 10) - parseInt(aMajor, 10) ||
parseInt(aMinor, 10) - parseInt(bMinor, 10) || 0;
}
if (aIsSemver) {
return -1;
}
if (bIsSemver) {
return 1;
}
return 0;
})[0];
}
/**
* Get the full sha of the ref provided.
*
* example: 1bc0c1a6c01ede7168f22fa9b3508ba51f1f464e
*/
function getShaFromRef(ref) {
return exec(`git rev-parse ${ref}`);
}
/**
* Get the list of branches which contain the provided sha, sorted in descending order
* by committerdate.
*
* example:
* upstream/master
* upstream/9.0.x
* upstream/test
* upstream/1.1.x
*/
function getBranchListForSha(sha, remote) {
return exec(`git branch -r '${remote}/*' --sort=-committerdate --contains ${sha}`);
}
/** Get the common ancestor sha of the two provided shas. */
function getCommonAncestorSha(sha1, sha2) {
return exec(`git merge-base ${sha1} ${sha2}`);
}
/** Removes the remote from git. */
function removeRemote(remote) {
exec(`git remote remove ${remote}`);
}
/**
* Adds the remote to git, if it doesn't already exist. Returns a boolean indicating
* whether the remote was added by the command.
*/
function addRemote(remote) {
return !exec(`git remote add ${remote} https://github.com/${remote}/angular.git`, false).code;
}
/** Fetch latest from the remote. */
function fetchRemote(remote) {
exec(`git fetch ${remote}`);
}
/**
* Get the nearest ref which the HEAD has a parent commit.
*
* Checks up to a limit of 100 previous shas.
*/
function getParentBranchForHead(remote) {
// Get the latest for the remote.
fetchRemote(remote);
let headCount = 0;
while (headCount < 100) {
// Attempt to get the ref on the remote for the sha.
const branches = getBranchListForSha(`HEAD~${headCount}`, remote);
const ref = getRefFromBranchList(branches, remote);
// If the ref exists, get the sha and latest sha for the remote ref.
if (ref) {
const sha = getShaFromRef(`HEAD~${headCount}`);
const latestSha = getShaFromRef(`${remote}/${ref}`);
return {ref, sha, latestSha, remote};
}
headCount++;
}
return {ref: '', latestSha: '', sha, remote};
}
/** Get the ref and latest shas for the provided sha on a specific remote. */
function getRefAndShas(sha, remote) {
// Ensure the remote is defined in git.
let markRemoteForClean = addRemote(remote);
// Get the latest from the remote.
fetchRemote(remote);
// Get the ref on the remote for the sha provided.
const branches = getBranchListForSha(sha, remote);
const ref = getRefFromBranchList(branches, remote);
// Get the latest sha on the discovered remote ref.
const latestSha = getShaFromRef(`${remote}/${ref}`);
// Clean up the remote if it didn't exist before execution.
if (markRemoteForClean) {
removeRemote(remote);
}
return {remote, ref, latestSha, sha};
}
/** Gets the refs and shas for the base and target of the current environment. */
function getRefsAndShasForChange() {
let base, target;
if (process.env['CI']) {
base = getRefAndShas(process.env['CI_GIT_BASE_REVISION'], process.env['CI_REPO_OWNER']);
target = getRefAndShas(process.env['CI_GIT_REVISION'], process.env['CI_PR_USERNAME']);
} else {
const originSha = getShaFromRef(`HEAD`);
target = getRefAndShas(originSha, 'origin');
base = getParentBranchForHead('upstream');
}
const commonAncestorSha = getCommonAncestorSha(base.sha, target.sha);
return {
base,
target,
commonAncestorSha,
};
}
module.exports = getRefsAndShasForChange;