diff --git a/build/bundle.js b/build/bundle.js index bc5c4fbd..3f6abdfb 100644 --- a/build/bundle.js +++ b/build/bundle.js @@ -7246,11 +7246,46 @@ GitEngine.prototype.getIsHg = function() { GitEngine.prototype.setMode = function(vcs) { var switchedToHg = (this.mode === 'git' && vcs === 'hg'); this.mode = vcs; - if (switchedToHg) { - // if we are switching to mercurial then we have some - // garbage collection and other tidying up to do - return this.pruneTree(); + if (!switchedToHg) { + return; } + // if we are switching to mercurial then we have some + // garbage collection and other tidying up to do. this + // may or may not require a refresh so lets check. + var deferred = Q.defer(); + deferred.resolve(); + var chain = deferred.promise; + + // this stuff is tricky because we dont animate when + // we didnt do anything, but we DO animate when + // either of the operations happen. so a lot of + // branching ahead... + var neededUpdate = this.updateAllBranchesForHg(); + if (neededUpdate) { + chain = chain.then(_.bind(function() { + return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + }, this)); + + // ok we need to refresh anyways, so do the prune after + chain = chain.then(_.bind(function() { + var neededPrune = this.pruneTree(); + if (!neededPrune) { + return; + } + return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + }, this)); + + return chain; + } + + // ok might need prune though + var pruned = this.pruneTree(); + if (!pruned) { + // do sync + return; + } + + return this.animationFactory.playRefreshAnimation(this.gitVisuals); }; GitEngine.prototype.assignLocalRepo = function(repo) { @@ -8306,13 +8341,10 @@ GitEngine.prototype.resolveRelativeRef = function(commit, relative) { } if (!next) { - var msg = intl.str( - 'git-error-relative-ref', - { - commit: commit.id, - match: matches[0] - } - ); + var msg = intl.str('git-error-relative-ref', { + commit: commit.id, + match: matches[0] + }); throw new GitError({ msg: msg }); @@ -8386,6 +8418,9 @@ GitEngine.prototype.setTargetLocation = function(ref, target) { }; GitEngine.prototype.updateBranchesFromSet = function(commitSet) { + if (!commitSet) { + throw new Error('need commit set here'); + } // commitSet is the set of commits that are stale or moved or whatever. // any branches POINTING to these commits need to be moved! @@ -8401,14 +8436,54 @@ GitEngine.prototype.updateBranchesFromSet = function(commitSet) { }); }, this); - console.log(branchesToUpdate); + var branchList = _.map(branchesToUpdate, function(val, id) { + return id; + }); + return this.updateBranchesForHg(branchList); +}; + +GitEngine.prototype.updateAllBranchesForHg = function() { + var branchList = this.branchCollection.map(function(branch) { + return branch.get('id'); + }); + return this.updateBranchesForHg(branchList); +}; + +GitEngine.prototype.updateBranchesForHg = function(branchList) { + var hasUpdated = false; + _.each(branchList, function(branchID) { + // ok now just check if this branch has a more recent commit available. + // that mapping is easy because we always do rebase alt id -- + // theres no way to have C3' and C3''' but no C3''. so just + // bump the ID once -- if thats not filled in we are updated, + // otherwise loop until you find undefined + var commitID = this.getCommitFromRef(branchID).get('id'); + var altID = this.getBumpedID(commitID); + if (!this.refs[altID]) { + return; + } + hasUpdated = true; + + var lastID; + while (this.refs[altID]) { + lastID = altID; + altID = this.rebaseAltID(altID); + } + + // last ID is the one we want to update to + this.setTargetLocation(this.refs[branchID], this.refs[lastID]); + }, this); + + if (!hasUpdated) { + return false; + } + return true; }; GitEngine.prototype.pruneTree = function() { var set = this.getUpstreamBranchSet(); // dont prune commits that HEAD depends on var headSet = this.getUpstreamSet('HEAD'); - console.log(headSet); _.each(headSet, function(val, commitID) { set[commitID] = true; }); @@ -8440,7 +8515,7 @@ GitEngine.prototype.pruneTree = function() { } }, this); - return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + return true; }; GitEngine.prototype.getUpstreamBranchSet = function() { @@ -8519,9 +8594,20 @@ GitEngine.prototype.scrapeBaseID = function(id) { return 'C' + results[1]; }; +/* + * grabs a bumped ID that is NOT currently reserved + */ GitEngine.prototype.rebaseAltID = function(id) { + var newID = this.getBumpedID(id); + while (this.refs[newID]) { + newID = this.getBumpedID(newID); + } + return newID; +}; + +GitEngine.prototype.getBumpedID = function(id) { // this function alters an ID to add a quote to the end, - // indicating that it was rebased. it also checks existence + // indicating that it was rebased. var regexMap = [ [/^C(\d+)[']{0,2}$/, function(bits) { // this id can use another quote, so just add it @@ -8536,19 +8622,13 @@ GitEngine.prototype.rebaseAltID = function(id) { }] ]; - // for loop for early return + // for loop for early return (instead of _.each) 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; - } + return func(results); } } throw new Error('could not modify the id ' + id); @@ -9454,6 +9534,14 @@ AnimationFactory.playRefreshAnimationAndFinish = function(gitVisuals, animationQ animationQueue.thenFinish(animation.getPromise()); }; +AnimationFactory.genRefreshPromiseAnimation = function(gitVisuals) { + return new PromiseAnimation({ + closure: function() { + gitVisuals.refreshTree(); + } + }); +}; + AnimationFactory.playRefreshAnimationSlow = function(gitVisuals) { var time = GRAPHICS.defaultAnimationTime; return this.playRefreshAnimation(gitVisuals, time * 2); @@ -10783,6 +10871,26 @@ var commandConfig = { } }, + graft: { + regex: /^hg +graft($|\s)/, + options: [ + '-r' + ], + delegate: function(engine, command) { + var options = command.getSupportedMap(); + if (!options['-r']) { + throw new GitError({ + msg: intl.str('git-error-options') + }); + } + command.setGeneralArgs(options['-r']); + return { + vcs: 'git', + name: 'cherrypick' + }; + } + }, + log: { regex: /^hg +log($|\s)/, options: [ @@ -17799,7 +17907,7 @@ var VisBranch = VisBase.extend({ var name = this.getName(); // when from a reload, we dont need to generate the text - text = paper.text(textPos.x, textPos.y, String(name)); + var text = paper.text(textPos.x, textPos.y, String(name)); text.attr({ 'font-size': 14, 'font-family': 'Monaco, Courier, font-monospace', @@ -25236,11 +25344,46 @@ GitEngine.prototype.getIsHg = function() { GitEngine.prototype.setMode = function(vcs) { var switchedToHg = (this.mode === 'git' && vcs === 'hg'); this.mode = vcs; - if (switchedToHg) { - // if we are switching to mercurial then we have some - // garbage collection and other tidying up to do - return this.pruneTree(); + if (!switchedToHg) { + return; } + // if we are switching to mercurial then we have some + // garbage collection and other tidying up to do. this + // may or may not require a refresh so lets check. + var deferred = Q.defer(); + deferred.resolve(); + var chain = deferred.promise; + + // this stuff is tricky because we dont animate when + // we didnt do anything, but we DO animate when + // either of the operations happen. so a lot of + // branching ahead... + var neededUpdate = this.updateAllBranchesForHg(); + if (neededUpdate) { + chain = chain.then(_.bind(function() { + return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + }, this)); + + // ok we need to refresh anyways, so do the prune after + chain = chain.then(_.bind(function() { + var neededPrune = this.pruneTree(); + if (!neededPrune) { + return; + } + return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + }, this)); + + return chain; + } + + // ok might need prune though + var pruned = this.pruneTree(); + if (!pruned) { + // do sync + return; + } + + return this.animationFactory.playRefreshAnimation(this.gitVisuals); }; GitEngine.prototype.assignLocalRepo = function(repo) { @@ -26296,13 +26439,10 @@ GitEngine.prototype.resolveRelativeRef = function(commit, relative) { } if (!next) { - var msg = intl.str( - 'git-error-relative-ref', - { - commit: commit.id, - match: matches[0] - } - ); + var msg = intl.str('git-error-relative-ref', { + commit: commit.id, + match: matches[0] + }); throw new GitError({ msg: msg }); @@ -26376,6 +26516,9 @@ GitEngine.prototype.setTargetLocation = function(ref, target) { }; GitEngine.prototype.updateBranchesFromSet = function(commitSet) { + if (!commitSet) { + throw new Error('need commit set here'); + } // commitSet is the set of commits that are stale or moved or whatever. // any branches POINTING to these commits need to be moved! @@ -26391,14 +26534,54 @@ GitEngine.prototype.updateBranchesFromSet = function(commitSet) { }); }, this); - console.log(branchesToUpdate); + var branchList = _.map(branchesToUpdate, function(val, id) { + return id; + }); + return this.updateBranchesForHg(branchList); +}; + +GitEngine.prototype.updateAllBranchesForHg = function() { + var branchList = this.branchCollection.map(function(branch) { + return branch.get('id'); + }); + return this.updateBranchesForHg(branchList); +}; + +GitEngine.prototype.updateBranchesForHg = function(branchList) { + var hasUpdated = false; + _.each(branchList, function(branchID) { + // ok now just check if this branch has a more recent commit available. + // that mapping is easy because we always do rebase alt id -- + // theres no way to have C3' and C3''' but no C3''. so just + // bump the ID once -- if thats not filled in we are updated, + // otherwise loop until you find undefined + var commitID = this.getCommitFromRef(branchID).get('id'); + var altID = this.getBumpedID(commitID); + if (!this.refs[altID]) { + return; + } + hasUpdated = true; + + var lastID; + while (this.refs[altID]) { + lastID = altID; + altID = this.rebaseAltID(altID); + } + + // last ID is the one we want to update to + this.setTargetLocation(this.refs[branchID], this.refs[lastID]); + }, this); + + if (!hasUpdated) { + return false; + } + return true; }; GitEngine.prototype.pruneTree = function() { var set = this.getUpstreamBranchSet(); // dont prune commits that HEAD depends on var headSet = this.getUpstreamSet('HEAD'); - console.log(headSet); _.each(headSet, function(val, commitID) { set[commitID] = true; }); @@ -26430,7 +26613,7 @@ GitEngine.prototype.pruneTree = function() { } }, this); - return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + return true; }; GitEngine.prototype.getUpstreamBranchSet = function() { @@ -26509,9 +26692,20 @@ GitEngine.prototype.scrapeBaseID = function(id) { return 'C' + results[1]; }; +/* + * grabs a bumped ID that is NOT currently reserved + */ GitEngine.prototype.rebaseAltID = function(id) { + var newID = this.getBumpedID(id); + while (this.refs[newID]) { + newID = this.getBumpedID(newID); + } + return newID; +}; + +GitEngine.prototype.getBumpedID = function(id) { // this function alters an ID to add a quote to the end, - // indicating that it was rebased. it also checks existence + // indicating that it was rebased. var regexMap = [ [/^C(\d+)[']{0,2}$/, function(bits) { // this id can use another quote, so just add it @@ -26526,19 +26720,13 @@ GitEngine.prototype.rebaseAltID = function(id) { }] ]; - // for loop for early return + // for loop for early return (instead of _.each) 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; - } + return func(results); } } throw new Error('could not modify the id ' + id); @@ -30350,6 +30538,26 @@ var commandConfig = { } }, + graft: { + regex: /^hg +graft($|\s)/, + options: [ + '-r' + ], + delegate: function(engine, command) { + var options = command.getSupportedMap(); + if (!options['-r']) { + throw new GitError({ + msg: intl.str('git-error-options') + }); + } + command.setGeneralArgs(options['-r']); + return { + vcs: 'git', + name: 'cherrypick' + }; + } + }, + log: { regex: /^hg +log($|\s)/, options: [ @@ -34194,6 +34402,14 @@ AnimationFactory.playRefreshAnimationAndFinish = function(gitVisuals, animationQ animationQueue.thenFinish(animation.getPromise()); }; +AnimationFactory.genRefreshPromiseAnimation = function(gitVisuals) { + return new PromiseAnimation({ + closure: function() { + gitVisuals.refreshTree(); + } + }); +}; + AnimationFactory.playRefreshAnimationSlow = function(gitVisuals) { var time = GRAPHICS.defaultAnimationTime; return this.playRefreshAnimation(gitVisuals, time * 2); @@ -35800,7 +36016,7 @@ var VisBranch = VisBase.extend({ var name = this.getName(); // when from a reload, we dont need to generate the text - text = paper.text(textPos.x, textPos.y, String(name)); + var text = paper.text(textPos.x, textPos.y, String(name)); text.attr({ 'font-size': 14, 'font-family': 'Monaco, Courier, font-monospace', diff --git a/src/js/git/index.js b/src/js/git/index.js index efd93903..389245e3 100644 --- a/src/js/git/index.js +++ b/src/js/git/index.js @@ -77,11 +77,46 @@ GitEngine.prototype.getIsHg = function() { GitEngine.prototype.setMode = function(vcs) { var switchedToHg = (this.mode === 'git' && vcs === 'hg'); this.mode = vcs; - if (switchedToHg) { - // if we are switching to mercurial then we have some - // garbage collection and other tidying up to do - return this.pruneTree(); + if (!switchedToHg) { + return; } + // if we are switching to mercurial then we have some + // garbage collection and other tidying up to do. this + // may or may not require a refresh so lets check. + var deferred = Q.defer(); + deferred.resolve(); + var chain = deferred.promise; + + // this stuff is tricky because we dont animate when + // we didnt do anything, but we DO animate when + // either of the operations happen. so a lot of + // branching ahead... + var neededUpdate = this.updateAllBranchesForHg(); + if (neededUpdate) { + chain = chain.then(_.bind(function() { + return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + }, this)); + + // ok we need to refresh anyways, so do the prune after + chain = chain.then(_.bind(function() { + var neededPrune = this.pruneTree(); + if (!neededPrune) { + return; + } + return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + }, this)); + + return chain; + } + + // ok might need prune though + var pruned = this.pruneTree(); + if (!pruned) { + // do sync + return; + } + + return this.animationFactory.playRefreshAnimation(this.gitVisuals); }; GitEngine.prototype.assignLocalRepo = function(repo) { @@ -1214,6 +1249,9 @@ GitEngine.prototype.setTargetLocation = function(ref, target) { }; GitEngine.prototype.updateBranchesFromSet = function(commitSet) { + if (!commitSet) { + throw new Error('need commit set here'); + } // commitSet is the set of commits that are stale or moved or whatever. // any branches POINTING to these commits need to be moved! @@ -1229,21 +1267,54 @@ GitEngine.prototype.updateBranchesFromSet = function(commitSet) { }); }, this); - _.each(branchesToUpdate, function(val, id) { + var branchList = _.map(branchesToUpdate, function(val, id) { + return id; + }); + return this.updateBranchesForHg(branchList); +}; + +GitEngine.prototype.updateAllBranchesForHg = function() { + var branchList = this.branchCollection.map(function(branch) { + return branch.get('id'); + }); + return this.updateBranchesForHg(branchList); +}; + +GitEngine.prototype.updateBranchesForHg = function(branchList) { + var hasUpdated = false; + _.each(branchList, function(branchID) { // ok now just check if this branch has a more recent commit available. // that mapping is easy because we always do rebase alt id -- // theres no way to have C3' and C3''' but no C3''. so just // bump the ID once -- if thats not filled in we are updated, // otherwise loop until you find undefined - var commit = this.getCommitFromRef(id); - }); + var commitID = this.getCommitFromRef(branchID).get('id'); + var altID = this.getBumpedID(commitID); + if (!this.refs[altID]) { + return; + } + hasUpdated = true; + + var lastID; + while (this.refs[altID]) { + lastID = altID; + altID = this.rebaseAltID(altID); + } + + // last ID is the one we want to update to + this.setTargetLocation(this.refs[branchID], this.refs[lastID]); + }, this); + + if (!hasUpdated) { + return false; + } + return true; }; GitEngine.prototype.pruneTree = function() { var set = this.getUpstreamBranchSet(); // dont prune commits that HEAD depends on var headSet = this.getUpstreamSet('HEAD'); - console.log(headSet); _.each(headSet, function(val, commitID) { set[commitID] = true; }); @@ -1275,7 +1346,7 @@ GitEngine.prototype.pruneTree = function() { } }, this); - return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); + return true; }; GitEngine.prototype.getUpstreamBranchSet = function() { @@ -1354,9 +1425,20 @@ GitEngine.prototype.scrapeBaseID = function(id) { return 'C' + results[1]; }; +/* + * grabs a bumped ID that is NOT currently reserved + */ GitEngine.prototype.rebaseAltID = function(id) { + var newID = this.getBumpedID(id); + while (this.refs[newID]) { + newID = this.getBumpedID(newID); + } + return newID; +}; + +GitEngine.prototype.getBumpedID = function(id) { // this function alters an ID to add a quote to the end, - // indicating that it was rebased. it also checks existence + // indicating that it was rebased. var regexMap = [ [/^C(\d+)[']{0,2}$/, function(bits) { // this id can use another quote, so just add it @@ -1371,19 +1453,13 @@ GitEngine.prototype.rebaseAltID = function(id) { }] ]; - // for loop for early return + // for loop for early return (instead of _.each) 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; - } + return func(results); } } throw new Error('could not modify the id ' + id); diff --git a/src/js/mercurial/commands.js b/src/js/mercurial/commands.js index 666915ec..25228a87 100644 --- a/src/js/mercurial/commands.js +++ b/src/js/mercurial/commands.js @@ -52,6 +52,26 @@ var commandConfig = { } }, + graft: { + regex: /^hg +graft($|\s)/, + options: [ + '-r' + ], + delegate: function(engine, command) { + var options = command.getSupportedMap(); + if (!options['-r']) { + throw new GitError({ + msg: intl.str('git-error-options') + }); + } + command.setGeneralArgs(options['-r']); + return { + vcs: 'git', + name: 'cherrypick' + }; + } + }, + log: { regex: /^hg +log($|\s)/, options: [ diff --git a/src/js/visuals/animation/animationFactory.js b/src/js/visuals/animation/animationFactory.js index 55419292..b2ac7af8 100644 --- a/src/js/visuals/animation/animationFactory.js +++ b/src/js/visuals/animation/animationFactory.js @@ -103,6 +103,14 @@ AnimationFactory.playRefreshAnimationAndFinish = function(gitVisuals, animationQ animationQueue.thenFinish(animation.getPromise()); }; +AnimationFactory.genRefreshPromiseAnimation = function(gitVisuals) { + return new PromiseAnimation({ + closure: function() { + gitVisuals.refreshTree(); + } + }); +}; + AnimationFactory.playRefreshAnimationSlow = function(gitVisuals) { var time = GRAPHICS.defaultAnimationTime; return this.playRefreshAnimation(gitVisuals, time * 2); diff --git a/src/js/visuals/visBranch.js b/src/js/visuals/visBranch.js index 49e8daa1..a431ec85 100644 --- a/src/js/visuals/visBranch.js +++ b/src/js/visuals/visBranch.js @@ -407,7 +407,7 @@ var VisBranch = VisBase.extend({ var name = this.getName(); // when from a reload, we dont need to generate the text - text = paper.text(textPos.x, textPos.y, String(name)); + var text = paper.text(textPos.x, textPos.y, String(name)); text.attr({ 'font-size': 14, 'font-family': 'Monaco, Courier, font-monospace',