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