diff options
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)); } |