ci: migrate rebase-pr CI script to the circleci directory (#39592)
Migrate the rebase-pr script used on CI out of tools and into the circleci directory. Additionally removes its support for running in the local repository as this is now better handled by `ng-dev pr rebase <pr-number>`. PR Close #39592
This commit is contained in:
parent
938abc03bc
commit
726d7c123c
@ -220,7 +220,7 @@ jobs:
|
|||||||
git config user.name "angular-ci"
|
git config user.name "angular-ci"
|
||||||
git config user.email "angular-ci"
|
git config user.email "angular-ci"
|
||||||
# Rebase PR on top of target branch.
|
# Rebase PR on top of target branch.
|
||||||
node tools/rebase-pr.js
|
node .circleci/rebase-pr.js
|
||||||
else
|
else
|
||||||
echo "This build is not over a PR, nothing to do."
|
echo "This build is not over a PR, nothing to do."
|
||||||
fi
|
fi
|
||||||
|
207
.circleci/rebase-pr.js
Normal file
207
.circleci/rebase-pr.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebases the current branch on top of the GitHub PR target branch.
|
||||||
|
*
|
||||||
|
* **Context:**
|
||||||
|
* Since a GitHub PR is not necessarily up to date with its target branch, it is useful to rebase
|
||||||
|
* prior to testing it on CI to ensure more up to date test results.
|
||||||
|
*
|
||||||
|
* **NOTE:**
|
||||||
|
* This script cannot use external dependencies or be compiled because it needs to run before the
|
||||||
|
* environment is setup.
|
||||||
|
* Use only features supported by the NodeJS versions used in the environment.
|
||||||
|
*/
|
||||||
|
// tslint:disable:no-console
|
||||||
|
const {execSync} = require('child_process');
|
||||||
|
|
||||||
|
|
||||||
|
/** A regex to select a ref that matches our semver refs. */
|
||||||
|
const semverRegex = /^(\d+)\.(\d+)\.x$/;
|
||||||
|
|
||||||
|
// Run
|
||||||
|
_main().catch(err => {
|
||||||
|
console.log('Failed to rebase on top of target branch.\n');
|
||||||
|
console.error(err);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
async function _main() {
|
||||||
|
const refs = await getRefsAndShasForChange();
|
||||||
|
|
||||||
|
// Log known refs and shas
|
||||||
|
console.log(`--------------------------------`);
|
||||||
|
console.log(` Target Branch: ${refs.base.ref}`);
|
||||||
|
console.log(` Latest Commit for Target Branch: ${refs.target.latestSha}`);
|
||||||
|
console.log(` Latest Commit for PR: ${refs.base.latestSha}`);
|
||||||
|
console.log(` First Common Ancestor SHA: ${refs.commonAncestorSha}`);
|
||||||
|
console.log(`--------------------------------`);
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Get the count of commits between the latest commit from origin and the common ancestor SHA.
|
||||||
|
const commitCount =
|
||||||
|
exec(`git rev-list --count origin/${refs.base.ref}...${refs.commonAncestorSha}`);
|
||||||
|
console.log(`Checking ${commitCount} commits for changes in the CircleCI config file.`);
|
||||||
|
|
||||||
|
// Check if the files changed between the latest commit from origin and the common ancestor SHA
|
||||||
|
// includes the CircleCI config.
|
||||||
|
const circleCIConfigChanged = exec(`git diff --name-only origin/${refs.base.ref} ${
|
||||||
|
refs.commonAncestorSha} -- .circleci/config.yml`);
|
||||||
|
|
||||||
|
if (!!circleCIConfigChanged) {
|
||||||
|
throw Error(`
|
||||||
|
CircleCI config on ${refs.base.ref} has been modified since commit
|
||||||
|
${refs.commonAncestorSha.slice(0, 7)}, which this PR is based on.
|
||||||
|
|
||||||
|
Please rebase the PR on ${refs.base.ref} after fetching from upstream.
|
||||||
|
|
||||||
|
Rebase instructions for PR Author, please run the following commands:
|
||||||
|
|
||||||
|
git fetch upstream ${refs.base.ref};
|
||||||
|
git checkout ${refs.target.ref};
|
||||||
|
git rebase upstream/${refs.base.ref};
|
||||||
|
git push --force-with-lease;
|
||||||
|
`);
|
||||||
|
} else {
|
||||||
|
console.log('No change found in the CircleCI config file, continuing.');
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Rebase the PR.
|
||||||
|
exec(`git rebase origin/${refs.base.ref}`);
|
||||||
|
console.log(`Rebased current branch onto ${refs.base.ref}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the remote to git, if it doesn't already exist. Returns a boolean indicating
|
||||||
|
* whether the remote was added by the command.
|
||||||
|
*/
|
||||||
|
function addAndFetchRemote(owner, name) {
|
||||||
|
const remoteName = `${owner}_${name}`;
|
||||||
|
exec(`git remote add ${remoteName} https://github.com/${owner}/${name}.git`, false);
|
||||||
|
exec(`git fetch ${remoteName}`);
|
||||||
|
return remoteName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Get the ref and latest shas for the provided sha on a specific remote. */
|
||||||
|
function getRefAndShas(sha, owner, name) {
|
||||||
|
const remoteName = addAndFetchRemote(owner, name);
|
||||||
|
|
||||||
|
// Get the ref on the remote for the sha provided.
|
||||||
|
const branches = getBranchListForSha(sha, remoteName);
|
||||||
|
const ref = getRefFromBranchList(branches);
|
||||||
|
|
||||||
|
// Get the latest sha on the discovered remote ref.
|
||||||
|
const latestSha = getShaFromRef(`${remoteName}/${ref}`);
|
||||||
|
|
||||||
|
return {remote: remoteName, ref, latestSha, sha};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Gets the refs and shas for the base and target of the current environment. */
|
||||||
|
function getRefsAndShasForChange() {
|
||||||
|
const base = getRefAndShas(
|
||||||
|
process.env['CI_GIT_BASE_REVISION'], process.env['CI_REPO_OWNER'],
|
||||||
|
process.env['CI_REPO_NAME']);
|
||||||
|
const target = getRefAndShas(
|
||||||
|
process.env['CI_GIT_REVISION'], process.env['CI_PR_USERNAME'], process.env['CI_PR_REPONAME']);
|
||||||
|
const commonAncestorSha = getCommonAncestorSha(base.sha, target.sha);
|
||||||
|
return {
|
||||||
|
base,
|
||||||
|
target,
|
||||||
|
commonAncestorSha,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
@ -1,102 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* **Usage:**
|
|
||||||
* ```
|
|
||||||
* node rebase-pr <github-repository> <pull-request-number>
|
|
||||||
* ```
|
|
||||||
* **Example:**
|
|
||||||
* ```
|
|
||||||
* node rebase-pr angular/angular 123
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Rebases the current branch on top of the GitHub PR target branch.
|
|
||||||
*
|
|
||||||
* **Context:**
|
|
||||||
* Since a GitHub PR is not necessarily up to date with its target branch, it is useful to rebase
|
|
||||||
* prior to testing it on CI to ensure more up to date test results.
|
|
||||||
*
|
|
||||||
* **Implementation details:**
|
|
||||||
* This script obtains the base for a GitHub PR via the
|
|
||||||
* [GitHub PR API](https://developer.github.com/v3/pulls/#get-a-single-pull-request), then
|
|
||||||
* fetches that branch, and rebases the current branch on top of it.
|
|
||||||
*
|
|
||||||
* **NOTE:**
|
|
||||||
* This script cannot use external dependencies or be compiled because it needs to run before the
|
|
||||||
* environment is setup.
|
|
||||||
* Use only features supported by the NodeJS versions used in the environment.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// This script uses `console` to print messages to the user.
|
|
||||||
// tslint:disable:no-console
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
const util = require('util');
|
|
||||||
const child_process = require('child_process');
|
|
||||||
const exec = util.promisify(child_process.exec);
|
|
||||||
const getRefsAndShasForChange = require('./utils/git-get-changeset-refs');
|
|
||||||
|
|
||||||
|
|
||||||
// Run
|
|
||||||
_main().catch(err => {
|
|
||||||
console.log('Failed to rebase on top of target branch.\n');
|
|
||||||
console.error(err);
|
|
||||||
process.exitCode = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
async function _main() {
|
|
||||||
const refs = await getRefsAndShasForChange();
|
|
||||||
|
|
||||||
// Log known refs and shas
|
|
||||||
console.log(`--------------------------------`);
|
|
||||||
console.log(` Target Branch: ${refs.base.ref}`);
|
|
||||||
console.log(` Latest Commit for Target Branch: ${refs.target.latestSha}`);
|
|
||||||
console.log(` Latest Commit for PR: ${refs.base.latestSha}`);
|
|
||||||
console.log(` First Common Ancestor SHA: ${refs.commonAncestorSha}`);
|
|
||||||
console.log(`--------------------------------`);
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Get the count of commits between the latest commit from origin and the common ancestor SHA.
|
|
||||||
const {stdout: commitCount} =
|
|
||||||
await exec(`git rev-list --count origin/${refs.base.ref}...${refs.commonAncestorSha}`);
|
|
||||||
console.log(`Checking ${commitCount.trim()} commits for changes in the CircleCI config file.`);
|
|
||||||
|
|
||||||
// Check if the files changed between the latest commit from origin and the common ancestor SHA
|
|
||||||
// includes the CircleCI config.
|
|
||||||
const {stdout: circleCIConfigChanged} = await exec(`git diff --name-only origin/${
|
|
||||||
refs.base.ref} ${refs.commonAncestorSha} -- .circleci/config.yml`);
|
|
||||||
|
|
||||||
if (!!circleCIConfigChanged) {
|
|
||||||
throw Error(`
|
|
||||||
CircleCI config on ${refs.base.ref} has been modified since commit ${
|
|
||||||
refs.commonAncestorSha.slice(0, 7)},
|
|
||||||
which this PR is based on.
|
|
||||||
|
|
||||||
Please rebase the PR on ${refs.base.ref} after fetching from upstream.
|
|
||||||
|
|
||||||
Rebase instructions for PR Author, please run the following commands:
|
|
||||||
|
|
||||||
git fetch upstream ${refs.base.ref};
|
|
||||||
git checkout ${refs.target.ref};
|
|
||||||
git rebase upstream/${refs.base.ref};
|
|
||||||
git push --force-with-lease;
|
|
||||||
`);
|
|
||||||
} else {
|
|
||||||
console.log('No change found in the CircleCI config file, continuing.');
|
|
||||||
}
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
// Rebase the PR.
|
|
||||||
console.log(`Rebasing current branch on ${refs.base.ref}.`);
|
|
||||||
await exec(`git rebase origin/${refs.base.ref}`);
|
|
||||||
console.log('Rebase successful.');
|
|
||||||
}
|
|
@ -1,181 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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;
|
|
Loading…
x
Reference in New Issue
Block a user