aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java140
1 files changed, 108 insertions, 32 deletions
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 e6e3665671..62e8ae0cce 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.text.MessageFormat;
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 @@ public class UploadPack {
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<String, Ref> refs;
/** Hook used while processing Git protocol v2 requests. */
@@ -283,6 +287,9 @@ public class UploadPack {
/** 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 class UploadPack {
}
private Map<String, Ref> 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<String, Ref> getFilteredRefs(Collection<String> 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<String, Ref> 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.
+ * <p>
+ * 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 @@ public class UploadPack {
if (req.getPeel()) {
adv.setDerefTags(true);
}
- Map<String, Ref> 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<String, Ref> refsToSend = getFilteredRefs(req.getRefPrefixes());
if (req.getSymrefs()) {
findSymrefs(adv, refsToSend);
}
@@ -953,8 +1023,7 @@ public class UploadPack {
}
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 @@ public class UploadPack {
// TODO(ifrade): Refactor to pass around the Request object, instead of
// copying data back to class fields
- wantIds = req.getWantIds();
-
List<ObjectId> deepenNots = new ArrayList<>();
for (String s : req.getDeepenNotRefs()) {
Ref ref = db.getRefDatabase().getRef(s);
@@ -974,6 +1041,24 @@ public class UploadPack {
deepenNots.add(ref.getObjectId());
}
+ Map<String, ObjectId> 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 @@ public class UploadPack {
sectionSent = true;
}
- if (!req.getWantedRefs().isEmpty()) {
+ if (!wantedRefs.isEmpty()) {
if (sectionSent) {
pckOut.writeDelim();
}
pckOut.writeString("wanted-refs\n"); //$NON-NLS-1$
- for (Map.Entry<String, ObjectId> entry : req.getWantedRefs()
- .entrySet()) {
+ for (Map.Entry<String, ObjectId> entry :
+ wantedRefs.entrySet()) {
pckOut.writeString(entry.getValue().getName() + ' ' +
entry.getKey() + '\n');
}
@@ -1286,15 +1371,7 @@ public class UploadPack {
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<String, Ref> advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs();
if (serviceName != null) {
adv.writeOne("# service=" + serviceName + '\n'); //$NON-NLS-1$
@@ -1326,7 +1403,6 @@ public class UploadPack {
adv.advertiseCapability(OPTION_FILTER);
}
adv.setDerefTags(true);
- Map<String, Ref> advertisedOrDefaultRefs = getAdvertisedOrDefaultRefs();
findSymrefs(adv, advertisedOrDefaultRefs);
advertised = adv.send(advertisedOrDefaultRefs);
if (adv.isEmpty())