mirror of
https://github.com/pcottle/learnGitBranching.git
synced 2025-06-27 16:38:50 +02:00
Merge pull request #565 from eatdrinksleepcode/revision-range
More robust support for revision ranges
This commit is contained in:
commit
06e0f29acf
6 changed files with 240 additions and 47 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
var Q = require('q');
|
||||||
|
|
||||||
var HeadlessGit = require('../src/js/git/headless').HeadlessGit;
|
var HeadlessGit = require('../src/js/git/headless').HeadlessGit;
|
||||||
var TreeCompare = require('../src/js/graph/treeCompare.js');
|
var TreeCompare = require('../src/js/graph/treeCompare.js');
|
||||||
|
|
||||||
|
@ -95,6 +97,27 @@ var expectLevelSolved = function(levelBlob) {
|
||||||
expectLevelAsync(headless, levelBlob);
|
expectLevelAsync(headless, levelBlob);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var runCommand = function(command, resultHandler) {
|
||||||
|
var headless = new HeadlessGit();
|
||||||
|
var deferred = Q.defer();
|
||||||
|
var msg = null;
|
||||||
|
|
||||||
|
deferred.promise.then(function(commands) {
|
||||||
|
msg = commands[commands.length - 1].get('error').get('msg');
|
||||||
|
});
|
||||||
|
|
||||||
|
runs(function() {
|
||||||
|
headless.sendCommand(command, deferred);
|
||||||
|
});
|
||||||
|
waitsFor(function() {
|
||||||
|
if(null == msg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resultHandler(msg);
|
||||||
|
return true;
|
||||||
|
}, 'commands should be finished', 500);
|
||||||
|
};
|
||||||
|
|
||||||
var TIME = 150;
|
var TIME = 150;
|
||||||
// useful for throwing garbage and then expecting one commit
|
// useful for throwing garbage and then expecting one commit
|
||||||
var ONE_COMMIT_TREE = '{"branches":{"master":{"target":"C2","id":"master"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"}},"HEAD":{"target":"master","id":"HEAD"}}';
|
var ONE_COMMIT_TREE = '{"branches":{"master":{"target":"C2","id":"master"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"}},"HEAD":{"target":"master","id":"HEAD"}}';
|
||||||
|
@ -105,6 +128,7 @@ module.exports = {
|
||||||
TIME: TIME,
|
TIME: TIME,
|
||||||
expectTreeAsync: expectTreeAsync,
|
expectTreeAsync: expectTreeAsync,
|
||||||
expectLevelSolved: expectLevelSolved,
|
expectLevelSolved: expectLevelSolved,
|
||||||
ONE_COMMIT_TREE: ONE_COMMIT_TREE
|
ONE_COMMIT_TREE: ONE_COMMIT_TREE,
|
||||||
|
runCommand: runCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var base = require('./base');
|
var base = require('./base');
|
||||||
var expectTreeAsync = base.expectTreeAsync;
|
var expectTreeAsync = base.expectTreeAsync;
|
||||||
|
var runCommand = base.runCommand;
|
||||||
|
|
||||||
describe('Git', function() {
|
describe('Git', function() {
|
||||||
it('Commits', function() {
|
it('Commits', function() {
|
||||||
|
@ -282,4 +283,104 @@ describe('Git', function() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('RevList', function() {
|
||||||
|
|
||||||
|
it('requires at least 1 argument', function() {
|
||||||
|
runCommand('git rev-list', function(commandMsg) {
|
||||||
|
expect(commandMsg).toContain('at least 1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('supports', function() {
|
||||||
|
var SETUP = 'git co -b left C0; gc; git merge master; git co -b right C0; gc; git merge master; git co -b all left; git merge right; ';
|
||||||
|
|
||||||
|
it('single included revision', function() {
|
||||||
|
runCommand(SETUP + 'git rev-list all', function(commandMsg) {
|
||||||
|
expect(commandMsg).toBe('C6\nC5\nC4\nC3\nC2\nC1\nC0\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('single excluded revision', function() {
|
||||||
|
runCommand(SETUP + 'git rev-list all ^right', function(commandMsg) {
|
||||||
|
expect(commandMsg).toBe('C6\nC3\nC2\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple included revisions', function() {
|
||||||
|
runCommand(SETUP + 'git rev-list right left', function(commandMsg) {
|
||||||
|
expect(commandMsg).toBe('C5\nC4\nC3\nC2\nC1\nC0\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple excluded revisions', function() {
|
||||||
|
runCommand(SETUP + 'git rev-list all ^right ^left', function(commandMsg) {
|
||||||
|
expect(commandMsg).toBe('C6\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Log supports', function() {
|
||||||
|
var SETUP = 'git co -b left C0; gc; git merge master; git co -b right C0; gc; git merge master; git co -b all left; git merge right; ';
|
||||||
|
|
||||||
|
it('implied HEAD', function() {
|
||||||
|
runCommand(SETUP + '; git co right; git log', function(commandMsg) {
|
||||||
|
expect(commandMsg).toContain('Commit: C0\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C1\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C2\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C3\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C4\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C5\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C6\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('single included revision', function() {
|
||||||
|
runCommand(SETUP + 'git log right', function(commandMsg) {
|
||||||
|
expect(commandMsg).toContain('Commit: C0\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C1\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C2\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C3\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C4\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C5\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C6\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('single excluded revision', function() {
|
||||||
|
runCommand(SETUP + 'git log all ^right', function(commandMsg) {
|
||||||
|
expect(commandMsg).not.toContain('Commit: C0\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C1\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C2\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C3\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C4\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C5\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C6\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple included revisions', function() {
|
||||||
|
runCommand(SETUP + 'git log right left', function(commandMsg) {
|
||||||
|
expect(commandMsg).toContain('Commit: C0\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C1\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C2\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C3\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C4\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C5\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C6\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multiple excluded revisions', function() {
|
||||||
|
runCommand(SETUP + 'git log all ^right ^left', function(commandMsg) {
|
||||||
|
expect(commandMsg).not.toContain('Commit: C0\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C1\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C2\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C3\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C4\n');
|
||||||
|
expect(commandMsg).not.toContain('Commit: C5\n');
|
||||||
|
expect(commandMsg).toContain('Commit: C6\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -574,25 +574,26 @@ var commandConfig = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
revlist: {
|
||||||
|
dontCountForGolf: true,
|
||||||
|
displayName: 'rev-list',
|
||||||
|
regex: /^git +rev-list($|\s)/,
|
||||||
|
execute: function(engine, command) {
|
||||||
|
var generalArgs = command.getGeneralArgs();
|
||||||
|
command.validateArgBounds(generalArgs, 1);
|
||||||
|
|
||||||
|
engine.revlist(generalArgs);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
log: {
|
log: {
|
||||||
dontCountForGolf: true,
|
dontCountForGolf: true,
|
||||||
regex: /^git +log($|\s)/,
|
regex: /^git +log($|\s)/,
|
||||||
execute: function(engine, command) {
|
execute: function(engine, command) {
|
||||||
var generalArgs = command.getGeneralArgs();
|
var generalArgs = command.getGeneralArgs();
|
||||||
|
|
||||||
if (generalArgs.length == 2) {
|
command.impliedHead(generalArgs, 0);
|
||||||
// do fancy git log branchA ^branchB
|
engine.log(generalArgs);
|
||||||
if (generalArgs[1][0] == '^') {
|
|
||||||
engine.logWithout(generalArgs[0], generalArgs[1]);
|
|
||||||
} else {
|
|
||||||
throw new GitError({
|
|
||||||
msg: intl.str('git-error-options')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
command.oneArgImpliedHead(generalArgs);
|
|
||||||
engine.log(generalArgs[0]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,8 @@ HeadlessGit.prototype.sendCommand = function(value, entireCommandPromise) {
|
||||||
var chain = deferred.promise;
|
var chain = deferred.promise;
|
||||||
var startTime = new Date().getTime();
|
var startTime = new Date().getTime();
|
||||||
|
|
||||||
|
var commands = [];
|
||||||
|
|
||||||
util.splitTextCommand(value, function(commandStr) {
|
util.splitTextCommand(value, function(commandStr) {
|
||||||
chain = chain.then(function() {
|
chain = chain.then(function() {
|
||||||
var commandObj = new Command({
|
var commandObj = new Command({
|
||||||
|
@ -117,6 +119,7 @@ HeadlessGit.prototype.sendCommand = function(value, entireCommandPromise) {
|
||||||
|
|
||||||
var thisDeferred = Q.defer();
|
var thisDeferred = Q.defer();
|
||||||
this.gitEngine.dispatch(commandObj, thisDeferred);
|
this.gitEngine.dispatch(commandObj, thisDeferred);
|
||||||
|
commands.push(commandObj);
|
||||||
return thisDeferred.promise;
|
return thisDeferred.promise;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}, this);
|
}, this);
|
||||||
|
@ -124,7 +127,7 @@ HeadlessGit.prototype.sendCommand = function(value, entireCommandPromise) {
|
||||||
chain.then(function() {
|
chain.then(function() {
|
||||||
var nowTime = new Date().getTime();
|
var nowTime = new Date().getTime();
|
||||||
if (entireCommandPromise) {
|
if (entireCommandPromise) {
|
||||||
entireCommandPromise.resolve();
|
entireCommandPromise.resolve(commands);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2740,37 +2740,26 @@ GitEngine.prototype.logWithout = function(ref, omitBranch) {
|
||||||
this.log(ref, Graph.getUpstreamSet(this, omitBranch));
|
this.log(ref, Graph.getUpstreamSet(this, omitBranch));
|
||||||
};
|
};
|
||||||
|
|
||||||
GitEngine.prototype.log = function(ref, omitSet) {
|
GitEngine.prototype.revlist = function(refs) {
|
||||||
// omit set is for doing stuff like git log branchA ^branchB
|
var range = new RevisionRange(this, refs);
|
||||||
omitSet = omitSet || {};
|
|
||||||
// first get the commit we referenced
|
|
||||||
var commit = this.getCommitFromRef(ref);
|
|
||||||
|
|
||||||
// then get as many far back as we can from here, order by commit date
|
// now go through and collect ids
|
||||||
var toDump = [];
|
var bigLogStr = range.formatRevisions(function(c) {
|
||||||
var pQueue = [commit];
|
return c.id + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
var seen = {};
|
throw new CommandResult({
|
||||||
|
msg: bigLogStr
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
while (pQueue.length) {
|
GitEngine.prototype.log = function(refs) {
|
||||||
var popped = pQueue.shift(0);
|
var range = new RevisionRange(this, refs);
|
||||||
if (seen[popped.get('id')] || omitSet[popped.get('id')]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen[popped.get('id')] = true;
|
|
||||||
|
|
||||||
toDump.push(popped);
|
|
||||||
|
|
||||||
if (popped.get('parents') && popped.get('parents').length) {
|
|
||||||
pQueue = pQueue.concat(popped.get('parents'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now go through and collect logs
|
// now go through and collect logs
|
||||||
var bigLogStr = '';
|
var bigLogStr = range.formatRevisions(function(c) {
|
||||||
toDump.forEach(function (c) {
|
return c.getLogEntry();
|
||||||
bigLogStr += c.getLogEntry();
|
});
|
||||||
}, this);
|
|
||||||
|
|
||||||
throw new CommandResult({
|
throw new CommandResult({
|
||||||
msg: bigLogStr
|
msg: bigLogStr
|
||||||
|
@ -3079,6 +3068,79 @@ var Tag = Ref.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function RevisionRange(engine, specifiers) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.included = {};
|
||||||
|
this.excluded = {};
|
||||||
|
this.revisions = [];
|
||||||
|
|
||||||
|
this.processSpecifiers(specifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
RevisionRange.prototype.isExclusion = function(specifier) {
|
||||||
|
return specifier.startsWith('^');
|
||||||
|
};
|
||||||
|
|
||||||
|
RevisionRange.prototype.processSpecifiers = function(specifiers) {
|
||||||
|
var self = this;
|
||||||
|
var inclusions = [];
|
||||||
|
var exclusions = [];
|
||||||
|
|
||||||
|
specifiers.forEach(function(specifier) {
|
||||||
|
if(self.isExclusion(specifier)) {
|
||||||
|
exclusions.push(specifier.slice(1));
|
||||||
|
} else {
|
||||||
|
inclusions.push(specifier);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exclusions.forEach(function(exclusion) {
|
||||||
|
self.addExcluded(Graph.getUpstreamSet(self.engine, exclusion));
|
||||||
|
});
|
||||||
|
|
||||||
|
inclusions.forEach(function(inclusion) {
|
||||||
|
self.addIncluded(Graph.getUpstreamSet(self.engine, inclusion));
|
||||||
|
});
|
||||||
|
|
||||||
|
var includedKeys = Array.from(Object.keys(self.included));
|
||||||
|
|
||||||
|
self.revisions = includedKeys.map(function(revision) {
|
||||||
|
return self.engine.resolveStringRef(revision);
|
||||||
|
});
|
||||||
|
self.revisions.sort(self.engine.dateSortFunc);
|
||||||
|
self.revisions.reverse();
|
||||||
|
};
|
||||||
|
|
||||||
|
RevisionRange.prototype.isExcluded = function(revision) {
|
||||||
|
return this.excluded.hasOwnProperty(revision);
|
||||||
|
};
|
||||||
|
|
||||||
|
RevisionRange.prototype.addExcluded = function(setToExclude) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(setToExclude).forEach(function(toExclude) {
|
||||||
|
if(!self.isExcluded(toExclude)) {
|
||||||
|
self.excluded[toExclude] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
RevisionRange.prototype.addIncluded = function(setToInclude) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(setToInclude).forEach(function(toInclude) {
|
||||||
|
if(!self.isExcluded(toInclude)) {
|
||||||
|
self.included[toInclude] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
RevisionRange.prototype.formatRevisions = function(revisionFormatter) {
|
||||||
|
var output = "";
|
||||||
|
this.revisions.forEach(function(c) {
|
||||||
|
output += revisionFormatter(c);
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
exports.GitEngine = GitEngine;
|
exports.GitEngine = GitEngine;
|
||||||
exports.Commit = Commit;
|
exports.Commit = Commit;
|
||||||
exports.Branch = Branch;
|
exports.Branch = Branch;
|
||||||
|
|
|
@ -126,18 +126,14 @@ var Command = Backbone.Model.extend({
|
||||||
oneArgImpliedHead: function(args, option) {
|
oneArgImpliedHead: function(args, option) {
|
||||||
this.validateArgBounds(args, 0, 1, option);
|
this.validateArgBounds(args, 0, 1, option);
|
||||||
// and if it's one, add a HEAD to the back
|
// and if it's one, add a HEAD to the back
|
||||||
if (args.length === 0) {
|
this.impliedHead(args, 0);
|
||||||
args.push('HEAD');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
twoArgsImpliedHead: function(args, option) {
|
twoArgsImpliedHead: function(args, option) {
|
||||||
// our args we expect to be between 1 and 2
|
// our args we expect to be between 1 and 2
|
||||||
this.validateArgBounds(args, 1, 2, option);
|
this.validateArgBounds(args, 1, 2, option);
|
||||||
// and if it's one, add a HEAD to the back
|
// and if it's one, add a HEAD to the back
|
||||||
if (args.length == 1) {
|
this.impliedHead(args, 1);
|
||||||
args.push('HEAD');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
oneArgImpliedOrigin: function(args) {
|
oneArgImpliedOrigin: function(args) {
|
||||||
|
@ -151,6 +147,12 @@ var Command = Backbone.Model.extend({
|
||||||
this.validateArgBounds(args, 0, 2);
|
this.validateArgBounds(args, 0, 2);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
impliedHead: function(args, min) {
|
||||||
|
if(args.length == min) {
|
||||||
|
args.push('HEAD');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// this is a little utility class to help arg validation that happens over and over again
|
// this is a little utility class to help arg validation that happens over and over again
|
||||||
validateArgBounds: function(args, lower, upper, option) {
|
validateArgBounds: function(args, lower, upper, option) {
|
||||||
var what = (option === undefined) ?
|
var what = (option === undefined) ?
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue