Update the license headers throughout the repository to reference Google LLC rather than Google Inc, for the required license headers. PR Close #37205
		
			
				
	
	
		
			182 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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;
 |