From 71dfd200e77f2fd50dc2129148caa5090d039702 Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Sun, 9 Dec 2012 21:14:28 -0800 Subject: [PATCH 1/6] grunt task also --- grunt.js | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/grunt.js b/grunt.js index e24e02cf..899f29a3 100644 --- a/grunt.js +++ b/grunt.js @@ -1,32 +1,10 @@ /*global module:false*/ module.exports = function(grunt) { - // eventually have sound...? - grunt.registerTask('compliment', function() { - grunt.log.writeln('You are awesome!'); - }); - grunt.initConfig({ lint: { files: ['grunt.js', 'src/*.js'] }, - /* - jasmine_node: { - specNameMatcher: "./spec", // load only specs containing specNameMatcher - projectRoot: ".", - requirejs: false, - forceExit: true, - jUnit: { - report: false, - savePath : "./build/reports/jasmine/", - useDotNotation: true, - consolidate: true - } - }, - watch: { - files: '', - tasks: 'lint' - },*/ jshint: { options: { curly: true, @@ -55,9 +33,6 @@ module.exports = function(grunt) { } }); - //grunt.loadNpmTasks('grunt-jasmine-node'); - // Default task. - grunt.registerTask('default', 'lint jasmine_node'); + grunt.registerTask('default', 'lint'); }; - From d1363e0e4443af427f96cf8a0c552121c56262ee Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Sun, 9 Dec 2012 21:16:05 -0800 Subject: [PATCH 2/6] Revert "grunt task also" This reverts commit 71dfd200e77f2fd50dc2129148caa5090d039702. --- grunt.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/grunt.js b/grunt.js index 899f29a3..e24e02cf 100644 --- a/grunt.js +++ b/grunt.js @@ -1,10 +1,32 @@ /*global module:false*/ module.exports = function(grunt) { + // eventually have sound...? + grunt.registerTask('compliment', function() { + grunt.log.writeln('You are awesome!'); + }); + grunt.initConfig({ lint: { files: ['grunt.js', 'src/*.js'] }, + /* + jasmine_node: { + specNameMatcher: "./spec", // load only specs containing specNameMatcher + projectRoot: ".", + requirejs: false, + forceExit: true, + jUnit: { + report: false, + savePath : "./build/reports/jasmine/", + useDotNotation: true, + consolidate: true + } + }, + watch: { + files: '', + tasks: 'lint' + },*/ jshint: { options: { curly: true, @@ -33,6 +55,9 @@ module.exports = function(grunt) { } }); + //grunt.loadNpmTasks('grunt-jasmine-node'); + // Default task. - grunt.registerTask('default', 'lint'); + grunt.registerTask('default', 'lint jasmine_node'); }; + From c75405bec94c65dfefc311fd9c1b0c3f02529f00 Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Sun, 9 Dec 2012 21:16:19 -0800 Subject: [PATCH 3/6] Revert "somewhat broken" This reverts commit 8d2803e4e453573de5e6f18f4149006e294020f9. --- build/bundle.js | 5623 +-------------------------------------- src/animationFactory.js | 3 +- src/collections.js | 9 - src/commandViews.js | 6 - src/git.js | 8 - src/index.html | 8 +- src/main.js | 5 - src/visuals.js | 6 - 8 files changed, 9 insertions(+), 5659 deletions(-) diff --git a/build/bundle.js b/build/bundle.js index 646a2b8b..443b9be3 100644 --- a/build/bundle.js +++ b/build/bundle.js @@ -402,7 +402,7 @@ require.define("/animationFactory.js",function(require,module,exports,__dirname, */ // essentially a static class -var AnimationFactory = function() { +function AnimationFactory() { } @@ -645,2819 +645,10 @@ AnimationFactory.prototype.genFromToSnapshotAnimation = function( exports.AnimationFactory = AnimationFactory; -}); - -require.define("/collections.js",function(require,module,exports,__dirname,__filename,process,global){var Commit = require('./git').Commit; -var Branch = require('./git').Branch; - -var CommitCollection = Backbone.Collection.extend({ - model: Commit -}); - -var CommandCollection = Backbone.Collection.extend({ - model: Command, -}); - -var BranchCollection = Backbone.Collection.extend({ - model: Branch -}); - -var CommandEntryCollection = Backbone.Collection.extend({ - model: CommandEntry, - localStorage: new Backbone.LocalStorage('CommandEntries') -}); - -var CommandBuffer = Backbone.Model.extend({ - defaults: { - collection: null, - }, - - initialize: function(options) { - events.on('gitCommandReady', _.bind( - this.addCommand, this - )); - - options.collection.bind('add', this.addCommand, this); - - this.buffer = []; - this.timeout = null; - }, - - addCommand: function(command) { - this.buffer.push(command); - this.touchBuffer(); - }, - - touchBuffer: function() { - // touch buffer just essentially means we just check if our buffer is being - // processed. if it's not, we immediately process the first item - // and then set the timeout. - if (this.timeout) { - // timeout existence implies its being processed - return; - } - this.setTimeout(); - }, - - - setTimeout: function() { - this.timeout = setTimeout(_.bind(function() { - this.sipFromBuffer(); - }, this), TIME.betweenCommandsDelay); - }, - - popAndProcess: function() { - var popped = this.buffer.shift(0); - var callback = _.bind(function() { - this.setTimeout(); - }, this); - - // find a command with no error - while (popped.get('error') && this.buffer.length) { - popped = this.buffer.pop(); - } - if (!popped.get('error')) { - // pass in a callback, so when this command is "done" we will process the next. - events.trigger('processCommand', popped, callback); - } else { - this.clear(); - } - }, - - clear: function() { - clearTimeout(this.timeout); - this.timeout = null; - }, - - sipFromBuffer: function() { - if (!this.buffer.length) { - this.clear(); - return; - } - - this.popAndProcess(); - }, -}); - -exports.CommitCollection = CommitCollection; -exports.CommandCollection = CommandCollection; -exports.BranchCollection = BranchCollection; -exports.CommandEntryCollection = CommandEntryCollection; -exports.CommandBuffer = CommandBuffer; - - -}); - -require.define("/git.js",function(require,module,exports,__dirname,__filename,process,global){var animationFactory = new require('./animationFactory').AnimationFactory(); - -// backbone or something uses _.uniqueId, so we make our own here -var uniqueId = (function() { - var n = 0; - return function(prepend) { - return prepend? prepend + n++ : n++; - }; -})(); - -function GitEngine(options) { - this.rootCommit = null; - this.refs = {}; - this.HEAD = null; - - this.branchCollection = options.branches; - this.commitCollection = options.collection; - this.gitVisuals = options.gitVisuals; - - // global variable to keep track of the options given - // along with the command call. - this.commandOptions = {}; - this.generalArgs = []; - - events.on('processCommand', _.bind(this.dispatch, this)); -} - -GitEngine.prototype.defaultInit = function() { - var defaultTree = JSON.parse(unescape("%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22master%22%2C%22type%22%3A%22branch%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22type%22%3A%22commit%22%2C%22parents%22%3A%5B%5D%2C%22author%22%3A%22Peter%20Cottle%22%2C%22createTime%22%3A%22Mon%20Nov%2005%202012%2000%3A56%3A47%20GMT-0800%20%28PST%29%22%2C%22commitMessage%22%3A%22Quick%20Commit.%20Go%20Bears%21%22%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22type%22%3A%22commit%22%2C%22parents%22%3A%5B%22C0%22%5D%2C%22author%22%3A%22Peter%20Cottle%22%2C%22createTime%22%3A%22Mon%20Nov%2005%202012%2000%3A56%3A47%20GMT-0800%20%28PST%29%22%2C%22commitMessage%22%3A%22Quick%20Commit.%20Go%20Bears%21%22%2C%22id%22%3A%22C1%22%7D%7D%2C%22HEAD%22%3A%7B%22id%22%3A%22HEAD%22%2C%22target%22%3A%22master%22%2C%22type%22%3A%22general%20ref%22%7D%7D")); - this.loadTree(defaultTree); -}; - -GitEngine.prototype.init = function() { - // make an initial commit and a master branch - this.rootCommit = this.makeCommit(null, null, {rootCommit: true}); - this.commitCollection.add(this.rootCommit); - - var master = this.makeBranch('master', this.rootCommit); - this.HEAD = new Ref({ - id: 'HEAD', - target: master - }); - this.refs[this.HEAD.get('id')] = this.HEAD; - - // commit once to get things going - this.commit(); -}; - -GitEngine.prototype.exportTree = function() { - // need to export all commits, their connectivity / messages, branches, and state of head. - // this would be simple if didn't have circular structures.... :P - // thus, we need to loop through and "flatten" our graph of objects referencing one another - var totalExport = { - branches: {}, - commits: {}, - HEAD: null - }; - - _.each(this.branchCollection.toJSON(), function(branch) { - branch.target = branch.target.get('id'); - branch.visBranch = undefined; - - totalExport.branches[branch.id] = branch; - }); - - _.each(this.commitCollection.toJSON(), function(commit) { - // clear out the fields that reference objects and create circular structure - _.each(Commit.prototype.constants.circularFields, function(field) { - commit[field] = undefined; - }, this); - - // convert parents - var parents = []; - _.each(commit.parents, function(par) { - parents.push(par.get('id')); - }); - commit.parents = parents; - - totalExport.commits[commit.id] = commit; - }, this); - - var HEAD = this.HEAD.toJSON(); - HEAD.visBranch = undefined; - HEAD.lastTarget = HEAD.lastLastTarget = HEAD.visBranch = undefined; - HEAD.target = HEAD.target.get('id'); - totalExport.HEAD = HEAD; - - return totalExport; -}; - -GitEngine.prototype.printTree = function() { - var str = escape(JSON.stringify(this.exportTree())); - return str; -}; - -GitEngine.prototype.printAndCopyTree = function() { - window.prompt('Copy the tree string below', this.printTree()); -}; - -GitEngine.prototype.loadTree = function(tree) { - // deep copy in case we use it a bunch - tree = $.extend(true, {}, tree); - - // first clear everything - this.removeAll(); - - this.instantiateFromTree(tree); - - this.reloadGraphics(); -}; - -GitEngine.prototype.loadTreeFromString = function(treeString) { - this.loadTree(JSON.parse(unescape(treeString))); -}; - -GitEngine.prototype.instantiateFromTree = function(tree) { - // now we do the loading part - var createdSoFar = {}; - - _.each(tree.commits, function(commitJSON) { - var commit = this.getOrMakeRecursive(tree, createdSoFar, commitJSON.id); - this.commitCollection.add(commit); - }, this); - - _.each(tree.branches, function(branchJSON) { - var branch = this.getOrMakeRecursive(tree, createdSoFar, branchJSON.id); - - this.branchCollection.add(branch, {silent: true}); - }, this); - - var HEAD = this.getOrMakeRecursive(tree, createdSoFar, tree.HEAD.id); - this.HEAD = HEAD; - - this.rootCommit = createdSoFar['C0']; - if (!this.rootCommit) { - throw new Error('Need root commit of C0 for calculations'); - } - this.refs = createdSoFar; - - this.branchCollection.each(function(branch) { - this.gitVisuals.addBranch(branch); - }, this); -}; - -GitEngine.prototype.reloadGraphics = function() { - // get the root commit, no better way to do it - var rootCommit = null; - this.commitCollection.each(function(commit) { - if (commit.get('id') == 'C0') { - rootCommit = commit; - } - }); - this.gitVisuals.rootCommit = rootCommit; - - // this just basically makes the HEAD branch. the head branch really should have been - // a member of a collection and not this annoying edge case stuff... one day - this.gitVisuals.initHeadBranch(); - - // when the paper is ready - this.gitVisuals.drawTreeFromReload(); - - this.gitVisuals.refreshTreeHarsh(); -}; - -GitEngine.prototype.getOrMakeRecursive = function(tree, createdSoFar, objID) { - if (createdSoFar[objID]) { - // base case - return createdSoFar[objID]; - } - - var getType = function(tree, id) { - if (tree.commits[id]) { - return 'commit'; - } else if (tree.branches[id]) { - return 'branch'; - } else if (id == 'HEAD') { - return 'HEAD'; - } - throw new Error("bad type for " + id); - }; - - // figure out what type - var type = getType(tree, objID); - - if (type == 'HEAD') { - var headJSON = tree.HEAD; - var HEAD = new Ref(_.extend( - tree.HEAD, - { - target: this.getOrMakeRecursive(tree, createdSoFar, headJSON.target) - } - )); - createdSoFar[objID] = HEAD; - return HEAD; - } - - if (type == 'branch') { - var branchJSON = tree.branches[objID]; - - var branch = new Branch(_.extend( - tree.branches[objID], - { - target: this.getOrMakeRecursive(tree, createdSoFar, branchJSON.target) - } - )); - createdSoFar[objID] = branch; - return branch; - } - - if (type == 'commit') { - // for commits, we need to grab all the parents - var commitJSON = tree.commits[objID]; - - var parentObjs = []; - _.each(commitJSON.parents, function(parentID) { - parentObjs.push(this.getOrMakeRecursive(tree, createdSoFar, parentID)); - }, this); - - var commit = new Commit(_.extend( - commitJSON, - { - parents: parentObjs, - gitVisuals: this.gitVisuals - } - )); - createdSoFar[objID] = commit; - return commit; - } - - throw new Error('ruh rho!! unsupported tyep for ' + objID); -}; - -GitEngine.prototype.removeAll = function() { - this.branchCollection.reset(); - this.commitCollection.reset(); - this.refs = {}; - this.HEAD = null; - this.rootCommit = null; - - this.gitVisuals.resetAll(); -}; - -GitEngine.prototype.getDetachedHead = function() { - // detached head is if HEAD points to a commit instead of a branch... - var target = this.HEAD.get('target'); - var targetType = target.get('type'); - return targetType !== 'branch'; -}; - -GitEngine.prototype.validateBranchName = function(name) { - name = name.replace(/\s/g, ''); - if (!/^[a-zA-Z0-9]+$/.test(name)) { - throw new GitError({ - msg: 'woah bad branch name!! This is not ok: ' + name - }); - } - if (/[hH][eE][aA][dD]/.test(name)) { - throw new GitError({ - msg: 'branch name of "head" is ambiguous, dont name it that' - }); - } - if (name.length > 9) { - name = name.slice(0, 9); - this.command.addWarning( - 'Sorry, we need to keep branch names short for the visuals. Your branch ' + - 'name was truncated to 9 characters, resulting in ' + name - ); - } - return name; -}; - -GitEngine.prototype.makeBranch = function(id, target) { - id = this.validateBranchName(id); - if (this.refs[id]) { - throw new GitError({ - msg: 'that branch id either matches a commit hash or already exists!' - }); - } - - var branch = new Branch({ - target: target, - id: id - }); - this.branchCollection.add(branch); - this.refs[branch.get('id')] = branch; - return branch; -}; - -GitEngine.prototype.getHead = function() { - return _.clone(this.HEAD); -}; - -GitEngine.prototype.getBranches = function() { - var toReturn = []; - this.branchCollection.each(function(branch) { - toReturn.push({ - id: branch.get('id'), - selected: this.HEAD.get('target') === branch, - target: branch.get('target'), - obj: branch - }); - }, this); - return toReturn; -}; - -GitEngine.prototype.printBranchesWithout = function(without) { - var commitToBranches = this.getUpstreamBranchSet(); - var commitID = this.getCommitFromRef(without).get('id'); - - var toPrint = []; - _.each(commitToBranches[commitID], function(branchJSON) { - branchJSON.selected = this.HEAD.get('target').get('id') == branchJSON.id; - toPrint.push(branchJSON); - }, this); - this.printBranches(toPrint); -}; - -GitEngine.prototype.printBranches = function(branches) { - var result = ''; - _.each(branches, function(branch) { - result += (branch.selected ? '* ' : '') + branch.id + '\n'; - }); - throw new CommandResult({ - msg: result - }); -}; - -GitEngine.prototype.makeCommit = function(parents, id, options) { - // ok we need to actually manually create commit IDs now because - // people like nikita (thanks for finding this!) could - // make branches named C2 before creating the commit C2 - if (!id) { - id = uniqueId('C'); - while (this.refs[id]) { - id = uniqueId('C'); - } - } - - var commit = new Commit(_.extend({ - parents: parents, - id: id, - gitVisuals: this.gitVisuals - }, - options || {} - )); - - this.refs[commit.get('id')] = commit; - this.commitCollection.add(commit); - return commit; -}; - -GitEngine.prototype.acceptNoGeneralArgs = function() { - if (this.generalArgs.length) { - throw new GitError({ - msg: "That command accepts no general arguments" - }); - } -}; - -GitEngine.prototype.validateArgBounds = function(args, lower, upper, option) { - // this is a little utility class to help arg validation that happens over and over again - var what = (option === undefined) ? - 'git ' + this.command.get('method') : - this.command.get('method') + ' ' + option + ' '; - what = 'with ' + what; - - if (args.length < lower) { - throw new GitError({ - msg: 'I expect at least ' + String(lower) + ' argument(s) ' + what - }); - } - if (args.length > upper) { - throw new GitError({ - msg: 'I expect at most ' + String(upper) + ' argument(s) ' + what - }); - } -}; - -GitEngine.prototype.oneArgImpliedHead = function(args, option) { - // for log, show, etc - this.validateArgBounds(args, 0, 1, option); - if (args.length == 0) { - args.push('HEAD'); - } -}; - -GitEngine.prototype.twoArgsImpliedHead = function(args, option) { - // our args we expect to be between 1 and 2 - this.validateArgBounds(args, 1, 2, option); - // and if it's one, add a HEAD to the back - if (args.length == 1) { - args.push('HEAD'); - } -}; - -GitEngine.prototype.revertStarter = function() { - this.validateArgBounds(this.generalArgs, 1, NaN); - - var response = this.revert(this.generalArgs); - - if (response) { - animationFactory.rebaseAnimation(this.animationQueue, response, this, this.gitVisuals); - } -}; - -GitEngine.prototype.revert = function(whichCommits) { - // for each commit, we want to revert it - var toRebase = []; - _.each(whichCommits, function(stringRef) { - toRebase.push(this.getCommitFromRef(stringRef)); - }, this); - - // we animate reverts now!! we use the rebase animation though so that's - // why the terminology is like it is - var animationResponse = {}; - animationResponse.destinationBranch = this.resolveID(toRebase[0]); - animationResponse.toRebaseArray = toRebase.slice(0); - animationResponse.rebaseSteps = []; - - beforeSnapshot = this.gitVisuals.genSnapshot(); - var afterSnapshot; - - // now make a bunch of commits on top of where we are - var base = this.getCommitFromRef('HEAD'); - _.each(toRebase, function(oldCommit) { - var newId = this.rebaseAltID(oldCommit.get('id')); - - var newCommit = this.makeCommit([base], newId, { - commitMessage: 'Reverting ' + this.resolveName(oldCommit) + - ': "' + oldCommit.get('commitMessage') + '"' - }); - - base = newCommit; - - // animation stuff - afterSnapshot = this.gitVisuals.genSnapshot(); - animationResponse.rebaseSteps.push({ - oldCommit: oldCommit, - newCommit: newCommit, - beforeSnapshot: beforeSnapshot, - afterSnapshot: afterSnapshot - }); - beforeSnapshot = afterSnapshot; - }, this); - // done! update our location - this.setTargetLocation('HEAD', base); - - // animation - return animationResponse; -}; - -GitEngine.prototype.resetStarter = function() { - if (this.commandOptions['--soft']) { - throw new GitError({ - msg: "You can't use --soft because there is no concept of stashing" + - " changes or staging files, so you will lose your progress." + - " Try using interactive rebasing (or just rebasing) to move commits." - }); - } - if (this.commandOptions['--hard']) { - this.command.addWarning( - 'Nice! You are using --hard. The default behavior is a hard reset in ' + - "this demo, so don't worry about specifying the option explicity" - ); - // dont absorb the arg off of --hard - this.generalArgs = this.generalArgs.concat(this.commandOptions['--hard']); - } - - this.validateArgBounds(this.generalArgs, 1, 1); - - if (this.getDetachedHead()) { - throw new GitError({ - msg: "Cant reset in detached head! Use checkout if you want to move" - }); - } - - this.reset(this.generalArgs[0]); -}; - -GitEngine.prototype.reset = function(target) { - this.setTargetLocation('HEAD', this.getCommitFromRef(target)); -}; - -GitEngine.prototype.cherrypickStarter = function() { - this.validateArgBounds(this.generalArgs, 1, 1); - var newCommit = this.cherrypick(this.generalArgs[0]); - - animationFactory.genCommitBirthAnimation(this.animationQueue, newCommit, this.gitVisuals); -}; - -GitEngine.prototype.cherrypick = function(ref) { - var commit = this.getCommitFromRef(ref); - // check if we already have that - var set = this.getUpstreamSet('HEAD'); - if (set[commit.get('id')]) { - throw new GitError({ - msg: "We already have that commit in our changes history! You can't cherry-pick it " + - "if it shows up in git log." - }); - } - - // alter the ID slightly - var id = this.rebaseAltID(commit.get('id')); - - // now commit with that id onto HEAD - var newCommit = this.makeCommit([this.getCommitFromRef('HEAD')], id); - this.setTargetLocation(this.HEAD, newCommit); - return newCommit; -}; - -GitEngine.prototype.commitStarter = function() { - this.acceptNoGeneralArgs(); - if (this.commandOptions['-am'] && ( - this.commandOptions['-a'] || this.commandOptions['-m'])) { - throw new GitError({ - msg: "You can't have -am with another -m or -a!" - }); - } - - var msg = null; - if (this.commandOptions['-a']) { - this.command.addWarning('No need to add files in this demo'); - } - - if (this.commandOptions['-am']) { - var args = this.commandOptions['-am']; - this.validateArgBounds(args, 1, 1, '-am'); - - this.command.addWarning("Don't worry about adding files in this demo. I'll take " + - "down your commit message anyways, but you can commit without a message " + - "in this demo as well"); - msg = args[0]; - } - - if (this.commandOptions['-m']) { - var args = this.commandOptions['-m']; - this.validateArgBounds(args, 1, 1, '-m'); - msg = args[0]; - } - - var newCommit = this.commit(); - if (msg) { - msg = msg - .replace(/"/g, '"') - .replace(/^"/g, '') - .replace(/"$/g, ''); - - newCommit.set('commitMessage', msg); - } - animationFactory.genCommitBirthAnimation(this.animationQueue, newCommit, this.gitVisuals); -}; - -GitEngine.prototype.commit = function() { - var targetCommit = this.getCommitFromRef(this.HEAD); - var id = undefined; - - // if we want to ammend, go one above - if (this.commandOptions['--amend']) { - targetCommit = this.resolveID('HEAD~1'); - id = this.rebaseAltID(this.getCommitFromRef('HEAD').get('id')); - } - - var newCommit = this.makeCommit([targetCommit], id); - if (this.getDetachedHead()) { - this.command.addWarning('Warning!! Detached HEAD state'); - } - - this.setTargetLocation(this.HEAD, newCommit); - return newCommit; -}; - -GitEngine.prototype.resolveName = function(someRef) { - // first get the obj - var obj = this.resolveID(someRef); - if (obj.get('type') == 'commit') { - return 'commit ' + obj.get('id'); - } - if (obj.get('type') == 'branch') { - return 'branch "' + obj.get('id') + '"'; - } - // we are dealing with HEAD - return this.resolveName(obj.get('target')); -}; - -GitEngine.prototype.resolveID = function(idOrTarget) { - if (idOrTarget === null || idOrTarget === undefined) { - throw new Error('Dont call this with null / undefined'); - } - - if (typeof idOrTarget !== 'string') { - return idOrTarget; - } - return this.resolveStringRef(idOrTarget); -}; - -GitEngine.prototype.resolveStringRef = function(ref) { - if (this.refs[ref]) { - return this.refs[ref]; - } - - // may be something like HEAD~2 or master^^ - var relativeRefs = [ - [/^([a-zA-Z0-9]+)~(\d+)\s*$/, function(matches) { - return parseInt(matches[2]); - }], - [/^([a-zA-Z0-9]+)(\^+)\s*$/, function(matches) { - return matches[2].length; - }] - ]; - - var startRef = null; - var numBack = null; - _.each(relativeRefs, function(config) { - var regex = config[0]; - var parse = config[1]; - if (regex.test(ref)) { - var matches = regex.exec(ref); - numBack = parse(matches); - startRef = matches[1]; - } - }, this); - - if (!startRef) { - throw new GitError({ - msg: 'unknown ref ' + ref - }); - } - if (!this.refs[startRef]) { - throw new GitError({ - msg: 'the ref ' + startRef +' does not exist.' - }); - } - var commit = this.getCommitFromRef(startRef); - - return this.numBackFrom(commit, numBack); -}; - -GitEngine.prototype.getCommitFromRef = function(ref) { - var start = this.resolveID(ref); - - // works for both HEAD and just a single layer. aka branch - while (start.get('type') !== 'commit') { - start = start.get('target'); - } - return start; -}; - -GitEngine.prototype.getType = function(ref) { - return this.resolveID(ref).get('type'); -}; - -GitEngine.prototype.setTargetLocation = function(ref, target) { - if (this.getType(ref) == 'commit') { - // nothing to do - return; - } - - // sets whatever ref is (branch, HEAD, etc) to a target. so if - // you pass in HEAD, and HEAD is pointing to a branch, it will update - // the branch to that commit, not the HEAD - var ref = this.getOneBeforeCommit(ref); - ref.set('target', target); -}; - -GitEngine.prototype.getUpstreamBranchSet = function() { - // this is expensive!! so only call once in a while - var commitToSet = {}; - - var inArray = function(arr, id) { - var found = false; - _.each(arr, function(wrapper) { - if (wrapper.id == id) { - found = true; - } - }); - - return found; - }; - - var bfsSearch = function(commit) { - var set = []; - var pQueue = [commit]; - while (pQueue.length) { - var popped = pQueue.pop(); - set.push(popped.get('id')); - - if (popped.get('parents') && popped.get('parents').length) { - pQueue = pQueue.concat(popped.get('parents')); - } - } - return set; - }; - - this.branchCollection.each(function(branch) { - var set = bfsSearch(branch.get('target')); - _.each(set, function(id) { - commitToSet[id] = commitToSet[id] || []; - - // only add it if it's not there, so hue blending is ok - if (!inArray(commitToSet[id], branch.get('id'))) { - commitToSet[id].push({ - obj: branch, - id: branch.get('id') - }); - } - }); - }); - - return commitToSet; -}; - -GitEngine.prototype.getUpstreamHeadSet = function() { - var set = this.getUpstreamSet('HEAD'); - var including = this.getCommitFromRef('HEAD').get('id'); - - set[including] = true; - return set; -}; - -GitEngine.prototype.getOneBeforeCommit = function(ref) { - // you can call this command on HEAD in detached, HEAD, or on a branch - // and it will return the ref that is one above a commit. aka - // it resolves HEAD to something that we can move the ref with - var start = this.resolveID(ref); - if (start === this.HEAD && !this.getDetachedHead()) { - start = start.get('target'); - } - return start; -}; - -GitEngine.prototype.numBackFrom = function(commit, numBack) { - // going back '3' from a given ref is not trivial, for you might have - // a bunch of merge commits and such. like this situation: - // - // * merge master into new - // |\ - // | \* commit here - // |* \ commit there - // | |* commit here - // \ / - // | * root - // - // - // hence we need to do a BFS search, with the commit date being the - // value to sort off of (rather than just purely the level) - if (numBack == 0) { - return commit; - } - - // we use a special sorting function here that - // prefers the later commits over the earlier ones - var sortQueue = _.bind(function(queue) { - queue.sort(this.idSortFunc); - queue.reverse(); - }, this); - - var pQueue = [].concat(commit.get('parents') || []); - sortQueue(pQueue); - numBack--; - - while (pQueue.length && numBack !== 0) { - var popped = pQueue.shift(0); - var parents = popped.get('parents'); - - if (parents && parents.length) { - pQueue = pQueue.concat(parents); - } - - sortQueue(pQueue); - numBack--; - } - - if (numBack !== 0 || pQueue.length == 0) { - throw new GitError({ - msg: "Sorry, I can't go that many commits back" - }); - } - return pQueue.shift(0); -}; - -GitEngine.prototype.scrapeBaseID = function(id) { - var results = /^C(\d+)/.exec(id); - - if (!results) { - throw new Error('regex failed on ' + id); - } - - return 'C' + results[1]; -}; - -GitEngine.prototype.rebaseAltID = function(id) { - // this function alters an ID to add a quote to the end, - // indicating that it was rebased. it also checks existence - var regexMap = [ - [/^C(\d+)[']{0,2}$/, function(bits) { - // this id can use another quote, so just add it - return bits[0] + "'"; - }], - [/^C(\d+)[']{3}$/, function(bits) { - // here we switch from C''' to C'^4 - return bits[0].slice(0, -3) + "'^4"; - }], - [/^C(\d+)['][^](\d+)$/, function(bits) { - return 'C' + String(bits[1]) + "'^" + String(Number(bits[2]) + 1); - }] - ]; - - for (var i = 0; i < regexMap.length; i++) { - var regex = regexMap[i][0]; - var func = regexMap[i][1]; - var results = regex.exec(id); - if (results) { - var newId = func(results); - // if this id exists, continue down the rabbit hole - if (this.refs[newId]) { - return this.rebaseAltID(newId); - } else { - return newId; - } - } - } - throw new Error('could not modify the id ' + id); -}; - -GitEngine.prototype.idSortFunc = function(cA, cB) { - // commit IDs can come in many forms: - // C4 - // C4' (from a rebase) - // C4'' (from multiple rebases) - // C4'^3 (from a BUNCH of rebases) - - var scale = 1000; - - var regexMap = [ - [/^C(\d+)$/, function(bits) { - // return the 4 from C4 - return scale * bits[1]; - }], - [/^C(\d+)([']+)$/, function(bits) { - // return the 4 from C4, plus the length of the quotes - return scale * bits[1] + bits[2].length; - }], - [/^C(\d+)['][^](\d+)$/, function(bits) { - return scale * bits[1] + Number(bits[2]); - }] - ]; - - var getNumToSort = function(id) { - for (var i = 0; i < regexMap.length; i++) { - var regex = regexMap[i][0]; - var func = regexMap[i][1]; - var results = regex.exec(id); - if (results) { - return func(results); - } - } - throw new Error('Could not parse commit ID ' + id); - } - - return getNumToSort(cA.get('id')) - getNumToSort(cB.get('id')); -}; - -GitEngine.prototype.rebaseInteractiveStarter = function() { - var args = this.commandOptions['-i']; - this.twoArgsImpliedHead(args, ' -i'); - - this.rebaseInteractive(args[0], args[1]); -}; - -GitEngine.prototype.rebaseStarter = function() { - if (this.commandOptions['-i']) { - this.rebaseInteractiveStarter(); - return; - } - - this.twoArgsImpliedHead(this.generalArgs); - - var response = this.rebase(this.generalArgs[0], this.generalArgs[1]); - - if (response === undefined) { - // was a fastforward or already up to date. returning now - // will trigger the refresh animation by not adding anything to - // the animation queue - return; - } - - animationFactory.rebaseAnimation(this.animationQueue, response, this, this.gitVisuals); -}; - -GitEngine.prototype.rebase = function(targetSource, currentLocation) { - // first some conditions - if (this.isUpstreamOf(targetSource, currentLocation)) { - this.command.setResult('Branch already up-to-date'); - - // git for some reason always checks out the branch you are rebasing, - // no matter the result of the rebase - this.checkout(currentLocation); - - // returning instead of throwing makes a tree refresh - return; - } - - if (this.isUpstreamOf(currentLocation, targetSource)) { - // just set the target of this current location to the source - this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource)); - // we need the refresh tree animation to happen, so set the result directly - // instead of throwing - this.command.setResult('Fast-forwarding...'); - - this.checkout(currentLocation); - return; - } - - // now the part of actually rebasing. - // We need to get the downstream set of targetSource first. - // then we BFS from currentLocation, using the downstream set as our stopping point. - // we need to BFS because we need to include all commits below - // pop these commits on top of targetSource and modify their ids with quotes - var stopSet = this.getUpstreamSet(targetSource) - - // now BFS from here on out - var toRebaseRough = []; - var pQueue = [this.getCommitFromRef(currentLocation)]; - - while (pQueue.length) { - var popped = pQueue.pop(); - - // if its in the set, dont add it - if (stopSet[popped.get('id')]) { - continue; - } - - // it's not in the set, so we need to rebase this commit - toRebaseRough.push(popped); - toRebaseRough.sort(this.idSortFunc); - toRebaseRough.reverse(); - // keep searching - pQueue = pQueue.concat(popped.get('parents')); - } - - return this.rebaseFinish(toRebaseRough, stopSet, targetSource, currentLocation); -}; - -GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation) { - // there are a reduced set of checks now, so we can't exactly use parts of the rebase function - // but it will look similar. - - // first if we are upstream of the target - if (this.isUpstreamOf(currentLocation, targetSource)) { - throw new GitError({ - msg: 'Nothing to do... (git throws a "noop" status here); ' + - 'Your source is upstream of your rebase target' - }); - } - - // now get the stop set - var stopSet = this.getUpstreamSet(targetSource); - - var toRebaseRough = []; - // standard BFS - var pQueue = [this.getCommitFromRef(currentLocation)]; - - while (pQueue.length) { - var popped = pQueue.pop(); - - if (stopSet[popped.get('id')]) { - continue; - } - - toRebaseRough.push(popped); - pQueue = pQueue.concat(popped.get('parents')); - pQueue.sort(this.idSortFunc); - } - - // throw our merge's real fast and see if we have anything to do - var toRebase = []; - _.each(toRebaseRough, function(commit) { - if (commit.get('parents').length == 1) { - toRebase.push(commit); - } - }); - - if (!toRebase.length) { - throw new GitError({ - msg: 'No commits to rebase! Everything is a merge commit' - }); - } - - // now do stuff :D since all our validation checks have passed, we are going to defer animation - // and actually launch the dialog - this.animationQueue.set('defer', true); - - var callback = _.bind(function(userSpecifiedRebase) { - // first, they might have dropped everything (annoying) - if (!userSpecifiedRebase.length) { - this.command.setResult('Nothing to do...'); - this.animationQueue.start(); - return; - } - - // finish the rebase crap and animate! - var animationData = this.rebaseFinish(userSpecifiedRebase, {}, targetSource, currentLocation); - animationFactory.rebaseAnimation(this.animationQueue, animationData, this, this.gitVisuals); - this.animationQueue.start(); - }, this); - - new InteractiveRebaseView({ - callback: callback, - toRebase: toRebase, - el: $('#dialogHolder') - }); -}; - -GitEngine.prototype.rebaseFinish = function(toRebaseRough, stopSet, targetSource, currentLocation) { - // now we have the all the commits between currentLocation and the set of target to rebase. - var animationResponse = {}; - animationResponse.destinationBranch = this.resolveID(targetSource); - - // we need to throw out merge commits - var toRebase = []; - _.each(toRebaseRough, function(commit) { - if (commit.get('parents').length == 1) { - toRebase.push(commit); - } - }); - - // we ALSO need to throw out commits that will do the same changes. like - // if the upstream set has a commit C4 and we have C4', we dont rebase the C4' again. - // get this by doing ID scraping - var changesAlreadyMade = {}; - _.each(stopSet, function(val, key) { - changesAlreadyMade[this.scrapeBaseID(key)] = val; // val == true - }, this); - - // now get rid of the commits that will redo same changes - toRebaseRough = toRebase; - toRebase = []; - _.each(toRebaseRough, function(commit) { - var baseID = this.scrapeBaseID(commit.get('id')); - if (!changesAlreadyMade[baseID]) { - toRebase.push(commit); - } - }, this); - - if (!toRebase.length) { - throw new GitError({ - msg: 'No Commits to Rebase! Everything else is merge commits or changes already have been applied' - }); - } - - // now reverse it once more to get it in the right order - toRebase.reverse(); - animationResponse.toRebaseArray = toRebase.slice(0); - - // now pop all of these commits onto targetLocation - var base = this.getCommitFromRef(targetSource); - - // do the rebase, and also maintain all our animation info during this - animationResponse.rebaseSteps = []; - var beforeSnapshot = this.gitVisuals.genSnapshot(); - var afterSnapshot; - _.each(toRebase, function(old) { - var newId = this.rebaseAltID(old.get('id')); - - var newCommit = this.makeCommit([base], newId); - base = newCommit; - - // animation info - afterSnapshot = this.gitVisuals.genSnapshot(); - animationResponse.rebaseSteps.push({ - oldCommit: old, - newCommit: newCommit, - beforeSnapshot: beforeSnapshot, - afterSnapshot: afterSnapshot - }); - beforeSnapshot = afterSnapshot; - }, this); - - if (this.resolveID(currentLocation).get('type') == 'commit') { - // we referenced a commit like git rebase C2 C1, so we have - // to manually check out C1' - - var steps = animationResponse.rebaseSteps; - var newestCommit = steps[steps.length - 1].newCommit; - - this.checkout(newestCommit); - } else { - // now we just need to update the rebased branch is - this.setTargetLocation(currentLocation, base); - this.checkout(currentLocation); - } - - // for animation - return animationResponse; -}; - -GitEngine.prototype.mergeStarter = function() { - this.twoArgsImpliedHead(this.generalArgs); - - var newCommit = this.merge(this.generalArgs[0], this.generalArgs[1]); - - if (newCommit === undefined) { - // its just a fast forwrard - animationFactory.refreshTree(this.animationQueue, this.gitVisuals); - return; - } - - animationFactory.genCommitBirthAnimation(this.animationQueue, newCommit, this.gitVisuals); -}; - -GitEngine.prototype.merge = function(targetSource, currentLocation) { - // first some conditions - if (this.isUpstreamOf(targetSource, currentLocation) || - this.getCommitFromRef(targetSource) === this.getCommitFromRef(currentLocation)) { - throw new CommandResult({ - msg: 'Branch already up-to-date' - }); - } - - if (this.isUpstreamOf(currentLocation, targetSource)) { - // just set the target of this current location to the source - this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource)); - // get fresh animation to happen - this.command.setResult('Fast-forwarding...'); - return; - } - - // now the part of making a merge commit - var parent1 = this.getCommitFromRef(currentLocation); - var parent2 = this.getCommitFromRef(targetSource); - - // we need a fancy commit message - var msg = 'Merge ' + this.resolveName(targetSource) + - ' into ' + this.resolveName(currentLocation); - - // since we specify parent 1 as the first parent, it is the "main" parent - // and the node will be displayed below that branch / commit / whatever - var mergeCommit = this.makeCommit( - [parent1, parent2], - null, - { - commitMessage: msg - } - ); - - this.setTargetLocation(currentLocation, mergeCommit) - return mergeCommit; -}; - -GitEngine.prototype.checkoutStarter = function() { - if (this.commandOptions['-b']) { - // the user is really trying to just make a branch and then switch to it. so first: - var args = this.commandOptions['-b']; - this.twoArgsImpliedHead(args, '-b'); - - var validId = this.validateBranchName(args[0]); - this.branch(validId, args[1]); - this.checkout(validId); - return; - } - - if (this.commandOptions['-']) { - // get the heads last location - var lastPlace = this.HEAD.get('lastLastTarget'); - if (!lastPlace) { - throw new GitError({ - msg: 'Need a previous location to do - switching' - }); - } - this.HEAD.set('target', lastPlace); - return; - } - - if (this.commandOptions['-B']) { - var args = this.commandOptions['-B']; - this.twoArgsImpliedHead(args, '-B'); - - this.forceBranch(args[0], args[1]); - this.checkout(args[0]); - return; - } - - this.validateArgBounds(this.generalArgs, 1, 1); - - this.checkout(this.unescapeQuotes(this.generalArgs[0])); -}; - -GitEngine.prototype.checkout = function(idOrTarget) { - var target = this.resolveID(idOrTarget); - if (target.get('id') === 'HEAD') { - // git checkout HEAD is a - // meaningless command but i used to do this back in the day - return; - } - - var type = target.get('type'); - if (type !== 'branch' && type !== 'commit') { - throw new GitError({ - msg: 'can only checkout branches and commits!' - }); - } - - this.HEAD.set('target', target); -}; - -GitEngine.prototype.branchStarter = function() { - // handle deletion first - if (this.commandOptions['-d'] || this.commandOptions['-D']) { - var names = this.commandOptions['-d'] || this.commandOptions['-D']; - this.validateArgBounds(names, 1, NaN, '-d'); - - _.each(names, function(name) { - this.deleteBranch(name); - }, this); - return; - } - - if (this.commandOptions['--contains']) { - var args = this.commandOptions['--contains']; - this.validateArgBounds(args, 1, 1, '--contains'); - this.printBranchesWithout(args[0]); - return; - } - - if (this.commandOptions['-f']) { - var args = this.commandOptions['-f']; - this.twoArgsImpliedHead(args, '-f'); - - // we want to force a branch somewhere - this.forceBranch(args[0], args[1]); - return; - } - - - if (this.generalArgs.length == 0) { - this.printBranches(this.getBranches()); - return; - } - - this.twoArgsImpliedHead(this.generalArgs); - this.branch(this.generalArgs[0], this.generalArgs[1]); -}; - -GitEngine.prototype.forceBranch = function(branchName, where) { - // if branchname doesn't exist... - if (!this.refs[branchName]) { - this.branch(branchName, where); - } - - var branch = this.resolveID(branchName); - if (branch.get('type') !== 'branch') { - throw new GitError({ - msg: "Can't force move anything but a branch!!" - }); - } - - var whereCommit = this.getCommitFromRef(where); - - this.setTargetLocation(branch, whereCommit); -}; - -GitEngine.prototype.branch = function(name, ref) { - var target = this.getCommitFromRef(ref); - this.makeBranch(name, target); -}; - -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({ - msg: "You can't delete things that arent branches with branch command" - }); - } - if (target.get('id') == 'master') { - throw new GitError({ - msg: "You can't delete the master branch!" - }); - } - if (this.HEAD.get('target') === target) { - throw new GitError({ - msg: "Cannot delete the branch you are currently on" - }); - } - - // now we know it's a branch - var branch = target; - - this.branchCollection.remove(branch); - this.refs[branch.get('id')] = undefined; - delete this.refs[branch.get('id')]; - - if (branch.get('visBranch')) { - branch.get('visBranch').remove(); - } -}; - -GitEngine.prototype.unescapeQuotes = function(str) { - return str.replace(/'/g, "'"); -} - -GitEngine.prototype.dispatch = function(command, callback) { - // current command, options, and args are stored in the gitEngine - // for easy reference during processing. - this.command = command; - this.commandOptions = command.get('supportedMap'); - this.generalArgs = command.get('generalArgs'); - - // set up the animation queue - var whenDone = _.bind(function() { - command.set('status', 'finished'); - callback(); - }, this); - this.animationQueue = new AnimationQueue({ - callback: whenDone - }); - - command.set('status', 'processing'); - try { - var methodName = command.get('method').replace(/-/g, '') + 'Starter'; - this[methodName](); - } catch (err) { - if (err instanceof GitError || - err instanceof CommandResult) { - // short circuit animation by just setting error and returning - command.set('error', err); - callback(); - return; - } else { - throw err; - } - } - - // only add the refresh if we didn't do manual animations - if (!this.animationQueue.get('animations').length && !this.animationQueue.get('defer')) { - animationFactory.refreshTree(this.animationQueue, this.gitVisuals); - } - - // animation queue will call the callback when its done - if (!this.animationQueue.get('defer')) { - this.animationQueue.start(); - } -}; - -GitEngine.prototype.showStarter = function() { - this.oneArgImpliedHead(this.generalArgs); - - this.show(this.generalArgs[0]); -}; - -GitEngine.prototype.show = function(ref) { - var commit = this.getCommitFromRef(ref); - - throw new CommandResult({ - msg: commit.getShowEntry() - }); -}; - -GitEngine.prototype.statusStarter = function() { - var lines = []; - if (this.getDetachedHead()) { - lines.push('Detached Head!'); - } else { - var branchName = this.HEAD.get('target').get('id'); - lines.push('On branch ' + branchName); - } - lines.push('Changes to be committed:'); - lines.push(''); - lines.push('    modified: cal/OskiCostume.stl'); - lines.push(''); - lines.push('Ready to commit! (as always in this demo)'); - - var msg = ''; - _.each(lines, function(line) { - msg += '# ' + line + '\n'; - }); - - throw new CommandResult({ - msg: msg - }); -}; - -GitEngine.prototype.logStarter = function() { - if (this.generalArgs.length == 2) { - // do fancy git log branchA ^branchB - if (this.generalArgs[1][0] == '^') { - this.logWithout(this.generalArgs[0], this.generalArgs[1]); - } else { - throw new GitError({ - msg: 'I need a not branch (^branchName) when getting two arguments!' - }); - } - } - - this.oneArgImpliedHead(this.generalArgs); - this.log(this.generalArgs[0]); -}; - -GitEngine.prototype.logWithout = function(ref, omitBranch) { - // slice off the ^branch - omitBranch = omitBranch.slice(1); - this.log(ref, this.getUpstreamSet(omitBranch)); -}; - -GitEngine.prototype.log = function(ref, omitSet) { - // omit set is for doing stuff like git log branchA ^branchB - 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 - var toDump = []; - var pQueue = [commit]; - - var seen = {}; - - while (pQueue.length) { - var popped = pQueue.shift(0); - 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 - var bigLogStr = ''; - _.each(toDump, function(c) { - bigLogStr += c.getLogEntry(); - }, this); - - throw new CommandResult({ - msg: bigLogStr - }); -}; - -GitEngine.prototype.addStarter = function() { - 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.getCommonAncestor = function(ancestor, cousin) { - if (this.isUpstreamOf(cousin, ancestor)) { - throw new Error('Dont use common ancestor if we are upstream!'); - } - - var upstreamSet = this.getUpstreamSet(ancestor); - // now BFS off of cousin until you find something - - var queue = [this.getCommitFromRef(cousin)]; - while (queue.length) { - var here = queue.pop(); - if (upstreamSet[here.get('id')]) { - return here; - } - queue = queue.concat(here.get('parents')); - } - throw new Error('something has gone very wrong... two nodes arent connected!'); -}; - -GitEngine.prototype.isUpstreamOf = function(child, ancestor) { - child = this.getCommitFromRef(child); - - // basically just do a completely BFS search on ancestor to the root, then - // check for membership of child in that set of explored nodes - var upstream = this.getUpstreamSet(ancestor); - return upstream[child.get('id')] !== undefined; -}; - -GitEngine.prototype.getUpstreamSet = function(ancestor) { - var commit = this.getCommitFromRef(ancestor); - var ancestorID = commit.get('id'); - var queue = [commit]; - - var exploredSet = {}; - exploredSet[ancestorID] = true; - while (queue.length) { - var here = queue.pop(); - var rents = here.get('parents'); - - _.each(rents, function(rent) { - exploredSet[rent.get('id')] = true; - queue.push(rent); - }); - } - return exploredSet; -}; - - -var Ref = Backbone.Model.extend({ - initialize: function() { - if (!this.get('target')) { - throw new Error('must be initialized with target'); - } - if (!this.get('id')) { - throw new Error('must be given an id'); - } - this.set('type', 'general ref'); - - if (this.get('id') == 'HEAD') { - this.set('lastLastTarget', null); - this.set('lastTarget', this.get('target')); - // have HEAD remember where it is for checkout - - this.on('change:target', this.targetChanged, this); - } - }, - - targetChanged: function(model, targetValue, ev) { - // push our little 3 stack back. we need to do this because - // backbone doesn't give you what the value WAS, only what it was changed - // TO - this.set('lastLastTarget', this.get('lastTarget')); - this.set('lastTarget', targetValue); - }, - - toString: function() { - return 'a ' + this.get('type') + 'pointing to ' + String(this.get('target')); - } -}); - -var Branch = Ref.extend({ - defaults: { - visBranch: null, - }, - - initialize: function() { - Ref.prototype.initialize.call(this); - this.set('type', 'branch'); - } -}); - -var Commit = Backbone.Model.extend({ - defaults: { - type: 'commit', - children: null, - parents: null, - author: 'Peter Cottle', - createTime: null, - commitMessage: null, - visNode: null, - gitVisuals: null - }, - - constants: { - circularFields: ['gitVisuals', 'visNode', 'children'] - }, - - getLogEntry: function() { - // for now we are just joining all these things with newlines which - // will get placed by paragraph tags. Not really a fan of this, but - // it's better than making an entire template and all that jazz - return [ - 'Author: ' + this.get('author'), - 'Date: ' + this.get('createTime'), - '
', - this.get('commitMessage'), - '
', - 'Commit: ' + this.get('id') - ].join('\n' ) + '\n'; - }, - - getShowEntry: function() { - // same deal as above, show log entry and some fake changes - return [ - this.getLogEntry(), - 'diff --git a/bigGameResults.html b/bigGameResults.html', - '--- bigGameResults.html', - '+++ bigGameResults.html', - '@@ 13,27 @@ Winner, Score', - '- Stanfurd, 14-7', - '+ Cal, 21-14', - ].join('\n') + '\n'; - }, - - validateAtInit: function() { - if (!this.get('id')) { - throw new Error('Need ID!!'); - } - - if (!this.get('createTime')) { - this.set('createTime', new Date().toString()); - } - if (!this.get('commitMessage')) { - this.set('commitMessage', 'Quick Commit. Go Bears!'); - } - - this.set('children', []); - - // root commits have no parents - if (!this.get('rootCommit')) { - if (!this.get('parents') || !this.get('parents').length) { - throw new Error('needs parents'); - } - } - }, - - addNodeToVisuals: function() { - var visNode = this.get('gitVisuals').addNode(this.get('id'), this); - this.set('visNode', visNode); - }, - - addEdgeToVisuals: function(parent) { - this.get('gitVisuals').addEdge(this.get('id'), parent.get('id')); - }, - - isMainParent: function(parent) { - var index = this.get('parents').indexOf(parent); - return index === 0; - }, - - initialize: function(options) { - this.validateAtInit(); - this.addNodeToVisuals(); - - _.each(this.get('parents'), function(parent) { - parent.get('children').push(this); - this.addEdgeToVisuals(parent); - }, this); - } -}); - -exports.GitEngine = GitEngine; -exports.Commit = Commit; -exports.Branch = Branch; -exports.Ref = Ref; - - -}); - -require.define("/commandViews.js",function(require,module,exports,__dirname,__filename,process,global){var CommandEntryCollection = require('./collections').CommandEntryCollection; - -var CommandPromptView = Backbone.View.extend({ - initialize: function(options) { - this.collection = options.collection; - - // uses local storage - this.commands = new CommandEntryCollection(); - this.commands.fetch({ - success: _.bind(function() { - // reverse the commands. this is ugly but needs to be done... - var commands = []; - this.commands.each(function(c) { - commands.push(c); - }); - - commands.reverse(); - this.commands.reset(); - - _.each(commands, function(c) { - this.commands.add(c); - }, this); - }, this) - }); - - this.index = -1; - - this.commandSpan = this.$('#prompt span.command')[0]; - this.commandCursor = this.$('#prompt span.cursor')[0]; - - // this is evil, but we will refer to HTML outside the document - // and attach a click event listener so we can focus / unfocus - $(document).delegate('#commandLineHistory', 'click', _.bind(function() { - this.focus(); - }, this)); - - - $(document).delegate('#commandTextField', 'blur', _.bind(function() { - this.blur(); - }, this)); - - events.on('processCommandFromEvent', this.addToCollection, this); - events.on('submitCommandValueFromEvent', this.submitValue, this); - events.on('rollupCommands', this.rollupCommands, this); - - // hacky timeout focus - setTimeout(_.bind(function() { - this.focus(); - }, this), 100); - }, - - events: { - 'keydown #commandTextField': 'onKey', - 'keyup #commandTextField': 'onKeyUp', - 'blur #commandTextField': 'hideCursor', - 'focus #commandTextField': 'showCursor' - }, - - blur: function() { - $(this.commandCursor).toggleClass('shown', false); - }, - - focus: function() { - this.$('#commandTextField').focus(); - this.showCursor(); - }, - - hideCursor: function() { - this.toggleCursor(false); - }, - - showCursor: function() { - this.toggleCursor(true); - }, - - toggleCursor: function(state) { - $(this.commandCursor).toggleClass('shown', state); - }, - - onKey: function(e) { - var el = e.srcElement; - this.updatePrompt(el) - }, - - onKeyUp: function(e) { - this.onKey(e); - - // we need to capture some of these events. - // WARNING: this key map is not internationalized :( - var keyMap = { - // enter - 13: _.bind(function() { - this.submit(); - }, this), - // up - 38: _.bind(function() { - this.commandSelectChange(1); - }, this), - // down - 40: _.bind(function() { - this.commandSelectChange(-1); - }, this) - }; - - if (keyMap[e.which] !== undefined) { - e.preventDefault(); - keyMap[e.which](); - this.onKey(e); - } - }, - - badHtmlEncode: function(text) { - return text.replace(/&/g,'&') - .replace(/= this.commands.length || this.index < 0) { - this.clear(); - this.index = -1; - return; - } - - // yay! we actually can display something - var commandEntry = this.commands.toArray()[this.index].get('text'); - this.setTextField(commandEntry); - }, - - clearLocalStorage: function() { - this.commands.each(function(c) { - Backbone.sync('delete', c, function() { }); - }, this); - localStorage.setItem('CommandEntries', ''); - }, - - setTextField: function(value) { - this.$('#commandTextField').val(value); - }, - - clear: function() { - this.setTextField(''); - }, - - submit: function() { - var value = this.$('#commandTextField').val().replace('\n', ''); - this.clear(); - this.submitValue(value); - }, - - rollupCommands: function(numBack) { - var which = this.commands.toArray().slice(1, Number(numBack) + 1); - which.reverse(); - - var str = ''; - _.each(which, function(commandEntry) { - str += commandEntry.get('text') + ';'; - }, this); - - console.log('the str', str); - - var rolled = new CommandEntry({text: str}); - this.commands.unshift(rolled); - Backbone.sync('create', rolled, function() { }); - }, - - submitValue: function(value) { - // we should add if it's not a blank line and this is a new command... - // or if we edited the command - var shouldAdd = (value.length && this.index == -1) || - ((value.length && this.index !== -1 && - this.commands.toArray()[this.index].get('text') !== value)); - - if (shouldAdd) { - var commandEntry = new CommandEntry({text: value}); - this.commands.unshift(commandEntry); - - // store to local storage - Backbone.sync('create', commandEntry, function() { }); - - // if our length is too egregious, reset - if (this.commands.length > 100) { - this.clearLocalStorage(); - } - } - this.index = -1; - - // split commands on semicolon - _.each(value.split(';'), _.bind(function(command, index) { - command = _.escape(command); - - command = command - .replace(/^(\s+)/, '') - .replace(/(\s+)$/, '') - .replace(/"/g, '"') - .replace(/'/g, "'"); - - if (index > 0 && !command.length) { - return; - } - - this.addToCollection(command); - }, this)); - }, - - addToCollection: function(value) { - var command = new Command({ - rawStr: value - }); - this.collection.add(command); - } -}); - - -// This is the view for all commands -- it will represent -// their status (inqueue, processing, finished, error), -// their value ("git commit --amend"), -// and the result (either errors or warnings or whatever) -var CommandView = Backbone.View.extend({ - tagName: 'div', - model: Command, - template: _.template($('#command-template').html()), - - events: { - 'click': 'clicked' - }, - - clicked: function(e) { - }, - - initialize: function() { - this.model.bind('change', this.wasChanged, this); - this.model.bind('destroy', this.remove, this); - }, - - wasChanged: function(model, changeEvent) { - // for changes that are just comestic, we actually only want to toggle classes - // with jquery rather than brutally delete a html of HTML - var changes = changeEvent.changes; - var changeKeys = _.keys(changes); - if (_.difference(changeKeys, ['status']) == 0) { - this.updateStatus(); - } else if (_.difference(changeKeys, ['error']) == 0) { - // the above will - this.render(); - } else { - this.render(); - } - }, - - updateStatus: function() { - var statuses = ['inqueue', 'processing', 'finished']; - var toggleMap = {}; - _.each(statuses, function(status) { - toggleMap[status] = false; - }); - toggleMap[this.model.get('status')] = true; - - var query = this.$('p.commandLine'); - - _.each(toggleMap, function(value, key) { - query.toggleClass(key, value); - }); - }, - - render: function() { - var json = _.extend( - { - resultType: '', - result: '', - formattedWarnings: this.model.getFormattedWarnings() - }, - this.model.toJSON() - ); - - this.$el.html(this.template(json)); - return this; - }, - - remove: function() { - $(this.el).hide(); - } -}); - - -var CommandLineHistoryView = Backbone.View.extend({ - initialize: function(options) { - this.collection = options.collection; - - this.collection.on('add', this.addOne, this); - this.collection.on('reset', this.addAll, this); - this.collection.on('all', this.render, this); - - this.collection.on('change', this.scrollDown, this); - - events.on('issueWarning', this.addWarning, this); - events.on('commandScrollDown', this.scrollDown, this); - }, - - addWarning: function(msg) { - var err = new Warning({ - msg: msg - }); - - var command = new Command({ - error: err, - rawStr: 'Warning:' - }); - - this.collection.add(command); - }, - - scrollDown: function() { - // if commandDisplay is ever bigger than #terminal, we need to - // add overflow-y to terminal and scroll down - var cD = $('#commandDisplay')[0]; - var t = $('#terminal')[0]; - - if ($(t).hasClass('scrolling')) { - t.scrollTop = t.scrollHeight; - return; - } - if (cD.clientHeight > t.clientHeight) { - $(t).css('overflow-y', 'scroll'); - $(t).css('overflow-x', 'hidden'); - $(t).addClass('scrolling'); - t.scrollTop = t.scrollHeight; - } - }, - - addOne: function(command) { - var view = new CommandView({ - model: command - }); - this.$('#commandDisplay').append(view.render().el); - this.scrollDown(); - }, - - addAll: function() { - this.collection.each(this.addOne); - } -}); - -exports.CommandPromptView = CommandPromptView; -exports.CommandLineHistoryView = CommandLineHistoryView; - - -}); - -require.define("/visuals.js",function(require,module,exports,__dirname,__filename,process,global){var CommitCollection = require('./collections').CommitCollection; -var BranchCollection = require('./collections').BranchCollection; -var GitEngine = require('./git').GitEngine; - -var Visualization = Backbone.View.extend({ - initialize: function(options) { - var _this = this; - Raphael(10, 10, 200, 200, function() { - - // for some reason raphael calls this function with a predefined - // context... - // so switch it - _this.paperInitialize(this); - }); - }, - - paperInitialize: function(paper, options) { - this.paper = paper; - - this.commitCollection = new CommitCollection(); - this.branchCollection = new BranchCollection(); - - this.gitVisuals = new GitVisuals({ - commitCollection: this.commitCollection, - branchCollection: this.branchCollection, - paper: this.paper - }); - - this.gitEngine = new GitEngine({ - collection: this.commitCollection, - branches: this.branchCollection, - gitVisuals: this.gitVisuals - }); - this.gitEngine.init(); - this.gitVisuals.assignGitEngine(this.gitEngine); - - this.myResize(); - $(window).on('resize', _.bind(this.myResize, this)); - this.gitVisuals.drawTreeFirstTime(); - }, - - myResize: function() { - var smaller = 1; - var el = this.el; - - var left = el.offsetLeft; - var top = el.offsetTop; - var width = el.clientWidth - smaller; - var height = el.clientHeight - smaller; - - $(this.paper.canvas).css({ - left: left + 'px', - top: top + 'px' - }); - this.paper.setSize(width, height); - this.gitVisuals.canvasResize(width, height); - } - -}); - -function GitVisuals(options) { - this.commitCollection = options.commitCollection; - this.branchCollection = options.branchCollection; - this.visNodeMap = {}; - - this.visEdgeCollection = new VisEdgeCollection(); - this.visBranchCollection = new VisBranchCollection(); - this.commitMap = {}; - - this.rootCommit = null; - this.branchStackMap = null; - this.upstreamBranchSet = null; - this.upstreamHeadSet = null; - - this.paper = options.paper; - this.gitReady = false; - - this.branchCollection.on('add', this.addBranchFromEvent, this); - this.branchCollection.on('remove', this.removeBranch, this); - this.deferred = []; - - events.on('refreshTree', _.bind( - this.refreshTree, this - )); -} - -GitVisuals.prototype.defer = function(action) { - this.deferred.push(action); -}; - -GitVisuals.prototype.deferFlush = function() { - _.each(this.deferred, function(action) { - action(); - }, this); - this.deferred = []; -}; - -GitVisuals.prototype.resetAll = function() { - // make sure to copy these collections because we remove - // items in place and underscore is too dumb to detect length change - var edges = this.visEdgeCollection.toArray(); - _.each(edges, function(visEdge) { - visEdge.remove(); - }, this); - - var branches = this.visBranchCollection.toArray(); - _.each(branches, function(visBranch) { - visBranch.remove(); - }, this); - - _.each(this.visNodeMap, function(visNode) { - visNode.remove(); - }, this); - - this.visEdgeCollection.reset(); - this.visBranchCollection.reset(); - - this.visNodeMap = {}; - this.rootCommit = null; - this.commitMap = {}; -}; - -GitVisuals.prototype.assignGitEngine = function(gitEngine) { - this.gitEngine = gitEngine; - this.initHeadBranch(); - this.deferFlush(); -}; - -GitVisuals.prototype.initHeadBranch = function() { - // it's unfortaunte we have to do this, but the head branch - // is an edge case because it's not part of a collection so - // we can't use events to load or unload it. thus we have to call - // this ugly method which will be deleted one day - - // seed this with the HEAD pseudo-branch - this.addBranchFromEvent(this.gitEngine.HEAD); -}; - -GitVisuals.prototype.getScreenBounds = function() { - // for now we return the node radius subtracted from the walls - return { - widthPadding: GRAPHICS.nodeRadius * 1.5, - heightPadding: GRAPHICS.nodeRadius * 1.5 - }; -}; - -GitVisuals.prototype.toScreenCoords = function(pos) { - if (!this.paper.width) { - throw new Error('being called too early for screen coords'); - } - var bounds = this.getScreenBounds(); - - var shrink = function(frac, total, padding) { - return padding + frac * (total - padding * 2); - }; - - return { - x: shrink(pos.x, this.paper.width, bounds.widthPadding), - y: shrink(pos.y, this.paper.height, bounds.heightPadding) - }; -}; - -GitVisuals.prototype.animateAllFromAttrToAttr = function(fromSnapshot, toSnapshot, idsToOmit) { - var animate = function(obj) { - var id = obj.getID(); - if (_.include(idsToOmit, id)) { - return; - } - - if (!fromSnapshot[id] || !toSnapshot[id]) { - // its actually ok it doesnt exist yet - return; - } - obj.animateFromAttrToAttr(fromSnapshot[id], toSnapshot[id]); - }; - - this.visBranchCollection.each(function(visBranch) { - animate(visBranch); - }); - this.visEdgeCollection.each(function(visEdge) { - animate(visEdge); - }); - _.each(this.visNodeMap, function(visNode) { - animate(visNode); - }); -}; - -/*************************************** - == BEGIN Tree Calculation Parts == - _ __ __ _ - \\/ / \ \//_ - \ \ / __| __ - \ \___/ /_____/ / - | _______ \ - \ ( ) / \_\ - \ / - | | - | | - ____+-_=+-^ ^+-=_=__________ - -^^ I drew that :D - - **************************************/ - -GitVisuals.prototype.genSnapshot = function() { - this.fullCalc(); - - var snapshot = {}; - _.each(this.visNodeMap, function(visNode) { - snapshot[visNode.get('id')] = visNode.getAttributes(); - }, this); - - this.visBranchCollection.each(function(visBranch) { - snapshot[visBranch.getID()] = visBranch.getAttributes(); - }, this); - - this.visEdgeCollection.each(function(visEdge) { - snapshot[visEdge.getID()] = visEdge.getAttributes(); - }, this); - - return snapshot; -}; - -GitVisuals.prototype.refreshTree = function(speed) { - if (!this.gitReady) { - return; - } - - // this method can only be called after graphics are rendered - this.fullCalc(); - - this.animateAll(speed); -}; - -GitVisuals.prototype.refreshTreeHarsh = function() { - this.fullCalc(); - - this.animateAll(0); -}; - -GitVisuals.prototype.animateAll = function(speed) { - this.zIndexReflow(); - - this.animateEdges(speed); - this.animateNodePositions(speed); - this.animateRefs(speed); -}; - -GitVisuals.prototype.fullCalc = function() { - this.calcTreeCoords(); - this.calcGraphicsCoords(); -}; - -GitVisuals.prototype.calcTreeCoords = function() { - // this method can only contain things that dont rely on graphics - if (!this.rootCommit) { - throw new Error('grr, no root commit!'); - } - - this.calcUpstreamSets(); - this.calcBranchStacks(); - - this.calcDepth(); - this.calcWidth(); -}; - -GitVisuals.prototype.calcGraphicsCoords = function() { - this.visBranchCollection.each(function(visBranch) { - visBranch.updateName(); - }); -}; - -GitVisuals.prototype.calcUpstreamSets = function() { - this.upstreamBranchSet = this.gitEngine.getUpstreamBranchSet(); - this.upstreamHeadSet = this.gitEngine.getUpstreamHeadSet(); -}; - -GitVisuals.prototype.getCommitUpstreamBranches = function(commit) { - return this.branchStackMap[commit.get('id')]; -}; - -GitVisuals.prototype.getBlendedHuesForCommit = function(commit) { - var branches = this.upstreamBranchSet[commit.get('id')]; - if (!branches) { - throw new Error('that commit doesnt have upstream branches!'); - } - - return this.blendHuesFromBranchStack(branches); -}; - -GitVisuals.prototype.blendHuesFromBranchStack = function(branchStackArray) { - var hueStrings = []; - _.each(branchStackArray, function(branchWrapper) { - var fill = branchWrapper.obj.get('visBranch').get('fill'); - - if (fill.slice(0,3) !== 'hsb') { - // crap! convert - var color = Raphael.color(fill); - fill = 'hsb(' + String(color.h) + ',' + String(color.l); - fill = fill + ',' + String(color.s) + ')'; - } - - hueStrings.push(fill); - }); - - return blendHueStrings(hueStrings); -}; - -GitVisuals.prototype.getCommitUpstreamStatus = function(commit) { - if (!this.upstreamBranchSet) { - throw new Error("Can't calculate this yet!"); - } - - var id = commit.get('id'); - var branch = this.upstreamBranchSet; - var head = this.upstreamHeadSet; - - if (branch[id]) { - return 'branch'; - } else if (head[id]) { - return 'head'; - } else { - return 'none'; - } -}; - -GitVisuals.prototype.calcBranchStacks = function() { - var branches = this.gitEngine.getBranches(); - var map = {}; - _.each(branches, function(branch) { - var thisId = branch.target.get('id'); - - map[thisId] = map[thisId] || []; - map[thisId].push(branch); - map[thisId].sort(function(a, b) { - var aId = a.obj.get('id'); - var bId = b.obj.get('id'); - if (aId == 'master' || bId == 'master') { - return aId == 'master' ? -1 : 1; - } - return aId.localeCompare(bId); - }); - }); - this.branchStackMap = map; -}; - -GitVisuals.prototype.calcWidth = function() { - this.maxWidthRecursive(this.rootCommit); - - this.assignBoundsRecursive(this.rootCommit, 0, 1); -}; - -GitVisuals.prototype.maxWidthRecursive = function(commit) { - var childrenTotalWidth = 0; - _.each(commit.get('children'), function(child) { - // only include this if we are the "main" parent of - // this child - if (child.isMainParent(commit)) { - var childWidth = this.maxWidthRecursive(child); - childrenTotalWidth += childWidth; - } - }, this); - - var maxWidth = Math.max(1, childrenTotalWidth); - commit.get('visNode').set('maxWidth', maxWidth); - return maxWidth; -}; - -GitVisuals.prototype.assignBoundsRecursive = function(commit, min, max) { - // I always center myself within my bounds - var myWidthPos = (min + max) / 2.0; - commit.get('visNode').get('pos').x = myWidthPos; - - if (commit.get('children').length == 0) { - return; - } - - // i have a certain length to divide up - var myLength = max - min; - // I will divide up that length based on my children's max width in a - // basic box-flex model - var totalFlex = 0; - var children = commit.get('children'); - _.each(children, function(child) { - if (child.isMainParent(commit)) { - totalFlex += child.get('visNode').getMaxWidthScaled(); - } - }, this); - - var prevBound = min; - - // now go through and do everything - // TODO: order so the max width children are in the middle!! - _.each(children, function(child) { - if (!child.isMainParent(commit)) { - return; - } - - var flex = child.get('visNode').getMaxWidthScaled(); - var portion = (flex / totalFlex) * myLength; - var childMin = prevBound; - var childMax = childMin + portion; - this.assignBoundsRecursive(child, childMin, childMax); - prevBound = childMax; - }, this); -}; - -GitVisuals.prototype.calcDepth = function() { - var maxDepth = this.calcDepthRecursive(this.rootCommit, 0); - if (maxDepth > 15) { - // issue warning - console.warn('graphics are degrading from too many layers'); - } - - var depthIncrement = this.getDepthIncrement(maxDepth); - _.each(this.visNodeMap, function(visNode) { - visNode.setDepthBasedOn(depthIncrement); - }, this); -}; - -/*************************************** - == END Tree Calculation == - _ __ __ _ - \\/ / \ \//_ - \ \ / __| __ - \ \___/ /_____/ / - | _______ \ - \ ( ) / \_\ - \ / - | | - | | - ____+-_=+-^ ^+-=_=__________ - -^^ I drew that :D - - **************************************/ - -GitVisuals.prototype.animateNodePositions = function(speed) { - _.each(this.visNodeMap, function(visNode) { - visNode.animateUpdatedPosition(speed); - }, this); -}; - -GitVisuals.prototype.turnOnPaper = function() { - this.gitReady = false; -}; - -// does making an accessor method make it any less hacky? that is the true question -GitVisuals.prototype.turnOffPaper = function() { - this.gitReady = true; -}; - -GitVisuals.prototype.addBranchFromEvent = function(branch, collection, index) { - var action = _.bind(function() { - this.addBranch(branch); - }, this); - - if (!this.gitEngine || !this.gitReady) { - this.defer(action); - } else { - action(); - } -}; - -GitVisuals.prototype.addBranch = function(branch) { - var visBranch = new VisBranch({ - branch: branch, - gitVisuals: this, - gitEngine: this.gitEngine - }); - - this.visBranchCollection.add(visBranch); - if (this.gitReady) { - visBranch.genGraphics(this.paper); - } -}; - -GitVisuals.prototype.removeVisBranch = function(visBranch) { - this.visBranchCollection.remove(visBranch); -}; - -GitVisuals.prototype.removeVisNode = function(visNode) { - this.visNodeMap[visNode.getID()] = undefined; -}; - -GitVisuals.prototype.removeVisEdge = function(visEdge) { - this.visEdgeCollection.remove(visEdge); -}; - -GitVisuals.prototype.animateRefs = function(speed) { - this.visBranchCollection.each(function(visBranch) { - visBranch.animateUpdatedPos(speed); - }, this); -}; - -GitVisuals.prototype.animateEdges = function(speed) { - this.visEdgeCollection.each(function(edge) { - edge.animateUpdatedPath(speed); - }, this); -}; - -GitVisuals.prototype.getDepthIncrement = function(maxDepth) { - // assume there are at least 7 layers until later - maxDepth = Math.max(maxDepth, 7); - var increment = 1.0 / maxDepth; - return increment; -}; - -GitVisuals.prototype.calcDepthRecursive = function(commit, depth) { - commit.get('visNode').setDepth(depth); - - var children = commit.get('children'); - var maxDepth = depth; - _.each(children, function(child) { - var d = this.calcDepthRecursive(child, depth + 1); - maxDepth = Math.max(d, maxDepth); - }, this); - - return maxDepth; -}; - -GitVisuals.prototype.canvasResize = function(width, height) { - // refresh when we are ready - if (GLOBAL.isAnimating) { - events.trigger('processCommandFromEvent', 'refresh'); - } else { - this.refreshTree(); - } -}; - -GitVisuals.prototype.addCommit = function(commit) { - // TODO -}; - -GitVisuals.prototype.addNode = function(id, commit) { - this.commitMap[id] = commit; - if (commit.get('rootCommit')) { - this.rootCommit = commit; - } - - var visNode = new VisNode({ - id: id, - commit: commit, - gitVisuals: this, - gitEngine: this.gitEngine - }); - this.visNodeMap[id] = visNode; - - if (this.gitReady) { - visNode.genGraphics(this.paper); - } - return visNode; -}; - -GitVisuals.prototype.addEdge = function(idTail, idHead) { - var visNodeTail = this.visNodeMap[idTail]; - var visNodeHead = this.visNodeMap[idHead]; - - if (!visNodeTail || !visNodeHead) { - throw new Error('one of the ids in (' + idTail + - ', ' + idHead + ') does not exist'); - } - - var edge = new VisEdge({ - tail: visNodeTail, - head: visNodeHead, - gitVisuals: this, - gitEngine: this.gitEngine - }); - this.visEdgeCollection.add(edge); - - if (this.gitReady) { - edge.genGraphics(this.paper); - } -}; - -GitVisuals.prototype.collectionChanged = function() { - // TODO ? -}; - -GitVisuals.prototype.zIndexReflow = function() { - this.visNodesFront(); - this.visBranchesFront(); -}; - -GitVisuals.prototype.visNodesFront = function() { - _.each(this.visNodeMap, function(visNode) { - visNode.toFront(); - }); -}; - -GitVisuals.prototype.visBranchesFront = function() { - this.visBranchCollection.each(function(vBranch) { - vBranch.nonTextToFront(); - }); - - this.visBranchCollection.each(function(vBranch) { - vBranch.textToFront(); - }); -}; - -GitVisuals.prototype.drawTreeFromReload = function() { - this.gitReady = true; - // gen all the graphics we need - this.deferFlush(); - - this.calcTreeCoords(); -}; - -GitVisuals.prototype.drawTreeFirstTime = function() { - this.gitReady = true; - this.calcTreeCoords(); - - _.each(this.visNodeMap, function(visNode) { - visNode.genGraphics(this.paper); - }, this); - - this.visEdgeCollection.each(function(edge) { - edge.genGraphics(this.paper); - }, this); - - this.visBranchCollection.each(function(visBranch) { - visBranch.genGraphics(this.paper); - }, this); - - this.zIndexReflow(); -}; - - -/************************ - * Random util functions, some from liquidGraph - ***********************/ -function blendHueStrings(hueStrings) { - // assumes a sat of 0.7 and brightness of 1 - - var x = 0; - var y = 0; - var totalSat = 0; - var totalBright = 0; - var length = hueStrings.length; - - _.each(hueStrings, function(hueString) { - var exploded = hueString.split('(')[1]; - exploded = exploded.split(')')[0]; - exploded = exploded.split(','); - - totalSat += parseFloat(exploded[1]); - totalBright += parseFloat(exploded[2]); - var hue = parseFloat(exploded[0]); - - var angle = hue * Math.PI * 2; - x += Math.cos(angle); - y += Math.sin(angle); - }); - - x = x / length; - y = y / length; - totalSat = totalSat / length; - totalBright = totalBright / length; - - var hue = Math.atan2(y, x) / (Math.PI * 2); // could fail on 0's - if (hue < 0) { - hue = hue + 1; - } - return 'hsb(' + String(hue) + ',' + String(totalSat) + ',' + String(totalBright) + ')'; -} - -function randomHueString() { - var hue = Math.random(); - var str = 'hsb(' + String(hue) + ',0.7,1)'; - return str; -}; - -exports.Visualization = Visualization; - }); require.define("/main.js",function(require,module,exports,__dirname,__filename,process,global){var AnimationFactory = require('./animationFactory').AnimationFactory; -var CommandCollection = require('./collections').CommandCollection; -var CommandBuffer = require('./collections').CommandBuffer; -var CommandPromptView = require('./commandViews').CommandPromptView; -var CommandLineHistoryView = require('./commandViews').CommandLineHistoryView; -var Visualization = require('./visuals').Visualization; /** * Globals @@ -3525,7 +716,7 @@ require.define("/animationFactory.js",function(require,module,exports,__dirname, */ // essentially a static class -var AnimationFactory = function() { +function AnimationFactory() { } @@ -3768,2815 +959,7 @@ AnimationFactory.prototype.genFromToSnapshotAnimation = function( exports.AnimationFactory = AnimationFactory; + }); require("/animationFactory.js"); - -require.define("/git.js",function(require,module,exports,__dirname,__filename,process,global){var animationFactory = new require('./animationFactory').AnimationFactory(); - -// backbone or something uses _.uniqueId, so we make our own here -var uniqueId = (function() { - var n = 0; - return function(prepend) { - return prepend? prepend + n++ : n++; - }; -})(); - -function GitEngine(options) { - this.rootCommit = null; - this.refs = {}; - this.HEAD = null; - - this.branchCollection = options.branches; - this.commitCollection = options.collection; - this.gitVisuals = options.gitVisuals; - - // global variable to keep track of the options given - // along with the command call. - this.commandOptions = {}; - this.generalArgs = []; - - events.on('processCommand', _.bind(this.dispatch, this)); -} - -GitEngine.prototype.defaultInit = function() { - var defaultTree = JSON.parse(unescape("%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22master%22%2C%22type%22%3A%22branch%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22type%22%3A%22commit%22%2C%22parents%22%3A%5B%5D%2C%22author%22%3A%22Peter%20Cottle%22%2C%22createTime%22%3A%22Mon%20Nov%2005%202012%2000%3A56%3A47%20GMT-0800%20%28PST%29%22%2C%22commitMessage%22%3A%22Quick%20Commit.%20Go%20Bears%21%22%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22type%22%3A%22commit%22%2C%22parents%22%3A%5B%22C0%22%5D%2C%22author%22%3A%22Peter%20Cottle%22%2C%22createTime%22%3A%22Mon%20Nov%2005%202012%2000%3A56%3A47%20GMT-0800%20%28PST%29%22%2C%22commitMessage%22%3A%22Quick%20Commit.%20Go%20Bears%21%22%2C%22id%22%3A%22C1%22%7D%7D%2C%22HEAD%22%3A%7B%22id%22%3A%22HEAD%22%2C%22target%22%3A%22master%22%2C%22type%22%3A%22general%20ref%22%7D%7D")); - this.loadTree(defaultTree); -}; - -GitEngine.prototype.init = function() { - // make an initial commit and a master branch - this.rootCommit = this.makeCommit(null, null, {rootCommit: true}); - this.commitCollection.add(this.rootCommit); - - var master = this.makeBranch('master', this.rootCommit); - this.HEAD = new Ref({ - id: 'HEAD', - target: master - }); - this.refs[this.HEAD.get('id')] = this.HEAD; - - // commit once to get things going - this.commit(); -}; - -GitEngine.prototype.exportTree = function() { - // need to export all commits, their connectivity / messages, branches, and state of head. - // this would be simple if didn't have circular structures.... :P - // thus, we need to loop through and "flatten" our graph of objects referencing one another - var totalExport = { - branches: {}, - commits: {}, - HEAD: null - }; - - _.each(this.branchCollection.toJSON(), function(branch) { - branch.target = branch.target.get('id'); - branch.visBranch = undefined; - - totalExport.branches[branch.id] = branch; - }); - - _.each(this.commitCollection.toJSON(), function(commit) { - // clear out the fields that reference objects and create circular structure - _.each(Commit.prototype.constants.circularFields, function(field) { - commit[field] = undefined; - }, this); - - // convert parents - var parents = []; - _.each(commit.parents, function(par) { - parents.push(par.get('id')); - }); - commit.parents = parents; - - totalExport.commits[commit.id] = commit; - }, this); - - var HEAD = this.HEAD.toJSON(); - HEAD.visBranch = undefined; - HEAD.lastTarget = HEAD.lastLastTarget = HEAD.visBranch = undefined; - HEAD.target = HEAD.target.get('id'); - totalExport.HEAD = HEAD; - - return totalExport; -}; - -GitEngine.prototype.printTree = function() { - var str = escape(JSON.stringify(this.exportTree())); - return str; -}; - -GitEngine.prototype.printAndCopyTree = function() { - window.prompt('Copy the tree string below', this.printTree()); -}; - -GitEngine.prototype.loadTree = function(tree) { - // deep copy in case we use it a bunch - tree = $.extend(true, {}, tree); - - // first clear everything - this.removeAll(); - - this.instantiateFromTree(tree); - - this.reloadGraphics(); -}; - -GitEngine.prototype.loadTreeFromString = function(treeString) { - this.loadTree(JSON.parse(unescape(treeString))); -}; - -GitEngine.prototype.instantiateFromTree = function(tree) { - // now we do the loading part - var createdSoFar = {}; - - _.each(tree.commits, function(commitJSON) { - var commit = this.getOrMakeRecursive(tree, createdSoFar, commitJSON.id); - this.commitCollection.add(commit); - }, this); - - _.each(tree.branches, function(branchJSON) { - var branch = this.getOrMakeRecursive(tree, createdSoFar, branchJSON.id); - - this.branchCollection.add(branch, {silent: true}); - }, this); - - var HEAD = this.getOrMakeRecursive(tree, createdSoFar, tree.HEAD.id); - this.HEAD = HEAD; - - this.rootCommit = createdSoFar['C0']; - if (!this.rootCommit) { - throw new Error('Need root commit of C0 for calculations'); - } - this.refs = createdSoFar; - - this.branchCollection.each(function(branch) { - this.gitVisuals.addBranch(branch); - }, this); -}; - -GitEngine.prototype.reloadGraphics = function() { - // get the root commit, no better way to do it - var rootCommit = null; - this.commitCollection.each(function(commit) { - if (commit.get('id') == 'C0') { - rootCommit = commit; - } - }); - this.gitVisuals.rootCommit = rootCommit; - - // this just basically makes the HEAD branch. the head branch really should have been - // a member of a collection and not this annoying edge case stuff... one day - this.gitVisuals.initHeadBranch(); - - // when the paper is ready - this.gitVisuals.drawTreeFromReload(); - - this.gitVisuals.refreshTreeHarsh(); -}; - -GitEngine.prototype.getOrMakeRecursive = function(tree, createdSoFar, objID) { - if (createdSoFar[objID]) { - // base case - return createdSoFar[objID]; - } - - var getType = function(tree, id) { - if (tree.commits[id]) { - return 'commit'; - } else if (tree.branches[id]) { - return 'branch'; - } else if (id == 'HEAD') { - return 'HEAD'; - } - throw new Error("bad type for " + id); - }; - - // figure out what type - var type = getType(tree, objID); - - if (type == 'HEAD') { - var headJSON = tree.HEAD; - var HEAD = new Ref(_.extend( - tree.HEAD, - { - target: this.getOrMakeRecursive(tree, createdSoFar, headJSON.target) - } - )); - createdSoFar[objID] = HEAD; - return HEAD; - } - - if (type == 'branch') { - var branchJSON = tree.branches[objID]; - - var branch = new Branch(_.extend( - tree.branches[objID], - { - target: this.getOrMakeRecursive(tree, createdSoFar, branchJSON.target) - } - )); - createdSoFar[objID] = branch; - return branch; - } - - if (type == 'commit') { - // for commits, we need to grab all the parents - var commitJSON = tree.commits[objID]; - - var parentObjs = []; - _.each(commitJSON.parents, function(parentID) { - parentObjs.push(this.getOrMakeRecursive(tree, createdSoFar, parentID)); - }, this); - - var commit = new Commit(_.extend( - commitJSON, - { - parents: parentObjs, - gitVisuals: this.gitVisuals - } - )); - createdSoFar[objID] = commit; - return commit; - } - - throw new Error('ruh rho!! unsupported tyep for ' + objID); -}; - -GitEngine.prototype.removeAll = function() { - this.branchCollection.reset(); - this.commitCollection.reset(); - this.refs = {}; - this.HEAD = null; - this.rootCommit = null; - - this.gitVisuals.resetAll(); -}; - -GitEngine.prototype.getDetachedHead = function() { - // detached head is if HEAD points to a commit instead of a branch... - var target = this.HEAD.get('target'); - var targetType = target.get('type'); - return targetType !== 'branch'; -}; - -GitEngine.prototype.validateBranchName = function(name) { - name = name.replace(/\s/g, ''); - if (!/^[a-zA-Z0-9]+$/.test(name)) { - throw new GitError({ - msg: 'woah bad branch name!! This is not ok: ' + name - }); - } - if (/[hH][eE][aA][dD]/.test(name)) { - throw new GitError({ - msg: 'branch name of "head" is ambiguous, dont name it that' - }); - } - if (name.length > 9) { - name = name.slice(0, 9); - this.command.addWarning( - 'Sorry, we need to keep branch names short for the visuals. Your branch ' + - 'name was truncated to 9 characters, resulting in ' + name - ); - } - return name; -}; - -GitEngine.prototype.makeBranch = function(id, target) { - id = this.validateBranchName(id); - if (this.refs[id]) { - throw new GitError({ - msg: 'that branch id either matches a commit hash or already exists!' - }); - } - - var branch = new Branch({ - target: target, - id: id - }); - this.branchCollection.add(branch); - this.refs[branch.get('id')] = branch; - return branch; -}; - -GitEngine.prototype.getHead = function() { - return _.clone(this.HEAD); -}; - -GitEngine.prototype.getBranches = function() { - var toReturn = []; - this.branchCollection.each(function(branch) { - toReturn.push({ - id: branch.get('id'), - selected: this.HEAD.get('target') === branch, - target: branch.get('target'), - obj: branch - }); - }, this); - return toReturn; -}; - -GitEngine.prototype.printBranchesWithout = function(without) { - var commitToBranches = this.getUpstreamBranchSet(); - var commitID = this.getCommitFromRef(without).get('id'); - - var toPrint = []; - _.each(commitToBranches[commitID], function(branchJSON) { - branchJSON.selected = this.HEAD.get('target').get('id') == branchJSON.id; - toPrint.push(branchJSON); - }, this); - this.printBranches(toPrint); -}; - -GitEngine.prototype.printBranches = function(branches) { - var result = ''; - _.each(branches, function(branch) { - result += (branch.selected ? '* ' : '') + branch.id + '\n'; - }); - throw new CommandResult({ - msg: result - }); -}; - -GitEngine.prototype.makeCommit = function(parents, id, options) { - // ok we need to actually manually create commit IDs now because - // people like nikita (thanks for finding this!) could - // make branches named C2 before creating the commit C2 - if (!id) { - id = uniqueId('C'); - while (this.refs[id]) { - id = uniqueId('C'); - } - } - - var commit = new Commit(_.extend({ - parents: parents, - id: id, - gitVisuals: this.gitVisuals - }, - options || {} - )); - - this.refs[commit.get('id')] = commit; - this.commitCollection.add(commit); - return commit; -}; - -GitEngine.prototype.acceptNoGeneralArgs = function() { - if (this.generalArgs.length) { - throw new GitError({ - msg: "That command accepts no general arguments" - }); - } -}; - -GitEngine.prototype.validateArgBounds = function(args, lower, upper, option) { - // this is a little utility class to help arg validation that happens over and over again - var what = (option === undefined) ? - 'git ' + this.command.get('method') : - this.command.get('method') + ' ' + option + ' '; - what = 'with ' + what; - - if (args.length < lower) { - throw new GitError({ - msg: 'I expect at least ' + String(lower) + ' argument(s) ' + what - }); - } - if (args.length > upper) { - throw new GitError({ - msg: 'I expect at most ' + String(upper) + ' argument(s) ' + what - }); - } -}; - -GitEngine.prototype.oneArgImpliedHead = function(args, option) { - // for log, show, etc - this.validateArgBounds(args, 0, 1, option); - if (args.length == 0) { - args.push('HEAD'); - } -}; - -GitEngine.prototype.twoArgsImpliedHead = function(args, option) { - // our args we expect to be between 1 and 2 - this.validateArgBounds(args, 1, 2, option); - // and if it's one, add a HEAD to the back - if (args.length == 1) { - args.push('HEAD'); - } -}; - -GitEngine.prototype.revertStarter = function() { - this.validateArgBounds(this.generalArgs, 1, NaN); - - var response = this.revert(this.generalArgs); - - if (response) { - animationFactory.rebaseAnimation(this.animationQueue, response, this, this.gitVisuals); - } -}; - -GitEngine.prototype.revert = function(whichCommits) { - // for each commit, we want to revert it - var toRebase = []; - _.each(whichCommits, function(stringRef) { - toRebase.push(this.getCommitFromRef(stringRef)); - }, this); - - // we animate reverts now!! we use the rebase animation though so that's - // why the terminology is like it is - var animationResponse = {}; - animationResponse.destinationBranch = this.resolveID(toRebase[0]); - animationResponse.toRebaseArray = toRebase.slice(0); - animationResponse.rebaseSteps = []; - - beforeSnapshot = this.gitVisuals.genSnapshot(); - var afterSnapshot; - - // now make a bunch of commits on top of where we are - var base = this.getCommitFromRef('HEAD'); - _.each(toRebase, function(oldCommit) { - var newId = this.rebaseAltID(oldCommit.get('id')); - - var newCommit = this.makeCommit([base], newId, { - commitMessage: 'Reverting ' + this.resolveName(oldCommit) + - ': "' + oldCommit.get('commitMessage') + '"' - }); - - base = newCommit; - - // animation stuff - afterSnapshot = this.gitVisuals.genSnapshot(); - animationResponse.rebaseSteps.push({ - oldCommit: oldCommit, - newCommit: newCommit, - beforeSnapshot: beforeSnapshot, - afterSnapshot: afterSnapshot - }); - beforeSnapshot = afterSnapshot; - }, this); - // done! update our location - this.setTargetLocation('HEAD', base); - - // animation - return animationResponse; -}; - -GitEngine.prototype.resetStarter = function() { - if (this.commandOptions['--soft']) { - throw new GitError({ - msg: "You can't use --soft because there is no concept of stashing" + - " changes or staging files, so you will lose your progress." + - " Try using interactive rebasing (or just rebasing) to move commits." - }); - } - if (this.commandOptions['--hard']) { - this.command.addWarning( - 'Nice! You are using --hard. The default behavior is a hard reset in ' + - "this demo, so don't worry about specifying the option explicity" - ); - // dont absorb the arg off of --hard - this.generalArgs = this.generalArgs.concat(this.commandOptions['--hard']); - } - - this.validateArgBounds(this.generalArgs, 1, 1); - - if (this.getDetachedHead()) { - throw new GitError({ - msg: "Cant reset in detached head! Use checkout if you want to move" - }); - } - - this.reset(this.generalArgs[0]); -}; - -GitEngine.prototype.reset = function(target) { - this.setTargetLocation('HEAD', this.getCommitFromRef(target)); -}; - -GitEngine.prototype.cherrypickStarter = function() { - this.validateArgBounds(this.generalArgs, 1, 1); - var newCommit = this.cherrypick(this.generalArgs[0]); - - animationFactory.genCommitBirthAnimation(this.animationQueue, newCommit, this.gitVisuals); -}; - -GitEngine.prototype.cherrypick = function(ref) { - var commit = this.getCommitFromRef(ref); - // check if we already have that - var set = this.getUpstreamSet('HEAD'); - if (set[commit.get('id')]) { - throw new GitError({ - msg: "We already have that commit in our changes history! You can't cherry-pick it " + - "if it shows up in git log." - }); - } - - // alter the ID slightly - var id = this.rebaseAltID(commit.get('id')); - - // now commit with that id onto HEAD - var newCommit = this.makeCommit([this.getCommitFromRef('HEAD')], id); - this.setTargetLocation(this.HEAD, newCommit); - return newCommit; -}; - -GitEngine.prototype.commitStarter = function() { - this.acceptNoGeneralArgs(); - if (this.commandOptions['-am'] && ( - this.commandOptions['-a'] || this.commandOptions['-m'])) { - throw new GitError({ - msg: "You can't have -am with another -m or -a!" - }); - } - - var msg = null; - if (this.commandOptions['-a']) { - this.command.addWarning('No need to add files in this demo'); - } - - if (this.commandOptions['-am']) { - var args = this.commandOptions['-am']; - this.validateArgBounds(args, 1, 1, '-am'); - - this.command.addWarning("Don't worry about adding files in this demo. I'll take " + - "down your commit message anyways, but you can commit without a message " + - "in this demo as well"); - msg = args[0]; - } - - if (this.commandOptions['-m']) { - var args = this.commandOptions['-m']; - this.validateArgBounds(args, 1, 1, '-m'); - msg = args[0]; - } - - var newCommit = this.commit(); - if (msg) { - msg = msg - .replace(/"/g, '"') - .replace(/^"/g, '') - .replace(/"$/g, ''); - - newCommit.set('commitMessage', msg); - } - animationFactory.genCommitBirthAnimation(this.animationQueue, newCommit, this.gitVisuals); -}; - -GitEngine.prototype.commit = function() { - var targetCommit = this.getCommitFromRef(this.HEAD); - var id = undefined; - - // if we want to ammend, go one above - if (this.commandOptions['--amend']) { - targetCommit = this.resolveID('HEAD~1'); - id = this.rebaseAltID(this.getCommitFromRef('HEAD').get('id')); - } - - var newCommit = this.makeCommit([targetCommit], id); - if (this.getDetachedHead()) { - this.command.addWarning('Warning!! Detached HEAD state'); - } - - this.setTargetLocation(this.HEAD, newCommit); - return newCommit; -}; - -GitEngine.prototype.resolveName = function(someRef) { - // first get the obj - var obj = this.resolveID(someRef); - if (obj.get('type') == 'commit') { - return 'commit ' + obj.get('id'); - } - if (obj.get('type') == 'branch') { - return 'branch "' + obj.get('id') + '"'; - } - // we are dealing with HEAD - return this.resolveName(obj.get('target')); -}; - -GitEngine.prototype.resolveID = function(idOrTarget) { - if (idOrTarget === null || idOrTarget === undefined) { - throw new Error('Dont call this with null / undefined'); - } - - if (typeof idOrTarget !== 'string') { - return idOrTarget; - } - return this.resolveStringRef(idOrTarget); -}; - -GitEngine.prototype.resolveStringRef = function(ref) { - if (this.refs[ref]) { - return this.refs[ref]; - } - - // may be something like HEAD~2 or master^^ - var relativeRefs = [ - [/^([a-zA-Z0-9]+)~(\d+)\s*$/, function(matches) { - return parseInt(matches[2]); - }], - [/^([a-zA-Z0-9]+)(\^+)\s*$/, function(matches) { - return matches[2].length; - }] - ]; - - var startRef = null; - var numBack = null; - _.each(relativeRefs, function(config) { - var regex = config[0]; - var parse = config[1]; - if (regex.test(ref)) { - var matches = regex.exec(ref); - numBack = parse(matches); - startRef = matches[1]; - } - }, this); - - if (!startRef) { - throw new GitError({ - msg: 'unknown ref ' + ref - }); - } - if (!this.refs[startRef]) { - throw new GitError({ - msg: 'the ref ' + startRef +' does not exist.' - }); - } - var commit = this.getCommitFromRef(startRef); - - return this.numBackFrom(commit, numBack); -}; - -GitEngine.prototype.getCommitFromRef = function(ref) { - var start = this.resolveID(ref); - - // works for both HEAD and just a single layer. aka branch - while (start.get('type') !== 'commit') { - start = start.get('target'); - } - return start; -}; - -GitEngine.prototype.getType = function(ref) { - return this.resolveID(ref).get('type'); -}; - -GitEngine.prototype.setTargetLocation = function(ref, target) { - if (this.getType(ref) == 'commit') { - // nothing to do - return; - } - - // sets whatever ref is (branch, HEAD, etc) to a target. so if - // you pass in HEAD, and HEAD is pointing to a branch, it will update - // the branch to that commit, not the HEAD - var ref = this.getOneBeforeCommit(ref); - ref.set('target', target); -}; - -GitEngine.prototype.getUpstreamBranchSet = function() { - // this is expensive!! so only call once in a while - var commitToSet = {}; - - var inArray = function(arr, id) { - var found = false; - _.each(arr, function(wrapper) { - if (wrapper.id == id) { - found = true; - } - }); - - return found; - }; - - var bfsSearch = function(commit) { - var set = []; - var pQueue = [commit]; - while (pQueue.length) { - var popped = pQueue.pop(); - set.push(popped.get('id')); - - if (popped.get('parents') && popped.get('parents').length) { - pQueue = pQueue.concat(popped.get('parents')); - } - } - return set; - }; - - this.branchCollection.each(function(branch) { - var set = bfsSearch(branch.get('target')); - _.each(set, function(id) { - commitToSet[id] = commitToSet[id] || []; - - // only add it if it's not there, so hue blending is ok - if (!inArray(commitToSet[id], branch.get('id'))) { - commitToSet[id].push({ - obj: branch, - id: branch.get('id') - }); - } - }); - }); - - return commitToSet; -}; - -GitEngine.prototype.getUpstreamHeadSet = function() { - var set = this.getUpstreamSet('HEAD'); - var including = this.getCommitFromRef('HEAD').get('id'); - - set[including] = true; - return set; -}; - -GitEngine.prototype.getOneBeforeCommit = function(ref) { - // you can call this command on HEAD in detached, HEAD, or on a branch - // and it will return the ref that is one above a commit. aka - // it resolves HEAD to something that we can move the ref with - var start = this.resolveID(ref); - if (start === this.HEAD && !this.getDetachedHead()) { - start = start.get('target'); - } - return start; -}; - -GitEngine.prototype.numBackFrom = function(commit, numBack) { - // going back '3' from a given ref is not trivial, for you might have - // a bunch of merge commits and such. like this situation: - // - // * merge master into new - // |\ - // | \* commit here - // |* \ commit there - // | |* commit here - // \ / - // | * root - // - // - // hence we need to do a BFS search, with the commit date being the - // value to sort off of (rather than just purely the level) - if (numBack == 0) { - return commit; - } - - // we use a special sorting function here that - // prefers the later commits over the earlier ones - var sortQueue = _.bind(function(queue) { - queue.sort(this.idSortFunc); - queue.reverse(); - }, this); - - var pQueue = [].concat(commit.get('parents') || []); - sortQueue(pQueue); - numBack--; - - while (pQueue.length && numBack !== 0) { - var popped = pQueue.shift(0); - var parents = popped.get('parents'); - - if (parents && parents.length) { - pQueue = pQueue.concat(parents); - } - - sortQueue(pQueue); - numBack--; - } - - if (numBack !== 0 || pQueue.length == 0) { - throw new GitError({ - msg: "Sorry, I can't go that many commits back" - }); - } - return pQueue.shift(0); -}; - -GitEngine.prototype.scrapeBaseID = function(id) { - var results = /^C(\d+)/.exec(id); - - if (!results) { - throw new Error('regex failed on ' + id); - } - - return 'C' + results[1]; -}; - -GitEngine.prototype.rebaseAltID = function(id) { - // this function alters an ID to add a quote to the end, - // indicating that it was rebased. it also checks existence - var regexMap = [ - [/^C(\d+)[']{0,2}$/, function(bits) { - // this id can use another quote, so just add it - return bits[0] + "'"; - }], - [/^C(\d+)[']{3}$/, function(bits) { - // here we switch from C''' to C'^4 - return bits[0].slice(0, -3) + "'^4"; - }], - [/^C(\d+)['][^](\d+)$/, function(bits) { - return 'C' + String(bits[1]) + "'^" + String(Number(bits[2]) + 1); - }] - ]; - - for (var i = 0; i < regexMap.length; i++) { - var regex = regexMap[i][0]; - var func = regexMap[i][1]; - var results = regex.exec(id); - if (results) { - var newId = func(results); - // if this id exists, continue down the rabbit hole - if (this.refs[newId]) { - return this.rebaseAltID(newId); - } else { - return newId; - } - } - } - throw new Error('could not modify the id ' + id); -}; - -GitEngine.prototype.idSortFunc = function(cA, cB) { - // commit IDs can come in many forms: - // C4 - // C4' (from a rebase) - // C4'' (from multiple rebases) - // C4'^3 (from a BUNCH of rebases) - - var scale = 1000; - - var regexMap = [ - [/^C(\d+)$/, function(bits) { - // return the 4 from C4 - return scale * bits[1]; - }], - [/^C(\d+)([']+)$/, function(bits) { - // return the 4 from C4, plus the length of the quotes - return scale * bits[1] + bits[2].length; - }], - [/^C(\d+)['][^](\d+)$/, function(bits) { - return scale * bits[1] + Number(bits[2]); - }] - ]; - - var getNumToSort = function(id) { - for (var i = 0; i < regexMap.length; i++) { - var regex = regexMap[i][0]; - var func = regexMap[i][1]; - var results = regex.exec(id); - if (results) { - return func(results); - } - } - throw new Error('Could not parse commit ID ' + id); - } - - return getNumToSort(cA.get('id')) - getNumToSort(cB.get('id')); -}; - -GitEngine.prototype.rebaseInteractiveStarter = function() { - var args = this.commandOptions['-i']; - this.twoArgsImpliedHead(args, ' -i'); - - this.rebaseInteractive(args[0], args[1]); -}; - -GitEngine.prototype.rebaseStarter = function() { - if (this.commandOptions['-i']) { - this.rebaseInteractiveStarter(); - return; - } - - this.twoArgsImpliedHead(this.generalArgs); - - var response = this.rebase(this.generalArgs[0], this.generalArgs[1]); - - if (response === undefined) { - // was a fastforward or already up to date. returning now - // will trigger the refresh animation by not adding anything to - // the animation queue - return; - } - - animationFactory.rebaseAnimation(this.animationQueue, response, this, this.gitVisuals); -}; - -GitEngine.prototype.rebase = function(targetSource, currentLocation) { - // first some conditions - if (this.isUpstreamOf(targetSource, currentLocation)) { - this.command.setResult('Branch already up-to-date'); - - // git for some reason always checks out the branch you are rebasing, - // no matter the result of the rebase - this.checkout(currentLocation); - - // returning instead of throwing makes a tree refresh - return; - } - - if (this.isUpstreamOf(currentLocation, targetSource)) { - // just set the target of this current location to the source - this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource)); - // we need the refresh tree animation to happen, so set the result directly - // instead of throwing - this.command.setResult('Fast-forwarding...'); - - this.checkout(currentLocation); - return; - } - - // now the part of actually rebasing. - // We need to get the downstream set of targetSource first. - // then we BFS from currentLocation, using the downstream set as our stopping point. - // we need to BFS because we need to include all commits below - // pop these commits on top of targetSource and modify their ids with quotes - var stopSet = this.getUpstreamSet(targetSource) - - // now BFS from here on out - var toRebaseRough = []; - var pQueue = [this.getCommitFromRef(currentLocation)]; - - while (pQueue.length) { - var popped = pQueue.pop(); - - // if its in the set, dont add it - if (stopSet[popped.get('id')]) { - continue; - } - - // it's not in the set, so we need to rebase this commit - toRebaseRough.push(popped); - toRebaseRough.sort(this.idSortFunc); - toRebaseRough.reverse(); - // keep searching - pQueue = pQueue.concat(popped.get('parents')); - } - - return this.rebaseFinish(toRebaseRough, stopSet, targetSource, currentLocation); -}; - -GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation) { - // there are a reduced set of checks now, so we can't exactly use parts of the rebase function - // but it will look similar. - - // first if we are upstream of the target - if (this.isUpstreamOf(currentLocation, targetSource)) { - throw new GitError({ - msg: 'Nothing to do... (git throws a "noop" status here); ' + - 'Your source is upstream of your rebase target' - }); - } - - // now get the stop set - var stopSet = this.getUpstreamSet(targetSource); - - var toRebaseRough = []; - // standard BFS - var pQueue = [this.getCommitFromRef(currentLocation)]; - - while (pQueue.length) { - var popped = pQueue.pop(); - - if (stopSet[popped.get('id')]) { - continue; - } - - toRebaseRough.push(popped); - pQueue = pQueue.concat(popped.get('parents')); - pQueue.sort(this.idSortFunc); - } - - // throw our merge's real fast and see if we have anything to do - var toRebase = []; - _.each(toRebaseRough, function(commit) { - if (commit.get('parents').length == 1) { - toRebase.push(commit); - } - }); - - if (!toRebase.length) { - throw new GitError({ - msg: 'No commits to rebase! Everything is a merge commit' - }); - } - - // now do stuff :D since all our validation checks have passed, we are going to defer animation - // and actually launch the dialog - this.animationQueue.set('defer', true); - - var callback = _.bind(function(userSpecifiedRebase) { - // first, they might have dropped everything (annoying) - if (!userSpecifiedRebase.length) { - this.command.setResult('Nothing to do...'); - this.animationQueue.start(); - return; - } - - // finish the rebase crap and animate! - var animationData = this.rebaseFinish(userSpecifiedRebase, {}, targetSource, currentLocation); - animationFactory.rebaseAnimation(this.animationQueue, animationData, this, this.gitVisuals); - this.animationQueue.start(); - }, this); - - new InteractiveRebaseView({ - callback: callback, - toRebase: toRebase, - el: $('#dialogHolder') - }); -}; - -GitEngine.prototype.rebaseFinish = function(toRebaseRough, stopSet, targetSource, currentLocation) { - // now we have the all the commits between currentLocation and the set of target to rebase. - var animationResponse = {}; - animationResponse.destinationBranch = this.resolveID(targetSource); - - // we need to throw out merge commits - var toRebase = []; - _.each(toRebaseRough, function(commit) { - if (commit.get('parents').length == 1) { - toRebase.push(commit); - } - }); - - // we ALSO need to throw out commits that will do the same changes. like - // if the upstream set has a commit C4 and we have C4', we dont rebase the C4' again. - // get this by doing ID scraping - var changesAlreadyMade = {}; - _.each(stopSet, function(val, key) { - changesAlreadyMade[this.scrapeBaseID(key)] = val; // val == true - }, this); - - // now get rid of the commits that will redo same changes - toRebaseRough = toRebase; - toRebase = []; - _.each(toRebaseRough, function(commit) { - var baseID = this.scrapeBaseID(commit.get('id')); - if (!changesAlreadyMade[baseID]) { - toRebase.push(commit); - } - }, this); - - if (!toRebase.length) { - throw new GitError({ - msg: 'No Commits to Rebase! Everything else is merge commits or changes already have been applied' - }); - } - - // now reverse it once more to get it in the right order - toRebase.reverse(); - animationResponse.toRebaseArray = toRebase.slice(0); - - // now pop all of these commits onto targetLocation - var base = this.getCommitFromRef(targetSource); - - // do the rebase, and also maintain all our animation info during this - animationResponse.rebaseSteps = []; - var beforeSnapshot = this.gitVisuals.genSnapshot(); - var afterSnapshot; - _.each(toRebase, function(old) { - var newId = this.rebaseAltID(old.get('id')); - - var newCommit = this.makeCommit([base], newId); - base = newCommit; - - // animation info - afterSnapshot = this.gitVisuals.genSnapshot(); - animationResponse.rebaseSteps.push({ - oldCommit: old, - newCommit: newCommit, - beforeSnapshot: beforeSnapshot, - afterSnapshot: afterSnapshot - }); - beforeSnapshot = afterSnapshot; - }, this); - - if (this.resolveID(currentLocation).get('type') == 'commit') { - // we referenced a commit like git rebase C2 C1, so we have - // to manually check out C1' - - var steps = animationResponse.rebaseSteps; - var newestCommit = steps[steps.length - 1].newCommit; - - this.checkout(newestCommit); - } else { - // now we just need to update the rebased branch is - this.setTargetLocation(currentLocation, base); - this.checkout(currentLocation); - } - - // for animation - return animationResponse; -}; - -GitEngine.prototype.mergeStarter = function() { - this.twoArgsImpliedHead(this.generalArgs); - - var newCommit = this.merge(this.generalArgs[0], this.generalArgs[1]); - - if (newCommit === undefined) { - // its just a fast forwrard - animationFactory.refreshTree(this.animationQueue, this.gitVisuals); - return; - } - - animationFactory.genCommitBirthAnimation(this.animationQueue, newCommit, this.gitVisuals); -}; - -GitEngine.prototype.merge = function(targetSource, currentLocation) { - // first some conditions - if (this.isUpstreamOf(targetSource, currentLocation) || - this.getCommitFromRef(targetSource) === this.getCommitFromRef(currentLocation)) { - throw new CommandResult({ - msg: 'Branch already up-to-date' - }); - } - - if (this.isUpstreamOf(currentLocation, targetSource)) { - // just set the target of this current location to the source - this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource)); - // get fresh animation to happen - this.command.setResult('Fast-forwarding...'); - return; - } - - // now the part of making a merge commit - var parent1 = this.getCommitFromRef(currentLocation); - var parent2 = this.getCommitFromRef(targetSource); - - // we need a fancy commit message - var msg = 'Merge ' + this.resolveName(targetSource) + - ' into ' + this.resolveName(currentLocation); - - // since we specify parent 1 as the first parent, it is the "main" parent - // and the node will be displayed below that branch / commit / whatever - var mergeCommit = this.makeCommit( - [parent1, parent2], - null, - { - commitMessage: msg - } - ); - - this.setTargetLocation(currentLocation, mergeCommit) - return mergeCommit; -}; - -GitEngine.prototype.checkoutStarter = function() { - if (this.commandOptions['-b']) { - // the user is really trying to just make a branch and then switch to it. so first: - var args = this.commandOptions['-b']; - this.twoArgsImpliedHead(args, '-b'); - - var validId = this.validateBranchName(args[0]); - this.branch(validId, args[1]); - this.checkout(validId); - return; - } - - if (this.commandOptions['-']) { - // get the heads last location - var lastPlace = this.HEAD.get('lastLastTarget'); - if (!lastPlace) { - throw new GitError({ - msg: 'Need a previous location to do - switching' - }); - } - this.HEAD.set('target', lastPlace); - return; - } - - if (this.commandOptions['-B']) { - var args = this.commandOptions['-B']; - this.twoArgsImpliedHead(args, '-B'); - - this.forceBranch(args[0], args[1]); - this.checkout(args[0]); - return; - } - - this.validateArgBounds(this.generalArgs, 1, 1); - - this.checkout(this.unescapeQuotes(this.generalArgs[0])); -}; - -GitEngine.prototype.checkout = function(idOrTarget) { - var target = this.resolveID(idOrTarget); - if (target.get('id') === 'HEAD') { - // git checkout HEAD is a - // meaningless command but i used to do this back in the day - return; - } - - var type = target.get('type'); - if (type !== 'branch' && type !== 'commit') { - throw new GitError({ - msg: 'can only checkout branches and commits!' - }); - } - - this.HEAD.set('target', target); -}; - -GitEngine.prototype.branchStarter = function() { - // handle deletion first - if (this.commandOptions['-d'] || this.commandOptions['-D']) { - var names = this.commandOptions['-d'] || this.commandOptions['-D']; - this.validateArgBounds(names, 1, NaN, '-d'); - - _.each(names, function(name) { - this.deleteBranch(name); - }, this); - return; - } - - if (this.commandOptions['--contains']) { - var args = this.commandOptions['--contains']; - this.validateArgBounds(args, 1, 1, '--contains'); - this.printBranchesWithout(args[0]); - return; - } - - if (this.commandOptions['-f']) { - var args = this.commandOptions['-f']; - this.twoArgsImpliedHead(args, '-f'); - - // we want to force a branch somewhere - this.forceBranch(args[0], args[1]); - return; - } - - - if (this.generalArgs.length == 0) { - this.printBranches(this.getBranches()); - return; - } - - this.twoArgsImpliedHead(this.generalArgs); - this.branch(this.generalArgs[0], this.generalArgs[1]); -}; - -GitEngine.prototype.forceBranch = function(branchName, where) { - // if branchname doesn't exist... - if (!this.refs[branchName]) { - this.branch(branchName, where); - } - - var branch = this.resolveID(branchName); - if (branch.get('type') !== 'branch') { - throw new GitError({ - msg: "Can't force move anything but a branch!!" - }); - } - - var whereCommit = this.getCommitFromRef(where); - - this.setTargetLocation(branch, whereCommit); -}; - -GitEngine.prototype.branch = function(name, ref) { - var target = this.getCommitFromRef(ref); - this.makeBranch(name, target); -}; - -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({ - msg: "You can't delete things that arent branches with branch command" - }); - } - if (target.get('id') == 'master') { - throw new GitError({ - msg: "You can't delete the master branch!" - }); - } - if (this.HEAD.get('target') === target) { - throw new GitError({ - msg: "Cannot delete the branch you are currently on" - }); - } - - // now we know it's a branch - var branch = target; - - this.branchCollection.remove(branch); - this.refs[branch.get('id')] = undefined; - delete this.refs[branch.get('id')]; - - if (branch.get('visBranch')) { - branch.get('visBranch').remove(); - } -}; - -GitEngine.prototype.unescapeQuotes = function(str) { - return str.replace(/'/g, "'"); -} - -GitEngine.prototype.dispatch = function(command, callback) { - // current command, options, and args are stored in the gitEngine - // for easy reference during processing. - this.command = command; - this.commandOptions = command.get('supportedMap'); - this.generalArgs = command.get('generalArgs'); - - // set up the animation queue - var whenDone = _.bind(function() { - command.set('status', 'finished'); - callback(); - }, this); - this.animationQueue = new AnimationQueue({ - callback: whenDone - }); - - command.set('status', 'processing'); - try { - var methodName = command.get('method').replace(/-/g, '') + 'Starter'; - this[methodName](); - } catch (err) { - if (err instanceof GitError || - err instanceof CommandResult) { - // short circuit animation by just setting error and returning - command.set('error', err); - callback(); - return; - } else { - throw err; - } - } - - // only add the refresh if we didn't do manual animations - if (!this.animationQueue.get('animations').length && !this.animationQueue.get('defer')) { - animationFactory.refreshTree(this.animationQueue, this.gitVisuals); - } - - // animation queue will call the callback when its done - if (!this.animationQueue.get('defer')) { - this.animationQueue.start(); - } -}; - -GitEngine.prototype.showStarter = function() { - this.oneArgImpliedHead(this.generalArgs); - - this.show(this.generalArgs[0]); -}; - -GitEngine.prototype.show = function(ref) { - var commit = this.getCommitFromRef(ref); - - throw new CommandResult({ - msg: commit.getShowEntry() - }); -}; - -GitEngine.prototype.statusStarter = function() { - var lines = []; - if (this.getDetachedHead()) { - lines.push('Detached Head!'); - } else { - var branchName = this.HEAD.get('target').get('id'); - lines.push('On branch ' + branchName); - } - lines.push('Changes to be committed:'); - lines.push(''); - lines.push('    modified: cal/OskiCostume.stl'); - lines.push(''); - lines.push('Ready to commit! (as always in this demo)'); - - var msg = ''; - _.each(lines, function(line) { - msg += '# ' + line + '\n'; - }); - - throw new CommandResult({ - msg: msg - }); -}; - -GitEngine.prototype.logStarter = function() { - if (this.generalArgs.length == 2) { - // do fancy git log branchA ^branchB - if (this.generalArgs[1][0] == '^') { - this.logWithout(this.generalArgs[0], this.generalArgs[1]); - } else { - throw new GitError({ - msg: 'I need a not branch (^branchName) when getting two arguments!' - }); - } - } - - this.oneArgImpliedHead(this.generalArgs); - this.log(this.generalArgs[0]); -}; - -GitEngine.prototype.logWithout = function(ref, omitBranch) { - // slice off the ^branch - omitBranch = omitBranch.slice(1); - this.log(ref, this.getUpstreamSet(omitBranch)); -}; - -GitEngine.prototype.log = function(ref, omitSet) { - // omit set is for doing stuff like git log branchA ^branchB - 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 - var toDump = []; - var pQueue = [commit]; - - var seen = {}; - - while (pQueue.length) { - var popped = pQueue.shift(0); - 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 - var bigLogStr = ''; - _.each(toDump, function(c) { - bigLogStr += c.getLogEntry(); - }, this); - - throw new CommandResult({ - msg: bigLogStr - }); -}; - -GitEngine.prototype.addStarter = function() { - 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.getCommonAncestor = function(ancestor, cousin) { - if (this.isUpstreamOf(cousin, ancestor)) { - throw new Error('Dont use common ancestor if we are upstream!'); - } - - var upstreamSet = this.getUpstreamSet(ancestor); - // now BFS off of cousin until you find something - - var queue = [this.getCommitFromRef(cousin)]; - while (queue.length) { - var here = queue.pop(); - if (upstreamSet[here.get('id')]) { - return here; - } - queue = queue.concat(here.get('parents')); - } - throw new Error('something has gone very wrong... two nodes arent connected!'); -}; - -GitEngine.prototype.isUpstreamOf = function(child, ancestor) { - child = this.getCommitFromRef(child); - - // basically just do a completely BFS search on ancestor to the root, then - // check for membership of child in that set of explored nodes - var upstream = this.getUpstreamSet(ancestor); - return upstream[child.get('id')] !== undefined; -}; - -GitEngine.prototype.getUpstreamSet = function(ancestor) { - var commit = this.getCommitFromRef(ancestor); - var ancestorID = commit.get('id'); - var queue = [commit]; - - var exploredSet = {}; - exploredSet[ancestorID] = true; - while (queue.length) { - var here = queue.pop(); - var rents = here.get('parents'); - - _.each(rents, function(rent) { - exploredSet[rent.get('id')] = true; - queue.push(rent); - }); - } - return exploredSet; -}; - - -var Ref = Backbone.Model.extend({ - initialize: function() { - if (!this.get('target')) { - throw new Error('must be initialized with target'); - } - if (!this.get('id')) { - throw new Error('must be given an id'); - } - this.set('type', 'general ref'); - - if (this.get('id') == 'HEAD') { - this.set('lastLastTarget', null); - this.set('lastTarget', this.get('target')); - // have HEAD remember where it is for checkout - - this.on('change:target', this.targetChanged, this); - } - }, - - targetChanged: function(model, targetValue, ev) { - // push our little 3 stack back. we need to do this because - // backbone doesn't give you what the value WAS, only what it was changed - // TO - this.set('lastLastTarget', this.get('lastTarget')); - this.set('lastTarget', targetValue); - }, - - toString: function() { - return 'a ' + this.get('type') + 'pointing to ' + String(this.get('target')); - } -}); - -var Branch = Ref.extend({ - defaults: { - visBranch: null, - }, - - initialize: function() { - Ref.prototype.initialize.call(this); - this.set('type', 'branch'); - } -}); - -var Commit = Backbone.Model.extend({ - defaults: { - type: 'commit', - children: null, - parents: null, - author: 'Peter Cottle', - createTime: null, - commitMessage: null, - visNode: null, - gitVisuals: null - }, - - constants: { - circularFields: ['gitVisuals', 'visNode', 'children'] - }, - - getLogEntry: function() { - // for now we are just joining all these things with newlines which - // will get placed by paragraph tags. Not really a fan of this, but - // it's better than making an entire template and all that jazz - return [ - 'Author: ' + this.get('author'), - 'Date: ' + this.get('createTime'), - '
', - this.get('commitMessage'), - '
', - 'Commit: ' + this.get('id') - ].join('\n' ) + '\n'; - }, - - getShowEntry: function() { - // same deal as above, show log entry and some fake changes - return [ - this.getLogEntry(), - 'diff --git a/bigGameResults.html b/bigGameResults.html', - '--- bigGameResults.html', - '+++ bigGameResults.html', - '@@ 13,27 @@ Winner, Score', - '- Stanfurd, 14-7', - '+ Cal, 21-14', - ].join('\n') + '\n'; - }, - - validateAtInit: function() { - if (!this.get('id')) { - throw new Error('Need ID!!'); - } - - if (!this.get('createTime')) { - this.set('createTime', new Date().toString()); - } - if (!this.get('commitMessage')) { - this.set('commitMessage', 'Quick Commit. Go Bears!'); - } - - this.set('children', []); - - // root commits have no parents - if (!this.get('rootCommit')) { - if (!this.get('parents') || !this.get('parents').length) { - throw new Error('needs parents'); - } - } - }, - - addNodeToVisuals: function() { - var visNode = this.get('gitVisuals').addNode(this.get('id'), this); - this.set('visNode', visNode); - }, - - addEdgeToVisuals: function(parent) { - this.get('gitVisuals').addEdge(this.get('id'), parent.get('id')); - }, - - isMainParent: function(parent) { - var index = this.get('parents').indexOf(parent); - return index === 0; - }, - - initialize: function(options) { - this.validateAtInit(); - this.addNodeToVisuals(); - - _.each(this.get('parents'), function(parent) { - parent.get('children').push(this); - this.addEdgeToVisuals(parent); - }, this); - } -}); - -exports.GitEngine = GitEngine; -exports.Commit = Commit; -exports.Branch = Branch; -exports.Ref = Ref; - - -}); -require("/git.js"); - -require.define("/collections.js",function(require,module,exports,__dirname,__filename,process,global){var Commit = require('./git').Commit; -var Branch = require('./git').Branch; - -var CommitCollection = Backbone.Collection.extend({ - model: Commit -}); - -var CommandCollection = Backbone.Collection.extend({ - model: Command, -}); - -var BranchCollection = Backbone.Collection.extend({ - model: Branch -}); - -var CommandEntryCollection = Backbone.Collection.extend({ - model: CommandEntry, - localStorage: new Backbone.LocalStorage('CommandEntries') -}); - -var CommandBuffer = Backbone.Model.extend({ - defaults: { - collection: null, - }, - - initialize: function(options) { - events.on('gitCommandReady', _.bind( - this.addCommand, this - )); - - options.collection.bind('add', this.addCommand, this); - - this.buffer = []; - this.timeout = null; - }, - - addCommand: function(command) { - this.buffer.push(command); - this.touchBuffer(); - }, - - touchBuffer: function() { - // touch buffer just essentially means we just check if our buffer is being - // processed. if it's not, we immediately process the first item - // and then set the timeout. - if (this.timeout) { - // timeout existence implies its being processed - return; - } - this.setTimeout(); - }, - - - setTimeout: function() { - this.timeout = setTimeout(_.bind(function() { - this.sipFromBuffer(); - }, this), TIME.betweenCommandsDelay); - }, - - popAndProcess: function() { - var popped = this.buffer.shift(0); - var callback = _.bind(function() { - this.setTimeout(); - }, this); - - // find a command with no error - while (popped.get('error') && this.buffer.length) { - popped = this.buffer.pop(); - } - if (!popped.get('error')) { - // pass in a callback, so when this command is "done" we will process the next. - events.trigger('processCommand', popped, callback); - } else { - this.clear(); - } - }, - - clear: function() { - clearTimeout(this.timeout); - this.timeout = null; - }, - - sipFromBuffer: function() { - if (!this.buffer.length) { - this.clear(); - return; - } - - this.popAndProcess(); - }, -}); - -exports.CommitCollection = CommitCollection; -exports.CommandCollection = CommandCollection; -exports.BranchCollection = BranchCollection; -exports.CommandEntryCollection = CommandEntryCollection; -exports.CommandBuffer = CommandBuffer; - - -}); -require("/collections.js"); - -require.define("/commandViews.js",function(require,module,exports,__dirname,__filename,process,global){var CommandEntryCollection = require('./collections').CommandEntryCollection; - -var CommandPromptView = Backbone.View.extend({ - initialize: function(options) { - this.collection = options.collection; - - // uses local storage - this.commands = new CommandEntryCollection(); - this.commands.fetch({ - success: _.bind(function() { - // reverse the commands. this is ugly but needs to be done... - var commands = []; - this.commands.each(function(c) { - commands.push(c); - }); - - commands.reverse(); - this.commands.reset(); - - _.each(commands, function(c) { - this.commands.add(c); - }, this); - }, this) - }); - - this.index = -1; - - this.commandSpan = this.$('#prompt span.command')[0]; - this.commandCursor = this.$('#prompt span.cursor')[0]; - - // this is evil, but we will refer to HTML outside the document - // and attach a click event listener so we can focus / unfocus - $(document).delegate('#commandLineHistory', 'click', _.bind(function() { - this.focus(); - }, this)); - - - $(document).delegate('#commandTextField', 'blur', _.bind(function() { - this.blur(); - }, this)); - - events.on('processCommandFromEvent', this.addToCollection, this); - events.on('submitCommandValueFromEvent', this.submitValue, this); - events.on('rollupCommands', this.rollupCommands, this); - - // hacky timeout focus - setTimeout(_.bind(function() { - this.focus(); - }, this), 100); - }, - - events: { - 'keydown #commandTextField': 'onKey', - 'keyup #commandTextField': 'onKeyUp', - 'blur #commandTextField': 'hideCursor', - 'focus #commandTextField': 'showCursor' - }, - - blur: function() { - $(this.commandCursor).toggleClass('shown', false); - }, - - focus: function() { - this.$('#commandTextField').focus(); - this.showCursor(); - }, - - hideCursor: function() { - this.toggleCursor(false); - }, - - showCursor: function() { - this.toggleCursor(true); - }, - - toggleCursor: function(state) { - $(this.commandCursor).toggleClass('shown', state); - }, - - onKey: function(e) { - var el = e.srcElement; - this.updatePrompt(el) - }, - - onKeyUp: function(e) { - this.onKey(e); - - // we need to capture some of these events. - // WARNING: this key map is not internationalized :( - var keyMap = { - // enter - 13: _.bind(function() { - this.submit(); - }, this), - // up - 38: _.bind(function() { - this.commandSelectChange(1); - }, this), - // down - 40: _.bind(function() { - this.commandSelectChange(-1); - }, this) - }; - - if (keyMap[e.which] !== undefined) { - e.preventDefault(); - keyMap[e.which](); - this.onKey(e); - } - }, - - badHtmlEncode: function(text) { - return text.replace(/&/g,'&') - .replace(/= this.commands.length || this.index < 0) { - this.clear(); - this.index = -1; - return; - } - - // yay! we actually can display something - var commandEntry = this.commands.toArray()[this.index].get('text'); - this.setTextField(commandEntry); - }, - - clearLocalStorage: function() { - this.commands.each(function(c) { - Backbone.sync('delete', c, function() { }); - }, this); - localStorage.setItem('CommandEntries', ''); - }, - - setTextField: function(value) { - this.$('#commandTextField').val(value); - }, - - clear: function() { - this.setTextField(''); - }, - - submit: function() { - var value = this.$('#commandTextField').val().replace('\n', ''); - this.clear(); - this.submitValue(value); - }, - - rollupCommands: function(numBack) { - var which = this.commands.toArray().slice(1, Number(numBack) + 1); - which.reverse(); - - var str = ''; - _.each(which, function(commandEntry) { - str += commandEntry.get('text') + ';'; - }, this); - - console.log('the str', str); - - var rolled = new CommandEntry({text: str}); - this.commands.unshift(rolled); - Backbone.sync('create', rolled, function() { }); - }, - - submitValue: function(value) { - // we should add if it's not a blank line and this is a new command... - // or if we edited the command - var shouldAdd = (value.length && this.index == -1) || - ((value.length && this.index !== -1 && - this.commands.toArray()[this.index].get('text') !== value)); - - if (shouldAdd) { - var commandEntry = new CommandEntry({text: value}); - this.commands.unshift(commandEntry); - - // store to local storage - Backbone.sync('create', commandEntry, function() { }); - - // if our length is too egregious, reset - if (this.commands.length > 100) { - this.clearLocalStorage(); - } - } - this.index = -1; - - // split commands on semicolon - _.each(value.split(';'), _.bind(function(command, index) { - command = _.escape(command); - - command = command - .replace(/^(\s+)/, '') - .replace(/(\s+)$/, '') - .replace(/"/g, '"') - .replace(/'/g, "'"); - - if (index > 0 && !command.length) { - return; - } - - this.addToCollection(command); - }, this)); - }, - - addToCollection: function(value) { - var command = new Command({ - rawStr: value - }); - this.collection.add(command); - } -}); - - -// This is the view for all commands -- it will represent -// their status (inqueue, processing, finished, error), -// their value ("git commit --amend"), -// and the result (either errors or warnings or whatever) -var CommandView = Backbone.View.extend({ - tagName: 'div', - model: Command, - template: _.template($('#command-template').html()), - - events: { - 'click': 'clicked' - }, - - clicked: function(e) { - }, - - initialize: function() { - this.model.bind('change', this.wasChanged, this); - this.model.bind('destroy', this.remove, this); - }, - - wasChanged: function(model, changeEvent) { - // for changes that are just comestic, we actually only want to toggle classes - // with jquery rather than brutally delete a html of HTML - var changes = changeEvent.changes; - var changeKeys = _.keys(changes); - if (_.difference(changeKeys, ['status']) == 0) { - this.updateStatus(); - } else if (_.difference(changeKeys, ['error']) == 0) { - // the above will - this.render(); - } else { - this.render(); - } - }, - - updateStatus: function() { - var statuses = ['inqueue', 'processing', 'finished']; - var toggleMap = {}; - _.each(statuses, function(status) { - toggleMap[status] = false; - }); - toggleMap[this.model.get('status')] = true; - - var query = this.$('p.commandLine'); - - _.each(toggleMap, function(value, key) { - query.toggleClass(key, value); - }); - }, - - render: function() { - var json = _.extend( - { - resultType: '', - result: '', - formattedWarnings: this.model.getFormattedWarnings() - }, - this.model.toJSON() - ); - - this.$el.html(this.template(json)); - return this; - }, - - remove: function() { - $(this.el).hide(); - } -}); - - -var CommandLineHistoryView = Backbone.View.extend({ - initialize: function(options) { - this.collection = options.collection; - - this.collection.on('add', this.addOne, this); - this.collection.on('reset', this.addAll, this); - this.collection.on('all', this.render, this); - - this.collection.on('change', this.scrollDown, this); - - events.on('issueWarning', this.addWarning, this); - events.on('commandScrollDown', this.scrollDown, this); - }, - - addWarning: function(msg) { - var err = new Warning({ - msg: msg - }); - - var command = new Command({ - error: err, - rawStr: 'Warning:' - }); - - this.collection.add(command); - }, - - scrollDown: function() { - // if commandDisplay is ever bigger than #terminal, we need to - // add overflow-y to terminal and scroll down - var cD = $('#commandDisplay')[0]; - var t = $('#terminal')[0]; - - if ($(t).hasClass('scrolling')) { - t.scrollTop = t.scrollHeight; - return; - } - if (cD.clientHeight > t.clientHeight) { - $(t).css('overflow-y', 'scroll'); - $(t).css('overflow-x', 'hidden'); - $(t).addClass('scrolling'); - t.scrollTop = t.scrollHeight; - } - }, - - addOne: function(command) { - var view = new CommandView({ - model: command - }); - this.$('#commandDisplay').append(view.render().el); - this.scrollDown(); - }, - - addAll: function() { - this.collection.each(this.addOne); - } -}); - -exports.CommandPromptView = CommandPromptView; -exports.CommandLineHistoryView = CommandLineHistoryView; - - -}); -require("/commandViews.js"); - -require.define("/visuals.js",function(require,module,exports,__dirname,__filename,process,global){var CommitCollection = require('./collections').CommitCollection; -var BranchCollection = require('./collections').BranchCollection; -var GitEngine = require('./git').GitEngine; - -var Visualization = Backbone.View.extend({ - initialize: function(options) { - var _this = this; - Raphael(10, 10, 200, 200, function() { - - // for some reason raphael calls this function with a predefined - // context... - // so switch it - _this.paperInitialize(this); - }); - }, - - paperInitialize: function(paper, options) { - this.paper = paper; - - this.commitCollection = new CommitCollection(); - this.branchCollection = new BranchCollection(); - - this.gitVisuals = new GitVisuals({ - commitCollection: this.commitCollection, - branchCollection: this.branchCollection, - paper: this.paper - }); - - this.gitEngine = new GitEngine({ - collection: this.commitCollection, - branches: this.branchCollection, - gitVisuals: this.gitVisuals - }); - this.gitEngine.init(); - this.gitVisuals.assignGitEngine(this.gitEngine); - - this.myResize(); - $(window).on('resize', _.bind(this.myResize, this)); - this.gitVisuals.drawTreeFirstTime(); - }, - - myResize: function() { - var smaller = 1; - var el = this.el; - - var left = el.offsetLeft; - var top = el.offsetTop; - var width = el.clientWidth - smaller; - var height = el.clientHeight - smaller; - - $(this.paper.canvas).css({ - left: left + 'px', - top: top + 'px' - }); - this.paper.setSize(width, height); - this.gitVisuals.canvasResize(width, height); - } - -}); - -function GitVisuals(options) { - this.commitCollection = options.commitCollection; - this.branchCollection = options.branchCollection; - this.visNodeMap = {}; - - this.visEdgeCollection = new VisEdgeCollection(); - this.visBranchCollection = new VisBranchCollection(); - this.commitMap = {}; - - this.rootCommit = null; - this.branchStackMap = null; - this.upstreamBranchSet = null; - this.upstreamHeadSet = null; - - this.paper = options.paper; - this.gitReady = false; - - this.branchCollection.on('add', this.addBranchFromEvent, this); - this.branchCollection.on('remove', this.removeBranch, this); - this.deferred = []; - - events.on('refreshTree', _.bind( - this.refreshTree, this - )); -} - -GitVisuals.prototype.defer = function(action) { - this.deferred.push(action); -}; - -GitVisuals.prototype.deferFlush = function() { - _.each(this.deferred, function(action) { - action(); - }, this); - this.deferred = []; -}; - -GitVisuals.prototype.resetAll = function() { - // make sure to copy these collections because we remove - // items in place and underscore is too dumb to detect length change - var edges = this.visEdgeCollection.toArray(); - _.each(edges, function(visEdge) { - visEdge.remove(); - }, this); - - var branches = this.visBranchCollection.toArray(); - _.each(branches, function(visBranch) { - visBranch.remove(); - }, this); - - _.each(this.visNodeMap, function(visNode) { - visNode.remove(); - }, this); - - this.visEdgeCollection.reset(); - this.visBranchCollection.reset(); - - this.visNodeMap = {}; - this.rootCommit = null; - this.commitMap = {}; -}; - -GitVisuals.prototype.assignGitEngine = function(gitEngine) { - this.gitEngine = gitEngine; - this.initHeadBranch(); - this.deferFlush(); -}; - -GitVisuals.prototype.initHeadBranch = function() { - // it's unfortaunte we have to do this, but the head branch - // is an edge case because it's not part of a collection so - // we can't use events to load or unload it. thus we have to call - // this ugly method which will be deleted one day - - // seed this with the HEAD pseudo-branch - this.addBranchFromEvent(this.gitEngine.HEAD); -}; - -GitVisuals.prototype.getScreenBounds = function() { - // for now we return the node radius subtracted from the walls - return { - widthPadding: GRAPHICS.nodeRadius * 1.5, - heightPadding: GRAPHICS.nodeRadius * 1.5 - }; -}; - -GitVisuals.prototype.toScreenCoords = function(pos) { - if (!this.paper.width) { - throw new Error('being called too early for screen coords'); - } - var bounds = this.getScreenBounds(); - - var shrink = function(frac, total, padding) { - return padding + frac * (total - padding * 2); - }; - - return { - x: shrink(pos.x, this.paper.width, bounds.widthPadding), - y: shrink(pos.y, this.paper.height, bounds.heightPadding) - }; -}; - -GitVisuals.prototype.animateAllFromAttrToAttr = function(fromSnapshot, toSnapshot, idsToOmit) { - var animate = function(obj) { - var id = obj.getID(); - if (_.include(idsToOmit, id)) { - return; - } - - if (!fromSnapshot[id] || !toSnapshot[id]) { - // its actually ok it doesnt exist yet - return; - } - obj.animateFromAttrToAttr(fromSnapshot[id], toSnapshot[id]); - }; - - this.visBranchCollection.each(function(visBranch) { - animate(visBranch); - }); - this.visEdgeCollection.each(function(visEdge) { - animate(visEdge); - }); - _.each(this.visNodeMap, function(visNode) { - animate(visNode); - }); -}; - -/*************************************** - == BEGIN Tree Calculation Parts == - _ __ __ _ - \\/ / \ \//_ - \ \ / __| __ - \ \___/ /_____/ / - | _______ \ - \ ( ) / \_\ - \ / - | | - | | - ____+-_=+-^ ^+-=_=__________ - -^^ I drew that :D - - **************************************/ - -GitVisuals.prototype.genSnapshot = function() { - this.fullCalc(); - - var snapshot = {}; - _.each(this.visNodeMap, function(visNode) { - snapshot[visNode.get('id')] = visNode.getAttributes(); - }, this); - - this.visBranchCollection.each(function(visBranch) { - snapshot[visBranch.getID()] = visBranch.getAttributes(); - }, this); - - this.visEdgeCollection.each(function(visEdge) { - snapshot[visEdge.getID()] = visEdge.getAttributes(); - }, this); - - return snapshot; -}; - -GitVisuals.prototype.refreshTree = function(speed) { - if (!this.gitReady) { - return; - } - - // this method can only be called after graphics are rendered - this.fullCalc(); - - this.animateAll(speed); -}; - -GitVisuals.prototype.refreshTreeHarsh = function() { - this.fullCalc(); - - this.animateAll(0); -}; - -GitVisuals.prototype.animateAll = function(speed) { - this.zIndexReflow(); - - this.animateEdges(speed); - this.animateNodePositions(speed); - this.animateRefs(speed); -}; - -GitVisuals.prototype.fullCalc = function() { - this.calcTreeCoords(); - this.calcGraphicsCoords(); -}; - -GitVisuals.prototype.calcTreeCoords = function() { - // this method can only contain things that dont rely on graphics - if (!this.rootCommit) { - throw new Error('grr, no root commit!'); - } - - this.calcUpstreamSets(); - this.calcBranchStacks(); - - this.calcDepth(); - this.calcWidth(); -}; - -GitVisuals.prototype.calcGraphicsCoords = function() { - this.visBranchCollection.each(function(visBranch) { - visBranch.updateName(); - }); -}; - -GitVisuals.prototype.calcUpstreamSets = function() { - this.upstreamBranchSet = this.gitEngine.getUpstreamBranchSet(); - this.upstreamHeadSet = this.gitEngine.getUpstreamHeadSet(); -}; - -GitVisuals.prototype.getCommitUpstreamBranches = function(commit) { - return this.branchStackMap[commit.get('id')]; -}; - -GitVisuals.prototype.getBlendedHuesForCommit = function(commit) { - var branches = this.upstreamBranchSet[commit.get('id')]; - if (!branches) { - throw new Error('that commit doesnt have upstream branches!'); - } - - return this.blendHuesFromBranchStack(branches); -}; - -GitVisuals.prototype.blendHuesFromBranchStack = function(branchStackArray) { - var hueStrings = []; - _.each(branchStackArray, function(branchWrapper) { - var fill = branchWrapper.obj.get('visBranch').get('fill'); - - if (fill.slice(0,3) !== 'hsb') { - // crap! convert - var color = Raphael.color(fill); - fill = 'hsb(' + String(color.h) + ',' + String(color.l); - fill = fill + ',' + String(color.s) + ')'; - } - - hueStrings.push(fill); - }); - - return blendHueStrings(hueStrings); -}; - -GitVisuals.prototype.getCommitUpstreamStatus = function(commit) { - if (!this.upstreamBranchSet) { - throw new Error("Can't calculate this yet!"); - } - - var id = commit.get('id'); - var branch = this.upstreamBranchSet; - var head = this.upstreamHeadSet; - - if (branch[id]) { - return 'branch'; - } else if (head[id]) { - return 'head'; - } else { - return 'none'; - } -}; - -GitVisuals.prototype.calcBranchStacks = function() { - var branches = this.gitEngine.getBranches(); - var map = {}; - _.each(branches, function(branch) { - var thisId = branch.target.get('id'); - - map[thisId] = map[thisId] || []; - map[thisId].push(branch); - map[thisId].sort(function(a, b) { - var aId = a.obj.get('id'); - var bId = b.obj.get('id'); - if (aId == 'master' || bId == 'master') { - return aId == 'master' ? -1 : 1; - } - return aId.localeCompare(bId); - }); - }); - this.branchStackMap = map; -}; - -GitVisuals.prototype.calcWidth = function() { - this.maxWidthRecursive(this.rootCommit); - - this.assignBoundsRecursive(this.rootCommit, 0, 1); -}; - -GitVisuals.prototype.maxWidthRecursive = function(commit) { - var childrenTotalWidth = 0; - _.each(commit.get('children'), function(child) { - // only include this if we are the "main" parent of - // this child - if (child.isMainParent(commit)) { - var childWidth = this.maxWidthRecursive(child); - childrenTotalWidth += childWidth; - } - }, this); - - var maxWidth = Math.max(1, childrenTotalWidth); - commit.get('visNode').set('maxWidth', maxWidth); - return maxWidth; -}; - -GitVisuals.prototype.assignBoundsRecursive = function(commit, min, max) { - // I always center myself within my bounds - var myWidthPos = (min + max) / 2.0; - commit.get('visNode').get('pos').x = myWidthPos; - - if (commit.get('children').length == 0) { - return; - } - - // i have a certain length to divide up - var myLength = max - min; - // I will divide up that length based on my children's max width in a - // basic box-flex model - var totalFlex = 0; - var children = commit.get('children'); - _.each(children, function(child) { - if (child.isMainParent(commit)) { - totalFlex += child.get('visNode').getMaxWidthScaled(); - } - }, this); - - var prevBound = min; - - // now go through and do everything - // TODO: order so the max width children are in the middle!! - _.each(children, function(child) { - if (!child.isMainParent(commit)) { - return; - } - - var flex = child.get('visNode').getMaxWidthScaled(); - var portion = (flex / totalFlex) * myLength; - var childMin = prevBound; - var childMax = childMin + portion; - this.assignBoundsRecursive(child, childMin, childMax); - prevBound = childMax; - }, this); -}; - -GitVisuals.prototype.calcDepth = function() { - var maxDepth = this.calcDepthRecursive(this.rootCommit, 0); - if (maxDepth > 15) { - // issue warning - console.warn('graphics are degrading from too many layers'); - } - - var depthIncrement = this.getDepthIncrement(maxDepth); - _.each(this.visNodeMap, function(visNode) { - visNode.setDepthBasedOn(depthIncrement); - }, this); -}; - -/*************************************** - == END Tree Calculation == - _ __ __ _ - \\/ / \ \//_ - \ \ / __| __ - \ \___/ /_____/ / - | _______ \ - \ ( ) / \_\ - \ / - | | - | | - ____+-_=+-^ ^+-=_=__________ - -^^ I drew that :D - - **************************************/ - -GitVisuals.prototype.animateNodePositions = function(speed) { - _.each(this.visNodeMap, function(visNode) { - visNode.animateUpdatedPosition(speed); - }, this); -}; - -GitVisuals.prototype.turnOnPaper = function() { - this.gitReady = false; -}; - -// does making an accessor method make it any less hacky? that is the true question -GitVisuals.prototype.turnOffPaper = function() { - this.gitReady = true; -}; - -GitVisuals.prototype.addBranchFromEvent = function(branch, collection, index) { - var action = _.bind(function() { - this.addBranch(branch); - }, this); - - if (!this.gitEngine || !this.gitReady) { - this.defer(action); - } else { - action(); - } -}; - -GitVisuals.prototype.addBranch = function(branch) { - var visBranch = new VisBranch({ - branch: branch, - gitVisuals: this, - gitEngine: this.gitEngine - }); - - this.visBranchCollection.add(visBranch); - if (this.gitReady) { - visBranch.genGraphics(this.paper); - } -}; - -GitVisuals.prototype.removeVisBranch = function(visBranch) { - this.visBranchCollection.remove(visBranch); -}; - -GitVisuals.prototype.removeVisNode = function(visNode) { - this.visNodeMap[visNode.getID()] = undefined; -}; - -GitVisuals.prototype.removeVisEdge = function(visEdge) { - this.visEdgeCollection.remove(visEdge); -}; - -GitVisuals.prototype.animateRefs = function(speed) { - this.visBranchCollection.each(function(visBranch) { - visBranch.animateUpdatedPos(speed); - }, this); -}; - -GitVisuals.prototype.animateEdges = function(speed) { - this.visEdgeCollection.each(function(edge) { - edge.animateUpdatedPath(speed); - }, this); -}; - -GitVisuals.prototype.getDepthIncrement = function(maxDepth) { - // assume there are at least 7 layers until later - maxDepth = Math.max(maxDepth, 7); - var increment = 1.0 / maxDepth; - return increment; -}; - -GitVisuals.prototype.calcDepthRecursive = function(commit, depth) { - commit.get('visNode').setDepth(depth); - - var children = commit.get('children'); - var maxDepth = depth; - _.each(children, function(child) { - var d = this.calcDepthRecursive(child, depth + 1); - maxDepth = Math.max(d, maxDepth); - }, this); - - return maxDepth; -}; - -GitVisuals.prototype.canvasResize = function(width, height) { - // refresh when we are ready - if (GLOBAL.isAnimating) { - events.trigger('processCommandFromEvent', 'refresh'); - } else { - this.refreshTree(); - } -}; - -GitVisuals.prototype.addCommit = function(commit) { - // TODO -}; - -GitVisuals.prototype.addNode = function(id, commit) { - this.commitMap[id] = commit; - if (commit.get('rootCommit')) { - this.rootCommit = commit; - } - - var visNode = new VisNode({ - id: id, - commit: commit, - gitVisuals: this, - gitEngine: this.gitEngine - }); - this.visNodeMap[id] = visNode; - - if (this.gitReady) { - visNode.genGraphics(this.paper); - } - return visNode; -}; - -GitVisuals.prototype.addEdge = function(idTail, idHead) { - var visNodeTail = this.visNodeMap[idTail]; - var visNodeHead = this.visNodeMap[idHead]; - - if (!visNodeTail || !visNodeHead) { - throw new Error('one of the ids in (' + idTail + - ', ' + idHead + ') does not exist'); - } - - var edge = new VisEdge({ - tail: visNodeTail, - head: visNodeHead, - gitVisuals: this, - gitEngine: this.gitEngine - }); - this.visEdgeCollection.add(edge); - - if (this.gitReady) { - edge.genGraphics(this.paper); - } -}; - -GitVisuals.prototype.collectionChanged = function() { - // TODO ? -}; - -GitVisuals.prototype.zIndexReflow = function() { - this.visNodesFront(); - this.visBranchesFront(); -}; - -GitVisuals.prototype.visNodesFront = function() { - _.each(this.visNodeMap, function(visNode) { - visNode.toFront(); - }); -}; - -GitVisuals.prototype.visBranchesFront = function() { - this.visBranchCollection.each(function(vBranch) { - vBranch.nonTextToFront(); - }); - - this.visBranchCollection.each(function(vBranch) { - vBranch.textToFront(); - }); -}; - -GitVisuals.prototype.drawTreeFromReload = function() { - this.gitReady = true; - // gen all the graphics we need - this.deferFlush(); - - this.calcTreeCoords(); -}; - -GitVisuals.prototype.drawTreeFirstTime = function() { - this.gitReady = true; - this.calcTreeCoords(); - - _.each(this.visNodeMap, function(visNode) { - visNode.genGraphics(this.paper); - }, this); - - this.visEdgeCollection.each(function(edge) { - edge.genGraphics(this.paper); - }, this); - - this.visBranchCollection.each(function(visBranch) { - visBranch.genGraphics(this.paper); - }, this); - - this.zIndexReflow(); -}; - - -/************************ - * Random util functions, some from liquidGraph - ***********************/ -function blendHueStrings(hueStrings) { - // assumes a sat of 0.7 and brightness of 1 - - var x = 0; - var y = 0; - var totalSat = 0; - var totalBright = 0; - var length = hueStrings.length; - - _.each(hueStrings, function(hueString) { - var exploded = hueString.split('(')[1]; - exploded = exploded.split(')')[0]; - exploded = exploded.split(','); - - totalSat += parseFloat(exploded[1]); - totalBright += parseFloat(exploded[2]); - var hue = parseFloat(exploded[0]); - - var angle = hue * Math.PI * 2; - x += Math.cos(angle); - y += Math.sin(angle); - }); - - x = x / length; - y = y / length; - totalSat = totalSat / length; - totalBright = totalBright / length; - - var hue = Math.atan2(y, x) / (Math.PI * 2); // could fail on 0's - if (hue < 0) { - hue = hue + 1; - } - return 'hsb(' + String(hue) + ',' + String(totalSat) + ',' + String(totalBright) + ')'; -} - -function randomHueString() { - var hue = Math.random(); - var str = 'hsb(' + String(hue) + ',0.7,1)'; - return str; -}; - -exports.Visualization = Visualization; - - -}); -require("/visuals.js"); })(); diff --git a/src/animationFactory.js b/src/animationFactory.js index e9d9bcdb..bc6156c1 100644 --- a/src/animationFactory.js +++ b/src/animationFactory.js @@ -9,7 +9,7 @@ */ // essentially a static class -var AnimationFactory = function() { +function AnimationFactory() { } @@ -251,3 +251,4 @@ AnimationFactory.prototype.genFromToSnapshotAnimation = function( exports.AnimationFactory = AnimationFactory; + diff --git a/src/collections.js b/src/collections.js index 39007a55..4044115c 100644 --- a/src/collections.js +++ b/src/collections.js @@ -1,6 +1,3 @@ -var Commit = require('./git').Commit; -var Branch = require('./git').Branch; - var CommitCollection = Backbone.Collection.extend({ model: Commit }); @@ -90,9 +87,3 @@ var CommandBuffer = Backbone.Model.extend({ }, }); -exports.CommitCollection = CommitCollection; -exports.CommandCollection = CommandCollection; -exports.BranchCollection = BranchCollection; -exports.CommandEntryCollection = CommandEntryCollection; -exports.CommandBuffer = CommandBuffer; - diff --git a/src/commandViews.js b/src/commandViews.js index 69bd36e7..d3cddd28 100644 --- a/src/commandViews.js +++ b/src/commandViews.js @@ -1,5 +1,3 @@ -var CommandEntryCollection = require('./collections').CommandEntryCollection; - var CommandPromptView = Backbone.View.extend({ initialize: function(options) { this.collection = options.collection; @@ -381,7 +379,3 @@ var CommandLineHistoryView = Backbone.View.extend({ this.collection.each(this.addOne); } }); - -exports.CommandPromptView = CommandPromptView; -exports.CommandLineHistoryView = CommandLineHistoryView; - diff --git a/src/git.js b/src/git.js index 95cd4afc..f6059def 100644 --- a/src/git.js +++ b/src/git.js @@ -1,6 +1,3 @@ -var animationFactory = new require('./animationFactory').AnimationFactory(); -console.log('this is what animatioinf actory is', require('./animationFactory')); - // backbone or something uses _.uniqueId, so we make our own here var uniqueId = (function() { var n = 0; @@ -1627,8 +1624,3 @@ var Commit = Backbone.Model.extend({ } }); -exports.GitEngine = GitEngine; -exports.Commit = Commit; -exports.Branch = Branch; -exports.Ref = Ref; - diff --git a/src/index.html b/src/index.html index 08de639d..fb67e28a 100644 --- a/src/index.html +++ b/src/index.html @@ -135,15 +135,15 @@ - + - + - - + + diff --git a/src/main.js b/src/main.js index 978d4867..0f22ab9f 100644 --- a/src/main.js +++ b/src/main.js @@ -1,9 +1,4 @@ var AnimationFactory = require('./animationFactory').AnimationFactory; -var CommandCollection = require('./collections').CommandCollection; -var CommandBuffer = require('./collections').CommandBuffer; -var CommandPromptView = require('./commandViews').CommandPromptView; -var CommandLineHistoryView = require('./commandViews').CommandLineHistoryView; -var Visualization = require('./visuals').Visualization; /** * Globals diff --git a/src/visuals.js b/src/visuals.js index 17dc2ddd..64a1f12b 100644 --- a/src/visuals.js +++ b/src/visuals.js @@ -1,7 +1,3 @@ -var CommitCollection = require('./collections').CommitCollection; -var BranchCollection = require('./collections').BranchCollection; -var GitEngine = require('./git').GitEngine; - var Visualization = Backbone.View.extend({ initialize: function(options) { var _this = this; @@ -671,5 +667,3 @@ function randomHueString() { return str; }; -exports.Visualization = Visualization; - From b9d65eae451f6bdb323a287a7cea13af34060166 Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Sun, 9 Dec 2012 21:16:32 -0800 Subject: [PATCH 4/6] Revert "broken right now on master (i know bad) but im transitioning to require, hold ya horses" This reverts commit d7f3653ad98163393f0e0417f91f6dfa64aecd13. --- build/bundle.js | 965 ---------------------------------------- src/animationFactory.js | 3 - src/index.html | 10 +- src/main.js | 10 +- 4 files changed, 4 insertions(+), 984 deletions(-) delete mode 100644 build/bundle.js diff --git a/build/bundle.js b/build/bundle.js deleted file mode 100644 index 443b9be3..00000000 --- a/build/bundle.js +++ /dev/null @@ -1,965 +0,0 @@ -(function(){var require = function (file, cwd) { - var resolved = require.resolve(file, cwd || '/'); - var mod = require.modules[resolved]; - if (!mod) throw new Error( - 'Failed to resolve module ' + file + ', tried ' + resolved - ); - var cached = require.cache[resolved]; - var res = cached? cached.exports : mod(); - return res; -}; - -require.paths = []; -require.modules = {}; -require.cache = {}; -require.extensions = [".js",".coffee",".json"]; - -require._core = { - 'assert': true, - 'events': true, - 'fs': true, - 'path': true, - 'vm': true -}; - -require.resolve = (function () { - return function (x, cwd) { - if (!cwd) cwd = '/'; - - if (require._core[x]) return x; - var path = require.modules.path(); - cwd = path.resolve('/', cwd); - var y = cwd || '/'; - - if (x.match(/^(?:\.\.?\/|\/)/)) { - var m = loadAsFileSync(path.resolve(y, x)) - || loadAsDirectorySync(path.resolve(y, x)); - if (m) return m; - } - - var n = loadNodeModulesSync(x, y); - if (n) return n; - - throw new Error("Cannot find module '" + x + "'"); - - function loadAsFileSync (x) { - x = path.normalize(x); - if (require.modules[x]) { - return x; - } - - for (var i = 0; i < require.extensions.length; i++) { - var ext = require.extensions[i]; - if (require.modules[x + ext]) return x + ext; - } - } - - function loadAsDirectorySync (x) { - x = x.replace(/\/+$/, ''); - var pkgfile = path.normalize(x + '/package.json'); - if (require.modules[pkgfile]) { - var pkg = require.modules[pkgfile](); - var b = pkg.browserify; - if (typeof b === 'object' && b.main) { - var m = loadAsFileSync(path.resolve(x, b.main)); - if (m) return m; - } - else if (typeof b === 'string') { - var m = loadAsFileSync(path.resolve(x, b)); - if (m) return m; - } - else if (pkg.main) { - var m = loadAsFileSync(path.resolve(x, pkg.main)); - if (m) return m; - } - } - - return loadAsFileSync(x + '/index'); - } - - function loadNodeModulesSync (x, start) { - var dirs = nodeModulesPathsSync(start); - for (var i = 0; i < dirs.length; i++) { - var dir = dirs[i]; - var m = loadAsFileSync(dir + '/' + x); - if (m) return m; - var n = loadAsDirectorySync(dir + '/' + x); - if (n) return n; - } - - var m = loadAsFileSync(x); - if (m) return m; - } - - function nodeModulesPathsSync (start) { - var parts; - if (start === '/') parts = [ '' ]; - else parts = path.normalize(start).split('/'); - - var dirs = []; - for (var i = parts.length - 1; i >= 0; i--) { - if (parts[i] === 'node_modules') continue; - var dir = parts.slice(0, i + 1).join('/') + '/node_modules'; - dirs.push(dir); - } - - return dirs; - } - }; -})(); - -require.alias = function (from, to) { - var path = require.modules.path(); - var res = null; - try { - res = require.resolve(from + '/package.json', '/'); - } - catch (err) { - res = require.resolve(from, '/'); - } - var basedir = path.dirname(res); - - var keys = (Object.keys || function (obj) { - var res = []; - for (var key in obj) res.push(key); - return res; - })(require.modules); - - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (key.slice(0, basedir.length + 1) === basedir + '/') { - var f = key.slice(basedir.length); - require.modules[to + f] = require.modules[basedir + f]; - } - else if (key === basedir) { - require.modules[to] = require.modules[basedir]; - } - } -}; - -(function () { - var process = {}; - var global = typeof window !== 'undefined' ? window : {}; - var definedProcess = false; - - require.define = function (filename, fn) { - if (!definedProcess && require.modules.__browserify_process) { - process = require.modules.__browserify_process(); - definedProcess = true; - } - - var dirname = require._core[filename] - ? '' - : require.modules.path().dirname(filename) - ; - - var require_ = function (file) { - var requiredModule = require(file, dirname); - var cached = require.cache[require.resolve(file, dirname)]; - - if (cached && cached.parent === null) { - cached.parent = module_; - } - - return requiredModule; - }; - require_.resolve = function (name) { - return require.resolve(name, dirname); - }; - require_.modules = require.modules; - require_.define = require.define; - require_.cache = require.cache; - var module_ = { - id : filename, - filename: filename, - exports : {}, - loaded : false, - parent: null - }; - - require.modules[filename] = function () { - require.cache[filename] = module_; - fn.call( - module_.exports, - require_, - module_, - module_.exports, - dirname, - filename, - process, - global - ); - module_.loaded = true; - return module_.exports; - }; - }; -})(); - - -require.define("path",function(require,module,exports,__dirname,__filename,process,global){function filter (xs, fn) { - var res = []; - for (var i = 0; i < xs.length; i++) { - if (fn(xs[i], i, xs)) res.push(xs[i]); - } - return res; -} - -// resolves . and .. elements in a path array with directory names there -// must be no slashes, empty elements, or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length; i >= 0; i--) { - var last = parts[i]; - if (last == '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up--; up) { - parts.unshift('..'); - } - } - - return parts; -} - -// Regex to split a filename into [*, dir, basename, ext] -// posix version -var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; - -// path.resolve([from ...], to) -// posix version -exports.resolve = function() { -var resolvedPath = '', - resolvedAbsolute = false; - -for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) - ? arguments[i] - : process.cwd(); - - // Skip empty and invalid entries - if (typeof path !== 'string' || !path) { - continue; - } - - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; -} - -// At this point the path should be resolved to a full absolute path, but -// handle relative paths to be safe (might happen when process.cwd() fails) - -// Normalize the path -resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { - return !!p; - }), !resolvedAbsolute).join('/'); - - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; -}; - -// path.normalize(path) -// posix version -exports.normalize = function(path) { -var isAbsolute = path.charAt(0) === '/', - trailingSlash = path.slice(-1) === '/'; - -// Normalize the path -path = normalizeArray(filter(path.split('/'), function(p) { - return !!p; - }), !isAbsolute).join('/'); - - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - - return (isAbsolute ? '/' : '') + path; -}; - - -// posix version -exports.join = function() { - var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(filter(paths, function(p, index) { - return p && typeof p === 'string'; - }).join('/')); -}; - - -exports.dirname = function(path) { - var dir = splitPathRe.exec(path)[1] || ''; - var isWindows = false; - if (!dir) { - // No dirname - return '.'; - } else if (dir.length === 1 || - (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) { - // It is just a slash or a drive letter with a slash - return dir; - } else { - // It is a full dirname, strip trailing slash - return dir.substring(0, dir.length - 1); - } -}; - - -exports.basename = function(path, ext) { - var f = splitPathRe.exec(path)[2] || ''; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; - - -exports.extname = function(path) { - return splitPathRe.exec(path)[3] || ''; -}; - -}); - -require.define("__browserify_process",function(require,module,exports,__dirname,__filename,process,global){var process = module.exports = {}; - -process.nextTick = (function () { - var canSetImmediate = typeof window !== 'undefined' - && window.setImmediate; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; - - if (canSetImmediate) { - return function (f) { return window.setImmediate(f) }; - } - - if (canPost) { - var queue = []; - window.addEventListener('message', function (ev) { - if (ev.source === window && ev.data === 'browserify-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); - - return function nextTick(fn) { - queue.push(fn); - window.postMessage('browserify-tick', '*'); - }; - } - - return function nextTick(fn) { - setTimeout(fn, 0); - }; -})(); - -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; - -process.binding = function (name) { - if (name === 'evals') return (require)('vm') - else throw new Error('No such module. (Possibly not yet loaded)') -}; - -(function () { - var cwd = '/'; - var path; - process.cwd = function () { return cwd }; - process.chdir = function (dir) { - if (!path) path = require('path'); - cwd = path.resolve(dir, cwd); - }; -})(); - -}); - -require.define("/animationFactory.js",function(require,module,exports,__dirname,__filename,process,global){/****************** - * This class is responsible for a lot of the heavy lifting around creating an animation at a certain state in time. - * The tricky thing is that when a new commit has to be "born," say in the middle of a rebase - * or something, it must animate out from the parent position to it's birth position. - - * These two positions though may not be where the commit finally ends up. So we actually need to take a snapshot of the tree, - * store all those positions, take a snapshot of the tree after a layout refresh afterwards, and then animate between those two spots. - * and then essentially animate the entire tree too. - */ - -// essentially a static class -function AnimationFactory() { - -} - -AnimationFactory.prototype.genCommitBirthAnimation = function(animationQueue, commit, gitVisuals) { - if (!animationQueue) { - throw new Error("Need animation queue to add closure to!"); - } - - var time = GRAPHICS.defaultAnimationTime * 1.0; - var bounceTime = time * 2; - - // essentially refresh the entire tree, but do a special thing for the commit - var visNode = commit.get('visNode'); - - var animation = function() { - // this takes care of refs and all that jazz, and updates all the positions - gitVisuals.refreshTree(time); - - visNode.setBirth(); - visNode.parentInFront(); - gitVisuals.visBranchesFront(); - - visNode.animateUpdatedPosition(bounceTime, 'bounce'); - visNode.animateOutgoingEdges(time); - }; - - animationQueue.add(new Animation({ - closure: animation, - duration: Math.max(time, bounceTime) - })); -}; - -AnimationFactory.prototype.overrideOpacityDepth2 = function(attr, opacity) { - opacity = (opacity === undefined) ? 1 : opacity; - - var newAttr = {}; - - _.each(attr, function(partObj, partName) { - newAttr[partName] = {}; - _.each(partObj, function(val, key) { - if (key == 'opacity') { - newAttr[partName][key] = opacity; - } else { - newAttr[partName][key] = val; - } - }); - }); - return newAttr; -}; - -AnimationFactory.prototype.overrideOpacityDepth3 = function(snapShot, opacity) { - var newSnap = {}; - - _.each(snapShot, function(visObj, visID) { - newSnap[visID] = this.overrideOpacityDepth2(visObj, opacity); - }, this); - return newSnap; -}; - -AnimationFactory.prototype.genCommitBirthClosureFromSnapshot = function(step, gitVisuals) { - var time = GRAPHICS.defaultAnimationTime * 1.0; - var bounceTime = time * 1.5; - - var visNode = step.newCommit.get('visNode'); - var afterAttrWithOpacity = this.overrideOpacityDepth2(step.afterSnapshot[visNode.getID()]); - var afterSnapWithOpacity = this.overrideOpacityDepth3(step.afterSnapshot); - - var animation = function() { - visNode.setBirthFromSnapshot(step.beforeSnapshot); - visNode.parentInFront(); - gitVisuals.visBranchesFront(); - - visNode.animateToAttr(afterAttrWithOpacity, bounceTime, 'bounce'); - visNode.animateOutgoingEdgesToAttr(afterSnapWithOpacity, bounceTime); - }; - - return animation; -}; - -AnimationFactory.prototype.refreshTree = function(animationQueue, gitVisuals) { - animationQueue.add(new Animation({ - closure: function() { - gitVisuals.refreshTree(); - } - })); -}; - -AnimationFactory.prototype.rebaseAnimation = function(animationQueue, rebaseResponse, - gitEngine, gitVisuals) { - - this.rebaseHighlightPart(animationQueue, rebaseResponse, gitEngine); - this.rebaseBirthPart(animationQueue, rebaseResponse, gitEngine, gitVisuals); -}; - -AnimationFactory.prototype.rebaseHighlightPart = function(animationQueue, rebaseResponse, gitEngine) { - var fullTime = GRAPHICS.defaultAnimationTime * 0.66; - var slowTime = fullTime * 2.0; - - // we want to highlight all the old commits - var oldCommits = rebaseResponse.toRebaseArray; - // we are either highlighting to a visBranch or a visNode - var visBranch = rebaseResponse.destinationBranch.get('visBranch'); - if (!visBranch) { - // in the case where we rebase onto a commit - visBranch = rebaseResponse.destinationBranch.get('visNode'); - } - - _.each(oldCommits, function(oldCommit) { - var visNode = oldCommit.get('visNode'); - animationQueue.add(new Animation({ - closure: function() { - visNode.highlightTo(visBranch, slowTime, 'easeInOut'); - }, - duration: fullTime * 1.5 - })); - - }, this); - - this.delay(animationQueue, fullTime * 2); -}; - -AnimationFactory.prototype.rebaseBirthPart = function(animationQueue, rebaseResponse, - gitEngine, gitVisuals) { - var rebaseSteps = rebaseResponse.rebaseSteps; - - var newVisNodes = []; - _.each(rebaseSteps, function(step) { - var visNode = step.newCommit.get('visNode'); - - newVisNodes.push(visNode); - visNode.setOpacity(0); - visNode.setOutgoingEdgesOpacity(0); - }, this); - - var previousVisNodes = []; - _.each(rebaseSteps, function(rebaseStep, index) { - var toOmit = newVisNodes.slice(index + 1); - - var snapshotPart = this.genFromToSnapshotAnimation( - rebaseStep.beforeSnapshot, - rebaseStep.afterSnapshot, - toOmit, - previousVisNodes, - gitVisuals - ); - var birthPart = this.genCommitBirthClosureFromSnapshot(rebaseStep, gitVisuals); - - var animation = function() { - snapshotPart(); - birthPart(); - }; - - animationQueue.add(new Animation({ - closure: animation, - duration: GRAPHICS.defaultAnimationTime * 1.5 - })); - - previousVisNodes.push(rebaseStep.newCommit.get('visNode')); - }, this); - - // need to delay to let bouncing finish - this.delay(animationQueue); - - this.refreshTree(animationQueue, gitVisuals); -}; - -AnimationFactory.prototype.delay = function(animationQueue, time) { - time = time || GRAPHICS.defaultAnimationTime; - animationQueue.add(new Animation({ - closure: function() { }, - duration: time - })); -}; - -AnimationFactory.prototype.genSetAllCommitOpacities = function(visNodes, opacity) { - // need to slice for closure - var nodesToAnimate = visNodes.slice(0); - - return function() { - _.each(nodesToAnimate, function(visNode) { - visNode.setOpacity(opacity); - visNode.setOutgoingEdgesOpacity(opacity); - }); - }; -}; - -AnimationFactory.prototype.stripObjectsFromSnapshot = function(snapShot, toOmit) { - var ids = []; - _.each(toOmit, function(obj) { - ids.push(obj.getID()); - }); - - var newSnapshot = {}; - _.each(snapShot, function(val, key) { - if (_.include(ids, key)) { - // omit - return; - } - newSnapshot[key] = val; - }, this); - return newSnapshot; -}; - -AnimationFactory.prototype.genFromToSnapshotAnimation = function( - beforeSnapshot, - afterSnapshot, - commitsToOmit, - commitsToFixOpacity, - gitVisuals) { - - // we want to omit the commit outgoing edges - var toOmit = []; - _.each(commitsToOmit, function(visNode) { - toOmit.push(visNode); - toOmit = toOmit.concat(visNode.get('outgoingEdges')); - }); - - var fixOpacity = function(obj) { - if (!obj) { return; } - _.each(obj, function(attr, partName) { - obj[partName].opacity = 1; - }); - }; - - // HORRIBLE loop to fix opacities all throughout the snapshot - _.each([beforeSnapshot, afterSnapshot], function(snapShot) { - _.each(commitsToFixOpacity, function(visNode) { - fixOpacity(snapShot[visNode.getID()]); - _.each(visNode.get('outgoingEdges'), function(visEdge) { - fixOpacity(snapShot[visEdge.getID()]); - }); - }); - }); - - return function() { - gitVisuals.animateAllFromAttrToAttr(beforeSnapshot, afterSnapshot, toOmit); - }; -}; - -exports.AnimationFactory = AnimationFactory; - - - -}); - -require.define("/main.js",function(require,module,exports,__dirname,__filename,process,global){var AnimationFactory = require('./animationFactory').AnimationFactory; - -/** - * Globals - */ -var events = _.clone(Backbone.Events); -var ui = null; -var animationFactory = null; - -/** - * Static Classes - */ -animationFactory = new AnimationFactory(); - -/////////////////////////////////////////////////////////////////////// - -$(document).ready(function(){ - ui = new UI(); - mainVis = new Visualization({ - el: $('#canvasWrapper')[0] - }); - - if (/\?demo/.test(window.location.href)) { - setTimeout(function() { - events.trigger('submitCommandValueFromEvent', "gc; git checkout HEAD~1; git commit; git checkout -b bugFix; gc; gc; git rebase master; git checkout master; gc; gc; git merge bugFix"); - }, 500); - } -}); - -function UI() { - // static classes - this.commandCollection = new CommandCollection(); - - this.commandBuffer = new CommandBuffer({ - collection: this.commandCollection - }); - - this.commandPromptView = new CommandPromptView({ - el: $('#commandLineBar'), - collection: this.commandCollection - }); - this.commandLineHistoryView = new CommandLineHistoryView({ - el: $('#commandLineHistory'), - collection: this.commandCollection - }); - - $('#commandTextField').focus(); -} - -exports.events = events; -exports.ui = ui; -exports.animationFactory = animationFactory; - - -}); -require("/main.js"); - -require.define("/animationFactory.js",function(require,module,exports,__dirname,__filename,process,global){/****************** - * This class is responsible for a lot of the heavy lifting around creating an animation at a certain state in time. - * The tricky thing is that when a new commit has to be "born," say in the middle of a rebase - * or something, it must animate out from the parent position to it's birth position. - - * These two positions though may not be where the commit finally ends up. So we actually need to take a snapshot of the tree, - * store all those positions, take a snapshot of the tree after a layout refresh afterwards, and then animate between those two spots. - * and then essentially animate the entire tree too. - */ - -// essentially a static class -function AnimationFactory() { - -} - -AnimationFactory.prototype.genCommitBirthAnimation = function(animationQueue, commit, gitVisuals) { - if (!animationQueue) { - throw new Error("Need animation queue to add closure to!"); - } - - var time = GRAPHICS.defaultAnimationTime * 1.0; - var bounceTime = time * 2; - - // essentially refresh the entire tree, but do a special thing for the commit - var visNode = commit.get('visNode'); - - var animation = function() { - // this takes care of refs and all that jazz, and updates all the positions - gitVisuals.refreshTree(time); - - visNode.setBirth(); - visNode.parentInFront(); - gitVisuals.visBranchesFront(); - - visNode.animateUpdatedPosition(bounceTime, 'bounce'); - visNode.animateOutgoingEdges(time); - }; - - animationQueue.add(new Animation({ - closure: animation, - duration: Math.max(time, bounceTime) - })); -}; - -AnimationFactory.prototype.overrideOpacityDepth2 = function(attr, opacity) { - opacity = (opacity === undefined) ? 1 : opacity; - - var newAttr = {}; - - _.each(attr, function(partObj, partName) { - newAttr[partName] = {}; - _.each(partObj, function(val, key) { - if (key == 'opacity') { - newAttr[partName][key] = opacity; - } else { - newAttr[partName][key] = val; - } - }); - }); - return newAttr; -}; - -AnimationFactory.prototype.overrideOpacityDepth3 = function(snapShot, opacity) { - var newSnap = {}; - - _.each(snapShot, function(visObj, visID) { - newSnap[visID] = this.overrideOpacityDepth2(visObj, opacity); - }, this); - return newSnap; -}; - -AnimationFactory.prototype.genCommitBirthClosureFromSnapshot = function(step, gitVisuals) { - var time = GRAPHICS.defaultAnimationTime * 1.0; - var bounceTime = time * 1.5; - - var visNode = step.newCommit.get('visNode'); - var afterAttrWithOpacity = this.overrideOpacityDepth2(step.afterSnapshot[visNode.getID()]); - var afterSnapWithOpacity = this.overrideOpacityDepth3(step.afterSnapshot); - - var animation = function() { - visNode.setBirthFromSnapshot(step.beforeSnapshot); - visNode.parentInFront(); - gitVisuals.visBranchesFront(); - - visNode.animateToAttr(afterAttrWithOpacity, bounceTime, 'bounce'); - visNode.animateOutgoingEdgesToAttr(afterSnapWithOpacity, bounceTime); - }; - - return animation; -}; - -AnimationFactory.prototype.refreshTree = function(animationQueue, gitVisuals) { - animationQueue.add(new Animation({ - closure: function() { - gitVisuals.refreshTree(); - } - })); -}; - -AnimationFactory.prototype.rebaseAnimation = function(animationQueue, rebaseResponse, - gitEngine, gitVisuals) { - - this.rebaseHighlightPart(animationQueue, rebaseResponse, gitEngine); - this.rebaseBirthPart(animationQueue, rebaseResponse, gitEngine, gitVisuals); -}; - -AnimationFactory.prototype.rebaseHighlightPart = function(animationQueue, rebaseResponse, gitEngine) { - var fullTime = GRAPHICS.defaultAnimationTime * 0.66; - var slowTime = fullTime * 2.0; - - // we want to highlight all the old commits - var oldCommits = rebaseResponse.toRebaseArray; - // we are either highlighting to a visBranch or a visNode - var visBranch = rebaseResponse.destinationBranch.get('visBranch'); - if (!visBranch) { - // in the case where we rebase onto a commit - visBranch = rebaseResponse.destinationBranch.get('visNode'); - } - - _.each(oldCommits, function(oldCommit) { - var visNode = oldCommit.get('visNode'); - animationQueue.add(new Animation({ - closure: function() { - visNode.highlightTo(visBranch, slowTime, 'easeInOut'); - }, - duration: fullTime * 1.5 - })); - - }, this); - - this.delay(animationQueue, fullTime * 2); -}; - -AnimationFactory.prototype.rebaseBirthPart = function(animationQueue, rebaseResponse, - gitEngine, gitVisuals) { - var rebaseSteps = rebaseResponse.rebaseSteps; - - var newVisNodes = []; - _.each(rebaseSteps, function(step) { - var visNode = step.newCommit.get('visNode'); - - newVisNodes.push(visNode); - visNode.setOpacity(0); - visNode.setOutgoingEdgesOpacity(0); - }, this); - - var previousVisNodes = []; - _.each(rebaseSteps, function(rebaseStep, index) { - var toOmit = newVisNodes.slice(index + 1); - - var snapshotPart = this.genFromToSnapshotAnimation( - rebaseStep.beforeSnapshot, - rebaseStep.afterSnapshot, - toOmit, - previousVisNodes, - gitVisuals - ); - var birthPart = this.genCommitBirthClosureFromSnapshot(rebaseStep, gitVisuals); - - var animation = function() { - snapshotPart(); - birthPart(); - }; - - animationQueue.add(new Animation({ - closure: animation, - duration: GRAPHICS.defaultAnimationTime * 1.5 - })); - - previousVisNodes.push(rebaseStep.newCommit.get('visNode')); - }, this); - - // need to delay to let bouncing finish - this.delay(animationQueue); - - this.refreshTree(animationQueue, gitVisuals); -}; - -AnimationFactory.prototype.delay = function(animationQueue, time) { - time = time || GRAPHICS.defaultAnimationTime; - animationQueue.add(new Animation({ - closure: function() { }, - duration: time - })); -}; - -AnimationFactory.prototype.genSetAllCommitOpacities = function(visNodes, opacity) { - // need to slice for closure - var nodesToAnimate = visNodes.slice(0); - - return function() { - _.each(nodesToAnimate, function(visNode) { - visNode.setOpacity(opacity); - visNode.setOutgoingEdgesOpacity(opacity); - }); - }; -}; - -AnimationFactory.prototype.stripObjectsFromSnapshot = function(snapShot, toOmit) { - var ids = []; - _.each(toOmit, function(obj) { - ids.push(obj.getID()); - }); - - var newSnapshot = {}; - _.each(snapShot, function(val, key) { - if (_.include(ids, key)) { - // omit - return; - } - newSnapshot[key] = val; - }, this); - return newSnapshot; -}; - -AnimationFactory.prototype.genFromToSnapshotAnimation = function( - beforeSnapshot, - afterSnapshot, - commitsToOmit, - commitsToFixOpacity, - gitVisuals) { - - // we want to omit the commit outgoing edges - var toOmit = []; - _.each(commitsToOmit, function(visNode) { - toOmit.push(visNode); - toOmit = toOmit.concat(visNode.get('outgoingEdges')); - }); - - var fixOpacity = function(obj) { - if (!obj) { return; } - _.each(obj, function(attr, partName) { - obj[partName].opacity = 1; - }); - }; - - // HORRIBLE loop to fix opacities all throughout the snapshot - _.each([beforeSnapshot, afterSnapshot], function(snapShot) { - _.each(commitsToFixOpacity, function(visNode) { - fixOpacity(snapShot[visNode.getID()]); - _.each(visNode.get('outgoingEdges'), function(visEdge) { - fixOpacity(snapShot[visEdge.getID()]); - }); - }); - }); - - return function() { - gitVisuals.animateAllFromAttrToAttr(beforeSnapshot, afterSnapshot, toOmit); - }; -}; - -exports.AnimationFactory = AnimationFactory; - - - -}); -require("/animationFactory.js"); -})(); diff --git a/src/animationFactory.js b/src/animationFactory.js index bc6156c1..d420a2b0 100644 --- a/src/animationFactory.js +++ b/src/animationFactory.js @@ -249,6 +249,3 @@ AnimationFactory.prototype.genFromToSnapshotAnimation = function( }; }; -exports.AnimationFactory = AnimationFactory; - - diff --git a/src/index.html b/src/index.html index fb67e28a..13bf2313 100644 --- a/src/index.html +++ b/src/index.html @@ -145,18 +145,12 @@ - + - - - - - + diff --git a/src/main.js b/src/main.js index 0f22ab9f..f6b1fb4b 100644 --- a/src/main.js +++ b/src/main.js @@ -1,16 +1,14 @@ -var AnimationFactory = require('./animationFactory').AnimationFactory; - /** * Globals */ var events = _.clone(Backbone.Events); + var ui = null; -var animationFactory = null; /** * Static Classes */ -animationFactory = new AnimationFactory(); +var animationFactory = new AnimationFactory(); /////////////////////////////////////////////////////////////////////// @@ -47,7 +45,3 @@ function UI() { $('#commandTextField').focus(); } -exports.events = events; -exports.ui = ui; -exports.animationFactory = animationFactory; - From c7d857957f69115dd98d2dd01ce542632dfe47c9 Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Thu, 13 Dec 2012 19:41:55 -0800 Subject: [PATCH 5/6] Revert "with grunt" This reverts commit 9ee1490aa319ab4ea91b23d11bfad3bdf2d6a748. --- grunt.js | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/grunt.js b/grunt.js index e24e02cf..899f29a3 100644 --- a/grunt.js +++ b/grunt.js @@ -1,32 +1,10 @@ /*global module:false*/ module.exports = function(grunt) { - // eventually have sound...? - grunt.registerTask('compliment', function() { - grunt.log.writeln('You are awesome!'); - }); - grunt.initConfig({ lint: { files: ['grunt.js', 'src/*.js'] }, - /* - jasmine_node: { - specNameMatcher: "./spec", // load only specs containing specNameMatcher - projectRoot: ".", - requirejs: false, - forceExit: true, - jUnit: { - report: false, - savePath : "./build/reports/jasmine/", - useDotNotation: true, - consolidate: true - } - }, - watch: { - files: '', - tasks: 'lint' - },*/ jshint: { options: { curly: true, @@ -55,9 +33,6 @@ module.exports = function(grunt) { } }); - //grunt.loadNpmTasks('grunt-jasmine-node'); - // Default task. - grunt.registerTask('default', 'lint jasmine_node'); + grunt.registerTask('default', 'lint'); }; - From 6c9734659eb835ec94fb940d1345a68d29fd2747 Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Thu, 13 Dec 2012 19:42:10 -0800 Subject: [PATCH 6/6] Revert "starting dev environment reboot" This reverts commit 7a838f89f7304461c5f974254896cf4719dd3d5c. --- grunt.js | 38 -------------------------------------- package.json | 9 --------- 2 files changed, 47 deletions(-) delete mode 100644 grunt.js delete mode 100644 package.json diff --git a/grunt.js b/grunt.js deleted file mode 100644 index 899f29a3..00000000 --- a/grunt.js +++ /dev/null @@ -1,38 +0,0 @@ -/*global module:false*/ -module.exports = function(grunt) { - - grunt.initConfig({ - lint: { - files: ['grunt.js', 'src/*.js'] - }, - jshint: { - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - boss: true, - eqnull: true, - browser: true - }, - globals: { - _: true, - Backbone: true, - '$': true, - require: true, - define: true, - requirejs: true, - describe: true, - expect: true, - it: true - } - } - }); - - // Default task. - grunt.registerTask('default', 'lint'); -}; diff --git a/package.json b/package.json deleted file mode 100644 index 3d1790a3..00000000 --- a/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "LearnGitBranching", - "version": "0.5.0", - "devDependencies": { - "grunt": "latest", - "browserify": "latest" - } -} -