168 lines
3.6 KiB
JavaScript
Executable File
168 lines
3.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
'use strict';
|
|
|
|
var util = require('util');
|
|
var cp = require('child_process');
|
|
|
|
var Q = require('q');
|
|
var _ = require('lodash');
|
|
var semver = require('semver');
|
|
|
|
var exec = function(cmd) {
|
|
return function() {
|
|
var args = Array.prototype.slice.call(arguments, 0);
|
|
args.unshift(cmd);
|
|
var fullCmd = util.format.apply(util, args);
|
|
return Q.nfcall(cp.exec, fullCmd).then(function(out) {
|
|
return out[0].split('\n');
|
|
});
|
|
};
|
|
};
|
|
|
|
var andThen = function(fn, after) {
|
|
return /** @this */ function() {
|
|
return fn.apply(this, arguments).then(after);
|
|
};
|
|
};
|
|
|
|
var oneArg = function(fn) {
|
|
return function(arg) {
|
|
return fn(arg);
|
|
};
|
|
};
|
|
|
|
var oneLine = function(lines) {
|
|
return lines[0].trim();
|
|
};
|
|
|
|
var noArgs = function(fn) {
|
|
return function() {
|
|
return fn();
|
|
};
|
|
};
|
|
|
|
var identity = function(i) { return i; };
|
|
|
|
// like Q.all, but runs the commands in series
|
|
// useful for ensuring env state (like which branch is checked out)
|
|
var allInSeries = function(fn) {
|
|
return function(args) {
|
|
var results = [];
|
|
var def;
|
|
while (args.length > 0) {
|
|
(function(arg) {
|
|
if (def) {
|
|
def = def.then(function() {
|
|
return fn(arg);
|
|
});
|
|
} else {
|
|
def = fn(arg);
|
|
}
|
|
def = def.then(function(res) {
|
|
results.push(res);
|
|
});
|
|
})(args.pop());
|
|
}
|
|
return def.then(function() {
|
|
return results;
|
|
});
|
|
};
|
|
};
|
|
|
|
var compareBranches = function(left, right) {
|
|
console.log('# These commits are in ' + left.name + ' but not in ' + right.name + '\n');
|
|
console.log(_(left.log).
|
|
difference(right.log).
|
|
map(function(line) {
|
|
return left.full[left.log.indexOf(line)]; // lol O(n^2)
|
|
}).
|
|
value().
|
|
join('\n'));
|
|
};
|
|
|
|
var checkout = oneArg(exec('git checkout %s'));
|
|
|
|
var getCurrentBranch = andThen(noArgs(exec('git rev-parse --abbrev-ref HEAD')), oneLine);
|
|
var getTags = noArgs(exec('git tag'));
|
|
var getTheLog = oneArg(exec('git log --pretty=oneline %s..HEAD | cat'));
|
|
|
|
// remember this so we can restore state
|
|
var currentBranch;
|
|
|
|
getCurrentBranch().
|
|
then(function(branch) {
|
|
currentBranch = branch;
|
|
}).
|
|
then(getTags).
|
|
then(function(tags) {
|
|
return tags.
|
|
filter(semver.valid).
|
|
map(semver.clean).
|
|
sort(semver.rcompare);
|
|
}).
|
|
then(function(tags) {
|
|
var major = semver(tags[0]).major;
|
|
return tags.
|
|
filter(function(ver) {
|
|
return semver(ver).major === major;
|
|
});
|
|
}).
|
|
then(function(tags) {
|
|
return _(tags).
|
|
groupBy(function(tag) {
|
|
return tag.split('.')[1];
|
|
}).
|
|
map(function(group) {
|
|
return _.first(group);
|
|
}).
|
|
map(function(tag) {
|
|
return 'v' + tag;
|
|
}).
|
|
value();
|
|
}).
|
|
then(function(tags) {
|
|
var master = tags.pop();
|
|
var stable = tags.pop();
|
|
|
|
return [
|
|
{ name: stable.replace(/\d+$/, 'x'), tag: stable },
|
|
{ name: 'master', tag: master}
|
|
];
|
|
}).
|
|
then(allInSeries(function(branch) {
|
|
return checkout(branch.name).
|
|
then(function() {
|
|
return getTheLog(branch.tag);
|
|
}).
|
|
then(function(log) {
|
|
return log.
|
|
filter(identity);
|
|
}).
|
|
then(function(log) {
|
|
branch.full = log.map(function(line) {
|
|
line = line.split(' ');
|
|
var sha = line.shift();
|
|
var msg = line.join(' ');
|
|
return sha + ((/fix\([^)]+\):/i.test(msg)) ? ' * ' : ' ') + msg;
|
|
});
|
|
branch.log = log.map(function(line) {
|
|
return line.substr(41);
|
|
});
|
|
return branch;
|
|
});
|
|
})).
|
|
then(function(pairs) {
|
|
compareBranches(pairs[0], pairs[1]);
|
|
console.log('\n');
|
|
compareBranches(pairs[1], pairs[0]);
|
|
return pairs;
|
|
}).
|
|
then(function() {
|
|
return checkout(currentBranch);
|
|
}).
|
|
catch(function(e) {
|
|
console.log(e.stack);
|
|
});
|
|
|