mirror of
https://github.com/pcottle/learnGitBranching.git
synced 2025-06-27 16:38:50 +02:00
have visual refs working and better error models
This commit is contained in:
parent
9221088853
commit
af76c03ad1
10 changed files with 212 additions and 74 deletions
5
src/collections.js
Normal file
5
src/collections.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
var CommitCollection = Backbone.Collection.extend({
|
||||
model: Commit
|
||||
});
|
||||
|
||||
var commitCollection = new CommitCollection();
|
|
@ -37,22 +37,24 @@ Command.prototype.getRegexMap = function() {
|
|||
Command.prototype.getSandboxCommands = function() {
|
||||
return [
|
||||
[/^ls/, function() {
|
||||
throw new CommandResult("\
|
||||
DontWorryAboutFilesInThisDemo.txt\
|
||||
");
|
||||
throw new CommandResult({
|
||||
msg: "DontWorryAboutFilesInThisDemo.txt"
|
||||
});
|
||||
}],
|
||||
[/^cd/, function() {
|
||||
throw new CommandResult("\
|
||||
Directory Changed to '/directories/dont/matter/in/this/demo' \
|
||||
");
|
||||
throw new CommandResult({
|
||||
msg: "Directory Changed to '/directories/dont/matter/in/this/demo'"
|
||||
});
|
||||
}],
|
||||
[/^git$/, function() {
|
||||
throw new CommandResult(_.escape("\
|
||||
throw new CommandResult({
|
||||
msg: _.escape("\
|
||||
Git Version \n \
|
||||
PCOTTLE.1.0 \
|
||||
Usage: \n \
|
||||
git <command> [<args>] \
|
||||
"));
|
||||
")
|
||||
});
|
||||
}]
|
||||
];
|
||||
};
|
||||
|
@ -60,7 +62,7 @@ Command.prototype.getSandboxCommands = function() {
|
|||
Command.prototype.parse = function(str) {
|
||||
// first if the string is empty, they just want a blank line
|
||||
if (!str.length) {
|
||||
throw new CommandResult("");
|
||||
throw new CommandResult({msg: ""});
|
||||
}
|
||||
|
||||
// then check if it's one of our sandbox commands
|
||||
|
@ -82,7 +84,9 @@ Command.prototype.parse = function(str) {
|
|||
|
||||
// see if begins with git
|
||||
if (str.slice(0,3) !== 'git') {
|
||||
throw new CommandProcessError('Git commands only, sorry!');
|
||||
throw new CommandProcessError({
|
||||
msg: 'Git commands only, sorry!'
|
||||
});
|
||||
}
|
||||
|
||||
// ok, we have a (probably) valid command. actually parse it
|
||||
|
@ -106,9 +110,9 @@ Command.prototype.gitParse = function(str) {
|
|||
}, this);
|
||||
|
||||
if (!matched) {
|
||||
throw new CommandProcessError(
|
||||
"Sorry, this demo does not support that git command: " + this.fullCommand
|
||||
);
|
||||
throw new CommandProcessError({
|
||||
msg: "Sorry, this demo does not support that git command: " + this.fullCommand
|
||||
});
|
||||
}
|
||||
|
||||
this.optionParser = new OptionParser(this.method, this.options);
|
||||
|
@ -170,7 +174,9 @@ OptionParser.prototype.explodeAndSet = function() {
|
|||
if (part.slice(0,1) == '-') {
|
||||
// it's an option, check supportedMap
|
||||
if (this.supportedMap[part] === undefined) {
|
||||
throw new CommandProcessError('The option "' + part + '" is not supported');
|
||||
throw new CommandProcessError({
|
||||
msg: 'The option "' + part + '" is not supported'
|
||||
});
|
||||
}
|
||||
|
||||
// go through and include all the next args until we hit another option or the end
|
||||
|
|
|
@ -20,10 +20,21 @@ var graphics = {
|
|||
edgeStroke: 'rgba(94%, 96%, 98%, 0.5)', // '#EFF5FB',
|
||||
nodeEdge: 'rgba(94%, 96%, 98%, 0.9)', // '#EFF5FB',
|
||||
nodeFill: '#0066cc',
|
||||
nodeRadius: 10,
|
||||
|
||||
// widths
|
||||
nodeStrokeWidth: 15,
|
||||
edgeWidth: 2,
|
||||
|
||||
// ref names
|
||||
refFont: '14pt Courier New',
|
||||
refFontFill: '#FFF',
|
||||
|
||||
// ref arrows
|
||||
arrowFill: '#FFF',
|
||||
arrowStroke: '#000',
|
||||
arrowWidth: 4,
|
||||
arrowHeadWidth: 5
|
||||
};
|
||||
|
||||
function randomString(string_length) {
|
||||
|
|
|
@ -1,31 +1,35 @@
|
|||
function CommandProcessError(msg) {
|
||||
this.msg = msg;
|
||||
var MyError = Backbone.Model.extend({
|
||||
defaults: {
|
||||
type: 'MyError',
|
||||
msg: 'Unknown Error'
|
||||
},
|
||||
toString: function() {
|
||||
return this.get('type') + ': ' + this.get('msg');
|
||||
},
|
||||
|
||||
getMsg: function() {
|
||||
return this.get('msg') || 'Unknown Error';
|
||||
},
|
||||
|
||||
toResult: function() {
|
||||
return this.get('msg').replace('\n', '</br>');
|
||||
}
|
||||
});
|
||||
|
||||
CommandProcessError.prototype.toString = function() {
|
||||
return 'Command Process Error: ' + this.msg;
|
||||
};
|
||||
|
||||
CommandProcessError.prototype.toResult = function() {
|
||||
return this.msg.replace('\n', '</br>');
|
||||
};
|
||||
|
||||
function CommandResult(msg) {
|
||||
this.msg = msg;
|
||||
var CommandProcessError = MyError.extend({
|
||||
defaults: {
|
||||
type: 'Command Process Error'
|
||||
}
|
||||
});
|
||||
|
||||
CommandResult.prototype.toString = function() {
|
||||
return 'Command Result: ' + this.msg;
|
||||
};
|
||||
|
||||
CommandResult.prototype.toResult = function() {
|
||||
return this.msg.replace('\n', '</br>');
|
||||
};
|
||||
|
||||
function GitError(msg) {
|
||||
this.msg = msg;
|
||||
var CommandResult = MyError.extend({
|
||||
defaults: {
|
||||
type: 'Command Result'
|
||||
}
|
||||
});
|
||||
|
||||
GitError.prototype.toString = function() {
|
||||
return 'Git Error: ' + this.msg;
|
||||
};
|
||||
var GitError = MyError.extend({
|
||||
defaults: {
|
||||
type: 'Git Error'
|
||||
}
|
||||
});
|
||||
|
|
90
src/git.js
90
src/git.js
|
@ -13,6 +13,7 @@ function GitEngine() {
|
|||
this.HEAD = null;
|
||||
this.id_gen = 0;
|
||||
this.branches = [];
|
||||
this.collection = commitCollection;
|
||||
|
||||
// global variable to keep track of the options given
|
||||
// along with the command call.
|
||||
|
@ -27,6 +28,8 @@ function GitEngine() {
|
|||
GitEngine.prototype.init = function() {
|
||||
// make an initial commit and a master branch
|
||||
this.rootCommit = new Commit({rootCommit: true});
|
||||
commitCollection.add(this.rootCommit);
|
||||
|
||||
this.refs[this.rootCommit.get('id')] = this.rootCommit;
|
||||
|
||||
var master = this.makeBranch('master', this.rootCommit);
|
||||
|
@ -50,10 +53,14 @@ GitEngine.prototype.getDetachedHead = function() {
|
|||
GitEngine.prototype.validateBranchName = function(name) {
|
||||
name = name.replace(/\s/g, '');
|
||||
if (!/^[a-zA-Z0-9]+$/.test(name)) {
|
||||
throw new Error('woah bad branch name!! This is not ok: ' + name);
|
||||
throw new GitError({
|
||||
msg: 'woah bad branch name!! This is not ok: ' + name
|
||||
});
|
||||
}
|
||||
if (/[hH][eE][aA][dD]/.test(name)) {
|
||||
throw new Error('branch name of "head" is ambiguous, dont name it that');
|
||||
throw new GitError({
|
||||
msg: 'branch name of "head" is ambiguous, dont name it that'
|
||||
});
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
@ -61,7 +68,9 @@ GitEngine.prototype.validateBranchName = function(name) {
|
|||
GitEngine.prototype.makeBranch = function(id, target) {
|
||||
id = this.validateBranchName(id);
|
||||
if (this.refs[id]) {
|
||||
throw new Error('that branch id already exists!');
|
||||
throw new GitError({
|
||||
msg: 'that branch id already exists!'
|
||||
});
|
||||
}
|
||||
|
||||
var branch = new Branch({
|
||||
|
@ -73,18 +82,34 @@ GitEngine.prototype.makeBranch = function(id, target) {
|
|||
return branch;
|
||||
};
|
||||
|
||||
GitEngine.prototype.getHead = function() {
|
||||
return _.clone(this.HEAD);
|
||||
};
|
||||
|
||||
GitEngine.prototype.getBranches = function() {
|
||||
var toReturn = [];
|
||||
_.each(this.branches, function(branch) {
|
||||
toReturn.push({
|
||||
id: branch.get('id'),
|
||||
selected: this.HEAD.get('target') === branch
|
||||
selected: this.HEAD.get('target') === branch,
|
||||
target: branch.get('target')
|
||||
});
|
||||
}, this);
|
||||
return toReturn;
|
||||
};
|
||||
|
||||
GitEngine.prototype.printBranches = function() {
|
||||
var branches = this.getBranches();
|
||||
var result = '';
|
||||
_.each(branches, function(branch) {
|
||||
result += (branch.selected ? '* ' : '') + branch.id + '\n';
|
||||
});
|
||||
throw new CommandResult({
|
||||
msg: result
|
||||
});
|
||||
};
|
||||
|
||||
GitEngine.prototype.logBranches = function() {
|
||||
var branches = this.getBranches();
|
||||
_.each(branches, function(branch) {
|
||||
console.log((branch.selected ? '* ' : '') + branch.id);
|
||||
|
@ -96,12 +121,15 @@ GitEngine.prototype.makeCommit = function(parent) {
|
|||
parents: [parent]
|
||||
});
|
||||
this.refs[commit.get('id')] = commit;
|
||||
this.collection.add(commit);
|
||||
return commit;
|
||||
};
|
||||
|
||||
GitEngine.prototype.acceptNoGeneralArgs = function() {
|
||||
if (this.generalArgs.length) {
|
||||
throw new GitError("That command accepts no general arguments");
|
||||
throw new GitError({
|
||||
msg: "That command accepts no general arguments"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -167,10 +195,14 @@ GitEngine.prototype.resolveStringRef = function(ref) {
|
|||
}, this);
|
||||
|
||||
if (!startRef) {
|
||||
throw new Error('unknown ref ' + ref);
|
||||
throw new GitError({
|
||||
msg: 'unknown ref ' + ref
|
||||
});
|
||||
}
|
||||
if (!this.refs[startRef]) {
|
||||
throw new Error('the ref ' + startRef +' does not exist.');
|
||||
throw new GitError({
|
||||
msg: 'the ref ' + startRef +' does not exist.'
|
||||
});
|
||||
}
|
||||
var commit = this.getCommitFromRef(startRef);
|
||||
|
||||
|
@ -224,14 +256,18 @@ GitEngine.prototype.numBackFrom = function(commit, numBack) {
|
|||
}
|
||||
|
||||
if (numBack !== 0 || pQueue.length == 0) {
|
||||
throw new Error('exhausted search, sorry');
|
||||
throw new GitError({
|
||||
msg: "Sorry, I can't go that many commits back"
|
||||
});
|
||||
}
|
||||
return pQueue.shift(0);
|
||||
};
|
||||
|
||||
GitEngine.prototype.checkoutStarter = function() {
|
||||
if (this.generalArgs.length != 1) {
|
||||
throw new GitError('I expect one argument along with git checkout (dont reference files)');
|
||||
throw new GitError({
|
||||
msg: 'I expect one argument along with git checkout (dont reference files)'
|
||||
});
|
||||
}
|
||||
|
||||
this.checkout(this.generalArgs[0]);
|
||||
|
@ -249,7 +285,9 @@ GitEngine.prototype.checkout = function(idOrTarget) {
|
|||
|
||||
var type = target.get('type');
|
||||
if (type !== 'branch' && type !== 'commit') {
|
||||
throw new Error('can only checkout branches and commits!');
|
||||
throw new GitError({
|
||||
msg: 'can only checkout branches and commits!'
|
||||
});
|
||||
}
|
||||
|
||||
this.HEAD.set('target', target);
|
||||
|
@ -259,7 +297,9 @@ GitEngine.prototype.branchStarter = function() {
|
|||
// handle deletion first
|
||||
if (this.commandOptions['-d'] || this.commandOptions['-D']) {
|
||||
if (!this.generalArgs.length) {
|
||||
throw new GitError('I expect branch names when deleting');
|
||||
throw new GitError({
|
||||
msg: 'I expect branch names when deleting'
|
||||
});
|
||||
}
|
||||
_.each(this.generalArgs, function(name) {
|
||||
this.deleteBranch(name);
|
||||
|
@ -269,11 +309,14 @@ GitEngine.prototype.branchStarter = function() {
|
|||
|
||||
var len = this.generalArgs.length;
|
||||
if (len > 2) {
|
||||
throw new GitError('git branch with more than two general args does not make sense!');
|
||||
throw new GitError({
|
||||
msg: 'git branch with more than two general args does not make sense!'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (len == 0) {
|
||||
throw new Error('going to implement eventually');
|
||||
//TODO: print branches, etc
|
||||
this.printBranches();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -293,13 +336,19 @@ GitEngine.prototype.deleteBranch = function(name) {
|
|||
// trying to delete, lets check our refs
|
||||
var target = this.resolveId(name);
|
||||
if (target.get('type') !== 'branch') {
|
||||
throw new GitError("You can't delete things that arent branches with branch command");
|
||||
throw new GitError({
|
||||
msg: "You can't delete things that arent branches with branch command"
|
||||
});
|
||||
}
|
||||
if (target.get('id') == 'master') {
|
||||
throw new GitError("You can't delete the master branch!");
|
||||
throw new GitError({
|
||||
msg: "You can't delete the master branch!"
|
||||
});
|
||||
}
|
||||
if (this.HEAD.get('target') === target) {
|
||||
throw new GitError("Cannot delete the branch you are currently on");
|
||||
throw new GitError({
|
||||
msg: "Cannot delete the branch you are currently on"
|
||||
});
|
||||
}
|
||||
|
||||
var id = target.get('id');
|
||||
|
@ -315,10 +364,10 @@ GitEngine.prototype.dispatch = function(commandObj) {
|
|||
};
|
||||
|
||||
GitEngine.prototype.add = function() {
|
||||
throw new Error(
|
||||
"This demo is meant to demonstrate git branching, so don't worry about " +
|
||||
throw new CommandResult({
|
||||
msg: "This demo is meant to demonstrate git branching, so don't worry about " +
|
||||
"adding / staging files. Just go ahead and commit away!"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
GitEngine.prototype.execute = function(command, callback) {
|
||||
|
@ -401,7 +450,6 @@ var Commit = Backbone.Model.extend({
|
|||
initialize: function() {
|
||||
this.validateAtInit();
|
||||
this.addNodeToVisuals();
|
||||
console.log('MAKING NEW COMMIT', this.get('id'));
|
||||
|
||||
_.each(this.get('parents'), function(parent) {
|
||||
parent.get('children').push(this);
|
||||
|
|
|
@ -51,5 +51,7 @@
|
|||
<script src="commandline.js"></script>
|
||||
<script src="views.js"></script>
|
||||
<script src="errors.js"></script>
|
||||
<script src="collections.js"></script>
|
||||
<script src="visuals.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -39,6 +39,7 @@ Renderer = function(canvas) {
|
|||
ctx.clearRect(0,0, canvas.width, canvas.height);
|
||||
particleSystem.eachEdge(this.drawEdge);
|
||||
particleSystem.eachNode(this.drawNode);
|
||||
events.trigger('drawGitVisuals', particleSystem, ctx, canvas);
|
||||
},
|
||||
|
||||
resize: function(){
|
||||
|
|
|
@ -5,6 +5,7 @@ var events = _.clone(Backbone.Events);
|
|||
var sys = null;
|
||||
var graphicsEffects = {};
|
||||
var gitEngine = null;
|
||||
var gitVisuals = null;
|
||||
|
||||
$(document).ready(function(){
|
||||
sys = arbor.ParticleSystem(4000, 500, 0.5, false, 55, 0.005, 'verlet');
|
||||
|
@ -18,6 +19,7 @@ $(document).ready(function(){
|
|||
});
|
||||
|
||||
gitEngine = new GitEngine();
|
||||
gitVisuals = new GitVisuals();
|
||||
|
||||
var repulsionBreathe = function(r) {
|
||||
sys.parameters({repulsion: r});
|
||||
|
@ -40,9 +42,10 @@ Node.prototype.drawCircleNode = function(ctx, pt) {
|
|||
ctx.strokeStyle = graphics.nodeEdge;
|
||||
ctx.lineWidth = graphics.nodeStrokeWidth;
|
||||
ctx.fillStyle = graphics.nodeFill;
|
||||
var radius = graphics.nodeRadius;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(pt.x, pt.y, 10, 0, Math.PI*2, true);
|
||||
ctx.arc(pt.x, pt.y, radius, 0, Math.PI*2, true);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
|
|
15
src/views.js
15
src/views.js
|
@ -7,7 +7,7 @@ var CommandLineView = Backbone.View.extend({
|
|||
$.proxy(this.keyUp, this)
|
||||
);
|
||||
|
||||
events.on('commandConsumed', _.bind(
|
||||
events.on('commandReadyForProcess', _.bind(
|
||||
this.parseOrCatch, this
|
||||
));
|
||||
},
|
||||
|
@ -53,8 +53,8 @@ var CommandLineView = Backbone.View.extend({
|
|||
processError: function(err) {
|
||||
// in this demo, every command that's not a git command will
|
||||
// throw an exception. Some of these errors might be just to
|
||||
// short-circuit the normal programatic flow, so we handle them
|
||||
// here
|
||||
// short-circuit the normal programatic flow and print stuff,
|
||||
// so we handle them here
|
||||
if (err instanceof CommandProcessError) {
|
||||
events.trigger('commandProcessError', err);
|
||||
} else if (err instanceof CommandResult) {
|
||||
|
@ -81,13 +81,13 @@ var CommandLineView = Backbone.View.extend({
|
|||
}
|
||||
this.index = -1;
|
||||
|
||||
events.trigger('commandConsumed', value);
|
||||
events.trigger('commandSubmitted', value);
|
||||
events.trigger('commandReadyForProcess', value);
|
||||
},
|
||||
|
||||
parseOrCatch: function(value) {
|
||||
try {
|
||||
var command = new Command(value);
|
||||
console.log(command);
|
||||
events.trigger('gitCommandReady', command);
|
||||
} catch (err) {
|
||||
this.processError(err);
|
||||
|
@ -97,7 +97,7 @@ var CommandLineView = Backbone.View.extend({
|
|||
|
||||
var CommandLineHistoryView = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
events.on('commandConsumed', _.bind(
|
||||
events.on('commandSubmitted', _.bind(
|
||||
this.addCommand, this
|
||||
));
|
||||
|
||||
|
@ -165,7 +165,8 @@ var CommandLineHistoryView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
commandResultPrint: function(err) {
|
||||
if (!err.msg.length) {
|
||||
if (!err.get('msg') || !err.get('msg').length) {
|
||||
console.log(err);
|
||||
// blank lines
|
||||
return;
|
||||
}
|
||||
|
|
57
src/visuals.js
Normal file
57
src/visuals.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
function GitVisuals() {
|
||||
this.collection = commitCollection;
|
||||
|
||||
this.collection.on('change', _.bind(this.collectionChanged, this));
|
||||
events.on('drawGitVisuals', _.bind(this.drawVisuals, this));
|
||||
}
|
||||
|
||||
GitVisuals.prototype.drawVisuals = function(sys, ctx, canvas) {
|
||||
// we need to draw refs here
|
||||
var branches = gitEngine.getBranches();
|
||||
var detachedHead = gitEngine.getDetachedHead();
|
||||
var HEAD = gitEngine.getHead();
|
||||
|
||||
_.forEach(branches, _.bind(function(branch) {
|
||||
// get the location of the arbor node and then somehow draw the ref to the side?
|
||||
var node = branch.target.get('arborNode');
|
||||
var nodePoint = sys.toScreen(node._p);
|
||||
|
||||
// text position
|
||||
// TODO: better positioning of text here
|
||||
var screenPoint = _.clone(nodePoint);
|
||||
screenPoint.x += 100;
|
||||
|
||||
ctx.font = graphics.refFont;
|
||||
ctx.fillStyle = graphics.refFontFill;
|
||||
ctx.fillText(branch.id, screenPoint.x, screenPoint.y);
|
||||
|
||||
// also draw an arrow
|
||||
var offset = Math.round(graphics.nodeRadius * 2.5);
|
||||
this.drawArrow(ctx, screenPoint, nodePoint, graphics.arrowHeadWidth, offset);
|
||||
}, this));
|
||||
};
|
||||
|
||||
GitVisuals.prototype.drawArrow = function(ctx, start, end, headWidth, offset) {
|
||||
// TODO only horizontal arrows for now, fix this later
|
||||
var end = _.clone(end);
|
||||
end.x += offset;
|
||||
|
||||
ctx.lineWidth = graphics.arrowWidth;
|
||||
ctx.fillStyle = graphics.arrowFill;
|
||||
ctx.strokeStyle = graphics.arrowStroke;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(start.x, start.y);
|
||||
ctx.lineTo(end.x, end.y);
|
||||
|
||||
// now do the little arrow head
|
||||
ctx.lineTo(end.x + headWidth, end.y + headWidth);
|
||||
ctx.lineTo(end.x + headWidth, end.y - headWidth);
|
||||
ctx.lineTo(end.x, end.y);
|
||||
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
GitVisuals.prototype.collectionChanged = function() {
|
||||
// redo the algorithms
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue