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 88060c05a..88ac49a51 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 @@ -19,6 +19,7 @@ import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.transport.UploadPack.RequestPolicy; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; @@ -368,6 +369,7 @@ public class UploadPackTest { // capability advertisement (always sent) assertThat(pckIn.readString(), is("version 2")); + assertThat(pckIn.readString(), is("ls-refs")); assertTrue(pckIn.readString() == PacketLineIn.END); return recvStream; } @@ -380,4 +382,83 @@ public class UploadPackTest { assertThat(recvStream.available(), is(0)); } + @Test + public void testV2LsRefs() throws Exception { + RevCommit tip = remote.commit().message("message").create(); + remote.update("master", tip); + server.updateRef("HEAD").link("refs/heads/master"); + RevTag tag = remote.tag("tag", tip); + remote.update("refs/tags/tag", tag); + + ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n", PacketLineIn.END); + PacketLineIn pckIn = new PacketLineIn(recvStream); + + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD")); + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master")); + assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag")); + assertTrue(pckIn.readString() == PacketLineIn.END); + } + + @Test + public void testV2LsRefsSymrefs() throws Exception { + RevCommit tip = remote.commit().message("message").create(); + remote.update("master", tip); + server.updateRef("HEAD").link("refs/heads/master"); + RevTag tag = remote.tag("tag", tip); + remote.update("refs/tags/tag", tag); + + ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n", PacketLineIn.DELIM, "symrefs", PacketLineIn.END); + PacketLineIn pckIn = new PacketLineIn(recvStream); + + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD symref-target:refs/heads/master")); + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master")); + assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag")); + assertTrue(pckIn.readString() == PacketLineIn.END); + } + + @Test + public void testV2LsRefsPeel() throws Exception { + RevCommit tip = remote.commit().message("message").create(); + remote.update("master", tip); + server.updateRef("HEAD").link("refs/heads/master"); + RevTag tag = remote.tag("tag", tip); + remote.update("refs/tags/tag", tag); + + ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n", PacketLineIn.DELIM, "peel", PacketLineIn.END); + PacketLineIn pckIn = new PacketLineIn(recvStream); + + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD")); + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master")); + assertThat( + pckIn.readString(), + is(tag.toObjectId().getName() + " refs/tags/tag peeled:" + + tip.toObjectId().getName())); + assertTrue(pckIn.readString() == PacketLineIn.END); + } + + @Test + public void testV2LsRefsMultipleCommands() throws Exception { + RevCommit tip = remote.commit().message("message").create(); + remote.update("master", tip); + server.updateRef("HEAD").link("refs/heads/master"); + RevTag tag = remote.tag("tag", tip); + remote.update("refs/tags/tag", tag); + + ByteArrayInputStream recvStream = uploadPackV2( + "command=ls-refs\n", PacketLineIn.DELIM, "symrefs", "peel", PacketLineIn.END, + "command=ls-refs\n", PacketLineIn.DELIM, PacketLineIn.END); + PacketLineIn pckIn = new PacketLineIn(recvStream); + + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD symref-target:refs/heads/master")); + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master")); + assertThat( + pckIn.readString(), + is(tag.toObjectId().getName() + " refs/tags/tag peeled:" + + tip.toObjectId().getName())); + assertTrue(pckIn.readString() == PacketLineIn.END); + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD")); + assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master")); + assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag")); + assertTrue(pckIn.readString() == 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 6d39dcd8a..ccefb5168 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java @@ -222,6 +222,13 @@ public class GitProtocolConstants { */ public static final String CAPABILITY_PUSH_OPTIONS = "push-options"; //$NON-NLS-1$ + /** + * The server supports listing refs using protocol v2. + * + * @since 5.0 + */ + public static final String COMMAND_LS_REFS = "ls-refs"; //$NON-NLS-1$ + static enum MultiAck { OFF, CONTINUE, DETAILED; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java index 6ad39e3dd..0257ebec6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java @@ -53,6 +53,7 @@ import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; @@ -176,6 +177,11 @@ public abstract class RefAdvertiser { boolean first = true; + private boolean useProtocolV2; + + /* only used in protocol v2 */ + private final Map symrefs = new HashMap<>(); + /** * Initialize this advertiser with a repository for peeling tags. * @@ -186,6 +192,15 @@ public abstract class RefAdvertiser { repository = src; } + /** + * @param b + * true if this advertiser should advertise using the + * protocol v2 format, false otherwise + */ + public void setUseProtocolV2(boolean b) { + useProtocolV2 = b; + } + /** * Toggle tag peeling. *

@@ -253,7 +268,11 @@ public abstract class RefAdvertiser { * @since 3.6 */ public void addSymref(String from, String to) { - advertiseCapability(OPTION_SYMREF, from + ':' + to); + if (useProtocolV2) { + symrefs.put(from, to); + } else { + advertiseCapability(OPTION_SYMREF, from + ':' + to); + } } /** @@ -273,6 +292,23 @@ public abstract class RefAdvertiser { if (ref.getObjectId() == null) continue; + if (useProtocolV2) { + String symrefPart = symrefs.containsKey(ref.getName()) + ? (" symref-target:" + symrefs.get(ref.getName())) + : ""; + String peelPart = ""; + if (derefTags) { + if (!ref.isPeeled() && repository != null) { + ref = repository.peel(ref); + } + if (ref.getPeeledObjectId() != null) { + peelPart = " peeled:" + ref.getPeeledObjectId().getName(); + } + } + writeOne(ref.getObjectId().getName() + " " + ref.getName() + symrefPart + peelPart + "\n"); + continue; + } + advertiseAny(ref.getObjectId(), ref.getName()); if (!derefTags) 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 7f6182bf9..b2b346a6b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.transport; import static org.eclipse.jgit.lib.RefDatabase.ALL; +import static org.eclipse.jgit.transport.GitProtocolConstants.COMMAND_LS_REFS; 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; @@ -117,6 +118,7 @@ public class UploadPack { // supports protocol version 2. private static final String[] v2CapabilityAdvertisement = { "version 2", + COMMAND_LS_REFS }; /** Policy the server uses to validate client requests */ @@ -864,6 +866,35 @@ public class UploadPack { sendPack(accumulator); } + private void lsRefsV2() throws IOException { + PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut); + Map refs = getAdvertisedOrDefaultRefs(); + String line; + + adv.setUseProtocolV2(true); + + line = pckIn.readString(); + + // Currently, we do not support any capabilities, so the next + // line is DELIM if there are arguments or END if not. + if (line == PacketLineIn.DELIM) { + while ((line = pckIn.readString()) != PacketLineIn.END) { + if (line.equals("peel")) { + adv.setDerefTags(true); + } else if (line.equals("symrefs")) { + findSymrefs(adv, refs); + } else { + throw new PackProtocolException("unexpected " + line); + } + } + } else if (line != PacketLineIn.END) { + throw new PackProtocolException("unexpected " + line); + } + + adv.send(refs); + adv.end(); + } + /* * Returns true if this is the last command and we should tear down the * connection. @@ -882,6 +913,10 @@ public class UploadPack { // case. return true; } + if (command.equals("command=" + COMMAND_LS_REFS)) { + lsRefsV2(); + return false; + } throw new PackProtocolException("unknown command " + command); }