diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF index cce70819b..a1432e617 100644 --- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF @@ -50,5 +50,7 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)", org.hamcrest;version="[1.1.0,2.0.0)", org.hamcrest.core;version="[1.1.0,2.0.0)", org.junit;version="[4.12,5.0.0)", + org.junit.rules;version="[4.12,5.0.0)", org.junit.runner;version="[4.12,5.0.0)", org.junit.runners;version="[4.12,5.0.0)" +Require-Bundle: org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml index c4208b3c1..0f7cdd26e 100644 --- a/org.eclipse.jgit.http.test/pom.xml +++ b/org.eclipse.jgit.http.test/pom.xml @@ -70,6 +70,13 @@ test + + org.hamcrest + hamcrest-library + test + [1.1.0,2.0.0) + + org.eclipse.jgit org.eclipse.jgit @@ -83,13 +90,6 @@ test - - org.hamcrest - hamcrest-library - test - [1.1.0,2.0.0) - - org.eclipse.jgit org.eclipse.jgit.junit.http diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index d1c7737d4..b26324d4f 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -85,6 +85,7 @@ import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; import org.eclipse.jgit.junit.TestRepository; @@ -105,24 +106,35 @@ import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.transport.AbstractAdvertiseRefsHook; +import org.eclipse.jgit.transport.AdvertiseRefsHook; import org.eclipse.jgit.transport.CredentialItem; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.FetchConnection; import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.TransportHttp; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.transport.http.HttpConnectionFactory; import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.transport.resolver.UploadPackFactory; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.HttpSupport; import org.eclipse.jgit.util.SystemReader; +import org.hamcrest.Matchers; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -131,6 +143,11 @@ public class SmartClientSmartServerTest extends HttpTestCase { private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding"; + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private AdvertiseRefsHook advertiseRefsHook; + private Repository remoteRepository; private CredentialsProvider testCredentials = new UsernamePasswordCredentialsProvider( @@ -148,7 +165,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { private RevBlob A_txt; - private RevCommit A, B; + private RevCommit A, B, unreachableCommit; @Parameters public static Collection data() { @@ -175,6 +192,19 @@ public void setUp() throws Exception { ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); GitServlet gs = new GitServlet(); + gs.setUploadPackFactory(new UploadPackFactory() { + @Override + public UploadPack create(HttpServletRequest req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + DefaultUploadPackFactory f = new DefaultUploadPackFactory(); + UploadPack up = f.create(req, db); + if (advertiseRefsHook != null) { + up.setAdvertiseRefsHook(advertiseRefsHook); + } + return up; + } + }); ServletContextHandler app = addNormalContext(gs, src, srcName); @@ -200,6 +230,8 @@ public void setUp() throws Exception { B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); src.update(master, B); + unreachableCommit = src.commit().add("A_txt", A_txt).create(); + src.update("refs/garbage/a/very/long/ref/name/to/compress", B); } @@ -452,6 +484,56 @@ public void testListRemote_BadName() throws IOException, URISyntaxException { info.getResponseHeader(HDR_CONTENT_TYPE)); } + @Test + public void testFetchBySHA1() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, remoteURI)) { + t.fetch(NullProgressMonitor.INSTANCE, + Collections.singletonList(new RefSpec(B.name()))); + } + + assertTrue(dst.hasObject(A_txt)); + } + + @Test + public void testFetchBySHA1Unreachable() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + try (Transport t = Transport.open(dst, remoteURI)) { + thrown.expect(TransportException.class); + thrown.expectMessage(Matchers.containsString( + "want " + unreachableCommit.name() + " not valid")); + t.fetch(NullProgressMonitor.INSTANCE, Collections + .singletonList(new RefSpec(unreachableCommit.name()))); + } + } + + @Test + public void testFetchBySHA1UnreachableByAdvertiseRefsHook() + throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + advertiseRefsHook = new AbstractAdvertiseRefsHook() { + @Override + protected Map getAdvertisedRefs(Repository repository, + RevWalk revWalk) { + return Collections.emptyMap(); + } + }; + + try (Transport t = Transport.open(dst, remoteURI)) { + thrown.expect(TransportException.class); + thrown.expectMessage(Matchers.containsString( + "want " + A.name() + " not valid")); + t.fetch(NullProgressMonitor.INSTANCE, Collections + .singletonList(new RefSpec(A.name()))); + } + } + @Test public void testInitialClone_Small() throws Exception { Repository dst = createBareRepository(); diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml index c0e2bead3..e4a75ad4f 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml @@ -82,4 +82,4 @@ - \ No newline at end of file + 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 6b1cbdd54..dafa81ecd 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 @@ -42,7 +42,6 @@ */ package org.eclipse.jgit.transport; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -152,8 +151,7 @@ public void testFetchBasicArguments() PacketLineIn.END); ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.getDefault()); - FetchV2Request request = parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); assertTrue(request.getClientCapabilities() .contains(GitProtocolConstants.OPTION_THIN_PACK)); assertTrue(request.getClientCapabilities() @@ -183,8 +181,7 @@ public void testFetchWithShallow_deepen() throws IOException { PacketLineIn.END); ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.getDefault()); - FetchV2Request request = parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); assertThat(request.getClientShallowCommits(), hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0", "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); @@ -203,8 +200,7 @@ public void testFetchWithShallow_deepenNot() throws IOException { PacketLineIn.END); ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.getDefault()); - FetchV2Request request = parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); assertThat(request.getClientShallowCommits(), hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0", "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); @@ -221,8 +217,7 @@ public void testFetchWithShallow_deepenSince() throws IOException { PacketLineIn.END); ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.getDefault()); - FetchV2Request request = parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); assertThat(request.getClientShallowCommits(), hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0", "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); @@ -236,8 +231,7 @@ public void testFetchWithNoneFilter() throws IOException { PacketLineIn.END); ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.start().allowFilter().done()); - FetchV2Request request = parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); assertEquals(0, request.getFilterBlobLimit()); } @@ -248,8 +242,7 @@ public void testFetchWithBlobSizeFilter() throws IOException { PacketLineIn.END); ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.start().allowFilter().done()); - FetchV2Request request = parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); assertEquals(15, request.getFilterBlobLimit()); } @@ -263,8 +256,7 @@ public void testFetchMustNotHaveMultipleFilters() throws IOException { ConfigBuilder.start().allowFilter().done()); thrown.expect(PackProtocolException.class); - parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + parser.parseFetchRequest(pckIn); } @Test @@ -275,8 +267,7 @@ public void testFetchFilterWithoutAllowFilter() throws IOException { ConfigBuilder.getDefault()); thrown.expect(PackProtocolException.class); - parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + parser.parseFetchRequest(pckIn); } @Test @@ -293,16 +284,13 @@ public void testFetchWithRefInWant() throws Exception { ProtocolV2Parser parser = new ProtocolV2Parser( ConfigBuilder.start().allowRefInWant().done()); - - FetchV2Request request = parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); assertEquals(1, request.getWantedRefs().size()); - assertThat(request.getWantedRefs().keySet(), + assertThat(request.getWantedRefs(), hasItems("refs/heads/branchA")); - assertEquals(2, request.getWantIds().size()); + assertEquals(1, request.getWantIds().size()); assertThat(request.getWantIds(), hasOnlyObjectIds( - "e4980cdc48cfa1301493ca94eb70523f6788b819", - one.getName())); + "e4980cdc48cfa1301493ca94eb70523f6788b819")); } @Test @@ -319,10 +307,9 @@ public void testFetchWithRefInWantUnknownRef() throws Exception { testRepo.update("branchA", one); testRepo.update("branchB", two); - thrown.expect(PackProtocolException.class); - thrown.expectMessage(containsString("refs/heads/branchC")); - parser.parseFetchRequest(pckIn, - testRepo.getRepository().getRefDatabase()); + FetchV2Request request = parser.parseFetchRequest(pckIn); + assertEquals(1, request.getWantedRefs().size()); + assertThat(request.getWantedRefs(), hasItems("refs/heads/branchC")); } @Test diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index 2304fa35b..79183a6cd 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -66,6 +66,20 @@ + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java index 8e36a109e..ac6361cde 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java @@ -48,9 +48,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.TreeMap; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; @@ -67,7 +65,7 @@ public final class FetchV2Request extends FetchRequest { private final List peerHas; - private final TreeMap wantedRefs; + private final List wantedRefs; private final boolean doneReceived; @@ -75,7 +73,7 @@ public final class FetchV2Request extends FetchRequest { private final List serverOptions; FetchV2Request(@NonNull List peerHas, - @NonNull TreeMap wantedRefs, + @NonNull List wantedRefs, @NonNull Set wantIds, @NonNull Set clientShallowCommits, int deepenSince, @NonNull List deepenNotRefs, int depth, @@ -102,7 +100,7 @@ List getPeerHas() { * @return list of references received in "want-ref" lines */ @NonNull - Map getWantedRefs() { + List getWantedRefs() { return wantedRefs; } @@ -135,7 +133,7 @@ static Builder builder() { static final class Builder { final List peerHas = new ArrayList<>(); - final TreeMap wantedRefs = new TreeMap<>(); + final List wantedRefs = new ArrayList<>(); final Set wantIds = new HashSet<>(); @@ -176,12 +174,10 @@ Builder addPeerHas(ObjectId objectId) { * * @param refName * reference name - * @param oid - * object id the reference is pointing at * @return this builder */ - Builder addWantedRef(String refName, ObjectId oid) { - wantedRefs.put(refName, oid); + Builder addWantedRef(String refName) { + wantedRefs.add(refName); return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java index a03f02146..8f4b86ee0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java @@ -62,8 +62,6 @@ import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.RefDatabase; /** * Parse the incoming git protocol lines from the wire and translate them into a @@ -113,24 +111,17 @@ private static String consumeCapabilities(PacketLineIn pckIn, * Parse the incoming fetch request arguments from the wire. The caller must * be sure that what is comings is a fetch request before coming here. * - * This operation requires the reference database to validate incoming - * references. - * * @param pckIn * incoming lines - * @param refdb - * reference database (to validate that received references exist - * and point to valid objects) * @return A FetchV2Request populated with information received from the * wire. * @throws PackProtocolException * incompatible options, wrong type of arguments or other issues * where the request breaks the protocol. * @throws IOException - * an IO error prevented reading the incoming message or - * accessing the ref database. + * an IO error prevented reading the incoming message. */ - FetchV2Request parseFetchRequest(PacketLineIn pckIn, RefDatabase refdb) + FetchV2Request parseFetchRequest(PacketLineIn pckIn) throws PackProtocolException, IOException { FetchV2Request.Builder reqBuilder = FetchV2Request.builder(); @@ -158,22 +149,7 @@ FetchV2Request parseFetchRequest(PacketLineIn pckIn, RefDatabase refdb) reqBuilder.addWantId(ObjectId.fromString(line.substring(5))); } else if (transferConfig.isAllowRefInWant() && line.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$ - String refName = line.substring(OPTION_WANT_REF.length() + 1); - // TODO(ifrade): This validation should be done after the - // protocol parsing. It is not a protocol problem asking for an - // unexisting ref and we wouldn't need the ref database here - Ref ref = refdb.exactRef(refName); - if (ref == null) { - throw new PackProtocolException(MessageFormat - .format(JGitText.get().invalidRefName, refName)); - } - ObjectId oid = ref.getObjectId(); - if (oid == null) { - throw new PackProtocolException(MessageFormat - .format(JGitText.get().invalidRefName, refName)); - } - reqBuilder.addWantedRef(refName, oid); - reqBuilder.addWantId(oid); + reqBuilder.addWantedRef(line.substring(OPTION_WANT_REF.length() + 1)); } else if (line.startsWith("have ")) { //$NON-NLS-1$ reqBuilder.addPeerHas(ObjectId.fromString(line.substring(5))); } else if (line.equals("done")) { //$NON-NLS-1$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java index d0db9f0e9..a3e655cd9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -327,6 +327,16 @@ private boolean prefixMatch(String p, String s) { }; } + /** + * Like {@code getRefFilter() == RefFilter.DEFAULT}, but faster. + * + * @return {@code true} if no ref filtering is needed because there + * are no configured hidden refs. + */ + boolean hasDefaultRefFilter() { + return hideRefs.length == 0; + } + static class FsckKeyNameHolder { private static final Map errors; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index e6e366567..62e8ae0cc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -43,8 +43,9 @@ package org.eclipse.jgit.transport; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; import static org.eclipse.jgit.lib.Constants.R_TAGS; -import static org.eclipse.jgit.lib.RefDatabase.ALL; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_WANT; import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_FETCH; import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS; @@ -75,11 +76,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; @@ -274,7 +275,10 @@ private static interface IOConsumer { private OutputStream msgOut = NullOutputStream.INSTANCE; - /** The refs we advertised as existing at the start of the connection. */ + /** + * Refs eligible for advertising to the client, set using + * {@link #setAdvertisedRefs}. + */ private Map refs; /** Hook used while processing Git protocol v2 requests. */ @@ -283,6 +287,9 @@ private static interface IOConsumer { /** Hook used while advertising the refs to the client. */ private AdvertiseRefsHook advertiseRefsHook = AdvertiseRefsHook.DEFAULT; + /** Whether the {@link #advertiseRefsHook} has been invoked. */ + private boolean advertiseRefsHookCalled; + /** Filter used while advertising the refs to the client. */ private RefFilter refFilter = RefFilter.DEFAULT; @@ -794,11 +801,83 @@ public PackStatistics getStatistics() { } private Map getAdvertisedOrDefaultRefs() throws IOException { - if (refs == null) - setAdvertisedRefs(db.getRefDatabase().getRefs(ALL)); + if (refs != null) { + return refs; + } + + if (!advertiseRefsHookCalled) { + advertiseRefsHook.advertiseRefs(this); + advertiseRefsHookCalled = true; + } + if (refs == null) { + // Fall back to all refs. + setAdvertisedRefs( + db.getRefDatabase().getRefs().stream() + .collect(toMap(Ref::getName, identity()))); + } return refs; } + private Map getFilteredRefs(Collection refPrefixes) + throws IOException { + if (refPrefixes.isEmpty()) { + return getAdvertisedOrDefaultRefs(); + } + if (refs == null && !advertiseRefsHookCalled) { + advertiseRefsHook.advertiseRefs(this); + advertiseRefsHookCalled = true; + } + if (refs == null) { + // Fast path: the advertised refs hook did not set advertised refs. + String[] prefixes = refPrefixes.toArray(new String[0]); + Map rs = + db.getRefDatabase().getRefsByPrefix(prefixes).stream() + .collect(toMap(Ref::getName, identity(), (a, b) -> b)); + if (refFilter != RefFilter.DEFAULT) { + return refFilter.filter(rs); + } + return transferConfig.getRefFilter().filter(rs); + } + + // Slow path: filter the refs provided by the advertised refs hook. + // refFilter has already been applied to refs. + return refs.values().stream() + .filter(ref -> refPrefixes.stream() + .anyMatch(ref.getName()::startsWith)) + .collect(toMap(Ref::getName, identity())); + } + + /** + * Read a ref on behalf of the client. + *

+ * This checks that the ref is present in the ref advertisement since + * otherwise the client might not be supposed to be able to read it. + * + * @param name + * the unabbreviated name of the reference. + * @return the requested Ref, or {@code null} if it is not visible or + * does not exist. + * @throws java.io.IOException + * on failure to read the ref or check it for visibility. + */ + @Nullable + private Ref getRef(String name) throws IOException { + if (refs != null) { + return refs.get(name); + } + if (!advertiseRefsHookCalled) { + advertiseRefsHook.advertiseRefs(this); + advertiseRefsHookCalled = true; + } + if (refs == null && + refFilter == RefFilter.DEFAULT && + transferConfig.hasDefaultRefFilter()) { + // Fast path: no ref filtering is needed. + return db.getRefDatabase().exactRef(name); + } + return getAdvertisedOrDefaultRefs().get(name); + } + private void service() throws IOException { boolean sendPack = false; // If it's a non-bidi request, we need to read the entire request before @@ -921,16 +1000,7 @@ private void lsRefsV2() throws IOException { if (req.getPeel()) { adv.setDerefTags(true); } - Map refsToSend; - if (req.getRefPrefixes().isEmpty()) { - refsToSend = getAdvertisedOrDefaultRefs(); - } else { - refsToSend = new HashMap<>(); - String[] prefixes = req.getRefPrefixes().toArray(new String[0]); - for (Ref ref : db.getRefDatabase().getRefsByPrefix(prefixes)) { - refsToSend.put(ref.getName(), ref); - } - } + Map refsToSend = getFilteredRefs(req.getRefPrefixes()); if (req.getSymrefs()) { findSymrefs(adv, refsToSend); } @@ -953,8 +1023,7 @@ private void fetchV2() throws IOException { } ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig); - FetchV2Request req = parser.parseFetchRequest(pckIn, - db.getRefDatabase()); + FetchV2Request req = parser.parseFetchRequest(pckIn); currentRequest = req; rawOut.stopBuffering(); @@ -962,8 +1031,6 @@ private void fetchV2() throws IOException { // TODO(ifrade): Refactor to pass around the Request object, instead of // copying data back to class fields - wantIds = req.getWantIds(); - List deepenNots = new ArrayList<>(); for (String s : req.getDeepenNotRefs()) { Ref ref = db.getRefDatabase().getRef(s); @@ -974,6 +1041,24 @@ private void fetchV2() throws IOException { deepenNots.add(ref.getObjectId()); } + Map wantedRefs = new TreeMap<>(); + for (String refName : req.getWantedRefs()) { + Ref ref = getRef(refName); + if (ref == null) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidRefName, refName)); + } + ObjectId oid = ref.getObjectId(); + if (oid == null) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidRefName, refName)); + } + // TODO(ifrade): Avoid mutating the parsed request. + req.getWantIds().add(oid); + wantedRefs.put(refName, oid); + } + wantIds = req.getWantIds(); + boolean sectionSent = false; boolean mayHaveShallow = req.getDepth() != 0 || req.getDeepenSince() != 0 @@ -1027,13 +1112,13 @@ private void fetchV2() throws IOException { sectionSent = true; } - if (!req.getWantedRefs().isEmpty()) { + if (!wantedRefs.isEmpty()) { if (sectionSent) { pckOut.writeDelim(); } pckOut.writeString("wanted-refs\n"); //$NON-NLS-1$ - for (Map.Entry entry : req.getWantedRefs() - .entrySet()) { + for (Map.Entry entry : + wantedRefs.entrySet()) { pckOut.writeString(entry.getValue().getName() + ' ' + entry.getKey() + '\n'); } @@ -1286,15 +1371,7 @@ public void sendAdvertisedRefs(RefAdvertiser adv, return; } - try { - advertiseRefsHook.advertiseRefs(this); - } catch (ServiceMayNotContinueException fail) { - if (fail.getMessage() != null) { - adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$ - fail.setOutput(); - } - throw fail; - } + Map advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs(); if (serviceName != null) { adv.writeOne("# service=" + serviceName + '\n'); //$NON-NLS-1$ @@ -1326,7 +1403,6 @@ public void sendAdvertisedRefs(RefAdvertiser adv, adv.advertiseCapability(OPTION_FILTER); } adv.setDerefTags(true); - Map advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs(); findSymrefs(adv, advertisedOrDefaultRefs); advertised = adv.send(advertisedOrDefaultRefs); if (adv.isEmpty())