diff options
7 files changed, 193 insertions, 32 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java index 965a2faf8e..c0db83a820 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java @@ -9,6 +9,10 @@ */ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.transport.ObjectIdMatcher.hasOnlyObjectIds; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -160,6 +164,10 @@ public class ProtocolV0ParserTest { assertThat(request.getWantIds(), hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e", "f900c8326a43303685c46b279b9f70411bff1a4b")); + assertTrue(request.getFilterSpec().allowsType(OBJ_BLOB)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TREE)); + assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TAG)); assertEquals(13000, request.getFilterSpec().getBlobLimit()); assertEquals(-1, request.getFilterSpec().getTreeDepthLimit()); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java index 038ce717b3..837bdce919 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java @@ -9,6 +9,10 @@ */ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.transport.ObjectIdMatcher.hasOnlyObjectIds; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItems; @@ -195,7 +199,11 @@ public class ProtocolV2ParserTest { ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.start().allowFilter().done()); FetchV2Request request = parser.parseFetchRequest(pckIn); - assertEquals(0, request.getFilterSpec().getBlobLimit()); + assertFalse(request.getFilterSpec().allowsType(OBJ_BLOB)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TREE)); + assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TAG)); + assertEquals(-1, request.getFilterSpec().getBlobLimit()); assertEquals(-1, request.getFilterSpec().getTreeDepthLimit()); } @@ -207,6 +215,10 @@ public class ProtocolV2ParserTest { ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.start().allowFilter().done()); FetchV2Request request = parser.parseFetchRequest(pckIn); + assertTrue(request.getFilterSpec().allowsType(OBJ_BLOB)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TREE)); + assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TAG)); assertEquals(15, request.getFilterSpec().getBlobLimit()); assertEquals(-1, request.getFilterSpec().getTreeDepthLimit()); } @@ -219,6 +231,10 @@ public class ProtocolV2ParserTest { ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.start().allowFilter().done()); FetchV2Request request = parser.parseFetchRequest(pckIn); + assertTrue(request.getFilterSpec().allowsType(OBJ_BLOB)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TREE)); + assertTrue(request.getFilterSpec().allowsType(OBJ_COMMIT)); + assertTrue(request.getFilterSpec().allowsType(OBJ_TAG)); assertEquals(-1, request.getFilterSpec().getBlobLimit()); assertEquals(3, request.getFilterSpec().getTreeDepthLimit()); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java index d58e576984..e9b4af932e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java @@ -127,7 +127,7 @@ public class UploadPackTest { } @Test - public void testFetchWithBlobNoneFilter() throws Exception { + public void testFetchWithBlobZeroFilter() throws Exception { InMemoryRepository server2 = newRepo("server2"); try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>( server2)) { @@ -298,6 +298,38 @@ public class UploadPackTest { } @Test + public void testFetchWithTreeZeroFilter() throws Exception { + InMemoryRepository server2 = newRepo("server2"); + try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>( + server2)) { + RevBlob blob1 = remote2.blob("foobar"); + RevBlob blob2 = remote2.blob("fooba"); + RevTree tree = remote2.tree(remote2.file("1", blob1), + remote2.file("2", blob2)); + RevCommit commit = remote2.commit(tree); + remote2.update("master", commit); + + server2.getConfig().setBoolean("uploadpack", null, "allowfilter", + true); + + testProtocol = new TestProtocol<>((Object req, Repository db) -> { + UploadPack up = new UploadPack(db); + return up; + }, null); + uri = testProtocol.register(ctx, server2); + + try (Transport tn = testProtocol.open(uri, client, "server2")) { + tn.setFilterSpec(FilterSpec.withTreeDepthLimit(0)); + tn.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(commit.name()))); + assertFalse(client.getObjectDatabase().has(tree.toObjectId())); + assertFalse(client.getObjectDatabase().has(blob1.toObjectId())); + assertFalse(client.getObjectDatabase().has(blob2.toObjectId())); + } + } + } + + @Test public void testFetchWithNonSupportingServer() throws Exception { InMemoryRepository server2 = newRepo("server2"); try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 824c62ad9a..2d574887b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -2182,10 +2182,12 @@ public class PackWriter implements AutoCloseable { // Check if this object needs to be rejected, doing the cheaper // checks first. - boolean reject = filterSpec.getBlobLimit() >= 0 && - type == OBJ_BLOB && - !want.contains(src) && - reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit(); + boolean reject = + (!filterSpec.allowsType(type) && !want.contains(src)) || + (filterSpec.getBlobLimit() >= 0 && + type == OBJ_BLOB && + !want.contains(src) && + reader.getObjectSize(src, OBJ_BLOB) > filterSpec.getBlobLimit()); if (!reject) { addObject(src, type, pathHashCode); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java index d09b5579fa..a8cf849fed 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java @@ -10,6 +10,15 @@ package org.eclipse.jgit.transport; +import static java.math.BigInteger.ZERO; +import static java.util.Objects.requireNonNull; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; + +import java.math.BigInteger; import java.text.MessageFormat; import org.eclipse.jgit.annotations.Nullable; @@ -24,11 +33,54 @@ import org.eclipse.jgit.internal.JGitText; */ public final class FilterSpec { + /** Immutable bit-set representation of a set of Git object types. */ + static class ObjectTypes { + static ObjectTypes ALL = allow(OBJ_BLOB, OBJ_TREE, OBJ_COMMIT, OBJ_TAG); + + private final BigInteger val; + + private ObjectTypes(BigInteger val) { + this.val = requireNonNull(val); + } + + static ObjectTypes allow(int... types) { + BigInteger bits = ZERO; + for (int type : types) { + bits = bits.setBit(type); + } + return new ObjectTypes(bits); + } + + boolean contains(int type) { + return val.testBit(type); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ObjectTypes)) { + return false; + } + + ObjectTypes other = (ObjectTypes) obj; + return other.val.equals(val); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return val.hashCode(); + } + } + + private final ObjectTypes types; + private final long blobLimit; private final long treeDepthLimit; - private FilterSpec(long blobLimit, long treeDepthLimit) { + private FilterSpec(ObjectTypes types, long blobLimit, long treeDepthLimit) { + this.types = requireNonNull(types); this.blobLimit = blobLimit; this.treeDepthLimit = treeDepthLimit; } @@ -53,7 +105,8 @@ public final class FilterSpec { public static FilterSpec fromFilterLine(String filterLine) throws PackProtocolException { if (filterLine.equals("blob:none")) { //$NON-NLS-1$ - return FilterSpec.withBlobLimit(0); + return FilterSpec.withObjectTypes( + ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG)); } else if (filterLine.startsWith("blob:limit=")) { //$NON-NLS-1$ long blobLimit = -1; try { @@ -86,8 +139,18 @@ public final class FilterSpec { } /** + * @param types + * set of permitted object types, for use in "blob:none" and + * "object:none" filters + * @return a filter spec which restricts to objects of the specified types + */ + static FilterSpec withObjectTypes(ObjectTypes types) { + return new FilterSpec(types, -1, -1); + } + + /** * @param blobLimit - * the blob limit in a "blob:[limit]" or "blob:none" filter line + * the blob limit in a "blob:[limit]" filter line * @return a filter spec which filters blobs above a certain size */ static FilterSpec withBlobLimit(long blobLimit) { @@ -95,7 +158,7 @@ public final class FilterSpec { throw new IllegalArgumentException( "blobLimit cannot be negative: " + blobLimit); //$NON-NLS-1$ } - return new FilterSpec(blobLimit, -1); + return new FilterSpec(ObjectTypes.ALL, blobLimit, -1); } /** @@ -109,13 +172,25 @@ public final class FilterSpec { throw new IllegalArgumentException( "treeDepthLimit cannot be negative: " + treeDepthLimit); //$NON-NLS-1$ } - return new FilterSpec(-1, treeDepthLimit); + return new FilterSpec(ObjectTypes.ALL, -1, treeDepthLimit); } /** * A placeholder that indicates no filtering. */ - public static final FilterSpec NO_FILTER = new FilterSpec(-1, -1); + public static final FilterSpec NO_FILTER = new FilterSpec(ObjectTypes.ALL, -1, -1); + + /** + * @param type + * a Git object type, such as + * {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB} + * @return whether this filter allows objects of the specified type + * + * @since 5.9 + */ + public boolean allowsType(int type) { + return types.contains(type); + } /** * @return -1 if this filter does not filter blobs based on size, or a @@ -138,7 +213,7 @@ public final class FilterSpec { * @return true if this filter doesn't filter out anything */ public boolean isNoOp() { - return blobLimit == -1 && treeDepthLimit == -1; + return types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit == -1; } /** @@ -146,14 +221,17 @@ public final class FilterSpec { */ @Nullable public String filterLine() { - if (blobLimit == 0) { - return GitProtocolConstants.OPTION_FILTER + " blob:none"; //$NON-NLS-1$ + if (isNoOp()) { + return null; + } else if (types.equals(ObjectTypes.allow(OBJ_TREE, OBJ_COMMIT, OBJ_TAG)) && + blobLimit == -1 && treeDepthLimit == -1) { + return OPTION_FILTER + " blob:none"; //$NON-NLS-1$ + } else if (types.equals(ObjectTypes.ALL) && blobLimit >= 0 && treeDepthLimit == -1) { + return OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$ + } else if (types.equals(ObjectTypes.ALL) && blobLimit == -1 && treeDepthLimit >= 0) { + return OPTION_FILTER + " tree:" + treeDepthLimit; //$NON-NLS-1$ + } else { + throw new IllegalStateException(); } - - if (blobLimit > 0) { - return GitProtocolConstants.OPTION_FILTER + " blob:limit=" + blobLimit; //$NON-NLS-1$ - } - - return null; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 91574efec4..9c5a1e32d3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -52,7 +52,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -235,7 +235,7 @@ public abstract class FS { * @see java.util.concurrent.Executors#newCachedThreadPool() */ private static final Executor FUTURE_RUNNER = new ThreadPoolExecutor(0, - 5, 30L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), + 5, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), runnable -> { Thread t = new Thread(runnable, "FileStoreAttributeReader-" //$NON-NLS-1$ + threadNumber.getAndIncrement()); @@ -1356,7 +1356,7 @@ public abstract class FS { String v; try { v = readPipe(gitExe.getParentFile(), - new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$ + new String[] { gitExe.getPath(), "--version" }, //$NON-NLS-1$ Charset.defaultCharset().name()); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); @@ -1375,7 +1375,8 @@ public abstract class FS { String w; try { w = readPipe(gitExe.getParentFile(), - new String[] { "git", "config", "--system", "--edit" }, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + new String[] { gitExe.getPath(), "config", "--system", //$NON-NLS-1$ //$NON-NLS-2$ + "--edit" }, //$NON-NLS-1$ Charset.defaultCharset().name(), env); } catch (CommandFailedException e) { LOG.warn(e.getMessage()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java index c9d2770b18..fb63dc02bb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java @@ -54,6 +54,8 @@ import org.slf4j.LoggerFactory; public class FS_POSIX extends FS { private static final Logger LOG = LoggerFactory.getLogger(FS_POSIX.class); + private static final String DEFAULT_GIT_LOCATION = "/usr/bin/git"; //$NON-NLS-1$ + private static final int DEFAULT_UMASK = 0022; private volatile int umask = -1; @@ -138,24 +140,46 @@ public class FS_POSIX extends FS { String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$ File gitExe = searchPath(path, "git"); //$NON-NLS-1$ - if (gitExe == null) { - if (SystemReader.getInstance().isMacOS()) { + if (SystemReader.getInstance().isMacOS()) { + if (gitExe == null + || DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) { if (searchPath(path, "bash") != null) { //$NON-NLS-1$ // On MacOSX, PATH is shorter when Eclipse is launched from the // Finder than from a terminal. Therefore try to launch bash as a // login shell and search using that. - String w; try { - w = readPipe(userHome(), + String w = readPipe(userHome(), new String[]{"bash", "--login", "-c", "which git"}, // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ Charset.defaultCharset().name()); + if (!StringUtils.isEmptyOrNull(w)) { + gitExe = new File(w); + } } catch (CommandFailedException e) { LOG.warn(e.getMessage()); - return null; } - if (!StringUtils.isEmptyOrNull(w)) { - gitExe = new File(w); + } + } + if (gitExe != null + && DEFAULT_GIT_LOCATION.equals(gitExe.getPath())) { + // If we still have the default git exe, it's an XCode wrapper + // that may prompt the user to install the XCode command line + // tools if not already present. Avoid the prompt by returning + // null if no XCode git is there. + try { + String w = readPipe(userHome(), + new String[] { "xcode-select", "-p" }, //$NON-NLS-1$ //$NON-NLS-2$ + Charset.defaultCharset().name()); + if (StringUtils.isEmptyOrNull(w)) { + gitExe = null; + } else { + File realGitExe = new File(new File(w), + DEFAULT_GIT_LOCATION.substring(1)); + if (!realGitExe.exists()) { + gitExe = null; + } } + } catch (CommandFailedException e) { + gitExe = null; } } } |