/** * @license * Copyright Google Inc. 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 */ // NOTE: When invoked directly via node, this script will take the first positional // arguement as to be the PR number, and log out the ref and sha information in its // JSON format. For other usages, the function to get the ref and sha information // may be imported by another script to be invoked. // This script uses `console` to print messages to the user. // tslint:disable:no-console const https = require('https'); const util = require('util'); const child_process = require('child_process'); const exec = util.promisify(child_process.exec); async function requestDataFromGithub(url) { // GitHub requires a user agent: https://developer.github.com/v3/#user-agent-required let options = {headers: {'User-Agent': 'angular'}}; // If a github token is present, use it for authorization. const githubToken = process.env.TOKEN || process.env.GITHUB_TOKEN || ''; if (githubToken) { options = { headers: { Authorization: `token ${githubToken}`, ...options.headers, } }; } return new Promise((resolve, reject) => { https .get( url, options, (res) => { const {statusCode} = res; const contentType = res.headers['content-type']; let rawData = ''; res.on('data', (chunk) => { rawData += chunk; }); res.on('end', () => { let error; if (statusCode !== 200) { error = new Error( `Request Failed.\nStatus Code: ${statusCode}.\nResponse: ${rawData}`); } else if (!/^application\/json/.test(contentType)) { error = new Error( 'Invalid content-type.\n' + `Expected application/json but received ${contentType}`); } if (error) { reject(error); return; } try { resolve(JSON.parse(rawData)); } catch (e) { reject(e); } }); }) .on('error', (e) => { reject(e); }); }); } // clang-format off // clang keeps trying to put the function name on the next line. async function getRefsAndShasForTarget(prNumber, suppressLog) { // clang-format on // If the environment variable already contains the refs and shas, reuse them. if (process.env['GITHUB_REFS_AND_SHAS']) { suppressLog || console.info(`Retrieved refs and SHAs for PR ${prNumber} from environment variables.`); return JSON.parse(process.env['GITHUB_REFS_AND_SHAS']); } suppressLog || console.info(`Getting refs and SHAs for PR ${prNumber} on angular/angular from Github.`); const pullsUrl = `https://api.github.com/repos/angular/angular/pulls/${prNumber}`; const result = await requestDataFromGithub(pullsUrl); // Ensure the base ref is up to date await exec(`git fetch origin ${result.base.ref}`); // The sha of the latest commit on the target branch. const {stdout: latestShaOfTargetBranch} = await exec(`git rev-parse origin/${result.base.ref}`); // The sha of the latest commit on the PR. const {stdout: latestShaOfPrBranch} = await exec(`git rev-parse HEAD`); // The first common SHA in the history of the target branch and the latest commit in the PR. const {stdout: commonAncestorSha} = await exec(`git merge-base origin/${result.base.ref} ${latestShaOfPrBranch}`); const output = { base: { ref: result.base.ref, sha: result.base.sha, }, head: { ref: result.head.ref, sha: result.head.sha, }, commonAncestorSha: commonAncestorSha.trim(), latestShaOfTargetBranch: latestShaOfTargetBranch.trim(), latestShaOfPrBranch: latestShaOfPrBranch.trim(), }; return output; } // If the script is called directly, log the output of the refs and sha for the // requested PR. if (require.main === module) { const run = async () => { const prNumber = Number.parseInt(process.argv[2], 10); if (!!prNumber) { console.info(JSON.stringify(await getRefsAndShasForTarget(prNumber, true))); } }; run(); } module.exports = getRefsAndShasForTarget;