aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruno Albuquerque <bga@google.com>2021-07-16 11:44:46 -0700
committerBruno Albuquerque <bga@google.com>2021-08-31 14:36:06 -0700
commit5b8e387c6736ea4d3b70f4bd71173c3aa0048df6 (patch)
tree3a2396f64aa03f9207d07516ba827238beeb590d
parentc1961ad8094a09713badc6f8235d24ed99a55512 (diff)
downloadjgit-5b8e387c6736ea4d3b70f4bd71173c3aa0048df6.tar.gz
jgit-5b8e387c6736ea4d3b70f4bd71173c3aa0048df6.zip
transport: add object-info capability
Sometimes it is useful to obtain metadata associated with an object without the need to first download it locally. This is specially useful when using partial clones. This change implements the object-info capability that allows clients to query the remote server for object metadata (currently only size). This is a backport of the same capability that was recently added to the Git project a2ba162cda (object-info: support for retrieving object info, 2021-04-20). Signed-off-by: Bruno Albuquerque <bga@google.com> Change-Id: I4dc9828e1c247f08b0976b8810be92d124366165
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java47
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java31
8 files changed, 220 insertions, 0 deletions
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 b0b5f68efa..f4bbb4c9f8 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
@@ -460,6 +460,8 @@ public class UploadPackTest {
private FetchV2Request fetchRequest;
+ private ObjectInfoRequest objectInfoRequest;
+
@Override
public void onCapabilities(CapabilitiesV2Request req) {
capabilitiesRequest = req;
@@ -474,6 +476,11 @@ public class UploadPackTest {
public void onFetch(FetchV2Request req) {
fetchRequest = req;
}
+
+ @Override
+ public void onObjectInfo(ObjectInfoRequest req) {
+ objectInfoRequest = req;
+ }
}
@Test
@@ -2641,4 +2648,44 @@ public class UploadPackTest {
return refdb;
}
}
+
+ @Test
+ public void testObjectInfo() throws Exception {
+ server.getConfig().setBoolean("uploadpack", null, "advertiseobjectinfo",
+ true);
+
+ RevBlob blob1 = remote.blob("foobar");
+ RevBlob blob2 = remote.blob("fooba");
+ RevTree tree = remote.tree(remote.file("1", blob1),
+ remote.file("2", blob2));
+ RevCommit commit = remote.commit(tree);
+ remote.update("master", commit);
+
+ TestV2Hook hook = new TestV2Hook();
+ ByteArrayInputStream recvStream = uploadPackV2((UploadPack up) -> {
+ up.setProtocolV2Hook(hook);
+ }, "command=object-info\n", "size",
+ "oid " + ObjectId.toString(blob1.getId()),
+ "oid " + ObjectId.toString(blob2.getId()), PacketLineIn.end());
+ PacketLineIn pckIn = new PacketLineIn(recvStream);
+
+ assertThat(hook.objectInfoRequest, notNullValue());
+ assertThat(pckIn.readString(), is("size"));
+ assertThat(pckIn.readString(),
+ is(ObjectId.toString(blob1.getId()) + " 6"));
+ assertThat(pckIn.readString(),
+ is(ObjectId.toString(blob2.getId()) + " 5"));
+ assertTrue(PacketLineIn.isEnd(pckIn.readString()));
+ }
+
+ @Test
+ public void testObjectInfo_invalidOid() throws Exception {
+ server.getConfig().setBoolean("uploadpack", null, "advertiseobjectinfo",
+ true);
+
+ assertThrows(UploadPackInternalServerErrorException.class,
+ () -> uploadPackV2("command=object-info\n", "size",
+ "oid invalid",
+ PacketLineIn.end()));
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index c5e52bef98..aaa9308ac3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -255,6 +255,13 @@ public final class GitProtocolConstants {
public static final String COMMAND_FETCH = "fetch"; //$NON-NLS-1$
/**
+ * The server supports the object-info capability.
+ *
+ * @since 5.13
+ */
+ public static final String COMMAND_OBJECT_INFO = "object-info"; //$NON-NLS-1$
+
+ /**
* HTTP header to set by clients to request a specific git protocol version
* in the HTTP transport.
*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java
new file mode 100644
index 0000000000..86a2716675
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021, Google LLC. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.transport;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * object-info request.
+ *
+ * <p>
+ * This is the parsed request for an object-info call, used as input to
+ * {@link ProtocolV2Hook}.
+ *
+ * @see <a href=
+ * "https://www.kernel.org/pub/software/scm/git/docs/technical/protocol-v2.html#_object_info">object-info
+ * documentation</a>
+ *
+ * @since 5.13
+ */
+public final class ObjectInfoRequest {
+ private final List<ObjectId> objectIDs;
+
+ private ObjectInfoRequest(List<ObjectId> objectIDs) {
+ this.objectIDs = objectIDs;
+ }
+
+ /** @return object IDs that the client requested. */
+ public List<ObjectId> getObjectIDs() {
+ return this.objectIDs;
+ }
+
+ /** @return A builder of {@link ObjectInfoRequest}. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** A builder for {@link ObjectInfoRequest}. */
+ public static final class Builder {
+ private List<ObjectId> objectIDs = Collections.emptyList();
+
+ private Builder() {
+ }
+
+ /**
+ * @param value
+ * @return the Builder
+ */
+ public Builder setObjectIDs(List<ObjectId> value) {
+ objectIDs = value;
+ return this;
+ }
+
+ /** @return ObjectInfoRequest */
+ public ObjectInfoRequest build() {
+ return new ObjectInfoRequest(
+ Collections.unmodifiableList(objectIDs));
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
index bfa7490665..d7626df3fa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
@@ -55,4 +55,16 @@ public interface ProtocolV2Hook {
throws ServiceMayNotContinueException {
// Do nothing by default
}
+
+ /**
+ * @param req
+ * the object-info request
+ * @throws ServiceMayNotContinueException
+ * abort; the message will be sent to the user
+ * @since 5.13
+ */
+ default void onObjectInfo(ObjectInfoRequest req)
+ throws ServiceMayNotContinueException {
+ // Do nothing by default
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java
index 4cf8db6f5e..7b3a4cea06 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2HookChain.java
@@ -71,6 +71,14 @@ public class ProtocolV2HookChain implements ProtocolV2Hook {
}
}
+ @Override
+ public void onObjectInfo(ObjectInfoRequest req)
+ throws ServiceMayNotContinueException {
+ for (ProtocolV2Hook hook : hooks) {
+ hook.onObjectInfo(req);
+ }
+ }
+
private ProtocolV2HookChain(List<? extends ProtocolV2Hook> hooks) {
this.hooks = Collections.unmodifiableList(hooks);
}
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 92f0133f5a..6cec4b9a3f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ObjectId;
@@ -248,4 +249,38 @@ final class ProtocolV2Parser {
return builder.setRefPrefixes(prefixes).build();
}
+ ObjectInfoRequest parseObjectInfoRequest(PacketLineIn pckIn)
+ throws PackProtocolException, IOException {
+ ObjectInfoRequest.Builder builder = ObjectInfoRequest.builder();
+ List<ObjectId> objectIDs = new ArrayList<>();
+
+ String line = pckIn.readString();
+
+ if (PacketLineIn.isEnd(line)) {
+ return builder.build();
+ }
+
+ if (!line.equals("size")) { //$NON-NLS-1$
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().unexpectedPacketLine, line));
+ }
+
+ for (String line2 : pckIn.readStrings()) {
+ if (!line2.startsWith("oid ")) { //$NON-NLS-1$
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().unexpectedPacketLine, line2));
+ }
+
+ String oidStr = line2.substring("oid ".length()); //$NON-NLS-1$
+
+ try {
+ objectIDs.add(ObjectId.fromString(oidStr));
+ } catch (InvalidObjectIdException e) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().invalidObject, oidStr), e);
+ }
+ }
+
+ return builder.setObjectIDs(objectIDs).build();
+ }
}
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 da97f1e580..02be434887 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -123,6 +123,7 @@ public class TransferConfig {
private final boolean advertiseSidebandAll;
private final boolean advertiseWaitForDone;
+ private final boolean advertiseObjectInfo;
final @Nullable ProtocolVersion protocolVersion;
final String[] hideRefs;
@@ -211,6 +212,8 @@ public class TransferConfig {
"advertisesidebandall", false);
advertiseWaitForDone = rc.getBoolean("uploadpack",
"advertisewaitfordone", false);
+ advertiseObjectInfo = rc.getBoolean("uploadpack",
+ "advertiseobjectinfo", false);
}
/**
@@ -318,6 +321,14 @@ public class TransferConfig {
}
/**
+ * @return true to advertise object-info to all clients
+ * @since 5.13
+ */
+ public boolean isAdvertiseObjectInfo() {
+ return advertiseObjectInfo;
+ }
+
+ /**
* Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured
* hidden refs.
*
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 37a1c1e589..07b348d0e7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -17,6 +17,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SERVER_OPTION;
import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_FETCH;
import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_OBJECT_INFO;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_REACHABLE_SHA1_IN_WANT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
@@ -1269,6 +1270,32 @@ public class UploadPack {
}
}
+ private void objectInfo(PacketLineOut pckOut) throws IOException {
+ ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
+ ObjectInfoRequest req = parser.parseObjectInfoRequest(pckIn);
+
+ protocolV2Hook.onObjectInfo(req);
+
+ ObjectReader or = getRepository().newObjectReader();
+
+ // Size is the only attribute currently supported.
+ pckOut.writeString("size"); //$NON-NLS-1$
+
+ for (ObjectId oid : req.getObjectIDs()) {
+ long size;
+ try {
+ size = or.getObjectSize(oid, ObjectReader.OBJ_ANY);
+ } catch (MissingObjectException e) {
+ throw new PackProtocolException(MessageFormat
+ .format(JGitText.get().missingObject, oid.name()), e);
+ }
+
+ pckOut.writeString(oid.getName() + " " + size); //$NON-NLS-1$
+ }
+
+ pckOut.end();
+ }
+
/*
* Returns true if this is the last command and we should tear down the
* connection.
@@ -1295,6 +1322,10 @@ public class UploadPack {
fetchV2(pckOut);
return false;
}
+ if (command.equals("command=" + COMMAND_OBJECT_INFO)) { //$NON-NLS-1$
+ objectInfo(pckOut);
+ return false;
+ }
throw new PackProtocolException(MessageFormat
.format(JGitText.get().unknownTransportCommand, command));
}