diff --git a/spec/git.spec.js b/spec/git.spec.js index e3dbbfa9..0807598c 100644 --- a/spec/git.spec.js +++ b/spec/git.spec.js @@ -57,6 +57,27 @@ describe('Git', function() { '%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C2%22%2C%22id%22%3A%22master%22%7D%2C%22side%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22side%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C3%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22side%22%2C%22id%22%3A%22HEAD%22%7D%7D' ); }); + + it('Interactive rebase', function() { + expectTreeAsync( + 'gc; git checkout -b side C1; gc; git rebase -i master --interactive-test', + '%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C2%22%2C%22id%22%3A%22master%22%7D%2C%22side%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22side%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C3%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22side%22%2C%22id%22%3A%22HEAD%22%7D%7D' + ); + }); + + it('Interactive rebases with commit re-ordering', function() { + expectTreeAsync( + 'gc;gc;git rebase -i C0 --interactive-test C3,C1', + '%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C1%27%22%2C%22id%22%3A%22master%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C3%27%22%7D%2C%22C1%27%22%3A%7B%22parents%22%3A%5B%22C3%27%22%5D%2C%22id%22%3A%22C1%27%22%7D%7D%2C%22tags%22%3A%7B%7D%2C%22HEAD%22%3A%7B%22id%22%3A%22HEAD%22%2C%22target%22%3A%22master%22%7D%7D' + ); + }); + + it('Switch branch and execute interactive rebase', function() { + expectTreeAsync( + 'git branch test;git rebase -i C0 test --interactive-test', + '%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C1%22%2C%22id%22%3A%22master%22%7D%2C%22test%22%3A%7B%22target%22%3A%22C1%27%22%2C%22id%22%3A%22test%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C1%27%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%27%22%7D%7D%2C%22tags%22%3A%7B%7D%2C%22HEAD%22%3A%7B%22id%22%3A%22HEAD%22%2C%22target%22%3A%22test%22%7D%7D' + ); + }); it('Reverts', function() { expectTreeAsync( @@ -127,6 +148,13 @@ describe('Git', function() { '%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C2%22%2C%22id%22%3A%22master%22%7D%2C%22bugFix%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22bugFix%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C3%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22master%22%2C%22id%22%3A%22HEAD%22%7D%7D' ); }); + + it('checks out after an interactive rebase', function() { + expectTreeAsync( + 'git commit; git checkout -b bugFix C1; git commit; git rebase -i master --interactive-test;git checkout master', + '%7B%22branches%22%3A%7B%22master%22%3A%7B%22target%22%3A%22C2%22%2C%22id%22%3A%22master%22%7D%2C%22bugFix%22%3A%7B%22target%22%3A%22C3%27%22%2C%22id%22%3A%22bugFix%22%7D%7D%2C%22commits%22%3A%7B%22C0%22%3A%7B%22parents%22%3A%5B%5D%2C%22id%22%3A%22C0%22%2C%22rootCommit%22%3Atrue%7D%2C%22C1%22%3A%7B%22parents%22%3A%5B%22C0%22%5D%2C%22id%22%3A%22C1%22%7D%2C%22C2%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C2%22%7D%2C%22C3%22%3A%7B%22parents%22%3A%5B%22C1%22%5D%2C%22id%22%3A%22C3%22%7D%2C%22C3%27%22%3A%7B%22parents%22%3A%5B%22C2%22%5D%2C%22id%22%3A%22C3%27%22%7D%7D%2C%22HEAD%22%3A%7B%22target%22%3A%22master%22%2C%22id%22%3A%22HEAD%22%7D%7D' + ); + }); it('solves merging level', function() { expectTreeAsync( @@ -141,6 +169,13 @@ describe('Git', function() { '{"branches":{"master":{"target":"C1","id":"master"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"}},"HEAD":{"target":"master","id":"HEAD"}}' ); }); + + it('solves rebase level with interactive rebase', function() { + expectTreeAsync( + 'git checkout -b bugFix;git commit;git checkout master;git commit;git checkout bugFix;git rebase -i master --interactive-test', + '{"branches":{"master":{"target":"C1","id":"master"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"}},"HEAD":{"target":"master","id":"HEAD"}}' + ); + }); it('does a whole bunch of crazy merging', function() { expectTreeAsync( diff --git a/src/js/git/commands.js b/src/js/git/commands.js index ff774e01..4dc5abe9 100644 --- a/src/js/git/commands.js +++ b/src/js/git/commands.js @@ -569,6 +569,7 @@ var commandConfig = { sc: /^gr($|\s)/, options: [ '-i', + '--interactive-test', '--aboveAll', '-p', '--preserve-merges' @@ -581,12 +582,23 @@ var commandConfig = { if (commandOptions['-i']) { var args = commandOptions['-i'].concat(generalArgs); command.twoArgsImpliedHead(args, ' -i'); - engine.rebaseInteractive( - args[0], - args[1], { - aboveAll: !!commandOptions['--aboveAll'] - } - ); + + if (commandOptions['--interactive-test']) { + engine.rebaseInteractiveTest( + args[0], + args[1], { + interactiveTest: commandOptions['--interactive-test'] + } + ); + } + else { + engine.rebaseInteractive( + args[0], + args[1], { + aboveAll: !!commandOptions['--aboveAll'], + } + ); + } return; } diff --git a/src/js/git/index.js b/src/js/git/index.js index 0522ef25..f61c67bd 100644 --- a/src/js/git/index.js +++ b/src/js/git/index.js @@ -2070,15 +2070,10 @@ GitEngine.prototype.getUpstreamDiffFromSet = function(stopSet, location) { return result; }; -GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation, options) { - options = options || {}; - // there are a reduced set of checks now, so we can't exactly use parts of the rebase function - // but it will look similar. - - // now get the stop set +GitEngine.prototype.getInteractiveRebaseCommits = function(targetSource, currentLocation) { var stopSet = Graph.getUpstreamSet(this, targetSource); - var toRebaseRough = []; + // standard BFS var pQueue = [this.getCommitFromRef(currentLocation)]; @@ -2101,12 +2096,62 @@ GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation, toRebase.push(commit); } }); - + if (!toRebase.length) { throw new GitError({ msg: intl.str('git-error-rebase-none') }); } + + return toRebase; +}; + +GitEngine.prototype.rebaseInteractiveTest = function(targetSource, currentLocation, options) { + options = options || {}; + + // Get the list of commits that would be displayed to the user + var toRebase = this.getInteractiveRebaseCommits(targetSource, currentLocation); + + var rebaseMap = {}; + _.each(toRebase, function(commit) { + var id = commit.get('id'); + rebaseMap[id] = commit; + }); + + var rebaseOrder; + if (options['interactiveTest'].length === 0) { + // If no commits were explicitly specified for the rebase, act like the user didn't change anything + // in the rebase dialog and hit confirm + rebaseOrder = toRebase; + } else { + // Get the list and order of commits specified + var idsToRebase = options['interactiveTest'][0].split(','); + + // Verify each chosen commit exists in the list of commits given to the user + var extraCommits = []; + rebaseOrder = []; + _.each(idsToRebase, function(id) { + if (id in rebaseMap) { + rebaseOrder.push(rebaseMap[id]); + } else { + extraCommits.push(id); + } + }); + + if (extraCommits.length > 0) { + // What to do here? + } + } + + this.rebaseFinish(rebaseOrder, {}, targetSource, currentLocation); +}; + +GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation, options) { + options = options || {}; + + // there are a reduced set of checks now, so we can't exactly use parts of the rebase function + // but it will look similar. + var toRebase = this.getInteractiveRebaseCommits(targetSource, currentLocation); // now do stuff :D since all our validation checks have passed, we are going to defer animation // and actually launch the dialog