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