View Javadoc
1   package org.eclipse.jgit.transport;
2   
3   import static org.hamcrest.MatcherAssert.assertThat;
4   import static org.hamcrest.Matchers.containsInAnyOrder;
5   import static org.hamcrest.Matchers.containsString;
6   import static org.hamcrest.Matchers.hasItems;
7   import static org.hamcrest.Matchers.is;
8   import static org.hamcrest.Matchers.notNullValue;
9   import static org.junit.Assert.assertEquals;
10  import static org.junit.Assert.assertFalse;
11  import static org.junit.Assert.assertNotNull;
12  import static org.junit.Assert.assertThrows;
13  import static org.junit.Assert.assertTrue;
14  
15  import java.io.ByteArrayInputStream;
16  import java.io.ByteArrayOutputStream;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.io.StringWriter;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.function.Consumer;
29  
30  import org.eclipse.jgit.dircache.DirCache;
31  import org.eclipse.jgit.dircache.DirCacheBuilder;
32  import org.eclipse.jgit.dircache.DirCacheEntry;
33  import org.eclipse.jgit.errors.TransportException;
34  import org.eclipse.jgit.internal.storage.dfs.DfsGarbageCollector;
35  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
36  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
37  import org.eclipse.jgit.internal.storage.file.PackLock;
38  import org.eclipse.jgit.internal.storage.pack.CachedPack;
39  import org.eclipse.jgit.internal.storage.pack.CachedPackUriProvider;
40  import org.eclipse.jgit.junit.TestRepository;
41  import org.eclipse.jgit.lib.ConfigConstants;
42  import org.eclipse.jgit.lib.NullProgressMonitor;
43  import org.eclipse.jgit.lib.ObjectId;
44  import org.eclipse.jgit.lib.ObjectInserter;
45  import org.eclipse.jgit.lib.PersonIdent;
46  import org.eclipse.jgit.lib.ProgressMonitor;
47  import org.eclipse.jgit.lib.Ref;
48  import org.eclipse.jgit.lib.RefDatabase;
49  import org.eclipse.jgit.lib.Repository;
50  import org.eclipse.jgit.lib.Sets;
51  import org.eclipse.jgit.lib.TextProgressMonitor;
52  import org.eclipse.jgit.revwalk.RevBlob;
53  import org.eclipse.jgit.revwalk.RevCommit;
54  import org.eclipse.jgit.revwalk.RevTag;
55  import org.eclipse.jgit.revwalk.RevTree;
56  import org.eclipse.jgit.storage.pack.PackStatistics;
57  import org.eclipse.jgit.transport.UploadPack.RequestPolicy;
58  import org.eclipse.jgit.util.io.NullOutputStream;
59  import org.junit.After;
60  import org.junit.Before;
61  import org.junit.Test;
62  
63  /**
64   * Tests for server upload-pack utilities.
65   */
66  public class UploadPackTest {
67  	private URIish uri;
68  
69  	private TestProtocol<Object> testProtocol;
70  
71  	private final Object ctx = new Object();
72  
73  	private InMemoryRepository server;
74  
75  	private InMemoryRepository client;
76  
77  	private TestRepository<InMemoryRepository> remote;
78  
79  	private PackStatistics stats;
80  
81  	@Before
82  	public void setUp() throws Exception {
83  		server = newRepo("server");
84  		client = newRepo("client");
85  
86  		remote = new TestRepository<>(server);
87  	}
88  
89  	@After
90  	public void tearDown() {
91  		Transport.unregister(testProtocol);
92  	}
93  
94  	private static InMemoryRepository newRepo(String name) {
95  		return new InMemoryRepository(new DfsRepositoryDescription(name));
96  	}
97  
98  	private void generateBitmaps(InMemoryRepository repo) throws Exception {
99  		new DfsGarbageCollector(repo).pack(null);
100 		repo.scanForRepoChanges();
101 	}
102 
103 	@Test
104 	public void testFetchParentOfShallowCommit() throws Exception {
105 		RevCommit commit0 = remote.commit().message("0").create();
106 		RevCommit commit1 = remote.commit().message("1").parent(commit0).create();
107 		RevCommit tip = remote.commit().message("2").parent(commit1).create();
108 		remote.update("master", tip);
109 
110 		testProtocol = new TestProtocol<>((Object req, Repository db) -> {
111 			UploadPack up = new UploadPack(db);
112 			up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);
113 			// assume client has a shallow commit
114 			up.getRevWalk()
115 					.assumeShallow(Collections.singleton(commit1.getId()));
116 			return up;
117 		}, null);
118 		uri = testProtocol.register(ctx, server);
119 
120 		assertFalse(client.getObjectDatabase().has(commit0.toObjectId()));
121 
122 		// Fetch of the parent of the shallow commit
123 		try (Transport tn = testProtocol.open(uri, client, "server")) {
124 			tn.fetch(NullProgressMonitor.INSTANCE,
125 					Collections.singletonList(new RefSpec(commit0.name())));
126 			assertTrue(client.getObjectDatabase().has(commit0.toObjectId()));
127 		}
128 	}
129 
130 	@Test
131 	public void testFetchWithBlobZeroFilter() throws Exception {
132 		InMemoryRepository server2 = newRepo("server2");
133 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
134 				server2)) {
135 			RevBlob blob1 = remote2.blob("foobar");
136 			RevBlob blob2 = remote2.blob("fooba");
137 			RevTree tree = remote2.tree(remote2.file("1", blob1),
138 					remote2.file("2", blob2));
139 			RevCommit commit = remote2.commit(tree);
140 			remote2.update("master", commit);
141 
142 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
143 					true);
144 
145 			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
146 				UploadPack up = new UploadPack(db);
147 				return up;
148 			}, null);
149 			uri = testProtocol.register(ctx, server2);
150 
151 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
152 				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
153 				tn.fetch(NullProgressMonitor.INSTANCE,
154 						Collections.singletonList(new RefSpec(commit.name())));
155 				assertTrue(client.getObjectDatabase().has(tree.toObjectId()));
156 				assertFalse(client.getObjectDatabase().has(blob1.toObjectId()));
157 				assertFalse(client.getObjectDatabase().has(blob2.toObjectId()));
158 			}
159 		}
160 	}
161 
162 	@Test
163 	public void testFetchExplicitBlobWithFilter() throws Exception {
164 		InMemoryRepository server2 = newRepo("server2");
165 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
166 				server2)) {
167 			RevBlob blob1 = remote2.blob("foobar");
168 			RevBlob blob2 = remote2.blob("fooba");
169 			RevTree tree = remote2.tree(remote2.file("1", blob1),
170 					remote2.file("2", blob2));
171 			RevCommit commit = remote2.commit(tree);
172 			remote2.update("master", commit);
173 			remote2.update("a_blob", blob1);
174 
175 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
176 					true);
177 
178 			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
179 				UploadPack up = new UploadPack(db);
180 				return up;
181 			}, null);
182 			uri = testProtocol.register(ctx, server2);
183 
184 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
185 				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
186 				tn.fetch(NullProgressMonitor.INSTANCE, Arrays.asList(
187 						new RefSpec(commit.name()), new RefSpec(blob1.name())));
188 				assertTrue(client.getObjectDatabase().has(tree.toObjectId()));
189 				assertTrue(client.getObjectDatabase().has(blob1.toObjectId()));
190 				assertFalse(client.getObjectDatabase().has(blob2.toObjectId()));
191 			}
192 		}
193 	}
194 
195 	@Test
196 	public void testFetchWithBlobLimitFilter() throws Exception {
197 		InMemoryRepository server2 = newRepo("server2");
198 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
199 				server2)) {
200 			RevBlob longBlob = remote2.blob("foobar");
201 			RevBlob shortBlob = remote2.blob("fooba");
202 			RevTree tree = remote2.tree(remote2.file("1", longBlob),
203 					remote2.file("2", shortBlob));
204 			RevCommit commit = remote2.commit(tree);
205 			remote2.update("master", commit);
206 
207 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
208 					true);
209 
210 			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
211 				UploadPack up = new UploadPack(db);
212 				return up;
213 			}, null);
214 			uri = testProtocol.register(ctx, server2);
215 
216 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
217 				tn.setFilterSpec(FilterSpec.withBlobLimit(5));
218 				tn.fetch(NullProgressMonitor.INSTANCE,
219 						Collections.singletonList(new RefSpec(commit.name())));
220 				assertFalse(
221 						client.getObjectDatabase().has(longBlob.toObjectId()));
222 				assertTrue(
223 						client.getObjectDatabase().has(shortBlob.toObjectId()));
224 			}
225 		}
226 	}
227 
228 	@Test
229 	public void testFetchExplicitBlobWithFilterAndBitmaps() throws Exception {
230 		InMemoryRepository server2 = newRepo("server2");
231 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
232 				server2)) {
233 			RevBlob blob1 = remote2.blob("foobar");
234 			RevBlob blob2 = remote2.blob("fooba");
235 			RevTree tree = remote2.tree(remote2.file("1", blob1),
236 					remote2.file("2", blob2));
237 			RevCommit commit = remote2.commit(tree);
238 			remote2.update("master", commit);
239 			remote2.update("a_blob", blob1);
240 
241 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
242 					true);
243 
244 			// generate bitmaps
245 			new DfsGarbageCollector(server2).pack(null);
246 			server2.scanForRepoChanges();
247 
248 			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
249 				UploadPack up = new UploadPack(db);
250 				return up;
251 			}, null);
252 			uri = testProtocol.register(ctx, server2);
253 
254 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
255 				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
256 				tn.fetch(NullProgressMonitor.INSTANCE, Arrays.asList(
257 						new RefSpec(commit.name()), new RefSpec(blob1.name())));
258 				assertTrue(client.getObjectDatabase().has(blob1.toObjectId()));
259 				assertFalse(client.getObjectDatabase().has(blob2.toObjectId()));
260 			}
261 		}
262 	}
263 
264 	@Test
265 	public void testFetchWithBlobLimitFilterAndBitmaps() throws Exception {
266 		InMemoryRepository server2 = newRepo("server2");
267 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
268 				server2)) {
269 			RevBlob longBlob = remote2.blob("foobar");
270 			RevBlob shortBlob = remote2.blob("fooba");
271 			RevTree tree = remote2.tree(remote2.file("1", longBlob),
272 					remote2.file("2", shortBlob));
273 			RevCommit commit = remote2.commit(tree);
274 			remote2.update("master", commit);
275 
276 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
277 					true);
278 
279 			// generate bitmaps
280 			new DfsGarbageCollector(server2).pack(null);
281 			server2.scanForRepoChanges();
282 
283 			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
284 				UploadPack up = new UploadPack(db);
285 				return up;
286 			}, null);
287 			uri = testProtocol.register(ctx, server2);
288 
289 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
290 				tn.setFilterSpec(FilterSpec.withBlobLimit(5));
291 				tn.fetch(NullProgressMonitor.INSTANCE,
292 						Collections.singletonList(new RefSpec(commit.name())));
293 				assertFalse(
294 						client.getObjectDatabase().has(longBlob.toObjectId()));
295 				assertTrue(
296 						client.getObjectDatabase().has(shortBlob.toObjectId()));
297 			}
298 		}
299 	}
300 
301 	@Test
302 	public void testFetchWithTreeZeroFilter() throws Exception {
303 		InMemoryRepository server2 = newRepo("server2");
304 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
305 				server2)) {
306 			RevBlob blob1 = remote2.blob("foobar");
307 			RevBlob blob2 = remote2.blob("fooba");
308 			RevTree tree = remote2.tree(remote2.file("1", blob1),
309 					remote2.file("2", blob2));
310 			RevCommit commit = remote2.commit(tree);
311 			remote2.update("master", commit);
312 
313 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
314 					true);
315 
316 			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
317 				UploadPack up = new UploadPack(db);
318 				return up;
319 			}, null);
320 			uri = testProtocol.register(ctx, server2);
321 
322 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
323 				tn.setFilterSpec(FilterSpec.withTreeDepthLimit(0));
324 				tn.fetch(NullProgressMonitor.INSTANCE,
325 						Collections.singletonList(new RefSpec(commit.name())));
326 				assertFalse(client.getObjectDatabase().has(tree.toObjectId()));
327 				assertFalse(client.getObjectDatabase().has(blob1.toObjectId()));
328 				assertFalse(client.getObjectDatabase().has(blob2.toObjectId()));
329 			}
330 		}
331 	}
332 
333 	@Test
334 	public void testFetchWithNonSupportingServer() throws Exception {
335 		InMemoryRepository server2 = newRepo("server2");
336 		try (TestRepository<InMemoryRepository> remote2 = new TestRepository<>(
337 				server2)) {
338 			RevBlob blob = remote2.blob("foo");
339 			RevTree tree = remote2.tree(remote2.file("1", blob));
340 			RevCommit commit = remote2.commit(tree);
341 			remote2.update("master", commit);
342 
343 			server2.getConfig().setBoolean("uploadpack", null, "allowfilter",
344 					false);
345 
346 			testProtocol = new TestProtocol<>((Object req, Repository db) -> {
347 				UploadPack up = new UploadPack(db);
348 				return up;
349 			}, null);
350 			uri = testProtocol.register(ctx, server2);
351 
352 			try (Transport tn = testProtocol.open(uri, client, "server2")) {
353 				tn.setFilterSpec(FilterSpec.withBlobLimit(0));
354 
355 				TransportException e = assertThrows(TransportException.class,
356 						() -> tn.fetch(NullProgressMonitor.INSTANCE, Collections
357 								.singletonList(new RefSpec(commit.name()))));
358 				assertThat(e.getMessage(), containsString(
359 						"filter requires server to advertise that capability"));
360 			}
361 		}
362 	}
363 
364 	/*
365 	 * Invokes UploadPack with specified protocol version and sends it the given lines,
366 	 * and returns UploadPack's output stream.
367 	 */
368 	private ByteArrayInputStream uploadPackSetup(String version,
369 			Consumer<UploadPack> postConstructionSetup, String... inputLines)
370 			throws Exception {
371 
372 		ByteArrayInputStream send = linesAsInputStream(inputLines);
373 
374 		server.getConfig().setString(ConfigConstants.CONFIG_PROTOCOL_SECTION,
375 				null, ConfigConstants.CONFIG_KEY_VERSION, version);
376 		UploadPack up = new UploadPack(server);
377 		if (postConstructionSetup != null) {
378 			postConstructionSetup.accept(up);
379 		}
380 		up.setExtraParameters(Sets.of("version=".concat(version)));
381 
382 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
383 		up.upload(send, recv, null);
384 		stats = up.getStatistics();
385 
386 		return new ByteArrayInputStream(recv.toByteArray());
387 	}
388 
389 	private static ByteArrayInputStream linesAsInputStream(String... inputLines)
390 			throws IOException {
391 		try (ByteArrayOutputStream send = new ByteArrayOutputStream()) {
392 			PacketLineOut pckOut = new PacketLineOut(send);
393 			for (String line : inputLines) {
394 				Objects.requireNonNull(line);
395 				if (PacketLineIn.isEnd(line)) {
396 					pckOut.end();
397 				} else if (PacketLineIn.isDelimiter(line)) {
398 					pckOut.writeDelim();
399 				} else {
400 					pckOut.writeString(line);
401 				}
402 			}
403 			return new ByteArrayInputStream(send.toByteArray());
404 		}
405 	}
406 
407 	/*
408 	 * Invokes UploadPack with protocol v1 and sends it the given lines.
409 	 * Returns UploadPack's output stream, not including the capability
410 	 * advertisement by the server.
411 	 */
412 	private ByteArrayInputStream uploadPackV1(
413 			Consumer<UploadPack> postConstructionSetup,
414 			String... inputLines)
415 			throws Exception {
416 		ByteArrayInputStream recvStream =
417 				uploadPackSetup("1", postConstructionSetup, inputLines);
418 		PacketLineIn pckIn = new PacketLineIn(recvStream);
419 
420 		// drain capabilities
421 		while (!PacketLineIn.isEnd(pckIn.readString())) {
422 			// do nothing
423 		}
424 		return recvStream;
425 	}
426 
427 	private ByteArrayInputStream uploadPackV1(String... inputLines) throws Exception {
428 		return uploadPackV1(null, inputLines);
429 	}
430 
431 	/*
432 	 * Invokes UploadPack with protocol v2 and sends it the given lines.
433 	 * Returns UploadPack's output stream, not including the capability
434 	 * advertisement by the server.
435 	 */
436 	private ByteArrayInputStream uploadPackV2(
437 			Consumer<UploadPack> postConstructionSetup,
438 			String... inputLines)
439 			throws Exception {
440 		ByteArrayInputStream recvStream = uploadPackSetup(
441 				TransferConfig.ProtocolVersion.V2.version(),
442 				postConstructionSetup, inputLines);
443 		PacketLineIn pckIn = new PacketLineIn(recvStream);
444 
445 		// drain capabilities
446 		while (!PacketLineIn.isEnd(pckIn.readString())) {
447 			// do nothing
448 		}
449 		return recvStream;
450 	}
451 
452 	private ByteArrayInputStream uploadPackV2(String... inputLines) throws Exception {
453 		return uploadPackV2(null, inputLines);
454 	}
455 
456 	private static class TestV2Hook implements ProtocolV2Hook {
457 		private CapabilitiesV2Request capabilitiesRequest;
458 
459 		private LsRefsV2Request lsRefsRequest;
460 
461 		private FetchV2Request fetchRequest;
462 
463 		@Override
464 		public void onCapabilities(CapabilitiesV2Request req) {
465 			capabilitiesRequest = req;
466 		}
467 
468 		@Override
469 		public void onLsRefs(LsRefsV2Request req) {
470 			lsRefsRequest = req;
471 		}
472 
473 		@Override
474 		public void onFetch(FetchV2Request req) {
475 			fetchRequest = req;
476 		}
477 	}
478 
479 	@Test
480 	public void testV2Capabilities() throws Exception {
481 		TestV2Hook hook = new TestV2Hook();
482 		ByteArrayInputStream recvStream = uploadPackSetup(
483 				TransferConfig.ProtocolVersion.V2.version(),
484 				(UploadPack up) -> {
485 					up.setProtocolV2Hook(hook);
486 				}, PacketLineIn.end());
487 		PacketLineIn pckIn = new PacketLineIn(recvStream);
488 		assertThat(hook.capabilitiesRequest, notNullValue());
489 		assertThat(pckIn.readString(), is("version 2"));
490 		assertThat(
491 				Arrays.asList(pckIn.readString(), pckIn.readString(),
492 						pckIn.readString()),
493 				// TODO(jonathantanmy) This check is written this way
494 				// to make it simple to see that we expect this list of
495 				// capabilities, but probably should be loosened to
496 				// allow additional commands to be added to the list,
497 				// and additional capabilities to be added to existing
498 				// commands without requiring test changes.
499 				hasItems("ls-refs", "fetch=shallow", "server-option"));
500 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
501 	}
502 
503 	private void checkAdvertisedIfAllowed(String configSection, String configName,
504 			String fetchCapability) throws Exception {
505 		server.getConfig().setBoolean(configSection, null, configName, true);
506 		ByteArrayInputStream recvStream = uploadPackSetup(
507 				TransferConfig.ProtocolVersion.V2.version(), null,
508 				PacketLineIn.end());
509 		PacketLineIn pckIn = new PacketLineIn(recvStream);
510 
511 		assertThat(pckIn.readString(), is("version 2"));
512 
513 		ArrayList<String> lines = new ArrayList<>();
514 		String line;
515 		while (!PacketLineIn.isEnd((line = pckIn.readString()))) {
516 			if (line.startsWith("fetch=")) {
517 				assertThat(
518 					Arrays.asList(line.substring(6).split(" ")),
519 					containsInAnyOrder(fetchCapability, "shallow"));
520 				lines.add("fetch");
521 			} else {
522 				lines.add(line);
523 			}
524 		}
525 		assertThat(lines, containsInAnyOrder("ls-refs", "fetch", "server-option"));
526 	}
527 
528 	private void checkUnadvertisedIfUnallowed(String configSection,
529 			String configName, String fetchCapability) throws Exception {
530 		server.getConfig().setBoolean(configSection, null, configName, false);
531 		ByteArrayInputStream recvStream = uploadPackSetup(
532 				TransferConfig.ProtocolVersion.V2.version(), null,
533 				PacketLineIn.end());
534 		PacketLineIn pckIn = new PacketLineIn(recvStream);
535 
536 		assertThat(pckIn.readString(), is("version 2"));
537 
538 		ArrayList<String> lines = new ArrayList<>();
539 		String line;
540 		while (!PacketLineIn.isEnd((line = pckIn.readString()))) {
541 			if (line.startsWith("fetch=")) {
542 				List<String> fetchItems = Arrays.asList(line.substring(6).split(" "));
543 				assertThat(fetchItems, hasItems("shallow"));
544 				assertFalse(fetchItems.contains(fetchCapability));
545 				lines.add("fetch");
546 			} else {
547 				lines.add(line);
548 			}
549 		}
550 		assertThat(lines, hasItems("ls-refs", "fetch", "server-option"));
551 	}
552 
553 	@Test
554 	public void testV2CapabilitiesAllowFilter() throws Exception {
555 		checkAdvertisedIfAllowed("uploadpack", "allowfilter", "filter");
556 		checkUnadvertisedIfUnallowed("uploadpack", "allowfilter", "filter");
557 	}
558 
559 	@Test
560 	public void testV2CapabilitiesRefInWant() throws Exception {
561 		checkAdvertisedIfAllowed("uploadpack", "allowrefinwant", "ref-in-want");
562 	}
563 
564 	@Test
565 	public void testV2CapabilitiesRefInWantNotAdvertisedIfUnallowed() throws Exception {
566 		checkUnadvertisedIfUnallowed("uploadpack", "allowrefinwant",
567 				"ref-in-want");
568 	}
569 
570 	@Test
571 	public void testV2CapabilitiesAdvertiseSidebandAll() throws Exception {
572 		server.getConfig().setBoolean("uploadpack", null, "allowsidebandall",
573 				true);
574 		checkAdvertisedIfAllowed("uploadpack", "advertisesidebandall",
575 				"sideband-all");
576 		checkUnadvertisedIfUnallowed("uploadpack", "advertisesidebandall",
577 				"sideband-all");
578 	}
579 
580 	@Test
581 	public void testV2CapabilitiesRefInWantNotAdvertisedIfAdvertisingForbidden() throws Exception {
582 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
583 		server.getConfig().setBoolean("uploadpack", null, "advertiserefinwant", false);
584 		ByteArrayInputStream recvStream = uploadPackSetup(
585 				TransferConfig.ProtocolVersion.V2.version(), null,
586 				PacketLineIn.end());
587 		PacketLineIn pckIn = new PacketLineIn(recvStream);
588 
589 		assertThat(pckIn.readString(), is("version 2"));
590 		assertThat(
591 				Arrays.asList(pckIn.readString(), pckIn.readString(),
592 						pckIn.readString()),
593 				hasItems("ls-refs", "fetch=shallow", "server-option"));
594 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
595 	}
596 
597 	@Test
598 	public void testV2EmptyRequest() throws Exception {
599 		ByteArrayInputStream recvStream = uploadPackV2(PacketLineIn.end());
600 		// Verify that there is nothing more after the capability
601 		// advertisement.
602 		assertEquals(0, recvStream.available());
603 	}
604 
605 	@Test
606 	public void testV2LsRefs() throws Exception {
607 		RevCommit tip = remote.commit().message("message").create();
608 		remote.update("master", tip);
609 		server.updateRef("HEAD").link("refs/heads/master");
610 		RevTag tag = remote.tag("tag", tip);
611 		remote.update("refs/tags/tag", tag);
612 
613 		TestV2Hook hook = new TestV2Hook();
614 		ByteArrayInputStream recvStream = uploadPackV2(
615 				(UploadPack up) -> {up.setProtocolV2Hook(hook);},
616 				"command=ls-refs\n", PacketLineIn.end());
617 		PacketLineIn pckIn = new PacketLineIn(recvStream);
618 
619 		assertThat(hook.lsRefsRequest, notNullValue());
620 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD"));
621 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
622 		assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag"));
623 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
624 	}
625 
626 	@Test
627 	public void testV2LsRefsSymrefs() throws Exception {
628 		RevCommit tip = remote.commit().message("message").create();
629 		remote.update("master", tip);
630 		server.updateRef("HEAD").link("refs/heads/master");
631 		RevTag tag = remote.tag("tag", tip);
632 		remote.update("refs/tags/tag", tag);
633 
634 		ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n",
635 				PacketLineIn.delimiter(), "symrefs", PacketLineIn.end());
636 		PacketLineIn pckIn = new PacketLineIn(recvStream);
637 
638 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD symref-target:refs/heads/master"));
639 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
640 		assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag"));
641 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
642 	}
643 
644 	@Test
645 	public void testV2LsRefsPeel() throws Exception {
646 		RevCommit tip = remote.commit().message("message").create();
647 		remote.update("master", tip);
648 		server.updateRef("HEAD").link("refs/heads/master");
649 		RevTag tag = remote.tag("tag", tip);
650 		remote.update("refs/tags/tag", tag);
651 
652 		ByteArrayInputStream recvStream = uploadPackV2("command=ls-refs\n",
653 				PacketLineIn.delimiter(), "peel", PacketLineIn.end());
654 		PacketLineIn pckIn = new PacketLineIn(recvStream);
655 
656 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD"));
657 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
658 		assertThat(
659 			pckIn.readString(),
660 			is(tag.toObjectId().getName() + " refs/tags/tag peeled:"
661 				+ tip.toObjectId().getName()));
662 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
663 	}
664 
665 	@Test
666 	public void testV2LsRefsMultipleCommands() throws Exception {
667 		RevCommit tip = remote.commit().message("message").create();
668 		remote.update("master", tip);
669 		server.updateRef("HEAD").link("refs/heads/master");
670 		RevTag tag = remote.tag("tag", tip);
671 		remote.update("refs/tags/tag", tag);
672 
673 		ByteArrayInputStream recvStream = uploadPackV2(
674 				"command=ls-refs\n", PacketLineIn.delimiter(), "symrefs",
675 				"peel", PacketLineIn.end(), "command=ls-refs\n",
676 				PacketLineIn.delimiter(), PacketLineIn.end());
677 		PacketLineIn pckIn = new PacketLineIn(recvStream);
678 
679 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD symref-target:refs/heads/master"));
680 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
681 		assertThat(
682 			pckIn.readString(),
683 			is(tag.toObjectId().getName() + " refs/tags/tag peeled:"
684 				+ tip.toObjectId().getName()));
685 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
686 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " HEAD"));
687 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
688 		assertThat(pckIn.readString(), is(tag.toObjectId().getName() + " refs/tags/tag"));
689 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
690 	}
691 
692 	@Test
693 	public void testV2LsRefsRefPrefix() throws Exception {
694 		RevCommit tip = remote.commit().message("message").create();
695 		remote.update("master", tip);
696 		remote.update("other", tip);
697 		remote.update("yetAnother", tip);
698 
699 		ByteArrayInputStream recvStream = uploadPackV2(
700 			"command=ls-refs\n",
701 			PacketLineIn.delimiter(),
702 			"ref-prefix refs/heads/maste",
703 			"ref-prefix refs/heads/other",
704 				PacketLineIn.end());
705 		PacketLineIn pckIn = new PacketLineIn(recvStream);
706 
707 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
708 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/other"));
709 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
710 	}
711 
712 	@Test
713 	public void testV2LsRefsRefPrefixNoSlash() throws Exception {
714 		RevCommit tip = remote.commit().message("message").create();
715 		remote.update("master", tip);
716 		remote.update("other", tip);
717 
718 		ByteArrayInputStream recvStream = uploadPackV2(
719 			"command=ls-refs\n",
720 			PacketLineIn.delimiter(),
721 			"ref-prefix refs/heads/maste",
722 			"ref-prefix r",
723 				PacketLineIn.end());
724 		PacketLineIn pckIn = new PacketLineIn(recvStream);
725 
726 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/master"));
727 		assertThat(pckIn.readString(), is(tip.toObjectId().getName() + " refs/heads/other"));
728 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
729 	}
730 
731 	@Test
732 	public void testV2LsRefsUnrecognizedArgument() throws Exception {
733 		UploadPackInternalServerErrorException e = assertThrows(
734 				UploadPackInternalServerErrorException.class,
735 				() -> uploadPackV2("command=ls-refs\n",
736 						PacketLineIn.delimiter(), "invalid-argument\n",
737 						PacketLineIn.end()));
738 		assertThat(e.getCause().getMessage(),
739 				containsString("unexpected invalid-argument"));
740 	}
741 
742 	@Test
743 	public void testV2LsRefsServerOptions() throws Exception {
744 		String[] lines = { "command=ls-refs\n",
745 				"server-option=one\n", "server-option=two\n",
746 				PacketLineIn.delimiter(),
747 				PacketLineIn.end() };
748 
749 		TestV2Hook testHook = new TestV2Hook();
750 		uploadPackSetup(TransferConfig.ProtocolVersion.V2.version(),
751 				(UploadPack up) -> {
752 					up.setProtocolV2Hook(testHook);
753 				}, lines);
754 
755 		LsRefsV2Request req = testHook.lsRefsRequest;
756 		assertEquals(2, req.getServerOptions().size());
757 		assertThat(req.getServerOptions(), hasItems("one", "two"));
758 	}
759 
760 	/*
761 	 * Parse multiplexed packfile output from upload-pack using protocol V2
762 	 * into the client repository.
763 	 */
764 	private ReceivedPackStatistics parsePack(ByteArrayInputStream recvStream) throws Exception {
765 		return parsePack(recvStream, NullProgressMonitor.INSTANCE);
766 	}
767 
768 	private ReceivedPackStatistics parsePack(ByteArrayInputStream recvStream, ProgressMonitor pm)
769 			throws Exception {
770 		SideBandInputStream sb = new SideBandInputStream(
771 				recvStream, pm,
772 				new StringWriter(), NullOutputStream.INSTANCE);
773 		PackParser pp = client.newObjectInserter().newPackParser(sb);
774 		pp.parse(NullProgressMonitor.INSTANCE);
775 
776 		// Ensure that there is nothing left in the stream.
777 		assertEquals(-1, recvStream.read());
778 
779 		return pp.getReceivedPackStatistics();
780 	}
781 
782 	@Test
783 	public void testV2FetchRequestPolicyAdvertised() throws Exception {
784 		RevCommit advertized = remote.commit().message("x").create();
785 		RevCommit unadvertized = remote.commit().message("y").create();
786 		remote.update("branch1", advertized);
787 
788 		// This works
789 		uploadPackV2(
790 			(UploadPack up) -> {up.setRequestPolicy(RequestPolicy.ADVERTISED);},
791 			"command=fetch\n",
792 			PacketLineIn.delimiter(),
793 			"want " + advertized.name() + "\n",
794 			PacketLineIn.end());
795 
796 		// This doesn't
797 		UploadPackInternalServerErrorException e = assertThrows(
798 				UploadPackInternalServerErrorException.class,
799 				() -> uploadPackV2(
800 						(UploadPack up) -> {up.setRequestPolicy(RequestPolicy.ADVERTISED);},
801 						"command=fetch\n", PacketLineIn.delimiter(),
802 						"want " + unadvertized.name() + "\n",
803 						PacketLineIn.end()));
804 		assertThat(e.getCause().getMessage(),
805 				containsString("want " + unadvertized.name() + " not valid"));
806 	}
807 
808 	@Test
809 	public void testV2FetchRequestPolicyReachableCommit() throws Exception {
810 		RevCommit reachable = remote.commit().message("x").create();
811 		RevCommit advertized = remote.commit().message("x").parent(reachable)
812 				.create();
813 		RevCommit unreachable = remote.commit().message("y").create();
814 		remote.update("branch1", advertized);
815 
816 		// This works
817 		uploadPackV2(
818 			(UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
819 			"command=fetch\n",
820 			PacketLineIn.delimiter(),
821 			"want " + reachable.name() + "\n",
822 				PacketLineIn.end());
823 
824 		// This doesn't
825 		UploadPackInternalServerErrorException e = assertThrows(
826 				UploadPackInternalServerErrorException.class,
827 				() -> uploadPackV2(
828 						(UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
829 						"command=fetch\n", PacketLineIn.delimiter(),
830 						"want " + unreachable.name() + "\n",
831 						PacketLineIn.end()));
832 		assertThat(e.getCause().getMessage(),
833 				containsString("want " + unreachable.name() + " not valid"));
834 	}
835 
836 	@Test
837 	public void testV2FetchRequestPolicyTip() throws Exception {
838 		RevCommit parentOfTip = remote.commit().message("x").create();
839 		RevCommit tip = remote.commit().message("y").parent(parentOfTip)
840 				.create();
841 		remote.update("secret", tip);
842 
843 		// This works
844 		uploadPackV2(
845 			(UploadPack up) -> {
846 				up.setRequestPolicy(RequestPolicy.TIP);
847 				up.setRefFilter(new RejectAllRefFilter());
848 			},
849 			"command=fetch\n",
850 			PacketLineIn.delimiter(),
851 			"want " + tip.name() + "\n",
852 				PacketLineIn.end());
853 
854 		// This doesn't
855 		UploadPackInternalServerErrorException e = assertThrows(
856 				UploadPackInternalServerErrorException.class,
857 				() -> uploadPackV2(
858 						(UploadPack up) -> {
859 							up.setRequestPolicy(RequestPolicy.TIP);
860 							up.setRefFilter(new RejectAllRefFilter());
861 						},
862 						"command=fetch\n", PacketLineIn.delimiter(),
863 						"want " + parentOfTip.name() + "\n",
864 						PacketLineIn.end()));
865 		assertThat(e.getCause().getMessage(),
866 				containsString("want " + parentOfTip.name() + " not valid"));
867 	}
868 
869 	@Test
870 	public void testV2FetchRequestPolicyReachableCommitTip() throws Exception {
871 		RevCommit parentOfTip = remote.commit().message("x").create();
872 		RevCommit tip = remote.commit().message("y").parent(parentOfTip)
873 				.create();
874 		RevCommit unreachable = remote.commit().message("y").create();
875 		remote.update("secret", tip);
876 
877 		// This works
878 		uploadPackV2(
879 				(UploadPack up) -> {
880 					up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT_TIP);
881 					up.setRefFilter(new RejectAllRefFilter());
882 				},
883 				"command=fetch\n",
884 				PacketLineIn.delimiter(), "want " + parentOfTip.name() + "\n",
885 				PacketLineIn.end());
886 
887 		// This doesn't
888 		UploadPackInternalServerErrorException e = assertThrows(
889 				UploadPackInternalServerErrorException.class,
890 				() -> uploadPackV2(
891 						(UploadPack up) -> {
892 							up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT_TIP);
893 							up.setRefFilter(new RejectAllRefFilter());
894 						},
895 						"command=fetch\n",
896 						PacketLineIn.delimiter(),
897 						"want " + unreachable.name() + "\n",
898 						PacketLineIn.end()));
899 		assertThat(e.getCause().getMessage(),
900 				containsString("want " + unreachable.name() + " not valid"));
901 	}
902 
903 	@Test
904 	public void testV2FetchRequestPolicyAny() throws Exception {
905 		RevCommit unreachable = remote.commit().message("y").create();
906 
907 		// Exercise to make sure that even unreachable commits can be fetched
908 		uploadPackV2(
909 			(UploadPack up) -> {up.setRequestPolicy(RequestPolicy.ANY);},
910 			"command=fetch\n",
911 			PacketLineIn.delimiter(),
912 			"want " + unreachable.name() + "\n",
913 				PacketLineIn.end());
914 	}
915 
916 	@Test
917 	public void testV2FetchServerDoesNotStopNegotiation() throws Exception {
918 		RevCommit fooParent = remote.commit().message("x").create();
919 		RevCommit fooChild = remote.commit().message("x").parent(fooParent).create();
920 		RevCommit barParent = remote.commit().message("y").create();
921 		RevCommit barChild = remote.commit().message("y").parent(barParent).create();
922 		remote.update("branch1", fooChild);
923 		remote.update("branch2", barChild);
924 
925 		ByteArrayInputStream recvStream = uploadPackV2(
926 			"command=fetch\n",
927 			PacketLineIn.delimiter(),
928 			"want " + fooChild.toObjectId().getName() + "\n",
929 			"want " + barChild.toObjectId().getName() + "\n",
930 			"have " + fooParent.toObjectId().getName() + "\n",
931 				PacketLineIn.end());
932 		PacketLineIn pckIn = new PacketLineIn(recvStream);
933 
934 		assertThat(pckIn.readString(), is("acknowledgments"));
935 		assertThat(pckIn.readString(), is("ACK " + fooParent.toObjectId().getName()));
936 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
937 	}
938 
939 	@Test
940 	public void testV2FetchServerStopsNegotiation() throws Exception {
941 		RevCommit fooParent = remote.commit().message("x").create();
942 		RevCommit fooChild = remote.commit().message("x").parent(fooParent).create();
943 		RevCommit barParent = remote.commit().message("y").create();
944 		RevCommit barChild = remote.commit().message("y").parent(barParent).create();
945 		remote.update("branch1", fooChild);
946 		remote.update("branch2", barChild);
947 
948 		ByteArrayInputStream recvStream = uploadPackV2(
949 			"command=fetch\n",
950 			PacketLineIn.delimiter(),
951 			"want " + fooChild.toObjectId().getName() + "\n",
952 			"want " + barChild.toObjectId().getName() + "\n",
953 			"have " + fooParent.toObjectId().getName() + "\n",
954 			"have " + barParent.toObjectId().getName() + "\n",
955 				PacketLineIn.end());
956 		PacketLineIn pckIn = new PacketLineIn(recvStream);
957 
958 		assertThat(pckIn.readString(), is("acknowledgments"));
959 		assertThat(
960 			Arrays.asList(pckIn.readString(), pckIn.readString()),
961 			hasItems(
962 				"ACK " + fooParent.toObjectId().getName(),
963 				"ACK " + barParent.toObjectId().getName()));
964 		assertThat(pckIn.readString(), is("ready"));
965 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
966 		assertThat(pckIn.readString(), is("packfile"));
967 		parsePack(recvStream);
968 		assertFalse(client.getObjectDatabase().has(fooParent.toObjectId()));
969 		assertTrue(client.getObjectDatabase().has(fooChild.toObjectId()));
970 		assertFalse(client.getObjectDatabase().has(barParent.toObjectId()));
971 		assertTrue(client.getObjectDatabase().has(barChild.toObjectId()));
972 	}
973 
974 	@Test
975 	public void testV2FetchClientStopsNegotiation() throws Exception {
976 		RevCommit fooParent = remote.commit().message("x").create();
977 		RevCommit fooChild = remote.commit().message("x").parent(fooParent).create();
978 		RevCommit barParent = remote.commit().message("y").create();
979 		RevCommit barChild = remote.commit().message("y").parent(barParent).create();
980 		remote.update("branch1", fooChild);
981 		remote.update("branch2", barChild);
982 
983 		ByteArrayInputStream recvStream = uploadPackV2(
984 			"command=fetch\n",
985 			PacketLineIn.delimiter(),
986 			"want " + fooChild.toObjectId().getName() + "\n",
987 			"want " + barChild.toObjectId().getName() + "\n",
988 			"have " + fooParent.toObjectId().getName() + "\n",
989 			"done\n",
990 				PacketLineIn.end());
991 		PacketLineIn pckIn = new PacketLineIn(recvStream);
992 
993 		assertThat(pckIn.readString(), is("packfile"));
994 		parsePack(recvStream);
995 		assertFalse(client.getObjectDatabase().has(fooParent.toObjectId()));
996 		assertTrue(client.getObjectDatabase().has(fooChild.toObjectId()));
997 		assertTrue(client.getObjectDatabase().has(barParent.toObjectId()));
998 		assertTrue(client.getObjectDatabase().has(barChild.toObjectId()));
999 	}
1000 
1001 	@Test
1002 	public void testV2FetchThinPack() throws Exception {
1003 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
1004 
1005 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1006 		RevCommit parent = remote
1007 				.commit(remote.tree(remote.file("foo", parentBlob)));
1008 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1009 		RevCommit child = remote
1010 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
1011 		remote.update("branch1", child);
1012 
1013 		// Pretend that we have parent to get a thin pack based on it.
1014 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
1015 				PacketLineIn.delimiter(),
1016 				"want " + child.toObjectId().getName() + "\n",
1017 				"have " + parent.toObjectId().getName() + "\n", "thin-pack\n",
1018 				"done\n", PacketLineIn.end());
1019 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1020 
1021 		assertThat(pckIn.readString(), is("packfile"));
1022 
1023 		// Verify that we received a thin pack by trying to apply it
1024 		// against the client repo, which does not have parent.
1025 		IOException e = assertThrows(IOException.class,
1026 				() -> parsePack(recvStream));
1027 		assertThat(e.getMessage(),
1028 				containsString("pack has unresolved deltas"));
1029 	}
1030 
1031 	@Test
1032 	public void testV2FetchNoProgress() throws Exception {
1033 		RevCommit commit = remote.commit().message("x").create();
1034 		remote.update("branch1", commit);
1035 
1036 		// Without no-progress, progress is reported.
1037 		StringWriter sw = new StringWriter();
1038 		ByteArrayInputStream recvStream = uploadPackV2(
1039 			"command=fetch\n",
1040 			PacketLineIn.delimiter(),
1041 			"want " + commit.toObjectId().getName() + "\n",
1042 			"done\n",
1043 				PacketLineIn.end());
1044 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1045 		assertThat(pckIn.readString(), is("packfile"));
1046 		parsePack(recvStream, new TextProgressMonitor(sw));
1047 		assertFalse(sw.toString().isEmpty());
1048 
1049 		// With no-progress, progress is not reported.
1050 		sw = new StringWriter();
1051 		recvStream = uploadPackV2(
1052 			"command=fetch\n",
1053 			PacketLineIn.delimiter(),
1054 			"want " + commit.toObjectId().getName() + "\n",
1055 			"no-progress\n",
1056 			"done\n",
1057 				PacketLineIn.end());
1058 		pckIn = new PacketLineIn(recvStream);
1059 		assertThat(pckIn.readString(), is("packfile"));
1060 		parsePack(recvStream, new TextProgressMonitor(sw));
1061 		assertTrue(sw.toString().isEmpty());
1062 	}
1063 
1064 	@Test
1065 	public void testV2FetchIncludeTag() throws Exception {
1066 		RevCommit commit = remote.commit().message("x").create();
1067 		RevTag tag = remote.tag("tag", commit);
1068 		remote.update("branch1", commit);
1069 		remote.update("refs/tags/tag", tag);
1070 
1071 		// Without include-tag.
1072 		ByteArrayInputStream recvStream = uploadPackV2(
1073 			"command=fetch\n",
1074 			PacketLineIn.delimiter(),
1075 			"want " + commit.toObjectId().getName() + "\n",
1076 			"done\n",
1077 				PacketLineIn.end());
1078 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1079 		assertThat(pckIn.readString(), is("packfile"));
1080 		parsePack(recvStream);
1081 		assertFalse(client.getObjectDatabase().has(tag.toObjectId()));
1082 
1083 		// With tag.
1084 		recvStream = uploadPackV2(
1085 			"command=fetch\n",
1086 			PacketLineIn.delimiter(),
1087 			"want " + commit.toObjectId().getName() + "\n",
1088 			"include-tag\n",
1089 			"done\n",
1090 				PacketLineIn.end());
1091 		pckIn = new PacketLineIn(recvStream);
1092 		assertThat(pckIn.readString(), is("packfile"));
1093 		parsePack(recvStream);
1094 		assertTrue(client.getObjectDatabase().has(tag.toObjectId()));
1095 	}
1096 
1097 	@Test
1098 	public void testUploadNewBytes() throws Exception {
1099 		String commonInBlob = "abcdefghijklmnopqrstuvwx";
1100 
1101 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1102 		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
1103 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1104 		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
1105 		remote.update("branch1", child);
1106 
1107 		ByteArrayInputStream recvStream = uploadPackV2(
1108 			"command=fetch\n",
1109 			PacketLineIn.delimiter(),
1110 			"want " + child.toObjectId().getName() + "\n",
1111 			"ofs-delta\n",
1112 			"done\n",
1113 				PacketLineIn.end());
1114 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1115 		assertThat(pckIn.readString(), is("packfile"));
1116 		ReceivedPackStatistics receivedStats = parsePack(recvStream);
1117 		assertTrue(receivedStats.getNumBytesDuplicated() == 0);
1118 		assertTrue(receivedStats.getNumObjectsDuplicated() == 0);
1119 	}
1120 
1121 	@Test
1122 	public void testUploadRedundantBytes() throws Exception {
1123 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
1124 
1125 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1126 		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
1127 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1128 		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
1129 		remote.update("branch1", child);
1130 
1131 		try (TestRepository<InMemoryRepository> local = new TestRepository<>(
1132 				client)) {
1133 			RevBlob localParentBlob = local.blob(commonInBlob + "a");
1134 			RevCommit localParent = local
1135 					.commit(local.tree(local.file("foo", localParentBlob)));
1136 			RevBlob localChildBlob = local.blob(commonInBlob + "b");
1137 			RevCommit localChild = local.commit(
1138 					local.tree(local.file("foo", localChildBlob)), localParent);
1139 			local.update("branch1", localChild);
1140 		}
1141 
1142 		ByteArrayInputStream recvStream = uploadPackV2(
1143 				"command=fetch\n",
1144 				PacketLineIn.delimiter(),
1145 				"want " + child.toObjectId().getName() + "\n",
1146 				"ofs-delta\n",
1147 				"done\n",
1148 					PacketLineIn.end());
1149 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1150 		assertThat(pckIn.readString(), is("packfile"));
1151 		ReceivedPackStatistics receivedStats = parsePack(recvStream);
1152 
1153 		long sizeOfHeader = 12;
1154 		long sizeOfTrailer = 20;
1155 		long expectedSize = receivedStats.getNumBytesRead() - sizeOfHeader
1156 				- sizeOfTrailer;
1157 		assertTrue(receivedStats.getNumBytesDuplicated() == expectedSize);
1158 		assertTrue(receivedStats.getNumObjectsDuplicated() == 6);
1159 	}
1160 
1161 	@Test
1162 	public void testV2FetchOfsDelta() throws Exception {
1163 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
1164 
1165 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1166 		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
1167 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1168 		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
1169 		remote.update("branch1", child);
1170 
1171 		// Without ofs-delta.
1172 		ByteArrayInputStream recvStream = uploadPackV2(
1173 			"command=fetch\n",
1174 			PacketLineIn.delimiter(),
1175 			"want " + child.toObjectId().getName() + "\n",
1176 			"done\n",
1177 				PacketLineIn.end());
1178 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1179 		assertThat(pckIn.readString(), is("packfile"));
1180 		ReceivedPackStatistics receivedStats = parsePack(recvStream);
1181 		assertTrue(receivedStats.getNumOfsDelta() == 0);
1182 
1183 		// With ofs-delta.
1184 		recvStream = uploadPackV2(
1185 			"command=fetch\n",
1186 			PacketLineIn.delimiter(),
1187 			"want " + child.toObjectId().getName() + "\n",
1188 			"ofs-delta\n",
1189 			"done\n",
1190 				PacketLineIn.end());
1191 		pckIn = new PacketLineIn(recvStream);
1192 		assertThat(pckIn.readString(), is("packfile"));
1193 		receivedStats = parsePack(recvStream);
1194 		assertTrue(receivedStats.getNumOfsDelta() != 0);
1195 	}
1196 
1197 	@Test
1198 	public void testV2FetchShallow() throws Exception {
1199 		RevCommit commonParent = remote.commit().message("parent").create();
1200 		RevCommit fooChild = remote.commit().message("x").parent(commonParent).create();
1201 		RevCommit barChild = remote.commit().message("y").parent(commonParent).create();
1202 		remote.update("branch1", barChild);
1203 
1204 		// Without shallow, the server thinks that we have
1205 		// commonParent, so it doesn't send it.
1206 		ByteArrayInputStream recvStream = uploadPackV2(
1207 			"command=fetch\n",
1208 			PacketLineIn.delimiter(),
1209 			"want " + barChild.toObjectId().getName() + "\n",
1210 			"have " + fooChild.toObjectId().getName() + "\n",
1211 			"done\n",
1212 				PacketLineIn.end());
1213 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1214 		assertThat(pckIn.readString(), is("packfile"));
1215 		parsePack(recvStream);
1216 		assertTrue(client.getObjectDatabase().has(barChild.toObjectId()));
1217 		assertFalse(client.getObjectDatabase().has(commonParent.toObjectId()));
1218 
1219 		// With shallow, the server knows that we don't have
1220 		// commonParent, so it sends it.
1221 		recvStream = uploadPackV2(
1222 			"command=fetch\n",
1223 			PacketLineIn.delimiter(),
1224 			"want " + barChild.toObjectId().getName() + "\n",
1225 			"have " + fooChild.toObjectId().getName() + "\n",
1226 			"shallow " + fooChild.toObjectId().getName() + "\n",
1227 			"done\n",
1228 				PacketLineIn.end());
1229 		pckIn = new PacketLineIn(recvStream);
1230 		assertThat(pckIn.readString(), is("packfile"));
1231 		parsePack(recvStream);
1232 		assertTrue(client.getObjectDatabase().has(commonParent.toObjectId()));
1233 	}
1234 
1235 	@Test
1236 	public void testV2FetchDeepenAndDone() throws Exception {
1237 		RevCommit parent = remote.commit().message("parent").create();
1238 		RevCommit child = remote.commit().message("x").parent(parent).create();
1239 		remote.update("branch1", child);
1240 
1241 		// "deepen 1" sends only the child.
1242 		ByteArrayInputStream recvStream = uploadPackV2(
1243 			"command=fetch\n",
1244 			PacketLineIn.delimiter(),
1245 			"want " + child.toObjectId().getName() + "\n",
1246 			"deepen 1\n",
1247 			"done\n",
1248 				PacketLineIn.end());
1249 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1250 		assertThat(pckIn.readString(), is("shallow-info"));
1251 		assertThat(pckIn.readString(), is("shallow " + child.toObjectId().getName()));
1252 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1253 		assertThat(pckIn.readString(), is("packfile"));
1254 		parsePack(recvStream);
1255 		assertTrue(client.getObjectDatabase().has(child.toObjectId()));
1256 		assertFalse(client.getObjectDatabase().has(parent.toObjectId()));
1257 
1258 		// Without that, the parent is sent too.
1259 		recvStream = uploadPackV2(
1260 			"command=fetch\n",
1261 			PacketLineIn.delimiter(),
1262 			"want " + child.toObjectId().getName() + "\n",
1263 			"done\n",
1264 				PacketLineIn.end());
1265 		pckIn = new PacketLineIn(recvStream);
1266 		assertThat(pckIn.readString(), is("packfile"));
1267 		parsePack(recvStream);
1268 		assertTrue(client.getObjectDatabase().has(parent.toObjectId()));
1269 	}
1270 
1271 	@Test
1272 	public void testV2FetchDeepenWithoutDone() throws Exception {
1273 		RevCommit parent = remote.commit().message("parent").create();
1274 		RevCommit child = remote.commit().message("x").parent(parent).create();
1275 		remote.update("branch1", child);
1276 
1277 		ByteArrayInputStream recvStream = uploadPackV2(
1278 			"command=fetch\n",
1279 			PacketLineIn.delimiter(),
1280 			"want " + child.toObjectId().getName() + "\n",
1281 			"deepen 1\n",
1282 				PacketLineIn.end());
1283 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1284 
1285 		// Verify that only the correct section is sent. "shallow-info"
1286 		// is not sent because, according to the specification, it is
1287 		// sent only if a packfile is sent.
1288 		assertThat(pckIn.readString(), is("acknowledgments"));
1289 		assertThat(pckIn.readString(), is("NAK"));
1290 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
1291 	}
1292 
1293 	@Test
1294 	public void testV2FetchShallowSince() throws Exception {
1295 		PersonIdent person = new PersonIdent(remote.getRepository());
1296 
1297 		RevCommit beyondBoundary = remote.commit()
1298 			.committer(new PersonIdent(person, 1510000000, 0)).create();
1299 		RevCommit boundary = remote.commit().parent(beyondBoundary)
1300 			.committer(new PersonIdent(person, 1520000000, 0)).create();
1301 		RevCommit tooOld = remote.commit()
1302 			.committer(new PersonIdent(person, 1500000000, 0)).create();
1303 		RevCommit merge = remote.commit().parent(boundary).parent(tooOld)
1304 			.committer(new PersonIdent(person, 1530000000, 0)).create();
1305 
1306 		remote.update("branch1", merge);
1307 
1308 		// Report that we only have "boundary" as a shallow boundary.
1309 		ByteArrayInputStream recvStream = uploadPackV2(
1310 			"command=fetch\n",
1311 			PacketLineIn.delimiter(),
1312 			"shallow " + boundary.toObjectId().getName() + "\n",
1313 			"deepen-since 1510000\n",
1314 			"want " + merge.toObjectId().getName() + "\n",
1315 			"have " + boundary.toObjectId().getName() + "\n",
1316 			"done\n",
1317 				PacketLineIn.end());
1318 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1319 		assertThat(pckIn.readString(), is("shallow-info"));
1320 
1321 		// "merge" is shallow because one of its parents is committed
1322 		// earlier than the given deepen-since time.
1323 		assertThat(pckIn.readString(), is("shallow " + merge.toObjectId().getName()));
1324 
1325 		// "boundary" is unshallow because its parent committed at or
1326 		// later than the given deepen-since time.
1327 		assertThat(pckIn.readString(), is("unshallow " + boundary.toObjectId().getName()));
1328 
1329 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1330 		assertThat(pckIn.readString(), is("packfile"));
1331 		parsePack(recvStream);
1332 
1333 		// The server does not send this because it is committed
1334 		// earlier than the given deepen-since time.
1335 		assertFalse(client.getObjectDatabase().has(tooOld.toObjectId()));
1336 
1337 		// The server does not send this because the client claims to
1338 		// have it.
1339 		assertFalse(client.getObjectDatabase().has(boundary.toObjectId()));
1340 
1341 		// The server sends both these commits.
1342 		assertTrue(client.getObjectDatabase().has(beyondBoundary.toObjectId()));
1343 		assertTrue(client.getObjectDatabase().has(merge.toObjectId()));
1344 	}
1345 
1346 	@Test
1347 	public void testV2FetchShallowSince_excludedParentWithMultipleChildren() throws Exception {
1348 		PersonIdent person = new PersonIdent(remote.getRepository());
1349 
1350 		RevCommit base = remote.commit()
1351 			.committer(new PersonIdent(person, 1500000000, 0)).create();
1352 		RevCommit child1 = remote.commit().parent(base)
1353 			.committer(new PersonIdent(person, 1510000000, 0)).create();
1354 		RevCommit child2 = remote.commit().parent(base)
1355 			.committer(new PersonIdent(person, 1520000000, 0)).create();
1356 
1357 		remote.update("branch1", child1);
1358 		remote.update("branch2", child2);
1359 
1360 		ByteArrayInputStream recvStream = uploadPackV2(
1361 			"command=fetch\n",
1362 			PacketLineIn.delimiter(),
1363 			"deepen-since 1510000\n",
1364 			"want " + child1.toObjectId().getName() + "\n",
1365 			"want " + child2.toObjectId().getName() + "\n",
1366 			"done\n",
1367 				PacketLineIn.end());
1368 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1369 		assertThat(pckIn.readString(), is("shallow-info"));
1370 
1371 		// "base" is excluded, so its children are shallow.
1372 		assertThat(
1373 			Arrays.asList(pckIn.readString(), pckIn.readString()),
1374 			hasItems(
1375 				"shallow " + child1.toObjectId().getName(),
1376 				"shallow " + child2.toObjectId().getName()));
1377 
1378 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1379 		assertThat(pckIn.readString(), is("packfile"));
1380 		parsePack(recvStream);
1381 
1382 		// Only the children are sent.
1383 		assertFalse(client.getObjectDatabase().has(base.toObjectId()));
1384 		assertTrue(client.getObjectDatabase().has(child1.toObjectId()));
1385 		assertTrue(client.getObjectDatabase().has(child2.toObjectId()));
1386 	}
1387 
1388 	@Test
1389 	public void testV2FetchShallowSince_noCommitsSelected() throws Exception {
1390 		PersonIdent person = new PersonIdent(remote.getRepository());
1391 
1392 		RevCommit tooOld = remote.commit()
1393 				.committer(new PersonIdent(person, 1500000000, 0)).create();
1394 
1395 		remote.update("branch1", tooOld);
1396 
1397 		UploadPackInternalServerErrorException e = assertThrows(
1398 				UploadPackInternalServerErrorException.class,
1399 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
1400 						"deepen-since 1510000\n",
1401 						"want " + tooOld.toObjectId().getName() + "\n",
1402 						"done\n", PacketLineIn.end()));
1403 		assertThat(e.getCause().getMessage(),
1404 				containsString("No commits selected for shallow request"));
1405 	}
1406 
1407 	@Test
1408 	public void testV2FetchDeepenNot() throws Exception {
1409 		RevCommit one = remote.commit().message("one").create();
1410 		RevCommit two = remote.commit().message("two").parent(one).create();
1411 		RevCommit three = remote.commit().message("three").parent(two).create();
1412 		RevCommit side = remote.commit().message("side").parent(one).create();
1413 		RevCommit merge = remote.commit().message("merge")
1414 			.parent(three).parent(side).create();
1415 
1416 		remote.update("branch1", merge);
1417 		remote.update("side", side);
1418 
1419 		// The client is a shallow clone that only has "three", and
1420 		// wants "merge" while excluding "side".
1421 		ByteArrayInputStream recvStream = uploadPackV2(
1422 			"command=fetch\n",
1423 			PacketLineIn.delimiter(),
1424 			"shallow " + three.toObjectId().getName() + "\n",
1425 			"deepen-not side\n",
1426 			"want " + merge.toObjectId().getName() + "\n",
1427 			"have " + three.toObjectId().getName() + "\n",
1428 			"done\n",
1429 				PacketLineIn.end());
1430 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1431 		assertThat(pckIn.readString(), is("shallow-info"));
1432 
1433 		// "merge" is shallow because "side" is excluded by deepen-not.
1434 		// "two" is shallow because "one" (as parent of "side") is excluded by deepen-not.
1435 		assertThat(
1436 			Arrays.asList(pckIn.readString(), pckIn.readString()),
1437 			hasItems(
1438 				"shallow " + merge.toObjectId().getName(),
1439 				"shallow " + two.toObjectId().getName()));
1440 
1441 		// "three" is unshallow because its parent "two" is now available.
1442 		assertThat(pckIn.readString(), is("unshallow " + three.toObjectId().getName()));
1443 
1444 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1445 		assertThat(pckIn.readString(), is("packfile"));
1446 		parsePack(recvStream);
1447 
1448 		// The server does not send these because they are excluded by
1449 		// deepen-not.
1450 		assertFalse(client.getObjectDatabase().has(side.toObjectId()));
1451 		assertFalse(client.getObjectDatabase().has(one.toObjectId()));
1452 
1453 		// The server does not send this because the client claims to
1454 		// have it.
1455 		assertFalse(client.getObjectDatabase().has(three.toObjectId()));
1456 
1457 		// The server sends both these commits.
1458 		assertTrue(client.getObjectDatabase().has(merge.toObjectId()));
1459 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
1460 	}
1461 
1462 	@Test
1463 	public void testV2FetchDeepenNot_excludeDescendantOfWant()
1464 			throws Exception {
1465 		RevCommit one = remote.commit().message("one").create();
1466 		RevCommit two = remote.commit().message("two").parent(one).create();
1467 		RevCommit three = remote.commit().message("three").parent(two).create();
1468 		RevCommit four = remote.commit().message("four").parent(three).create();
1469 
1470 		remote.update("two", two);
1471 		remote.update("four", four);
1472 
1473 		UploadPackInternalServerErrorException e = assertThrows(
1474 				UploadPackInternalServerErrorException.class,
1475 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
1476 						"deepen-not four\n",
1477 						"want " + two.toObjectId().getName() + "\n", "done\n",
1478 						PacketLineIn.end()));
1479 		assertThat(e.getCause().getMessage(),
1480 				containsString("No commits selected for shallow request"));
1481 	}
1482 
1483 	@Test
1484 	public void testV2FetchDeepenNot_supportAnnotatedTags() throws Exception {
1485 		RevCommit one = remote.commit().message("one").create();
1486 		RevCommit two = remote.commit().message("two").parent(one).create();
1487 		RevCommit three = remote.commit().message("three").parent(two).create();
1488 		RevCommit four = remote.commit().message("four").parent(three).create();
1489 		RevTag twoTag = remote.tag("twotag", two);
1490 
1491 		remote.update("refs/tags/twotag", twoTag);
1492 		remote.update("four", four);
1493 
1494 		ByteArrayInputStream recvStream = uploadPackV2(
1495 			"command=fetch\n",
1496 			PacketLineIn.delimiter(),
1497 			"deepen-not twotag\n",
1498 			"want " + four.toObjectId().getName() + "\n",
1499 			"done\n",
1500 				PacketLineIn.end());
1501 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1502 		assertThat(pckIn.readString(), is("shallow-info"));
1503 		assertThat(pckIn.readString(), is("shallow " + three.toObjectId().getName()));
1504 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1505 		assertThat(pckIn.readString(), is("packfile"));
1506 		parsePack(recvStream);
1507 		assertFalse(client.getObjectDatabase().has(one.toObjectId()));
1508 		assertFalse(client.getObjectDatabase().has(two.toObjectId()));
1509 		assertTrue(client.getObjectDatabase().has(three.toObjectId()));
1510 		assertTrue(client.getObjectDatabase().has(four.toObjectId()));
1511 	}
1512 
1513 	@Test
1514 	public void testV2FetchDeepenNot_excludedParentWithMultipleChildren() throws Exception {
1515 		PersonIdent person = new PersonIdent(remote.getRepository());
1516 
1517 		RevCommit base = remote.commit()
1518 			.committer(new PersonIdent(person, 1500000000, 0)).create();
1519 		RevCommit child1 = remote.commit().parent(base)
1520 			.committer(new PersonIdent(person, 1510000000, 0)).create();
1521 		RevCommit child2 = remote.commit().parent(base)
1522 			.committer(new PersonIdent(person, 1520000000, 0)).create();
1523 
1524 		remote.update("base", base);
1525 		remote.update("branch1", child1);
1526 		remote.update("branch2", child2);
1527 
1528 		ByteArrayInputStream recvStream = uploadPackV2(
1529 			"command=fetch\n",
1530 			PacketLineIn.delimiter(),
1531 			"deepen-not base\n",
1532 			"want " + child1.toObjectId().getName() + "\n",
1533 			"want " + child2.toObjectId().getName() + "\n",
1534 			"done\n",
1535 				PacketLineIn.end());
1536 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1537 		assertThat(pckIn.readString(), is("shallow-info"));
1538 
1539 		// "base" is excluded, so its children are shallow.
1540 		assertThat(
1541 			Arrays.asList(pckIn.readString(), pckIn.readString()),
1542 			hasItems(
1543 				"shallow " + child1.toObjectId().getName(),
1544 				"shallow " + child2.toObjectId().getName()));
1545 
1546 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1547 		assertThat(pckIn.readString(), is("packfile"));
1548 		parsePack(recvStream);
1549 
1550 		// Only the children are sent.
1551 		assertFalse(client.getObjectDatabase().has(base.toObjectId()));
1552 		assertTrue(client.getObjectDatabase().has(child1.toObjectId()));
1553 		assertTrue(client.getObjectDatabase().has(child2.toObjectId()));
1554 	}
1555 
1556 	@Test
1557 	public void testV2FetchUnrecognizedArgument() throws Exception {
1558 		UploadPackInternalServerErrorException e = assertThrows(
1559 				UploadPackInternalServerErrorException.class,
1560 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
1561 						"invalid-argument\n", PacketLineIn.end()));
1562 		assertThat(e.getCause().getMessage(),
1563 				containsString("unexpected invalid-argument"));
1564 	}
1565 
1566 	@Test
1567 	public void testV2FetchServerOptions() throws Exception {
1568 		String[] lines = { "command=fetch\n", "server-option=one\n",
1569 				"server-option=two\n", PacketLineIn.delimiter(),
1570 				PacketLineIn.end() };
1571 
1572 		TestV2Hook testHook = new TestV2Hook();
1573 		uploadPackSetup(TransferConfig.ProtocolVersion.V2.version(),
1574 				(UploadPack up) -> {
1575 					up.setProtocolV2Hook(testHook);
1576 				}, lines);
1577 
1578 		FetchV2Request req = testHook.fetchRequest;
1579 		assertNotNull(req);
1580 		assertEquals(2, req.getServerOptions().size());
1581 		assertThat(req.getServerOptions(), hasItems("one", "two"));
1582 	}
1583 
1584 	@Test
1585 	public void testV2FetchFilter() throws Exception {
1586 		RevBlob big = remote.blob("foobar");
1587 		RevBlob small = remote.blob("fooba");
1588 		RevTree tree = remote.tree(remote.file("1", big),
1589 				remote.file("2", small));
1590 		RevCommit commit = remote.commit(tree);
1591 		remote.update("master", commit);
1592 
1593 		server.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
1594 
1595 		ByteArrayInputStream recvStream = uploadPackV2(
1596 			"command=fetch\n",
1597 			PacketLineIn.delimiter(),
1598 			"want " + commit.toObjectId().getName() + "\n",
1599 			"filter blob:limit=5\n",
1600 			"done\n",
1601 				PacketLineIn.end());
1602 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1603 		assertThat(pckIn.readString(), is("packfile"));
1604 		parsePack(recvStream);
1605 
1606 		assertFalse(client.getObjectDatabase().has(big.toObjectId()));
1607 		assertTrue(client.getObjectDatabase().has(small.toObjectId()));
1608 	}
1609 
1610 	abstract class TreeBuilder {
1611 		abstract void addElements(DirCacheBuilder dcBuilder) throws Exception;
1612 
1613 		RevTree build() throws Exception {
1614 			DirCache dc = DirCache.newInCore();
1615 			DirCacheBuilder dcBuilder = dc.builder();
1616 			addElements(dcBuilder);
1617 			dcBuilder.finish();
1618 			ObjectId id;
1619 			try (ObjectInserter ins =
1620 					remote.getRepository().newObjectInserter()) {
1621 				id = dc.writeTree(ins);
1622 				ins.flush();
1623 			}
1624 			return remote.getRevWalk().parseTree(id);
1625 		}
1626 	}
1627 
1628 	class DeepTreePreparator {
1629 		RevBlob blobLowDepth = remote.blob("lo");
1630 		RevBlob blobHighDepth = remote.blob("hi");
1631 
1632 		RevTree subtree = remote.tree(remote.file("1", blobHighDepth));
1633 		RevTree rootTree = (new TreeBuilder() {
1634 				@Override
1635 				void addElements(DirCacheBuilder dcBuilder) throws Exception {
1636 					dcBuilder.add(remote.file("1", blobLowDepth));
1637 					dcBuilder.addTree(new byte[] {'2'}, DirCacheEntry.STAGE_0,
1638 							remote.getRevWalk().getObjectReader(), subtree);
1639 				}
1640 			}).build();
1641 		RevCommit commit = remote.commit(rootTree);
1642 
1643 		DeepTreePreparator() throws Exception {}
1644 	}
1645 
1646 	private void uploadV2WithTreeDepthFilter(
1647 			long depth, ObjectId... wants) throws Exception {
1648 		server.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
1649 
1650 		List<String> input = new ArrayList<>();
1651 		input.add("command=fetch\n");
1652 		input.add(PacketLineIn.delimiter());
1653 		for (ObjectId want : wants) {
1654 			input.add("want " + want.getName() + "\n");
1655 		}
1656 		input.add("filter tree:" + depth + "\n");
1657 		input.add("done\n");
1658 		input.add(PacketLineIn.end());
1659 		ByteArrayInputStream recvStream =
1660 				uploadPackV2(
1661 						(UploadPack up) -> {up.setRequestPolicy(RequestPolicy.ANY);},
1662 						input.toArray(new String[0]));
1663 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1664 		assertThat(pckIn.readString(), is("packfile"));
1665 		parsePack(recvStream);
1666 	}
1667 
1668 	@Test
1669 	public void testV2FetchFilterTreeDepth0() throws Exception {
1670 		DeepTreePreparator preparator = new DeepTreePreparator();
1671 		remote.update("master", preparator.commit);
1672 
1673 		uploadV2WithTreeDepthFilter(0, preparator.commit.toObjectId());
1674 
1675 		assertFalse(client.getObjectDatabase()
1676 				.has(preparator.rootTree.toObjectId()));
1677 		assertFalse(client.getObjectDatabase()
1678 				.has(preparator.subtree.toObjectId()));
1679 		assertFalse(client.getObjectDatabase()
1680 				.has(preparator.blobLowDepth.toObjectId()));
1681 		assertFalse(client.getObjectDatabase()
1682 				.has(preparator.blobHighDepth.toObjectId()));
1683 		assertEquals(1, stats.getTreesTraversed());
1684 	}
1685 
1686 	@Test
1687 	public void testV2FetchFilterTreeDepth1_serverHasBitmap() throws Exception {
1688 		DeepTreePreparator preparator = new DeepTreePreparator();
1689 		remote.update("master", preparator.commit);
1690 
1691 		// The bitmap should be ignored since we need to track the depth while
1692 		// traversing the trees.
1693 		generateBitmaps(server);
1694 
1695 		uploadV2WithTreeDepthFilter(1, preparator.commit.toObjectId());
1696 
1697 		assertTrue(client.getObjectDatabase()
1698 				.has(preparator.rootTree.toObjectId()));
1699 		assertFalse(client.getObjectDatabase()
1700 				.has(preparator.subtree.toObjectId()));
1701 		assertFalse(client.getObjectDatabase()
1702 				.has(preparator.blobLowDepth.toObjectId()));
1703 		assertFalse(client.getObjectDatabase()
1704 				.has(preparator.blobHighDepth.toObjectId()));
1705 		assertEquals(1, stats.getTreesTraversed());
1706 	}
1707 
1708 	@Test
1709 	public void testV2FetchFilterTreeDepth2() throws Exception {
1710 		DeepTreePreparator preparator = new DeepTreePreparator();
1711 		remote.update("master", preparator.commit);
1712 
1713 		uploadV2WithTreeDepthFilter(2, preparator.commit.toObjectId());
1714 
1715 		assertTrue(client.getObjectDatabase()
1716 				.has(preparator.rootTree.toObjectId()));
1717 		assertTrue(client.getObjectDatabase()
1718 				.has(preparator.subtree.toObjectId()));
1719 		assertTrue(client.getObjectDatabase()
1720 				.has(preparator.blobLowDepth.toObjectId()));
1721 		assertFalse(client.getObjectDatabase()
1722 				.has(preparator.blobHighDepth.toObjectId()));
1723 		assertEquals(2, stats.getTreesTraversed());
1724 	}
1725 
1726 	/**
1727 	 * Creates a commit with the following files:
1728 	 * <pre>
1729 	 * a/x/b/foo
1730 	 * x/b/foo
1731 	 * </pre>
1732 	 * which has an identical tree in two locations: once at / and once at /a
1733 	 */
1734 	class RepeatedSubtreePreparator {
1735 		RevBlob foo = remote.blob("foo");
1736 		RevTree subtree3 = remote.tree(remote.file("foo", foo));
1737 		RevTree subtree2 = (new TreeBuilder() {
1738 			@Override
1739 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1740 				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
1741 						remote.getRevWalk().getObjectReader(), subtree3);
1742 			}
1743 		}).build();
1744 		RevTree subtree1 = (new TreeBuilder() {
1745 			@Override
1746 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1747 				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
1748 						remote.getRevWalk().getObjectReader(), subtree2);
1749 			}
1750 		}).build();
1751 		RevTree rootTree = (new TreeBuilder() {
1752 			@Override
1753 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1754 				dcBuilder.addTree(new byte[] {'a'}, DirCacheEntry.STAGE_0,
1755 						remote.getRevWalk().getObjectReader(), subtree1);
1756 				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
1757 						remote.getRevWalk().getObjectReader(), subtree2);
1758 			}
1759 		}).build();
1760 		RevCommit commit = remote.commit(rootTree);
1761 
1762 		RepeatedSubtreePreparator() throws Exception {}
1763 	}
1764 
1765 	@Test
1766 	public void testV2FetchFilterTreeDepth_iterateOverTreeAtTwoLevels()
1767 			throws Exception {
1768 		// Test tree:<depth> where a tree is iterated to twice - once where a
1769 		// subentry is too deep to be included, and again where the blob inside
1770 		// it is shallow enough to be included.
1771 		RepeatedSubtreePreparator preparator = new RepeatedSubtreePreparator();
1772 		remote.update("master", preparator.commit);
1773 
1774 		uploadV2WithTreeDepthFilter(4, preparator.commit.toObjectId());
1775 
1776 		assertTrue(client.getObjectDatabase()
1777 				.has(preparator.foo.toObjectId()));
1778 	}
1779 
1780 	/**
1781 	 * Creates a commit with the following files:
1782 	 * <pre>
1783 	 * a/x/b/foo
1784 	 * b/u/c/baz
1785 	 * y/x/b/foo
1786 	 * z/v/c/baz
1787 	 * </pre>
1788 	 * which has two pairs of identical trees:
1789 	 * <ul>
1790 	 * <li>one at /a and /y
1791 	 * <li>one at /b/u and /z/v
1792 	 * </ul>
1793 	 * Note that this class defines unique 8 trees (rootTree and subtree1-7)
1794 	 * which means PackStatistics should report having visited 8 trees.
1795 	 */
1796 	class RepeatedSubtreeAtSameLevelPreparator {
1797 		RevBlob foo = remote.blob("foo");
1798 
1799 		/** foo */
1800 		RevTree subtree1 = remote.tree(remote.file("foo", foo));
1801 
1802 		/** b/foo */
1803 		RevTree subtree2 = (new TreeBuilder() {
1804 			@Override
1805 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1806 				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
1807 						remote.getRevWalk().getObjectReader(), subtree1);
1808 			}
1809 		}).build();
1810 
1811 		/** x/b/foo */
1812 		RevTree subtree3 = (new TreeBuilder() {
1813 			@Override
1814 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1815 				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
1816 						remote.getRevWalk().getObjectReader(), subtree2);
1817 			}
1818 		}).build();
1819 
1820 		RevBlob baz = remote.blob("baz");
1821 
1822 		/** baz */
1823 		RevTree subtree4 = remote.tree(remote.file("baz", baz));
1824 
1825 		/** c/baz */
1826 		RevTree subtree5 = (new TreeBuilder() {
1827 			@Override
1828 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1829 				dcBuilder.addTree(new byte[] {'c'}, DirCacheEntry.STAGE_0,
1830 						remote.getRevWalk().getObjectReader(), subtree4);
1831 			}
1832 		}).build();
1833 
1834 		/** u/c/baz */
1835 		RevTree subtree6 = (new TreeBuilder() {
1836 			@Override
1837 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1838 				dcBuilder.addTree(new byte[] {'u'}, DirCacheEntry.STAGE_0,
1839 						remote.getRevWalk().getObjectReader(), subtree5);
1840 			}
1841 		}).build();
1842 
1843 		/** v/c/baz */
1844 		RevTree subtree7 = (new TreeBuilder() {
1845 			@Override
1846 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1847 				dcBuilder.addTree(new byte[] {'v'}, DirCacheEntry.STAGE_0,
1848 						remote.getRevWalk().getObjectReader(), subtree5);
1849 			}
1850 		}).build();
1851 
1852 		RevTree rootTree = (new TreeBuilder() {
1853 			@Override
1854 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1855 				dcBuilder.addTree(new byte[] {'a'}, DirCacheEntry.STAGE_0,
1856 						remote.getRevWalk().getObjectReader(), subtree3);
1857 				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
1858 						remote.getRevWalk().getObjectReader(), subtree6);
1859 				dcBuilder.addTree(new byte[] {'y'}, DirCacheEntry.STAGE_0,
1860 						remote.getRevWalk().getObjectReader(), subtree3);
1861 				dcBuilder.addTree(new byte[] {'z'}, DirCacheEntry.STAGE_0,
1862 						remote.getRevWalk().getObjectReader(), subtree7);
1863 			}
1864 		}).build();
1865 		RevCommit commit = remote.commit(rootTree);
1866 
1867 		RepeatedSubtreeAtSameLevelPreparator() throws Exception {}
1868 	}
1869 
1870 	@Test
1871 	public void testV2FetchFilterTreeDepth_repeatTreeAtSameLevelIncludeFile()
1872 			throws Exception {
1873 		RepeatedSubtreeAtSameLevelPreparator preparator =
1874 				new RepeatedSubtreeAtSameLevelPreparator();
1875 		remote.update("master", preparator.commit);
1876 
1877 		uploadV2WithTreeDepthFilter(5, preparator.commit.toObjectId());
1878 
1879 		assertTrue(client.getObjectDatabase()
1880 				.has(preparator.foo.toObjectId()));
1881 		assertTrue(client.getObjectDatabase()
1882 				.has(preparator.baz.toObjectId()));
1883 		assertEquals(8, stats.getTreesTraversed());
1884 	}
1885 
1886 	@Test
1887 	public void testV2FetchFilterTreeDepth_repeatTreeAtSameLevelExcludeFile()
1888 			throws Exception {
1889 		RepeatedSubtreeAtSameLevelPreparator preparator =
1890 				new RepeatedSubtreeAtSameLevelPreparator();
1891 		remote.update("master", preparator.commit);
1892 
1893 		uploadV2WithTreeDepthFilter(4, preparator.commit.toObjectId());
1894 
1895 		assertFalse(client.getObjectDatabase()
1896 				.has(preparator.foo.toObjectId()));
1897 		assertFalse(client.getObjectDatabase()
1898 				.has(preparator.baz.toObjectId()));
1899 		assertEquals(8, stats.getTreesTraversed());
1900 	}
1901 
1902 	@Test
1903 	public void testWantFilteredObject() throws Exception {
1904 		RepeatedSubtreePreparator preparator = new RepeatedSubtreePreparator();
1905 		remote.update("master", preparator.commit);
1906 
1907 		// Specify wanted blob objects that are deep enough to be filtered. We
1908 		// should still upload them.
1909 		uploadV2WithTreeDepthFilter(
1910 				3,
1911 				preparator.commit.toObjectId(),
1912 				preparator.foo.toObjectId());
1913 		assertTrue(client.getObjectDatabase()
1914 				.has(preparator.foo.toObjectId()));
1915 
1916 		client = newRepo("client");
1917 		// Specify a wanted tree object that is deep enough to be filtered. We
1918 		// should still upload it.
1919 		uploadV2WithTreeDepthFilter(
1920 				2,
1921 				preparator.commit.toObjectId(),
1922 				preparator.subtree3.toObjectId());
1923 		assertTrue(client.getObjectDatabase()
1924 				.has(preparator.foo.toObjectId()));
1925 		assertTrue(client.getObjectDatabase()
1926 				.has(preparator.subtree3.toObjectId()));
1927 	}
1928 
1929 	private void checkV2FetchWhenNotAllowed(String fetchLine, String expectedMessage)
1930 			throws Exception {
1931 		RevCommit commit = remote.commit().message("0").create();
1932 		remote.update("master", commit);
1933 
1934 		UploadPackInternalServerErrorException e = assertThrows(
1935 				UploadPackInternalServerErrorException.class,
1936 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
1937 						"want " + commit.toObjectId().getName() + "\n",
1938 						fetchLine, "done\n", PacketLineIn.end()));
1939 		assertThat(e.getCause().getMessage(),
1940 				containsString(expectedMessage));
1941 	}
1942 
1943 	@Test
1944 	public void testV2FetchFilterWhenNotAllowed() throws Exception {
1945 		checkV2FetchWhenNotAllowed(
1946 			"filter blob:limit=5\n",
1947 			"unexpected filter blob:limit=5");
1948 	}
1949 
1950 	@Test
1951 	public void testV2FetchWantRefIfNotAllowed() throws Exception {
1952 		checkV2FetchWhenNotAllowed(
1953 			"want-ref refs/heads/one\n",
1954 			"unexpected want-ref refs/heads/one");
1955 	}
1956 
1957 	@Test
1958 	public void testV2FetchSidebandAllIfNotAllowed() throws Exception {
1959 		checkV2FetchWhenNotAllowed(
1960 			"sideband-all\n",
1961 			"unexpected sideband-all");
1962 	}
1963 
1964 	@Test
1965 	public void testV2FetchWantRef() throws Exception {
1966 		RevCommit one = remote.commit().message("1").create();
1967 		RevCommit two = remote.commit().message("2").create();
1968 		RevCommit three = remote.commit().message("3").create();
1969 		remote.update("one", one);
1970 		remote.update("two", two);
1971 		remote.update("three", three);
1972 
1973 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
1974 
1975 		ByteArrayInputStream recvStream = uploadPackV2(
1976 			"command=fetch\n",
1977 			PacketLineIn.delimiter(),
1978 			"want-ref refs/heads/one\n",
1979 			"want-ref refs/heads/two\n",
1980 			"done\n",
1981 				PacketLineIn.end());
1982 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1983 		assertThat(pckIn.readString(), is("wanted-refs"));
1984 		assertThat(
1985 				Arrays.asList(pckIn.readString(), pckIn.readString()),
1986 				hasItems(
1987 					one.toObjectId().getName() + " refs/heads/one",
1988 					two.toObjectId().getName() + " refs/heads/two"));
1989 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1990 		assertThat(pckIn.readString(), is("packfile"));
1991 		parsePack(recvStream);
1992 
1993 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
1994 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
1995 		assertFalse(client.getObjectDatabase().has(three.toObjectId()));
1996 	}
1997 
1998 	@Test
1999 	public void testV2FetchBadWantRef() throws Exception {
2000 		RevCommit one = remote.commit().message("1").create();
2001 		remote.update("one", one);
2002 
2003 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant",
2004 				true);
2005 
2006 		UploadPackInternalServerErrorException e = assertThrows(
2007 				UploadPackInternalServerErrorException.class,
2008 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
2009 						"want-ref refs/heads/one\n",
2010 						"want-ref refs/heads/nonExistentRef\n", "done\n",
2011 						PacketLineIn.end()));
2012 		assertThat(e.getCause().getMessage(),
2013 				containsString("Invalid ref name: refs/heads/nonExistentRef"));
2014 	}
2015 
2016 	@Test
2017 	public void testV2FetchMixedWantRef() throws Exception {
2018 		RevCommit one = remote.commit().message("1").create();
2019 		RevCommit two = remote.commit().message("2").create();
2020 		RevCommit three = remote.commit().message("3").create();
2021 		remote.update("one", one);
2022 		remote.update("two", two);
2023 		remote.update("three", three);
2024 
2025 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2026 
2027 		ByteArrayInputStream recvStream = uploadPackV2(
2028 			"command=fetch\n",
2029 			PacketLineIn.delimiter(),
2030 			"want-ref refs/heads/one\n",
2031 			"want " + two.toObjectId().getName() + "\n",
2032 			"done\n",
2033 				PacketLineIn.end());
2034 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2035 		assertThat(pckIn.readString(), is("wanted-refs"));
2036 		assertThat(
2037 				pckIn.readString(),
2038 				is(one.toObjectId().getName() + " refs/heads/one"));
2039 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2040 		assertThat(pckIn.readString(), is("packfile"));
2041 		parsePack(recvStream);
2042 
2043 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2044 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
2045 		assertFalse(client.getObjectDatabase().has(three.toObjectId()));
2046 	}
2047 
2048 	@Test
2049 	public void testV2FetchWantRefWeAlreadyHave() throws Exception {
2050 		RevCommit one = remote.commit().message("1").create();
2051 		remote.update("one", one);
2052 
2053 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2054 
2055 		ByteArrayInputStream recvStream = uploadPackV2(
2056 			"command=fetch\n",
2057 			PacketLineIn.delimiter(),
2058 			"want-ref refs/heads/one\n",
2059 			"have " + one.toObjectId().getName(),
2060 			"done\n",
2061 				PacketLineIn.end());
2062 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2063 
2064 		// The client still needs to know the hash of the object that
2065 		// refs/heads/one points to, even though it already has the
2066 		// object ...
2067 		assertThat(pckIn.readString(), is("wanted-refs"));
2068 		assertThat(
2069 				pckIn.readString(),
2070 				is(one.toObjectId().getName() + " refs/heads/one"));
2071 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2072 
2073 		// ... but the client does not need the object itself.
2074 		assertThat(pckIn.readString(), is("packfile"));
2075 		parsePack(recvStream);
2076 		assertFalse(client.getObjectDatabase().has(one.toObjectId()));
2077 	}
2078 
2079 	@Test
2080 	public void testV2FetchWantRefAndDeepen() throws Exception {
2081 		RevCommit parent = remote.commit().message("parent").create();
2082 		RevCommit child = remote.commit().message("x").parent(parent).create();
2083 		remote.update("branch1", child);
2084 
2085 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2086 
2087 		ByteArrayInputStream recvStream = uploadPackV2(
2088 			"command=fetch\n",
2089 			PacketLineIn.delimiter(),
2090 			"want-ref refs/heads/branch1\n",
2091 			"deepen 1\n",
2092 			"done\n",
2093 				PacketLineIn.end());
2094 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2095 
2096 		// shallow-info appears first, then wanted-refs.
2097 		assertThat(pckIn.readString(), is("shallow-info"));
2098 		assertThat(pckIn.readString(), is("shallow " + child.toObjectId().getName()));
2099 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2100 		assertThat(pckIn.readString(), is("wanted-refs"));
2101 		assertThat(pckIn.readString(), is(child.toObjectId().getName() + " refs/heads/branch1"));
2102 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2103 		assertThat(pckIn.readString(), is("packfile"));
2104 		parsePack(recvStream);
2105 		assertTrue(client.getObjectDatabase().has(child.toObjectId()));
2106 		assertFalse(client.getObjectDatabase().has(parent.toObjectId()));
2107 	}
2108 
2109 	@Test
2110 	public void testV2FetchMissingShallow() throws Exception {
2111 		RevCommit one = remote.commit().message("1").create();
2112 		RevCommit two = remote.commit().message("2").parent(one).create();
2113 		RevCommit three = remote.commit().message("3").parent(two).create();
2114 		remote.update("three", three);
2115 
2116 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant",
2117 				true);
2118 
2119 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
2120 				PacketLineIn.delimiter(),
2121 				"want-ref refs/heads/three\n",
2122 				"deepen 3",
2123 				"shallow 0123012301230123012301230123012301230123",
2124 				"shallow " + two.getName() + '\n',
2125 				"done\n",
2126 				PacketLineIn.end());
2127 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2128 
2129 		assertThat(pckIn.readString(), is("shallow-info"));
2130 		assertThat(pckIn.readString(),
2131 				is("shallow " + one.toObjectId().getName()));
2132 		assertThat(pckIn.readString(),
2133 				is("unshallow " + two.toObjectId().getName()));
2134 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2135 		assertThat(pckIn.readString(), is("wanted-refs"));
2136 		assertThat(pckIn.readString(),
2137 				is(three.toObjectId().getName() + " refs/heads/three"));
2138 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2139 		assertThat(pckIn.readString(), is("packfile"));
2140 		parsePack(recvStream);
2141 
2142 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2143 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
2144 		assertTrue(client.getObjectDatabase().has(three.toObjectId()));
2145 	}
2146 
2147 	@Test
2148 	public void testV2FetchSidebandAllNoPackfile() throws Exception {
2149 		RevCommit fooParent = remote.commit().message("x").create();
2150 		RevCommit fooChild = remote.commit().message("x").parent(fooParent).create();
2151 		RevCommit barParent = remote.commit().message("y").create();
2152 		RevCommit barChild = remote.commit().message("y").parent(barParent).create();
2153 		remote.update("branch1", fooChild);
2154 		remote.update("branch2", barChild);
2155 
2156 		server.getConfig().setBoolean("uploadpack", null, "allowsidebandall", true);
2157 
2158 		ByteArrayInputStream recvStream = uploadPackV2(
2159 			"command=fetch\n",
2160 			PacketLineIn.delimiter(),
2161 			"sideband-all\n",
2162 			"want " + fooChild.toObjectId().getName() + "\n",
2163 			"want " + barChild.toObjectId().getName() + "\n",
2164 			"have " + fooParent.toObjectId().getName() + "\n",
2165 			PacketLineIn.end());
2166 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2167 
2168 		assertThat(pckIn.readString(), is("\001acknowledgments"));
2169 		assertThat(pckIn.readString(), is("\001ACK " + fooParent.getName()));
2170 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
2171 	}
2172 
2173 	@Test
2174 	public void testV2FetchSidebandAllPackfile() throws Exception {
2175 		RevCommit commit = remote.commit().message("x").create();
2176 		remote.update("master", commit);
2177 
2178 		server.getConfig().setBoolean("uploadpack", null, "allowsidebandall", true);
2179 
2180 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
2181 				PacketLineIn.delimiter(),
2182 				"want " + commit.getName() + "\n",
2183 				"sideband-all\n",
2184 				"done\n",
2185 				PacketLineIn.end());
2186 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2187 
2188 		String s;
2189 		// When sideband-all is used, object counting happens before
2190 		// "packfile" is written, and object counting outputs progress
2191 		// in sideband 2. Skip all these lines.
2192 		for (s = pckIn.readString(); s.startsWith("\002"); s = pckIn.readString()) {
2193 			// do nothing
2194 		}
2195 		assertThat(s, is("\001packfile"));
2196 		parsePack(recvStream);
2197 	}
2198 
2199 	@Test
2200 	public void testV2FetchPackfileUris() throws Exception {
2201 		// Inside the pack
2202 		RevCommit commit = remote.commit().message("x").create();
2203 		remote.update("master", commit);
2204 		generateBitmaps(server);
2205 
2206 		// Outside the pack
2207 		RevCommit commit2 = remote.commit().message("x").parent(commit).create();
2208 		remote.update("master", commit2);
2209 
2210 		server.getConfig().setBoolean("uploadpack", null, "allowsidebandall", true);
2211 
2212 		ByteArrayInputStream recvStream = uploadPackV2(
2213 			(UploadPack up) -> {
2214 				up.setCachedPackUriProvider(new CachedPackUriProvider() {
2215 					@Override
2216 					public PackInfo getInfo(CachedPack pack,
2217 							Collection<String> protocolsSupported)
2218 							throws IOException {
2219 						assertThat(protocolsSupported, hasItems("https"));
2220 						if (!protocolsSupported.contains("https"))
2221 							return null;
2222 						return new PackInfo("myhash", "myuri", 100);
2223 					}
2224 
2225 				});
2226 			},
2227 			"command=fetch\n",
2228 			PacketLineIn.delimiter(),
2229 			"want " + commit2.getName() + "\n",
2230 			"sideband-all\n",
2231 			"packfile-uris https\n",
2232 			"done\n",
2233 			PacketLineIn.end());
2234 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2235 
2236 		String s;
2237 		// skip all \002 strings
2238 		for (s = pckIn.readString(); s.startsWith("\002"); s = pckIn.readString()) {
2239 			// do nothing
2240 		}
2241 		assertThat(s, is("\001packfile-uris"));
2242 		assertThat(pckIn.readString(), is("\001myhash myuri"));
2243 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2244 		assertThat(pckIn.readString(), is("\001packfile"));
2245 		parsePack(recvStream);
2246 
2247 		assertFalse(client.getObjectDatabase().has(commit.toObjectId()));
2248 		assertTrue(client.getObjectDatabase().has(commit2.toObjectId()));
2249 	}
2250 
2251 	@Test
2252 	public void testGetPeerAgentProtocolV0() throws Exception {
2253 		RevCommit one = remote.commit().message("1").create();
2254 		remote.update("one", one);
2255 
2256 		UploadPack up = new UploadPack(server);
2257 		ByteArrayInputStream send = linesAsInputStream(
2258 				"want " + one.getName() + " agent=JGit-test/1.2.3\n",
2259 				PacketLineIn.end(),
2260 				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n");
2261 
2262 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
2263 		up.upload(send, recv, null);
2264 
2265 		assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.3");
2266 	}
2267 
2268 	@Test
2269 	public void testGetPeerAgentProtocolV2() throws Exception {
2270 		server.getConfig().setString(ConfigConstants.CONFIG_PROTOCOL_SECTION,
2271 				null, ConfigConstants.CONFIG_KEY_VERSION,
2272 				TransferConfig.ProtocolVersion.V2.version());
2273 
2274 		RevCommit one = remote.commit().message("1").create();
2275 		remote.update("one", one);
2276 
2277 		UploadPack up = new UploadPack(server);
2278 		up.setExtraParameters(Sets.of("version=2"));
2279 
2280 		ByteArrayInputStream send = linesAsInputStream(
2281 				"command=fetch\n", "agent=JGit-test/1.2.4\n",
2282 				PacketLineIn.delimiter(), "want " + one.getName() + "\n",
2283 				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n",
2284 				PacketLineIn.end());
2285 
2286 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
2287 		up.upload(send, recv, null);
2288 
2289 		assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.4");
2290 	}
2291 
2292 	private static class RejectAllRefFilter implements RefFilter {
2293 		@Override
2294 		public Map<String, Ref> filter(Map<String, Ref> refs) {
2295 			return new HashMap<>();
2296 		}
2297 	}
2298 
2299 	@Test
2300 	public void testSingleBranchCloneTagChain() throws Exception {
2301 		RevBlob blob0 = remote.blob("Initial content of first file");
2302 		RevBlob blob1 = remote.blob("Second file content");
2303 		RevCommit commit0 = remote
2304 				.commit(remote.tree(remote.file("prvni.txt", blob0)));
2305 		RevCommit commit1 = remote
2306 				.commit(remote.tree(remote.file("druhy.txt", blob1)), commit0);
2307 		remote.update("master", commit1);
2308 
2309 		RevTag heavyTag1 = remote.tag("commitTagRing", commit0);
2310 		remote.getRevWalk().parseHeaders(heavyTag1);
2311 		RevTag heavyTag2 = remote.tag("middleTagRing", heavyTag1);
2312 		remote.lightweightTag("refTagRing", heavyTag2);
2313 
2314 		UploadPack uploadPack = new UploadPack(remote.getRepository());
2315 
2316 		ByteArrayOutputStream cli = new ByteArrayOutputStream();
2317 		PacketLineOut clientWant = new PacketLineOut(cli);
2318 		clientWant.writeString("want " + commit1.name()
2319 				+ " multi_ack_detailed include-tag thin-pack ofs-delta agent=tempo/pflaska");
2320 		clientWant.end();
2321 		clientWant.writeString("done\n");
2322 
2323 		try (ByteArrayOutputStream serverResponse = new ByteArrayOutputStream()) {
2324 
2325 			uploadPack.setPreUploadHook(new PreUploadHook() {
2326 				@Override
2327 				public void onBeginNegotiateRound(UploadPack up,
2328 						Collection<? extends ObjectId> wants, int cntOffered)
2329 						throws ServiceMayNotContinueException {
2330 					// Do nothing.
2331 				}
2332 
2333 				@Override
2334 				public void onEndNegotiateRound(UploadPack up,
2335 						Collection<? extends ObjectId> wants, int cntCommon,
2336 						int cntNotFound, boolean ready)
2337 						throws ServiceMayNotContinueException {
2338 					// Do nothing.
2339 				}
2340 
2341 				@Override
2342 				public void onSendPack(UploadPack up,
2343 						Collection<? extends ObjectId> wants,
2344 						Collection<? extends ObjectId> haves)
2345 						throws ServiceMayNotContinueException {
2346 					// collect pack data
2347 					serverResponse.reset();
2348 				}
2349 			});
2350 			uploadPack.upload(new ByteArrayInputStream(cli.toByteArray()),
2351 					serverResponse, System.err);
2352 			InputStream packReceived = new ByteArrayInputStream(
2353 					serverResponse.toByteArray());
2354 			PackLock lock = null;
2355 			try (ObjectInserter ins = client.newObjectInserter()) {
2356 				PackParser parser = ins.newPackParser(packReceived);
2357 				parser.setAllowThin(true);
2358 				parser.setLockMessage("receive-tag-chain");
2359 				ProgressMonitor mlc = NullProgressMonitor.INSTANCE;
2360 				lock = parser.parse(mlc, mlc);
2361 				ins.flush();
2362 			} finally {
2363 				if (lock != null) {
2364 					lock.unlock();
2365 				}
2366 			}
2367 			InMemoryRepository.MemObjDatabase objDb = client
2368 					.getObjectDatabase();
2369 			assertTrue(objDb.has(blob0.toObjectId()));
2370 			assertTrue(objDb.has(blob1.toObjectId()));
2371 			assertTrue(objDb.has(commit0.toObjectId()));
2372 			assertTrue(objDb.has(commit1.toObjectId()));
2373 			assertTrue(objDb.has(heavyTag1.toObjectId()));
2374 			assertTrue(objDb.has(heavyTag2.toObjectId()));
2375 		}
2376 	}
2377 
2378 	@Test
2379 	public void testSafeToClearRefsInFetchV0() throws Exception {
2380 		server =
2381 			new RefCallsCountingRepository(
2382 				new DfsRepositoryDescription("server"));
2383 		remote = new TestRepository<>(server);
2384 		RevCommit one = remote.commit().message("1").create();
2385 		remote.update("one", one);
2386 		testProtocol = new TestProtocol<>((Object req, Repository db) -> {
2387 			UploadPack up = new UploadPack(db);
2388 			return up;
2389 		}, null);
2390 		uri = testProtocol.register(ctx, server);
2391 		try (Transport tn = testProtocol.open(uri, client, "server")) {
2392 			tn.fetch(NullProgressMonitor.INSTANCE,
2393 				Collections.singletonList(new RefSpec(one.name())));
2394 		}
2395 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2396 		assertEquals(1, ((RefCallsCountingRepository)server).numRefCalls());
2397 	}
2398 
2399 	@Test
2400 	public void testSafeToClearRefsInFetchV2() throws Exception {
2401 		server =
2402 			new RefCallsCountingRepository(
2403 				new DfsRepositoryDescription("server"));
2404 		remote = new TestRepository<>(server);
2405 		RevCommit one = remote.commit().message("1").create();
2406 		RevCommit two = remote.commit().message("2").create();
2407 		remote.update("one", one);
2408 		remote.update("two", two);
2409 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2410 		ByteArrayInputStream recvStream = uploadPackV2(
2411 			"command=fetch\n",
2412 			PacketLineIn.delimiter(),
2413 			"want-ref refs/heads/one\n",
2414 			"want-ref refs/heads/two\n",
2415 			"done\n",
2416 			PacketLineIn.end());
2417 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2418 		assertThat(pckIn.readString(), is("wanted-refs"));
2419 		assertThat(
2420 			Arrays.asList(pckIn.readString(), pckIn.readString()),
2421 			hasItems(
2422 				one.toObjectId().getName() + " refs/heads/one",
2423 				two.toObjectId().getName() + " refs/heads/two"));
2424 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2425 		assertThat(pckIn.readString(), is("packfile"));
2426 		parsePack(recvStream);
2427 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2428 		assertEquals(1, ((RefCallsCountingRepository)server).numRefCalls());
2429 	}
2430 
2431 	@Test
2432 	public void testNotAdvertisedWantsV1Fetch() throws Exception {
2433 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2434 
2435 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2436 		RevCommit parent = remote
2437 				.commit(remote.tree(remote.file("foo", parentBlob)));
2438 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2439 		RevCommit child = remote
2440 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2441 		remote.update("branch1", child);
2442 
2443 		uploadPackV1("want " + child.toObjectId().getName() + "\n",
2444 				PacketLineIn.end(),
2445 				"have " + parent.toObjectId().getName() + "\n",
2446 				"done\n", PacketLineIn.end());
2447 
2448 		assertEquals(0, stats.getNotAdvertisedWants());
2449 	}
2450 
2451 	@Test
2452 	public void testNotAdvertisedWantsV1FetchRequestPolicyReachableCommit() throws Exception {
2453 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2454 
2455 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2456 		RevCommit parent = remote
2457 				.commit(remote.tree(remote.file("foo", parentBlob)));
2458 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2459 		RevCommit child = remote
2460 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2461 
2462 		remote.update("branch1", child);
2463 
2464 		uploadPackV1((UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
2465 				"want " + parent.toObjectId().getName() + "\n",
2466 				PacketLineIn.end(),
2467 				"done\n", PacketLineIn.end());
2468 
2469 		assertEquals(1, stats.getNotAdvertisedWants());
2470 	}
2471 
2472 	@Test
2473 	public void testNotAdvertisedWantsV2FetchThinPack() throws Exception {
2474 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2475 
2476 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2477 		RevCommit parent = remote
2478 				.commit(remote.tree(remote.file("foo", parentBlob)));
2479 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2480 		RevCommit child = remote
2481 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2482 		remote.update("branch1", child);
2483 
2484 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
2485 				PacketLineIn.delimiter(),
2486 				"want " + child.toObjectId().getName() + "\n",
2487 				"have " + parent.toObjectId().getName() + "\n", "thin-pack\n",
2488 				"done\n", PacketLineIn.end());
2489 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2490 
2491 		assertThat(pckIn.readString(), is("packfile"));
2492 
2493 		assertEquals(0, stats.getNotAdvertisedWants());
2494 	}
2495 
2496 	@Test
2497 	public void testNotAdvertisedWantsV2FetchRequestPolicyReachableCommit() throws Exception {
2498 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2499 
2500 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2501 		RevCommit parent = remote
2502 				.commit(remote.tree(remote.file("foo", parentBlob)));
2503 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2504 		RevCommit child = remote
2505 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2506 
2507 		remote.update("branch1", child);
2508 
2509 		uploadPackV2((UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
2510 				"command=fetch\n",
2511 				PacketLineIn.delimiter(),
2512 				"want " + parent.toObjectId().getName() + "\n", "thin-pack\n",
2513 				"done\n", PacketLineIn.end());
2514 
2515 		assertEquals(1, stats.getNotAdvertisedWants());
2516 	}
2517 
2518 	private class RefCallsCountingRepository extends InMemoryRepository {
2519 		private final InMemoryRepository.MemRefDatabase refdb;
2520 		private int numRefCalls;
2521 
2522 		public RefCallsCountingRepository(DfsRepositoryDescription repoDesc) {
2523 			super(repoDesc);
2524 			refdb = new InMemoryRepository.MemRefDatabase() {
2525 				@Override
2526 				public List<Ref> getRefs() throws IOException {
2527 					numRefCalls++;
2528 					return super.getRefs();
2529 				}
2530 			};
2531 		}
2532 
2533 		public int numRefCalls() {
2534 			return numRefCalls;
2535 		}
2536 
2537 		@Override
2538 		public RefDatabase getRefDatabase() {
2539 			return refdb;
2540 		}
2541 	}
2542 }