Skip to content

Instantly share code, notes, and snippets.

@valueof
Created March 14, 2012 23:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save valueof/2040390 to your computer and use it in GitHub Desktop.
Save valueof/2040390 to your computer and use it in GitHub Desktop.
Unfinished GitHub patch helper written in JavaScript/Node
I started writing this program but then got bored and decided to
re-write it in Go. This is my unfinished JavaScript variant.
See https://github.com/antonkovalyov/r2go for more information.
var https = require('https');
var cliff = require('cliff');
var colors = require('colors');
var request = require('request');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var tty = require('tty');
var fs = require('fs');
var path = require('path');
var github = Object.create({
repoUser: '',
repoName: '',
get: function (method, callback) {
var host = 'https://api.github.com';
var path = method.replace(':user', this.repoUser)
path = path.replace(':repo', this.repoName);
request(host + path, function (err, res, body) {
if (err)
panic('Request failed. Reason: ' + err.message);
callback(JSON.parse(body));
});
}
});
var git = Object.create({
path: '',
template: '',
exec: function (commands, callback) {
var options = { cwd: this.path };
function execOne(cmd) {
exec(cmd, options, function (err, stdout, stderr) {
if (err)
panic('Git command failed. Reason: ' + err.message);
cmd = commands.shift();
if (!cmd)
return void callback(stdout);
execOne(cmd);
});
}
if (commands.length === 0)
return;
execOne(commands.shift());
},
execInteractive: function (cmd, opts, callback) {
var prog;
process.stdin.pause();
tty.setRawMode(false);
prog = spawn(cmd, opts, {
customFds: [ 0, 1, 2 ]
});
prog.on('exit', function () {
tty.setRawMode(true);
process.stdin.resume();
callback();
});
}
});
function listPullRequests(data) {
var rows = [];
for (var i = 0; i < data.length; i++) {
rows.push([
'GH-' + data[i].number,
data[i].state === 'open' ? data[i].title.green : data[i].title.red
]);
}
console.log(cliff.stringifyRows(rows));
}
function list() {
github.get('/repos/:user/:repo/pulls', function (pullRequests) {
var rows = [];
if (!pullRequests.length)
return;
pullRequests.forEach(function (pr) {
rows.push([ 'GH-' + pr.number, pr.title ]);
});
console.log(cliff.stringifyRows(rows));
});
}
function diff(id) {
if (!id)
panic("Pull request number is required for 'r2 diff' to work.");
github.get('/repos/:user/:repo/pulls/' + id, function (pullRequest) {
request(pullRequest.diff_url, function (err, res, body) {
var lines = body.split('\n');
lines.forEach(function (line, index) {
if (line.match(/^(diff|index|\-\-\-|\+\+\+).+$/))
lines[index] = line.white;
else if (line.match(/^@@.+$/))
lines[index] = line.cyan;
else if (line.match(/^\+.+$/))
lines[index] = line.green;
else if (line.match(/^\-.+$/))
lines[index] = line.red;
else
lines[index] = line.grey;
});
console.log(lines.join('\n'));
});
});
}
function patch(id) {
if (!id)
panic("Pull request number is required for 'r2 patch' to work.");
github.get('/repos/:user/:repo/pulls/' + id, function (pr) {
var commands = [
'git checkout -b gh' + id + ' master',
'git pull ' + pr.head.repo.clone_url + ' ' + pr.head.ref,
];
git.exec(commands, function () {
console.log('Patch has been successfully applied.'.green);
});
});
}
function land(id) {
if (!id)
panic("Pull request number is required for 'r2 land' to work.");
function merge(branch, author) {
var commands = [
'git checkout master',
'git merge --squash --no-commit ' + branch
];
process.stdout.write('Landing GH-' + id + ': ');
git.exec(commands, function () {
'git commit -t ' + git.template + '--author="' + author + '"'
git.execInteractive('git', [ 'commit', '-t ' + git.template, '--author="' + author + '"' ], function () {
console.log('Done.'.green);
process.exit(0);
});
});
}
var commands = [
'git log gh' + id + '^..gh' + id + ' --pretty=format:"%an <%ae>"'
];
git.exec(commands, function (author) {
merge('gh' + id, author);
});
}
function help() {
console.log('r2: a command line tool for managing pull requests on GitHub');
console.log();
var out = cliff.stringifyRows([
[ 'help'.blue, 'Show this help.' ],
[ 'list'.blue, 'Show all open pull requests.' ],
[ 'patch'.blue, 'Apply changes in the pull request to a working copy.' ],
[ 'land'.blue, 'Accept and merge changes from the pull requests.' ]
]);
console.log(out);
}
function panic(msg) {
console.log(msg.red);
process.exit(1);
}
function main() {
var commands = {
'help': help,
'list': list,
'diff': diff,
'patch': patch,
'land': land
};
if (process.argv.length < 3)
return void help();
var cmd = process.argv[2];
if (commands[cmd] === undefined)
panic('Unknown command: ' + cmd + '.');
fs.readFile(path.join(process.cwd(), '/.r2config'), function (err, data) {
var config;
if (err)
panic("Failed to parse '.r2config'. Reason: " + err.message);
try {
config = JSON.parse(data);
} catch (err) {
panic("Failed to parse '.r2config'. Reason: " + err.message);
}
if (!config.repo)
panic('Object repo is not set.');
git.path = process.cwd();
git.template = path.join(path.dirname(process.argv[1]), '/res/commit-template.txt');
github.repoUser = config.repo.user || panic('Variable repo.user is not set.');
github.repoName = config.repo.name || panic('Variable repo.name is not set.');
github.username = config.username;
github.password = config.password;
commands[cmd].apply({}, process.argv.slice(3));
});
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment