UploadPack: Receive and parse client session-id

Before this change JGit did not support the session-id capability
implemented by native Git in UploadPack. This change implements
advertising the capability from the server and parsing the session-id
received from the client during an UploadPack operation.

Enable the transfer.advertisesid config setting to advertise the
capability from the server. The client may send a session-id capability
in response. If received, the value from this is parsed and available
via the getClientSID method on the UploadPack object.

This change does not add the capability to send a session-id from the
JGit client.

https://git-scm.com/docs/gitprotocol-capabilities#_session_idsession_id

Change-Id: Ib1b6929ff1b3a4528e767925b5e5c44b5d18182f
Signed-off-by: Josh Brown <sjoshbrown@google.com>
This commit is contained in:
Josh Brown 2022-11-01 20:51:48 +00:00
parent 7b0a71a5e9
commit fe9aeb02e6
10 changed files with 206 additions and 12 deletions

View File

@ -98,6 +98,25 @@ public void testRecvWantsWithAgent()
"f900c8326a43303685c46b279b9f70411bff1a4b"));
}
@Test
public void testRecvWantsWithSessionID()
throws PackProtocolException, IOException {
PacketLineIn pckIn = formatAsPacketLine(String.join(" ", "want",
"4624442d68ee402a94364191085b77137618633e", "thin-pack",
"agent=JGit.test/0.0.1", "session-id=client-session-id", "\n"),
"want f900c8326a43303685c46b279b9f70411bff1a4b\n",
PacketLineIn.end());
ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig());
FetchV0Request request = parser.recvWants(pckIn);
assertTrue(request.getClientCapabilities()
.contains(GitProtocolConstants.OPTION_THIN_PACK));
assertEquals(1, request.getClientCapabilities().size());
assertEquals("client-session-id", request.getClientSID());
assertThat(request.getWantIds(),
hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e",
"f900c8326a43303685c46b279b9f70411bff1a4b"));
}
/*
* First round of protocol v0 negotiation. Client send wants, no
* capabilities.

View File

@ -361,4 +361,28 @@ public void testLsRefsRefPrefixes() throws IOException {
assertEquals(2, req.getRefPrefixes().size());
assertThat(req.getRefPrefixes(), hasItems("refs/for", "refs/heads"));
}
@Test
public void testFetchWithSessionID() throws IOException {
PacketLineIn pckIn = formatAsPacketLine("session-id=the.client.sid",
PacketLineIn.end());
ProtocolV2Parser parser = new ProtocolV2Parser(
ConfigBuilder.start().allowFilter().done());
FetchV2Request request = parser.parseFetchRequest(pckIn);
assertEquals("the.client.sid", request.getClientSID());
}
@Test
public void testLsRefsWithSessionID() throws IOException {
PacketLineIn pckIn = formatAsPacketLine("session-id=the.client.sid",
PacketLineIn.delimiter(), PacketLineIn.end());
ProtocolV2Parser parser = new ProtocolV2Parser(
ConfigBuilder.getDefault());
LsRefsV2Request req = parser.parseLsRefsRequest(pckIn);
assertEquals("the.client.sid", req.getClientSID());
}
}

View File

@ -2428,6 +2428,24 @@ public void testGetPeerAgentProtocolV0() throws Exception {
assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.3");
}
@Test
public void testGetSessionIDValueProtocolV0() throws Exception {
RevCommit one = remote.commit().message("1").create();
remote.update("one", one);
UploadPack up = new UploadPack(server);
ByteArrayInputStream send = linesAsInputStream(
"want " + one.getName() + " agent=JGit-test/1.2.3"
+ " session-id=client-session-id\n",
PacketLineIn.end(),
"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n");
ByteArrayOutputStream recv = new ByteArrayOutputStream();
up.upload(send, recv, null);
assertEquals(up.getClientSID(), "client-session-id");
}
@Test
public void testGetPeerAgentProtocolV2() throws Exception {
server.getConfig().setString(ConfigConstants.CONFIG_PROTOCOL_SECTION,
@ -2452,6 +2470,30 @@ public void testGetPeerAgentProtocolV2() throws Exception {
assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.4");
}
@Test
public void testGetSessionIDValueProtocolV2() throws Exception {
server.getConfig().setString(ConfigConstants.CONFIG_PROTOCOL_SECTION,
null, ConfigConstants.CONFIG_KEY_VERSION,
TransferConfig.ProtocolVersion.V2.version());
RevCommit one = remote.commit().message("1").create();
remote.update("one", one);
UploadPack up = new UploadPack(server);
up.setExtraParameters(Sets.of("version=2"));
ByteArrayInputStream send = linesAsInputStream("command=fetch\n",
"agent=JGit-test/1.2.4\n", "session-id=client-session-id\n",
PacketLineIn.delimiter(), "want " + one.getName() + "\n",
"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n",
PacketLineIn.end());
ByteArrayOutputStream recv = new ByteArrayOutputStream();
up.upload(send, recv, null);
assertEquals(up.getClientSID(), "client-session-id");
}
private static class RejectAllRefFilter implements RefFilter {
@Override
public Map<String, Ref> filter(Map<String, Ref> refs) {

View File

@ -40,6 +40,9 @@ abstract class FetchRequest {
@Nullable
final String agent;
@Nullable
final String clientSID;
/**
* Initialize the common fields of a fetch request.
*
@ -61,12 +64,15 @@ abstract class FetchRequest {
* specific time, instead of depth
* @param agent
* agent as reported by the client in the request body
* @param clientSID
* agent as reported by the client in the request body
*/
FetchRequest(@NonNull Set<ObjectId> wantIds, int depth,
@NonNull Set<ObjectId> clientShallowCommits,
@NonNull FilterSpec filterSpec,
@NonNull Set<String> clientCapabilities, int deepenSince,
@NonNull List<String> deepenNots, @Nullable String agent) {
@NonNull List<String> deepenNots, @Nullable String agent,
@Nullable String clientSID) {
this.wantIds = requireNonNull(wantIds);
this.depth = depth;
this.clientShallowCommits = requireNonNull(clientShallowCommits);
@ -75,6 +81,7 @@ abstract class FetchRequest {
this.deepenSince = deepenSince;
this.deepenNots = requireNonNull(deepenNots);
this.agent = agent;
this.clientSID = clientSID;
}
/**
@ -160,4 +167,13 @@ List<String> getDeepenNots() {
String getAgent() {
return agent;
}
/**
* @return string identifying the client session ID (as sent in the request body by the
* client)
*/
@Nullable
String getClientSID() {
return clientSID;
}
}

View File

@ -30,9 +30,11 @@ final class FetchV0Request extends FetchRequest {
@NonNull Set<ObjectId> clientShallowCommits,
@NonNull FilterSpec filterSpec,
@NonNull Set<String> clientCapabilities, int deepenSince,
@NonNull List<String> deepenNotRefs, @Nullable String agent) {
@NonNull List<String> deepenNotRefs, @Nullable String agent,
@Nullable String clientSID) {
super(wantIds, depth, clientShallowCommits, filterSpec,
clientCapabilities, deepenSince, deepenNotRefs, agent);
clientCapabilities, deepenSince, deepenNotRefs, agent,
clientSID);
}
static final class Builder {
@ -53,6 +55,8 @@ static final class Builder {
String agent;
String clientSID;
/**
* @param objectId
* object id received in a "want" line
@ -148,6 +152,16 @@ Builder setAgent(String clientAgent) {
return this;
}
/**
* @param clientSID
* session-id line sent by the client in the request body
* @return this builder
*/
Builder setClientSID(String clientSID) {
this.clientSID = clientSID;
return this;
}
/**
* @param filter
* the filter set in a filter line
@ -160,7 +174,8 @@ Builder setFilterSpec(@NonNull FilterSpec filter) {
FetchV0Request build() {
return new FetchV0Request(wantIds, depth, clientShallowCommits,
filterSpec, clientCaps, deepenSince, deepenNots, agent);
filterSpec, clientCaps, deepenSince, deepenNots, agent,
clientSID);
}
}

View File

@ -55,10 +55,11 @@ public final class FetchV2Request extends FetchRequest {
boolean doneReceived, boolean waitForDone,
@NonNull Set<String> clientCapabilities,
@Nullable String agent, @NonNull List<String> serverOptions,
boolean sidebandAll, @NonNull List<String> packfileUriProtocols) {
boolean sidebandAll, @NonNull List<String> packfileUriProtocols,
@Nullable String clientSID) {
super(wantIds, depth, clientShallowCommits, filterSpec,
clientCapabilities, deepenSince,
deepenNots, agent);
deepenNots, agent, clientSID);
this.peerHas = requireNonNull(peerHas);
this.wantedRefs = requireNonNull(wantedRefs);
this.doneReceived = doneReceived;
@ -157,6 +158,9 @@ static final class Builder {
@Nullable
String agent;
@Nullable
String clientSID;
final List<String> serverOptions = new ArrayList<>();
boolean sidebandAll;
@ -316,6 +320,17 @@ Builder setAgent(@Nullable String agentValue) {
return this;
}
/**
* @param clientSIDValue
* the client-supplied session capability, without the
* leading "session-id="
* @return this builder
*/
Builder setClientSID(@Nullable String clientSIDValue) {
clientSID = clientSIDValue;
return this;
}
/**
* Records an application-specific option supplied in a server-option
* line, for later retrieval with
@ -354,7 +369,8 @@ FetchV2Request build() {
depth, filterSpec, doneReceived, waitForDone, clientCapabilities,
agent, Collections.unmodifiableList(serverOptions),
sidebandAll,
Collections.unmodifiableList(packfileUriProtocols));
Collections.unmodifiableList(packfileUriProtocols),
clientSID);
}
}
}

View File

@ -36,17 +36,21 @@ public final class LsRefsV2Request {
@Nullable
private final String agent;
private final String clientSID;
@NonNull
private final List<String> serverOptions;
private LsRefsV2Request(List<String> refPrefixes, boolean symrefs,
boolean peel, @Nullable String agent,
@NonNull List<String> serverOptions) {
@NonNull List<String> serverOptions,
@Nullable String clientSID) {
this.refPrefixes = refPrefixes;
this.symrefs = symrefs;
this.peel = peel;
this.agent = agent;
this.serverOptions = requireNonNull(serverOptions);
this.clientSID = clientSID;
}
/** @return ref prefixes that the client requested. */
@ -74,6 +78,16 @@ public String getAgent() {
return agent;
}
/**
* @return session-id as reported by the client
*
* @since 6.4
*/
@Nullable
public String getClientSID() {
return clientSID;
}
/**
* Get application-specific options provided by the client using
* --server-option.
@ -109,6 +123,8 @@ public static final class Builder {
private String agent;
private String clientSID;
private Builder() {
}
@ -171,11 +187,28 @@ public Builder setAgent(@Nullable String value) {
return this;
}
/**
* Value of a session-id line received after the command and before the
* arguments. E.g. "session-id=a.b.c" should set "a.b.c".
*
* @param value
* the client-supplied session-id capability, without leading
* "session-id="
* @return this builder
*
* @since 6.4
*/
public Builder setClientSID(@Nullable String value) {
clientSID = value;
return this;
}
/** @return LsRefsV2Request */
public LsRefsV2Request build() {
return new LsRefsV2Request(
Collections.unmodifiableList(refPrefixes), symrefs, peel,
agent, Collections.unmodifiableList(serverOptions));
agent, Collections.unmodifiableList(serverOptions),
clientSID);
}
}
}

