* changes: UploadPack v0: Extract "agent" client capability at parse time UploadPack: Return correct peer user agent on v2 requeststags/v5.2.0.201811281532-m3
@@ -43,6 +43,7 @@ | |||
package org.eclipse.jgit.internal.transport.parser; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
@@ -66,9 +67,9 @@ public class FirstWantTest { | |||
r.getLine()); | |||
Set<String> capabilities = r.getCapabilities(); | |||
Set<String> expectedCapabilities = new HashSet<>( | |||
Arrays.asList("no-progress", "include-tag", "ofs-delta", | |||
"agent=JGit/unknown")); | |||
Arrays.asList("no-progress", "include-tag", "ofs-delta")); | |||
assertEquals(expectedCapabilities, capabilities); | |||
assertEquals("JGit/unknown", r.getAgent()); | |||
} | |||
@Test | |||
@@ -79,6 +80,7 @@ public class FirstWantTest { | |||
assertEquals("want b9d4d1eb2f93058814480eae9e1b67550f46ee38", | |||
r.getLine()); | |||
assertTrue(r.getCapabilities().isEmpty()); | |||
assertNull(r.getAgent()); | |||
} | |||
private String makeFirstWantLine(String capability) { | |||
@@ -110,7 +112,7 @@ public class FirstWantTest { | |||
List<String> validNames = Arrays.asList( | |||
"c", "cap", "C", "CAP", "1", "1cap", "cap-64k_test", | |||
"-", "-cap", | |||
"_", "_cap", "agent=pack.age/Version"); | |||
"_", "_cap"); | |||
for (String capability: validNames) { | |||
FirstWant r = FirstWant.fromLine(makeFirstWantLine(capability)); | |||
@@ -118,4 +120,11 @@ public class FirstWantTest { | |||
assertTrue(r.getCapabilities().contains(capability)); | |||
} | |||
} | |||
@Test | |||
public void testFirstWantValidAgentName() throws PackProtocolException { | |||
FirstWant r = FirstWant.fromLine(makeFirstWantLine("agent=pack.age/Version")); | |||
assertEquals(r.getCapabilities().size(), 0); | |||
assertEquals("pack.age/Version", r.getAgent()); | |||
} | |||
} |
@@ -429,17 +429,7 @@ public class UploadPackTest { | |||
RefFilter refFilter, ProtocolV2Hook hook, String... inputLines) | |||
throws Exception { | |||
ByteArrayOutputStream send = new ByteArrayOutputStream(); | |||
PacketLineOut pckOut = new PacketLineOut(send); | |||
for (String line : inputLines) { | |||
if (line == PacketLineIn.END) { | |||
pckOut.end(); | |||
} else if (line == PacketLineIn.DELIM) { | |||
pckOut.writeDelim(); | |||
} else { | |||
pckOut.writeString(line); | |||
} | |||
} | |||
ByteArrayInputStream send = linesAsInputStream(inputLines); | |||
server.getConfig().setString("protocol", null, "version", "2"); | |||
UploadPack up = new UploadPack(server); | |||
@@ -453,11 +443,28 @@ public class UploadPackTest { | |||
} | |||
ByteArrayOutputStream recv = new ByteArrayOutputStream(); | |||
up.upload(new ByteArrayInputStream(send.toByteArray()), recv, null); | |||
up.upload(send, recv, null); | |||
return new ByteArrayInputStream(recv.toByteArray()); | |||
} | |||
private static ByteArrayInputStream linesAsInputStream(String... inputLines) | |||
throws IOException { | |||
try (ByteArrayOutputStream send = new ByteArrayOutputStream()) { | |||
PacketLineOut pckOut = new PacketLineOut(send); | |||
for (String line : inputLines) { | |||
if (line == PacketLineIn.END) { | |||
pckOut.end(); | |||
} else if (line == PacketLineIn.DELIM) { | |||
pckOut.writeDelim(); | |||
} else { | |||
pckOut.writeString(line); | |||
} | |||
} | |||
return new ByteArrayInputStream(send.toByteArray()); | |||
} | |||
} | |||
/* | |||
* Invokes UploadPack with protocol v2 and sends it the given lines. | |||
* Returns UploadPack's output stream, not including the capability | |||
@@ -1552,6 +1559,45 @@ public class UploadPackTest { | |||
assertTrue(client.hasObject(three.toObjectId())); | |||
} | |||
@Test | |||
public void testGetPeerAgentProtocolV0() 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\n", | |||
PacketLineIn.END, | |||
"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n"); | |||
ByteArrayOutputStream recv = new ByteArrayOutputStream(); | |||
up.upload(send, recv, null); | |||
assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.3"); | |||
} | |||
@Test | |||
public void testGetPeerAgentProtocolV2() throws Exception { | |||
server.getConfig().setString("protocol", null, "version", "2"); | |||
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", | |||
PacketLineIn.DELIM, "want " + one.getName() + "\n", | |||
"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n", | |||
PacketLineIn.END); | |||
ByteArrayOutputStream recv = new ByteArrayOutputStream(); | |||
up.upload(send, recv, null); | |||
assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.4"); | |||
} | |||
private static class RejectAllRefFilter implements RefFilter { | |||
@Override | |||
public Map<String, Ref> filter(Map<String, Ref> refs) { |
@@ -42,11 +42,13 @@ | |||
*/ | |||
package org.eclipse.jgit.internal.transport.parser; | |||
import java.util.Arrays; | |||
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; | |||
import java.util.Collections; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.errors.PackProtocolException; | |||
import org.eclipse.jgit.internal.JGitText; | |||
@@ -72,6 +74,11 @@ public class FirstWant { | |||
private final Set<String> capabilities; | |||
@Nullable | |||
private final String agent; | |||
private static final String AGENT_PREFIX = OPTION_AGENT + '='; | |||
/** | |||
* Parse the first want line in the protocol v0/v1 pack negotiation. | |||
* | |||
@@ -84,6 +91,7 @@ public class FirstWant { | |||
public static FirstWant fromLine(String line) throws PackProtocolException { | |||
String wantLine; | |||
Set<String> capabilities; | |||
String agent = null; | |||
if (line.length() > 45) { | |||
String opt = line.substring(45); | |||
@@ -91,8 +99,15 @@ public class FirstWant { | |||
throw new PackProtocolException(JGitText.get().wantNoSpaceWithCapabilities); | |||
} | |||
opt = opt.substring(1); | |||
HashSet<String> opts = new HashSet<>( | |||
Arrays.asList(opt.split(" "))); //$NON-NLS-1$ | |||
HashSet<String> opts = new HashSet<>(); | |||
for (String clientCapability : opt.split(" ")) { //$NON-NLS-1$ | |||
if (clientCapability.startsWith(AGENT_PREFIX)) { | |||
agent = clientCapability.substring(AGENT_PREFIX.length()); | |||
} else { | |||
opts.add(clientCapability); | |||
} | |||
} | |||
wantLine = line.substring(0, 45); | |||
capabilities = Collections.unmodifiableSet(opts); | |||
} else { | |||
@@ -100,12 +115,14 @@ public class FirstWant { | |||
capabilities = Collections.emptySet(); | |||
} | |||
return new FirstWant(wantLine, capabilities); | |||
return new FirstWant(wantLine, capabilities, agent); | |||
} | |||
private FirstWant(String line, Set<String> capabilities) { | |||
private FirstWant(String line, Set<String> capabilities, | |||
@Nullable String agent) { | |||
this.line = line; | |||
this.capabilities = capabilities; | |||
this.agent = agent; | |||
} | |||
/** @return non-capabilities part of the line. */ | |||
@@ -113,8 +130,17 @@ public class FirstWant { | |||
return line; | |||
} | |||
/** @return capabilities parsed from the line as an immutable set. */ | |||
/** | |||
* @return capabilities parsed from the line as an immutable set (excluding | |||
* agent). | |||
*/ | |||
public Set<String> getCapabilities() { | |||
return capabilities; | |||
} | |||
/** @return client user agent parsed from the line. */ | |||
@Nullable | |||
public String getAgent() { | |||
return agent; | |||
} | |||
} |
@@ -48,6 +48,7 @@ import java.util.List; | |||
import java.util.Set; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
/** | |||
@@ -69,6 +70,9 @@ abstract class FetchRequest { | |||
final List<String> deepenNotRefs; | |||
@Nullable | |||
final String agent; | |||
/** | |||
* Initialize the common fields of a fetch request. | |||
* | |||
@@ -88,11 +92,13 @@ abstract class FetchRequest { | |||
* @param deepenSince | |||
* Requests that the shallow clone/fetch should be cut at a | |||
* specific time, instead of depth | |||
* @param agent | |||
* agent as reported by the client in the request body | |||
*/ | |||
FetchRequest(@NonNull Set<ObjectId> wantIds, int depth, | |||
@NonNull Set<ObjectId> clientShallowCommits, long filterBlobLimit, | |||
@NonNull Set<String> clientCapabilities, int deepenSince, | |||
@NonNull List<String> deepenNotRefs) { | |||
@NonNull List<String> deepenNotRefs, @Nullable String agent) { | |||
this.wantIds = requireNonNull(wantIds); | |||
this.depth = depth; | |||
this.clientShallowCommits = requireNonNull(clientShallowCommits); | |||
@@ -100,6 +106,7 @@ abstract class FetchRequest { | |||
this.clientCapabilities = requireNonNull(clientCapabilities); | |||
this.deepenSince = deepenSince; | |||
this.deepenNotRefs = requireNonNull(deepenNotRefs); | |||
this.agent = agent; | |||
} | |||
/** | |||
@@ -146,7 +153,11 @@ abstract class FetchRequest { | |||
* These options are listed and well-defined in the git protocol | |||
* specification. | |||
* | |||
* @return capabilities sent by the client | |||
* The agent capability is not included in this set. It can be retrieved via | |||
* {@link #getAgent()}. | |||
* | |||
* @return capabilities sent by the client (excluding the "agent" | |||
* capability) | |||
*/ | |||
@NonNull | |||
Set<String> getClientCapabilities() { | |||
@@ -171,4 +182,13 @@ abstract class FetchRequest { | |||
List<String> getDeepenNotRefs() { | |||
return deepenNotRefs; | |||
} | |||
/** | |||
* @return string identifying the agent (as sent in the request body by the | |||
* client) | |||
*/ | |||
@Nullable | |||
String getAgent() { | |||
return agent; | |||
} | |||
} |
@@ -48,6 +48,7 @@ import java.util.HashSet; | |||
import java.util.Set; | |||
import org.eclipse.jgit.annotations.NonNull; | |||
import org.eclipse.jgit.annotations.Nullable; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
/** | |||
@@ -57,9 +58,9 @@ final class FetchV0Request extends FetchRequest { | |||
FetchV0Request(@NonNull Set<ObjectId> wantIds, int depth, | |||
@NonNull Set<ObjectId> clientShallowCommits, long filterBlobLimit, | |||
@NonNull Set<String> clientCapabilities) { | |||
@NonNull Set<String> clientCapabilities, @Nullable String agent) { | |||
super(wantIds, depth, clientShallowCommits, filterBlobLimit, | |||
clientCapabilities, 0, Collections.emptyList()); | |||
clientCapabilities, 0, Collections.emptyList(), agent); | |||
} | |||
static final class Builder { | |||
@@ -74,6 +75,8 @@ final class FetchV0Request extends FetchRequest { | |||
final Set<String> clientCaps = new HashSet<>(); | |||
String agent; | |||
/** | |||
* @param objectId | |||
* object id received in a "want" line | |||
@@ -115,6 +118,16 @@ final class FetchV0Request extends FetchRequest { | |||
return this; | |||
} | |||
/** | |||
* @param clientAgent | |||
* agent line sent by the client in the request body | |||
* @return this builder | |||
*/ | |||
Builder setAgent(String clientAgent) { | |||
agent = clientAgent; | |||
return this; | |||
} | |||
/** | |||
* @param filterBlobLim | |||
* blob limit set in a "filter" line | |||
@@ -127,7 +140,8 @@ final class FetchV0Request extends FetchRequest { | |||
FetchV0Request build() { | |||
return new FetchV0Request(wantIds, depth, clientShallowCommits, | |||
filterBlobLimit, clientCaps); | |||
filterBlobLimit, clientCaps, agent); | |||
} | |||
} | |||
} |
@@ -71,9 +71,6 @@ public final class FetchV2Request extends FetchRequest { | |||
private final boolean doneReceived; | |||
@Nullable | |||
private final String agent; | |||
@NonNull | |||
private final List<String> serverOptions; | |||
@@ -86,11 +83,10 @@ public final class FetchV2Request extends FetchRequest { | |||
boolean doneReceived, @NonNull Set<String> clientCapabilities, | |||
@Nullable String agent, @NonNull List<String> serverOptions) { | |||
super(wantIds, depth, clientShallowCommits, filterBlobLimit, | |||
clientCapabilities, deepenSince, deepenNotRefs); | |||
clientCapabilities, deepenSince, deepenNotRefs, agent); | |||
this.peerHas = requireNonNull(peerHas); | |||
this.wantedRefs = requireNonNull(wantedRefs); | |||
this.doneReceived = doneReceived; | |||
this.agent = agent; | |||
this.serverOptions = requireNonNull(serverOptions); | |||
} | |||
@@ -117,15 +113,6 @@ public final class FetchV2Request extends FetchRequest { | |||
return doneReceived; | |||
} | |||
/** | |||
* @return string identifying the agent (as sent in the request body by the | |||
* client) | |||
*/ | |||
@Nullable | |||
String getAgent() { | |||
return agent; | |||
} | |||
/** | |||
* Options received in server-option lines. The caller can choose to act on | |||
* these in an application-specific way |
@@ -143,6 +143,7 @@ final class ProtocolV0Parser { | |||
if (line.length() > 45) { | |||
FirstWant firstLine = FirstWant.fromLine(line); | |||
reqBuilder.addClientCapabilities(firstLine.getCapabilities()); | |||
reqBuilder.setAgent(firstLine.getAgent()); | |||
line = firstLine.getLine(); | |||
} | |||
} |
@@ -209,6 +209,11 @@ public class UploadPack { | |||
/** @return capabilities parsed from the line. */ | |||
public Set<String> getOptions() { | |||
if (firstWant.getAgent() != null) { | |||
Set<String> caps = new HashSet<>(firstWant.getCapabilities()); | |||
caps.add(OPTION_AGENT + '=' + firstWant.getAgent()); | |||
return caps; | |||
} | |||
return firstWant.getCapabilities(); | |||
} | |||
} | |||
@@ -1369,12 +1374,11 @@ public class UploadPack { | |||
* @since 4.0 | |||
*/ | |||
public String getPeerUserAgent() { | |||
if (currentRequest == null) { | |||
return userAgent; | |||
if (currentRequest != null && currentRequest.getAgent() != null) { | |||
return currentRequest.getAgent(); | |||
} | |||
return UserAgent.getAgent(currentRequest.getClientCapabilities(), | |||
userAgent); | |||
return userAgent; | |||
} | |||
private boolean negotiate(FetchRequest req, |