diff options
author | Bruno Albuquerque <bga@google.com> | 2021-07-16 11:44:46 -0700 |
---|---|---|
committer | Bruno Albuquerque <bga@google.com> | 2021-08-31 14:36:06 -0700 |
commit | 5b8e387c6736ea4d3b70f4bd71173c3aa0048df6 (patch) | |
tree | 3a2396f64aa03f9207d07516ba827238beeb590d | |
parent | c1961ad8094a09713badc6f8235d24ed99a55512 (diff) | |
download | jgit-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
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)); } |