diff --git a/__tests__/git.spec.js b/__tests__/git.spec.js index b54e5b32..5d0df003 100644 --- a/__tests__/git.spec.js +++ b/__tests__/git.spec.js @@ -57,21 +57,21 @@ 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', @@ -148,7 +148,7 @@ 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', @@ -169,7 +169,7 @@ 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', @@ -247,5 +247,12 @@ describe('Git', function() { ); }); + it('can handle slashes and dashes in branch names but doesnt allow o/', function() { + expectTreeAsync( + 'git branch foo/bar; git commit; git checkout foo/bar; gc; go master; git merge foo/bar; go foo/bar; git checkout -b bar-baz; git commit; git branch o/foo', + '{"branches":{"master":{"target":"C4","id":"master","remoteTrackingBranchID":null},"foo/bar":{"target":"C3","id":"foo/bar","remoteTrackingBranchID":null},"bar-baz":{"target":"C5","id":"bar-baz","remoteTrackingBranchID":null}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"},"C3":{"parents":["C1"],"id":"C3"},"C4":{"parents":["C2","C3"],"id":"C4"},"C5":{"parents":["C3"],"id":"C5"}},"tags":{},"HEAD":{"target":"bar-baz","id":"HEAD"}}' + ); + }); + }); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 00000000..007fdc21 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,264 @@ +{ + "name": "LearnGitBranching", + "version": "0.8.0", + "dependencies": { + "backbone": { + "version": "1.1.2", + "from": "backbone@*", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.1.2.tgz", + "dependencies": { + "underscore": { + "version": "1.8.3", + "from": "underscore@>=1.5.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz" + } + } + }, + "events": { + "version": "1.0.2", + "from": "events@*", + "resolved": "https://registry.npmjs.org/events/-/events-1.0.2.tgz" + }, + "flux": { + "version": "2.0.1", + "from": "flux@*", + "resolved": "https://registry.npmjs.org/flux/-/flux-2.0.1.tgz" + }, + "jquery": { + "version": "1.7.3", + "from": "jquery@~1.7.3", + "dependencies": { + "jsdom": { + "version": "0.2.19", + "from": "jsdom@~0.2.14", + "dependencies": { + "request": { + "version": "2.30.0", + "from": "request@2.x", + "dependencies": { + "qs": { + "version": "0.6.6", + "from": "qs@~0.6.0" + }, + "json-stringify-safe": { + "version": "5.0.0", + "from": "json-stringify-safe@~5.0.0" + }, + "forever-agent": { + "version": "0.5.0", + "from": "forever-agent@~0.5.0" + }, + "node-uuid": { + "version": "1.4.1", + "from": "node-uuid@~1.4.0" + }, + "mime": { + "version": "1.2.11", + "from": "mime@~1.2.9" + }, + "tough-cookie": { + "version": "0.9.15", + "from": "tough-cookie@~0.9.15", + "dependencies": { + "punycode": { + "version": "1.2.3", + "from": "punycode@>=0.2.0" + } + } + }, + "form-data": { + "version": "0.1.2", + "from": "form-data@~0.1.0", + "dependencies": { + "combined-stream": { + "version": "0.0.4", + "from": "combined-stream@~0.0.4", + "dependencies": { + "delayed-stream": { + "version": "0.0.5", + "from": "delayed-stream@0.0.5" + } + } + }, + "async": { + "version": "0.2.9", + "from": "async@~0.2.9" + } + } + }, + "tunnel-agent": { + "version": "0.3.0", + "from": "tunnel-agent@~0.3.0" + }, + "http-signature": { + "version": "0.10.0", + "from": "http-signature@~0.10.0", + "dependencies": { + "assert-plus": { + "version": "0.1.2", + "from": "assert-plus@0.1.2" + }, + "asn1": { + "version": "0.1.11", + "from": "asn1@0.1.11" + }, + "ctype": { + "version": "0.5.2", + "from": "ctype@0.5.2" + } + } + }, + "oauth-sign": { + "version": "0.3.0", + "from": "oauth-sign@~0.3.0" + }, + "hawk": { + "version": "1.0.0", + "from": "hawk@~1.0.0", + "dependencies": { + "hoek": { + "version": "0.9.1", + "from": "hoek@0.9.x" + }, + "boom": { + "version": "0.4.2", + "from": "boom@0.4.x" + }, + "cryptiles": { + "version": "0.2.2", + "from": "cryptiles@0.2.x" + }, + "sntp": { + "version": "0.2.4", + "from": "sntp@0.2.x" + } + } + }, + "aws-sign2": { + "version": "0.5.0", + "from": "aws-sign2@~0.5.0" + } + } + }, + "cssom": { + "version": "0.2.5", + "from": "cssom@0.2.x" + }, + "cssstyle": { + "version": "0.2.9", + "from": "cssstyle@>=0.2.3", + "dependencies": { + "cssom": { + "version": "0.3.0", + "from": "cssom@0.3.x" + } + } + }, + "contextify": { + "version": "0.1.6", + "from": "contextify@0.1.x", + "dependencies": { + "bindings": { + "version": "1.1.1", + "from": "bindings@*" + } + } + } + } + }, + "htmlparser": { + "version": "1.7.6", + "from": "htmlparser@1.7.6" + }, + "xmlhttprequest": { + "version": "1.4.2", + "from": "xmlhttprequest@~1.4.2" + }, + "location": { + "version": "0.0.1", + "from": "location@0.0.1" + }, + "navigator": { + "version": "1.0.1", + "from": "navigator@~1.0.1" + } + } + }, + "markdown": { + "version": "0.4.0", + "from": "markdown@~0.4.0", + "dependencies": { + "nopt": { + "version": "1.0.10", + "from": "nopt@1", + "dependencies": { + "abbrev": { + "version": "1.0.4", + "from": "abbrev@1" + } + } + } + } + }, + "object-assign": { + "version": "2.0.0", + "from": "object-assign@*", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.0.0.tgz" + }, + "q": { + "version": "0.8.12", + "from": "q@~0.8.11" + }, + "react": { + "version": "0.13.1", + "from": "react@*", + "resolved": "https://registry.npmjs.org/react/-/react-0.13.1.tgz", + "dependencies": { + "envify": { + "version": "3.4.0", + "from": "envify@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/envify/-/envify-3.4.0.tgz", + "dependencies": { + "through": { + "version": "2.3.6", + "from": "through@>=2.3.4 <2.4.0", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.6.tgz" + }, + "jstransform": { + "version": "10.1.0", + "from": "jstransform@>=10.0.1 <11.0.0", + "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz", + "dependencies": { + "base62": { + "version": "0.1.1", + "from": "base62@0.1.1", + "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz" + }, + "esprima-fb": { + "version": "13001.1001.0-dev-harmony-fb", + "from": "esprima-fb@13001.1001.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz" + }, + "source-map": { + "version": "0.1.31", + "from": "source-map@0.1.31", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", + "dependencies": { + "amdefine": { + "version": "0.1.0", + "from": "amdefine@>=0.0.4" + } + } + } + } + } + } + } + } + }, + "underscore": { + "version": "1.4.4", + "from": "underscore@~1.4.3" + } + } +} diff --git a/src/js/git/index.js b/src/js/git/index.js index f8ecfd4a..3e41c84c 100644 --- a/src/js/git/index.js +++ b/src/js/git/index.js @@ -607,8 +607,15 @@ GitEngine.prototype.getDetachedHead = function() { }; GitEngine.prototype.validateBranchName = function(name) { + // Lets escape some of the nasty characters + name = name.replace(///g,"\/"); name = name.replace(/\s/g, ''); - if (!/^[a-zA-Z0-9]+$/.test(name)) { + // And then just make sure it starts with alpha-numeric, + // can contain a slash or dash, and then ends with alpha + if ( + !/^(\w+[.\/\-]?)+\w+$/.test(name) || + name.search('o/') === 0 + ) { throw new GitError({ msg: intl.str( 'bad-branch-name', @@ -1461,7 +1468,7 @@ GitEngine.prototype.pullFinishWithMerge = function( chain = chain.then(function() { return this.animationFactory.getDelayedPromise(300); }.bind(this)); - + chain = chain.then(function() { // highlight last commit on o/master to color of // local branch @@ -2129,7 +2136,7 @@ GitEngine.prototype.getUpstreamDiffFromSet = function(stopSet, location) { GitEngine.prototype.getInteractiveRebaseCommits = function(targetSource, currentLocation) { var stopSet = Graph.getUpstreamSet(this, targetSource); var toRebaseRough = []; - + // standard BFS var pQueue = [this.getCommitFromRef(currentLocation)]; @@ -2152,28 +2159,28 @@ GitEngine.prototype.getInteractiveRebaseCommits = function(targetSource, current 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 @@ -2182,7 +2189,7 @@ GitEngine.prototype.rebaseInteractiveTest = function(targetSource, currentLocati } 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 = []; @@ -2193,20 +2200,20 @@ GitEngine.prototype.rebaseInteractiveTest = function(targetSource, currentLocati extraCommits.push(id); } }); - + if (extraCommits.length > 0) { throw new GitError({ msg: intl.todo('Hey those commits dont exist in the set!') }); } } - + 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); @@ -2234,7 +2241,7 @@ GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation, this.animationQueue.start(); }.bind(this)) .done(); - + // If we have a solution provided, set up the GUI to display it by default var initialCommitOrdering; if (options.initialCommitOrdering && options.initialCommitOrdering.length > 0) { @@ -2242,7 +2249,7 @@ GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation, _.each(toRebase, function(commit) { rebaseMap[commit.get('id')] = true; }); - + // Verify each chosen commit exists in the list of commits given to the user initialCommitOrdering = []; _.each(options.initialCommitOrdering[0].split(','), function(id) { @@ -2462,7 +2469,7 @@ GitEngine.prototype.checkout = function(idOrTarget) { if (type === 'tag') { target = target.get('target'); } - + this.HEAD.set('target', target); };