Implement ls-refs in UploadPack

Implement support for Git protocol v2's "ls-refs" command and its
"symrefs" and "peel" parameters.

This adds support for this command to UploadPack but the git://,
ssh://, and git:// transports do not make use of it yet.  That will
have to wait for later patches.

Change-Id: I8abc6bcc6ed4a88c165677ff1245625aca01267b
Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Jonathan Nieder <jrn@google.com>
This commit is contained in:
Jonathan Tan 2018-02-22 10:24:19 -08:00 committed by Jonathan Nieder
parent 2661bc0813
commit 332bc61124
4 changed files with 160 additions and 1 deletions

View File

@ -19,6 +19,7 @@
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 @@ private ByteArrayInputStream uploadPackV2(String... inputLines) throws Exception
// 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 void testV2EmptyRequest() throws Exception {
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);
}
}

View File

@ -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;
}

View File

@ -53,6 +53,7 @@
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 @@ protected void end() throws IOException {
boolean first = true;
private boolean useProtocolV2;
/* only used in protocol v2 */
private final Map<String, String> symrefs = new HashMap<>();
/**
* Initialize this advertiser with a repository for peeling tags.
*
@ -186,6 +192,15 @@ public void init(Repository src) {
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.
* <p>
@ -253,7 +268,11 @@ public void advertiseCapability(String name, String value) {
* @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 Set<ObjectId> send(Map<String, Ref> refs) throws IOException {
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)

View File

@ -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 @@ else if (requestValidator instanceof AnyRequestValidator)
sendPack(accumulator);
}
private void lsRefsV2() throws IOException {
PacketLineOutRefAdvertiser adv = new PacketLineOutRefAdvertiser(pckOut);
Map<String, Ref> 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 @@ private boolean serveOneCommandV2() throws IOException {
// case.
return true;
}
if (command.equals("command=" + COMMAND_LS_REFS)) {
lsRefsV2();
return false;
}
throw new PackProtocolException("unknown command " + command);
}