ci: scripts to review PRs locally (#24623)

PR Close #24623
This commit is contained in:
Misko Hevery
2018-06-21 21:56:51 -07:00
committed by Miško Hevery
parent f229449c67
commit f841e36543
4 changed files with 366 additions and 0 deletions

56
scripts/github/push-pr Executable file
View File

@ -0,0 +1,56 @@
#!/usr/bin/env node
const shell = require('shelljs');
shell.config.fatal = true;
const util = require('./utils/git_util');
if (require.main === module) {
main(process.argv.splice(2)).then(
(v) => process.exitCode,
(e) => console.error(process.exitCode = 1, e)
);
}
async function main(args) {
let forceWithLease = '';
let prNumber = 0;
let printHelp = false;
args.forEach((arg) => {
if (prNumber == 0 && arg > 0) {
prNumber = arg;
} else if (arg == '--help') {
printHelp = true;
} else if (arg == '--force-with-lease') {
forceWithLease = ' --force-with-lease';
} else {
shell.echo('Unexpected argument: ', arg);
}
});
if (!prNumber) {
const branch = util.getCurrentBranch();
const maybePr = branch.split('/')[1];
if (maybePr > 0) {
shell.echo(`PR number not specified. Defaulting to #${maybePr}.`);
prNumber = maybePr;
}
}
if (!prNumber || printHelp) {
shell.echo(`Push the current HEAD into an existing pull request.`);
shell.echo(``);
shell.echo(`${process.argv[1]} [PR_NUMBER] [--force-with-lease]`);
shell.echo(``);
shell.echo(` --force-with-lease Continues even \if change can\'t be fast-forwarded.`);
shell.echo(` [PR_NUMBER] If not present the script guesses the PR from the branch name.`);
return 1;
}
const prInfo = await util.githubPrInfo(prNumber);
const prPushCmd = `git push${forceWithLease} ${prInfo.repository.gitUrl} HEAD:${prInfo.branch}`;
shell.echo(`>>> ${prPushCmd}`);
shell.exec(prPushCmd);
return 0;
}

60
scripts/github/review-pr Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env node
const shell = require('shelljs');
shell.config.fatal = true;
const util = require('./utils/git_util');
if (require.main === module) {
main(process.argv.splice(2)).then(
(v) => process.exitCode = v,
(e) => console.error(process.exitCode = 1, e)
);
}
async function main(args) {
let prNumber = 0;
args.forEach((arg) => {
if (prNumber == 0 && arg > 0) {
prNumber = arg;
} else {
shell.echo('Unexpected argument: ', arg);
}
});
if (prNumber === 0) {
shell.echo('Bring github pull request onto your local repo for review and edit');
shell.echo('');
shell.echo(`${process.argv[1]} PR_NUMBER`);
shell.echo('');
return 1;
}
if (util.gitHasLocalModifications()) {
shell.echo('Local modification detected. exiting...');
return 1;
}
let prShaCount = (await util.githubPrInfo(prNumber)).commits;
shell.exec(`git checkout master`);
if (util.execNoFatal(`git rev-parse --verify --quiet pr/${prNumber}`).code == 0) {
shell.exec(`git branch -D pr/${prNumber}`);
}
shell.echo(`Fetching pull request #${prNumber} with ${prNumber} SHA(s) into branch range: pr/${prNumber}_base..pr/${prNumber}_top`);
shell.exec(`git fetch -f git@github.com:angular/angular.git pull/${prNumber}/head:pr/${prNumber}_top`);
shell.exec(`git branch -f pr/${prNumber}_base pr/${prNumber}_top~${prShaCount}`);
shell.echo(`======================================================================================`);
shell.exec(`git log --oneline --color pr/${prNumber}_base..pr/${prNumber}_top`);
shell.echo(`======================================================================================`);
// Reset the HEAD so that we can see changed files for review
shell.exec(`git checkout --force -b pr/${prNumber} pr/${prNumber}_top`);
shell.exec(`git reset pr/${prNumber}_base`);
shell.exec(`git status`);
return 0;
}

View File

@ -0,0 +1,95 @@
/**
* @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
*/
const https = require('https');
const shell = require('shelljs');
function httpGet(server, path, headers) {
return new Promise((resolve, reject) => {
const options = {
hostname: server,
port: 443,
path: path,
method: 'GET',
headers: {'User-Agent': 'script', ...headers}
};
https
.get(
options,
(res) => {
let json = '';
res.on('data', (chunk) => json += chunk.toString());
res.on('end', () => resolve(json));
})
.on('error', (e) => reject(e));
});
};
let warnNoToken = true;
async function githubGet(path) {
const token = process.env['TOKEN'];
const headers = {};
if (token) {
headers.Authorization = 'token ' + token;
} else if (warnNoToken) {
warnNoToken = false;
console.warn('############################################################');
console.warn('############################################################');
console.warn('WARNING: you should set the TOKEN variable to a github token');
console.warn('############################################################');
console.warn('############################################################');
}
return JSON.parse(await httpGet('api.github.com', '/repos/angular/angular/' + path, headers));
};
async function githubPrInfo(prNumber) {
const pr = (await githubGet('pulls/' + prNumber));
const label = pr.head.label.split(':');
const user = label[0];
const branch = label[1];
return {
commits: pr.commits,
repository: {
user: user,
gitUrl: `git@github.com:${user}/angular.git`,
},
branch: branch
};
}; // trailing ; so that clang-format is not confused on async function
function gitHasLocalModifications() {
return execNoFatal('git diff-index --quiet HEAD --').code != 0;
}
function execNoFatal(cmd, options) {
const fatal = shell.config.fatal;
try {
shell.config.fatal = false;
return shell.exec(cmd, options);
} finally {
shell.config.fatal = fatal;
}
}
function getCurrentBranch() {
return shell.exec('git branch', {silent: true})
.stdout.toString()
.split('\n') // Break into lines
.map((v) => v.trim()) // trim
.filter((b) => b[0] == '*') // select current branch
.map((b) => b.split(' ')[1])[0]; // remove leading `*`
}
exports.httpGet = httpGet;
exports.githubGet = githubGet;
exports.githubPrInfo = githubPrInfo;
exports.gitHasLocalModifications = gitHasLocalModifications;
exports.execNoFatal = execNoFatal;
exports.getCurrentBranch = getCurrentBranch;