View File

@ -152,6 +152,7 @@ FetchV0Request recvWants(PacketLineIn pckIn)
FirstWant firstLine = FirstWant.fromLine(line);
reqBuilder.addClientCapabilities(firstLine.getCapabilities());
reqBuilder.setAgent(firstLine.getAgent());
reqBuilder.setClientSID(firstLine.getClientSID());
line = firstLine.getLine();
}
}

View File

@ -20,6 +20,7 @@
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
@ -63,10 +64,12 @@ final class ProtocolV2Parser {
*/
private static String consumeCapabilities(PacketLineIn pckIn,
Consumer<String> serverOptionConsumer,
Consumer<String> agentConsumer) throws IOException {
Consumer<String> agentConsumer,
Consumer<String> clientSIDConsumer) throws IOException {
String serverOptionPrefix = OPTION_SERVER_OPTION + '=';
String agentPrefix = OPTION_AGENT + '=';
String clientSIDPrefix = OPTION_SESSION_ID + '=';
String line = pckIn.readString();
while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) {
@ -75,6 +78,9 @@ private static String consumeCapabilities(PacketLineIn pckIn,
.accept(line.substring(serverOptionPrefix.length()));
} else if (line.startsWith(agentPrefix)) {
agentConsumer.accept(line.substring(agentPrefix.length()));
} else if (line.startsWith(clientSIDPrefix)) {
clientSIDConsumer
.accept(line.substring(clientSIDPrefix.length()));
} else {
// Unrecognized capability. Ignore it.
}
@ -108,7 +114,8 @@ FetchV2Request parseFetchRequest(PacketLineIn pckIn)
String line = consumeCapabilities(pckIn,
serverOption -> reqBuilder.addServerOption(serverOption),
agent -> reqBuilder.setAgent(agent));
agent -> reqBuilder.setAgent(agent),
clientSID -> reqBuilder.setClientSID(clientSID));
if (PacketLineIn.isEnd(line)) {
return reqBuilder.build();
@ -235,7 +242,8 @@ LsRefsV2Request parseLsRefsRequest(PacketLineIn pckIn)
String line = consumeCapabilities(pckIn,
serverOption -> builder.addServerOption(serverOption),
agent -> builder.setAgent(agent));
agent -> builder.setAgent(agent),
clientSID -> builder.setClientSID(clientSID));
if (PacketLineIn.isEnd(line)) {
return builder.build();

View File

@ -34,6 +34,7 @@
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ACK;
@ -1382,6 +1383,10 @@ private List<String> getV2CapabilityAdvertisement() {
: "")
+ OPTION_SHALLOW);
caps.add(CAPABILITY_SERVER_OPTION);
if (transferConfig.isAllowReceiveClientSID()) {
caps.add(OPTION_SESSION_ID);
}
return caps;
}
@ -1700,6 +1705,21 @@ public String getPeerUserAgent() {
return userAgent;
}
/**
* Get the session ID if received from the client.
*
* @return The session ID if it has been received from the client.
* @since 6.4
*/
@Nullable
public String getClientSID() {
if (currentRequest == null) {
return null;
}
return currentRequest.getClientSID();
}
private boolean negotiate(FetchRequest req,
PackStatistics.Accumulator accumulator,
PacketLineOut pckOut)