Resolves #201 -- allow dashes and slashes in branch names

This commit is contained in:
Peter Cottle 2016-09-25 13:48:40 -07:00
parent 0c4a3b7bcb
commit 32c80bdb87
3 changed files with 298 additions and 20 deletions

View file

@ -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"}}'
);
});
});

264
npm-shrinkwrap.json generated Normal file
View file

@ -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"
}
}
}

View file

@ -607,8 +607,15 @@ GitEngine.prototype.getDetachedHead = function() {
};
GitEngine.prototype.validateBranchName = function(name) {
// Lets escape some of the nasty characters
name = name.replace(/&#x2F;/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);
};