The following refspec, which can be used to fetch GitHub pull requests, is supported by C Git but was not yet by JGit: +refs/pull/*/head:refs/remotes/origin/pr/* The reason is that the wildcard in the source is in the middle. This change also includes more validation (e.g. "refs//heads" is not valid) and test cases. Bug: 405099 Change-Id: I9bcef7785a0762ed0a98ca95a0bdf8879d5702aatags/v3.0.0.201305281830-rc2
/* | /* | ||||
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> | * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> | ||||
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> | * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> | ||||
* Copyright (C) 2013, Robin Stocker <robin@nibor.org> | |||||
* and other copyright owners as documented in the project's IP log. | * and other copyright owners as documented in the project's IP log. | ||||
* | * | ||||
* This program and the accompanying materials are made available | * This program and the accompanying materials are made available | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||
import static org.junit.Assert.assertNotNull; | |||||
import static org.junit.Assert.assertNotSame; | import static org.junit.Assert.assertNotSame; | ||||
import static org.junit.Assert.assertNull; | import static org.junit.Assert.assertNull; | ||||
import static org.junit.Assert.assertSame; | import static org.junit.Assert.assertSame; | ||||
assertEquals(src, r.getSource()); | assertEquals(src, r.getSource()); | ||||
assertEquals(dst, r.getDestination()); | assertEquals(dst, r.getDestination()); | ||||
} | } | ||||
@Test | |||||
public void isWildcardShouldWorkForWildcardSuffixAndComponent() { | |||||
assertTrue(RefSpec.isWildcard("refs/heads/*")); | |||||
assertTrue(RefSpec.isWildcard("refs/pull/*/head")); | |||||
assertFalse(RefSpec.isWildcard("refs/heads/a")); | |||||
} | |||||
@Test | |||||
public void testWildcardInMiddleOfSource() { | |||||
RefSpec a = new RefSpec("+refs/pull/*/head:refs/remotes/origin/pr/*"); | |||||
assertTrue(a.isWildcard()); | |||||
assertTrue(a.matchSource("refs/pull/a/head")); | |||||
assertTrue(a.matchSource("refs/pull/foo/head")); | |||||
assertTrue(a.matchSource("refs/pull/foo/bar/head")); | |||||
assertFalse(a.matchSource("refs/pull/foo")); | |||||
assertFalse(a.matchSource("refs/pull/head")); | |||||
assertFalse(a.matchSource("refs/pull/foo/head/more")); | |||||
assertFalse(a.matchSource("refs/pullx/head")); | |||||
RefSpec b = a.expandFromSource("refs/pull/foo/head"); | |||||
assertEquals("refs/remotes/origin/pr/foo", b.getDestination()); | |||||
RefSpec c = a.expandFromDestination("refs/remotes/origin/pr/foo"); | |||||
assertEquals("refs/pull/foo/head", c.getSource()); | |||||
} | |||||
@Test | |||||
public void testWildcardInMiddleOfDestionation() { | |||||
RefSpec a = new RefSpec("+refs/heads/*:refs/remotes/origin/*/head"); | |||||
assertTrue(a.isWildcard()); | |||||
assertTrue(a.matchDestination("refs/remotes/origin/a/head")); | |||||
assertTrue(a.matchDestination("refs/remotes/origin/foo/head")); | |||||
assertTrue(a.matchDestination("refs/remotes/origin/foo/bar/head")); | |||||
assertFalse(a.matchDestination("refs/remotes/origin/foo")); | |||||
assertFalse(a.matchDestination("refs/remotes/origin/head")); | |||||
assertFalse(a.matchDestination("refs/remotes/origin/foo/head/more")); | |||||
assertFalse(a.matchDestination("refs/remotes/originx/head")); | |||||
RefSpec b = a.expandFromSource("refs/heads/foo"); | |||||
assertEquals("refs/remotes/origin/foo/head", b.getDestination()); | |||||
RefSpec c = a.expandFromDestination("refs/remotes/origin/foo/head"); | |||||
assertEquals("refs/heads/foo", c.getSource()); | |||||
} | |||||
@Test | |||||
public void testWildcardMirror() { | |||||
RefSpec a = new RefSpec("*:*"); | |||||
assertTrue(a.isWildcard()); | |||||
assertTrue(a.matchSource("a")); | |||||
assertTrue(a.matchSource("foo")); | |||||
assertTrue(a.matchSource("foo/bar")); | |||||
assertTrue(a.matchDestination("a")); | |||||
assertTrue(a.matchDestination("foo")); | |||||
assertTrue(a.matchDestination("foo/bar")); | |||||
RefSpec b = a.expandFromSource("refs/heads/foo"); | |||||
assertEquals("refs/heads/foo", b.getDestination()); | |||||
RefSpec c = a.expandFromDestination("refs/heads/foo"); | |||||
assertEquals("refs/heads/foo", c.getSource()); | |||||
} | |||||
@Test | |||||
public void testWildcardAtStart() { | |||||
RefSpec a = new RefSpec("*/head:refs/heads/*"); | |||||
assertTrue(a.isWildcard()); | |||||
assertTrue(a.matchSource("a/head")); | |||||
assertTrue(a.matchSource("foo/head")); | |||||
assertTrue(a.matchSource("foo/bar/head")); | |||||
assertFalse(a.matchSource("/head")); | |||||
assertFalse(a.matchSource("a/head/extra")); | |||||
RefSpec b = a.expandFromSource("foo/head"); | |||||
assertEquals("refs/heads/foo", b.getDestination()); | |||||
RefSpec c = a.expandFromDestination("refs/heads/foo"); | |||||
assertEquals("foo/head", c.getSource()); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenSourceOnlyAndWildcard() { | |||||
assertNotNull(new RefSpec("refs/heads/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenDestinationOnlyAndWildcard() { | |||||
assertNotNull(new RefSpec(":refs/heads/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenOnlySourceWildcard() { | |||||
assertNotNull(new RefSpec("refs/heads/*:refs/heads/foo")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenOnlyDestinationWildcard() { | |||||
assertNotNull(new RefSpec("refs/heads/foo:refs/heads/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenMoreThanOneWildcardInSource() { | |||||
assertNotNull(new RefSpec("refs/heads/*/*:refs/heads/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenMoreThanOneWildcardInDestination() { | |||||
assertNotNull(new RefSpec("refs/heads/*:refs/heads/*/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenWildcardAfterText() { | |||||
assertNotNull(new RefSpec("refs/heads/wrong*:refs/heads/right/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenWildcardBeforeText() { | |||||
assertNotNull(new RefSpec("*wrong:right/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidWhenWildcardBeforeTextAtEnd() { | |||||
assertNotNull(new RefSpec("refs/heads/*wrong:right/*")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidSourceDoubleSlashes() { | |||||
assertNotNull(new RefSpec("refs/heads//wrong")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidSlashAtStart() { | |||||
assertNotNull(new RefSpec("/foo:/foo")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidDestinationDoubleSlashes() { | |||||
assertNotNull(new RefSpec(":refs/heads//wrong")); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidSetSource() { | |||||
RefSpec a = new RefSpec("refs/heads/*:refs/remotes/origin/*"); | |||||
a.setSource("refs/heads/*/*"); | |||||
} | |||||
@Test(expected = IllegalArgumentException.class) | |||||
public void invalidSetDestination() { | |||||
RefSpec a = new RefSpec("refs/heads/*:refs/remotes/origin/*"); | |||||
a.setDestination("refs/remotes/origin/*/*"); | |||||
} | |||||
} | } |
invalidTimeout=Invalid timeout: {0} | invalidTimeout=Invalid timeout: {0} | ||||
invalidURL=Invalid URL {0} | invalidURL=Invalid URL {0} | ||||
invalidWildcards=Invalid wildcards {0} | invalidWildcards=Invalid wildcards {0} | ||||
invalidRefSpec=Invalid refspec {0} | |||||
invalidWindowSize=Invalid window size | invalidWindowSize=Invalid window size | ||||
isAStaticFlagAndHasNorevWalkInstance={0} is a static flag and has no RevWalk instance | isAStaticFlagAndHasNorevWalkInstance={0} is a static flag and has no RevWalk instance | ||||
JRELacksMD5Implementation=JRE lacks MD5 implementation | JRELacksMD5Implementation=JRE lacks MD5 implementation |
/***/ public String invalidTimeout; | /***/ public String invalidTimeout; | ||||
/***/ public String invalidURL; | /***/ public String invalidURL; | ||||
/***/ public String invalidWildcards; | /***/ public String invalidWildcards; | ||||
/***/ public String invalidRefSpec; | |||||
/***/ public String invalidWindowSize; | /***/ public String invalidWindowSize; | ||||
/***/ public String isAStaticFlagAndHasNorevWalkInstance; | /***/ public String isAStaticFlagAndHasNorevWalkInstance; | ||||
/***/ public String JRELacksMD5Implementation; | /***/ public String JRELacksMD5Implementation; |
package org.eclipse.jgit.transport; | package org.eclipse.jgit.transport; | ||||
import java.text.MessageFormat; | |||||
import java.io.Serializable; | import java.io.Serializable; | ||||
import java.text.MessageFormat; | |||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
public class RefSpec implements Serializable { | public class RefSpec implements Serializable { | ||||
private static final long serialVersionUID = 1L; | private static final long serialVersionUID = 1L; | ||||
/** | |||||
/** | |||||
* Suffix for wildcard ref spec component, that indicate matching all refs | * Suffix for wildcard ref spec component, that indicate matching all refs | ||||
* with specified prefix. | * with specified prefix. | ||||
*/ | */ | ||||
* @return true if provided string is a wildcard ref spec component. | * @return true if provided string is a wildcard ref spec component. | ||||
*/ | */ | ||||
public static boolean isWildcard(final String s) { | public static boolean isWildcard(final String s) { | ||||
return s != null && s.endsWith(WILDCARD_SUFFIX); | |||||
return s != null && s.contains("*"); //$NON-NLS-1$ | |||||
} | } | ||||
/** Does this specification ask for forced updated (rewind/reset)? */ | /** Does this specification ask for forced updated (rewind/reset)? */ | ||||
* <li><code>+refs/heads/master</code></li> | * <li><code>+refs/heads/master</code></li> | ||||
* <li><code>+refs/heads/master:refs/remotes/origin/master</code></li> | * <li><code>+refs/heads/master:refs/remotes/origin/master</code></li> | ||||
* <li><code>+refs/heads/*:refs/remotes/origin/*</code></li> | * <li><code>+refs/heads/*:refs/remotes/origin/*</code></li> | ||||
* <li><code>+refs/pull/*/head:refs/remotes/origin/pr/*</code></li> | |||||
* <li><code>:refs/heads/master</code></li> | * <li><code>:refs/heads/master</code></li> | ||||
* </ul> | * </ul> | ||||
* | * | ||||
s = s.substring(1); | s = s.substring(1); | ||||
if (isWildcard(s)) | if (isWildcard(s)) | ||||
throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); | throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); | ||||
dstName = s; | |||||
dstName = checkValid(s); | |||||
} else if (c > 0) { | } else if (c > 0) { | ||||
srcName = s.substring(0, c); | |||||
dstName = s.substring(c + 1); | |||||
if (isWildcard(srcName) && isWildcard(dstName)) | |||||
String src = s.substring(0, c); | |||||
String dst = s.substring(c + 1); | |||||
if (isWildcard(src) && isWildcard(dst)) { | |||||
// Both contain wildcard | |||||
wildcard = true; | wildcard = true; | ||||
else if (isWildcard(srcName) || isWildcard(dstName)) | |||||
} else if (isWildcard(src) || isWildcard(dst)) { | |||||
// If either source or destination has wildcard, the other one | |||||
// must have as well. | |||||
throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); | throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); | ||||
} | |||||
srcName = checkValid(src); | |||||
dstName = checkValid(dst); | |||||
} else { | } else { | ||||
if (isWildcard(s)) | if (isWildcard(s)) | ||||
throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); | throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidWildcards, spec)); | ||||
srcName = s; | |||||
srcName = checkValid(s); | |||||
} | } | ||||
} | } | ||||
*/ | */ | ||||
public RefSpec setSource(final String source) { | public RefSpec setSource(final String source) { | ||||
final RefSpec r = new RefSpec(this); | final RefSpec r = new RefSpec(this); | ||||
r.srcName = source; | |||||
r.srcName = checkValid(source); | |||||
if (isWildcard(r.srcName) && r.dstName == null) | if (isWildcard(r.srcName) && r.dstName == null) | ||||
throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard); | throw new IllegalStateException(JGitText.get().destinationIsNotAWildcard); | ||||
if (isWildcard(r.srcName) != isWildcard(r.dstName)) | if (isWildcard(r.srcName) != isWildcard(r.dstName)) | ||||
*/ | */ | ||||
public RefSpec setDestination(final String destination) { | public RefSpec setDestination(final String destination) { | ||||
final RefSpec r = new RefSpec(this); | final RefSpec r = new RefSpec(this); | ||||
r.dstName = destination; | |||||
r.dstName = checkValid(destination); | |||||
if (isWildcard(r.dstName) && r.srcName == null) | if (isWildcard(r.dstName) && r.srcName == null) | ||||
throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard); | throw new IllegalStateException(JGitText.get().sourceIsNotAWildcard); | ||||
if (isWildcard(r.srcName) != isWildcard(r.dstName)) | if (isWildcard(r.srcName) != isWildcard(r.dstName)) | ||||
final String psrc = srcName, pdst = dstName; | final String psrc = srcName, pdst = dstName; | ||||
wildcard = false; | wildcard = false; | ||||
srcName = name; | srcName = name; | ||||
dstName = pdst.substring(0, pdst.length() - 1) | |||||
+ name.substring(psrc.length() - 1); | |||||
dstName = expandWildcard(name, psrc, pdst); | |||||
return this; | return this; | ||||
} | } | ||||
private RefSpec expandFromDstImp(final String name) { | private RefSpec expandFromDstImp(final String name) { | ||||
final String psrc = srcName, pdst = dstName; | final String psrc = srcName, pdst = dstName; | ||||
wildcard = false; | wildcard = false; | ||||
srcName = psrc.substring(0, psrc.length() - 1) | |||||
+ name.substring(pdst.length() - 1); | |||||
srcName = expandWildcard(name, pdst, psrc); | |||||
dstName = name; | dstName = name; | ||||
return this; | return this; | ||||
} | } | ||||
return expandFromDestination(r.getName()); | return expandFromDestination(r.getName()); | ||||
} | } | ||||
private boolean match(final String refName, final String s) { | |||||
private boolean match(final String name, final String s) { | |||||
if (s == null) | if (s == null) | ||||
return false; | return false; | ||||
if (isWildcard()) | |||||
return refName.startsWith(s.substring(0, s.length() - 1)); | |||||
return refName.equals(s); | |||||
if (isWildcard()) { | |||||
int wildcardIndex = s.indexOf('*'); | |||||
String prefix = s.substring(0, wildcardIndex); | |||||
String suffix = s.substring(wildcardIndex + 1); | |||||
return name.length() > prefix.length() + suffix.length() | |||||
&& name.startsWith(prefix) && name.endsWith(suffix); | |||||
} | |||||
return name.equals(s); | |||||
} | |||||
private static String expandWildcard(String name, String patternA, | |||||
String patternB) { | |||||
int a = patternA.indexOf('*'); | |||||
int trailingA = patternA.length() - (a + 1); | |||||
int b = patternB.indexOf('*'); | |||||
String match = name.substring(a, name.length() - trailingA); | |||||
return patternB.substring(0, b) + match + patternB.substring(b + 1); | |||||
} | |||||
private static String checkValid(String spec) { | |||||
if (spec != null && !isValid(spec)) | |||||
throw new IllegalArgumentException(MessageFormat.format( | |||||
JGitText.get().invalidRefSpec, spec)); | |||||
return spec; | |||||
} | |||||
private static boolean isValid(final String s) { | |||||
if (s.startsWith("/")) //$NON-NLS-1$ | |||||
return false; | |||||
if (s.contains("//")) //$NON-NLS-1$ | |||||
return false; | |||||
int i = s.indexOf('*'); | |||||
if (i != -1) { | |||||
if (s.indexOf('*', i + 1) > i) | |||||
return false; | |||||
if (i > 0 && s.charAt(i - 1) != '/') | |||||
return false; | |||||
if (i < s.length() - 1 && s.charAt(i + 1) != '/') | |||||
return false; | |||||
} | |||||
return true; | |||||
} | } | ||||
public int hashCode() { | public int hashCode() { |