diff --git a/build/bundle.js b/build/bundle.js index c21caa65..23a61a7f 100644 --- a/build/bundle.js +++ b/build/bundle.js @@ -392,13 +392,13 @@ process.binding = function (name) { }); -require.define("/git.js",function(require,module,exports,__dirname,__filename,process,global){var AnimationFactoryModule = require('./animation/animationFactory'); +require.define("/git/index.js",function(require,module,exports,__dirname,__filename,process,global){var AnimationFactoryModule = require('../animation/animationFactory'); var animationFactory = new AnimationFactoryModule.AnimationFactory(); -var Main = require('./app/main'); -var AnimationQueue = require('./animation').AnimationQueue; -var InteractiveRebaseView = require('./views/miscViews').InteractiveRebaseView; +var Main = require('../app/main'); +var AnimationQueue = require('../animation').AnimationQueue; +var InteractiveRebaseView = require('../views/miscViews').InteractiveRebaseView; -var Errors = require('./errors'); +var Errors = require('../errors'); var GitError = Errors.GitError; var CommandResult = Errors.CommandResult; @@ -5486,1657 +5486,6 @@ window.events = toGlobalize.Main.getEvents(); }); require("/debug.js"); -require.define("/git.js",function(require,module,exports,__dirname,__filename,process,global){var AnimationFactoryModule = require('./animation/animationFactory'); -var animationFactory = new AnimationFactoryModule.AnimationFactory(); -var Main = require('./app/main'); -var AnimationQueue = require('./animation').AnimationQueue; -var InteractiveRebaseView = require('./views/miscViews').InteractiveRebaseView; - -var Errors = require('./errors'); -var GitError = Errors.GitError; -var CommandResult = Errors.CommandResult; - -// 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 = []; - - Main.getEvents().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 = []; - - var 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; - var args = null; - if (this.commandOptions['-a']) { - this.command.addWarning('No need to add files in this demo'); - } - - if (this.commandOptions['-am']) { - 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']) { - 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 = null; - - // 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], 10); - }], - [/^([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 - 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() { - var args = null; - if (this.commandOptions['-b']) { - // the user is really trying to just make a branch and then switch to it. so first: - 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']) { - 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() { - var args = null; - // 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']) { - args = this.commandOptions['--contains']; - this.validateArgBounds(args, 1, 1, '--contains'); - this.printBranchesWithout(args[0]); - return; - } - - if (this.commandOptions['-f']) { - 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; - - var addToExplored = function(rent) { - exploredSet[rent.get('id')] = true; - queue.push(rent); - }; - - while (queue.length) { - var here = queue.pop(); - var rents = here.get('parents'); - - _.each(rents, addToExplored); - } - 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("/levels.js",function(require,module,exports,__dirname,__filename,process,global){// static class... function LevelEngine() { diff --git a/src/git.js b/src/git/index.js similarity index 99% rename from src/git.js rename to src/git/index.js index 6e181d95..45c551ff 100644 --- a/src/git.js +++ b/src/git/index.js @@ -1,10 +1,10 @@ -var AnimationFactoryModule = require('./animation/animationFactory'); +var AnimationFactoryModule = require('../animation/animationFactory'); var animationFactory = new AnimationFactoryModule.AnimationFactory(); -var Main = require('./app/main'); -var AnimationQueue = require('./animation').AnimationQueue; -var InteractiveRebaseView = require('./views/miscViews').InteractiveRebaseView; +var Main = require('../app/main'); +var AnimationQueue = require('../animation').AnimationQueue; +var InteractiveRebaseView = require('../views/miscViews').InteractiveRebaseView; -var Errors = require('./errors'); +var Errors = require('../errors'); var GitError = Errors.GitError; var CommandResult = Errors.CommandResult;