Browse Source

Support refspecs with wildcard in middle (not only at end)

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: I9bcef7785a0762ed0a98ca95a0bdf8879d5702aa
stable-3.0
Robin Stocker 12 years ago committed by Gerrit Code Review @ Eclipse.org
parent
commit
a51899c203
  1. 150
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
  2. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  3. 1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  4. 81
      org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java

150
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java

@ -1,6 +1,7 @@
/* /*
* 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
@ -46,6 +47,7 @@ package org.eclipse.jgit.transport;
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;
@ -294,4 +296,152 @@ public class RefSpecTest {
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/*/*");
}
} }

1
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

@ -271,6 +271,7 @@ invalidTagOption=Invalid tag option: {0}
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

1
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

@ -333,6 +333,7 @@ public class JGitText extends TranslationBundle {
/***/ 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;

81
org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java

@ -43,8 +43,8 @@
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;
@ -59,7 +59,7 @@ import org.eclipse.jgit.lib.Ref;
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.
*/ */
@ -73,7 +73,7 @@ public class RefSpec implements Serializable {
* @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)? */
@ -112,6 +112,7 @@ public class RefSpec implements Serializable {
* <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/&#42;/head:refs/remotes/origin/pr/*</code></li>
* <li><code>:refs/heads/master</code></li> * <li><code>:refs/heads/master</code></li>
* </ul> * </ul>
* *
@ -132,18 +133,24 @@ public class RefSpec implements Serializable {
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); String src = s.substring(0, c);
dstName = s.substring(c + 1); String dst = s.substring(c + 1);
if (isWildcard(srcName) && isWildcard(dstName)) 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);
} }
} }
@ -215,7 +222,7 @@ public class RefSpec implements Serializable {
*/ */
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))
@ -254,7 +261,7 @@ public class RefSpec implements Serializable {
*/ */
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))
@ -350,8 +357,7 @@ public class RefSpec implements Serializable {
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) dstName = expandWildcard(name, psrc, pdst);
+ name.substring(psrc.length() - 1);
return this; return this;
} }
@ -392,8 +398,7 @@ public class RefSpec implements Serializable {
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) srcName = expandWildcard(name, pdst, psrc);
+ name.substring(pdst.length() - 1);
dstName = name; dstName = name;
return this; return this;
} }
@ -414,12 +419,50 @@ public class RefSpec implements Serializable {
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()) if (isWildcard()) {
return refName.startsWith(s.substring(0, s.length() - 1)); int wildcardIndex = s.indexOf('*');
return refName.equals(s); 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() {

Loading…
Cancel
Save