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 testV2FetchThinPack() throws Exception {
1111 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
1112 
1113 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1114 		RevCommit parent = remote
1115 				.commit(remote.tree(remote.file("foo", parentBlob)));
1116 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1117 		RevCommit child = remote
1118 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
1119 		remote.update("branch1", child);
1120 
1121 		// Pretend that we have parent to get a thin pack based on it.
1122 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
1123 				PacketLineIn.delimiter(),
1124 				"want " + child.toObjectId().getName() + "\n",
1125 				"have " + parent.toObjectId().getName() + "\n", "thin-pack\n",
1126 				"done\n", PacketLineIn.end());
1127 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1128 
1129 		assertThat(pckIn.readString(), is("packfile"));
1130 
1131 		// Verify that we received a thin pack by trying to apply it
1132 		// against the client repo, which does not have parent.
1133 		IOException e = assertThrows(IOException.class,
1134 				() -> parsePack(recvStream));
1135 		assertThat(e.getMessage(),
1136 				containsString("pack has unresolved deltas"));
1137 	}
1138 
1139 	@Test
1140 	public void testV2FetchNoProgress() throws Exception {
1141 		RevCommit commit = remote.commit().message("x").create();
1142 		remote.update("branch1", commit);
1143 
1144 		// Without no-progress, progress is reported.
1145 		StringWriter sw = new StringWriter();
1146 		ByteArrayInputStream recvStream = uploadPackV2(
1147 			"command=fetch\n",
1148 			PacketLineIn.delimiter(),
1149 			"want " + commit.toObjectId().getName() + "\n",
1150 			"done\n",
1151 				PacketLineIn.end());
1152 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1153 		assertThat(pckIn.readString(), is("packfile"));
1154 		parsePack(recvStream, new TextProgressMonitor(sw));
1155 		assertFalse(sw.toString().isEmpty());
1156 
1157 		// With no-progress, progress is not reported.
1158 		sw = new StringWriter();
1159 		recvStream = uploadPackV2(
1160 			"command=fetch\n",
1161 			PacketLineIn.delimiter(),
1162 			"want " + commit.toObjectId().getName() + "\n",
1163 			"no-progress\n",
1164 			"done\n",
1165 				PacketLineIn.end());
1166 		pckIn = new PacketLineIn(recvStream);
1167 		assertThat(pckIn.readString(), is("packfile"));
1168 		parsePack(recvStream, new TextProgressMonitor(sw));
1169 		assertTrue(sw.toString().isEmpty());
1170 	}
1171 
1172 	@Test
1173 	public void testV2FetchIncludeTag() throws Exception {
1174 		RevCommit commit = remote.commit().message("x").create();
1175 		RevTag tag = remote.tag("tag", commit);
1176 		remote.update("branch1", commit);
1177 		remote.update("refs/tags/tag", tag);
1178 
1179 		// Without include-tag.
1180 		ByteArrayInputStream recvStream = uploadPackV2(
1181 			"command=fetch\n",
1182 			PacketLineIn.delimiter(),
1183 			"want " + commit.toObjectId().getName() + "\n",
1184 			"done\n",
1185 				PacketLineIn.end());
1186 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1187 		assertThat(pckIn.readString(), is("packfile"));
1188 		parsePack(recvStream);
1189 		assertFalse(client.getObjectDatabase().has(tag.toObjectId()));
1190 
1191 		// With tag.
1192 		recvStream = uploadPackV2(
1193 			"command=fetch\n",
1194 			PacketLineIn.delimiter(),
1195 			"want " + commit.toObjectId().getName() + "\n",
1196 			"include-tag\n",
1197 			"done\n",
1198 				PacketLineIn.end());
1199 		pckIn = new PacketLineIn(recvStream);
1200 		assertThat(pckIn.readString(), is("packfile"));
1201 		parsePack(recvStream);
1202 		assertTrue(client.getObjectDatabase().has(tag.toObjectId()));
1203 	}
1204 
1205 	@Test
1206 	public void testUploadNewBytes() throws Exception {
1207 		String commonInBlob = "abcdefghijklmnopqrstuvwx";
1208 
1209 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1210 		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
1211 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1212 		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
1213 		remote.update("branch1", child);
1214 
1215 		ByteArrayInputStream recvStream = uploadPackV2(
1216 			"command=fetch\n",
1217 			PacketLineIn.delimiter(),
1218 			"want " + child.toObjectId().getName() + "\n",
1219 			"ofs-delta\n",
1220 			"done\n",
1221 				PacketLineIn.end());
1222 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1223 		assertThat(pckIn.readString(), is("packfile"));
1224 		ReceivedPackStatistics receivedStats = parsePack(recvStream);
1225 		assertTrue(receivedStats.getNumBytesDuplicated() == 0);
1226 		assertTrue(receivedStats.getNumObjectsDuplicated() == 0);
1227 	}
1228 
1229 	@Test
1230 	public void testUploadRedundantBytes() throws Exception {
1231 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
1232 
1233 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1234 		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
1235 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1236 		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
1237 		remote.update("branch1", child);
1238 
1239 		try (TestRepository<InMemoryRepository> local = new TestRepository<>(
1240 				client)) {
1241 			RevBlob localParentBlob = local.blob(commonInBlob + "a");
1242 			RevCommit localParent = local
1243 					.commit(local.tree(local.file("foo", localParentBlob)));
1244 			RevBlob localChildBlob = local.blob(commonInBlob + "b");
1245 			RevCommit localChild = local.commit(
1246 					local.tree(local.file("foo", localChildBlob)), localParent);
1247 			local.update("branch1", localChild);
1248 		}
1249 
1250 		ByteArrayInputStream recvStream = uploadPackV2(
1251 				"command=fetch\n",
1252 				PacketLineIn.delimiter(),
1253 				"want " + child.toObjectId().getName() + "\n",
1254 				"ofs-delta\n",
1255 				"done\n",
1256 					PacketLineIn.end());
1257 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1258 		assertThat(pckIn.readString(), is("packfile"));
1259 		ReceivedPackStatistics receivedStats = parsePack(recvStream);
1260 
1261 		long sizeOfHeader = 12;
1262 		long sizeOfTrailer = 20;
1263 		long expectedSize = receivedStats.getNumBytesRead() - sizeOfHeader
1264 				- sizeOfTrailer;
1265 		assertTrue(receivedStats.getNumBytesDuplicated() == expectedSize);
1266 		assertTrue(receivedStats.getNumObjectsDuplicated() == 6);
1267 	}
1268 
1269 	@Test
1270 	public void testV2FetchOfsDelta() throws Exception {
1271 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
1272 
1273 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
1274 		RevCommit parent = remote.commit(remote.tree(remote.file("foo", parentBlob)));
1275 		RevBlob childBlob = remote.blob(commonInBlob + "b");
1276 		RevCommit child = remote.commit(remote.tree(remote.file("foo", childBlob)), parent);
1277 		remote.update("branch1", child);
1278 
1279 		// Without ofs-delta.
1280 		ByteArrayInputStream recvStream = uploadPackV2(
1281 			"command=fetch\n",
1282 			PacketLineIn.delimiter(),
1283 			"want " + child.toObjectId().getName() + "\n",
1284 			"done\n",
1285 				PacketLineIn.end());
1286 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1287 		assertThat(pckIn.readString(), is("packfile"));
1288 		ReceivedPackStatistics receivedStats = parsePack(recvStream);
1289 		assertTrue(receivedStats.getNumOfsDelta() == 0);
1290 
1291 		// With ofs-delta.
1292 		recvStream = uploadPackV2(
1293 			"command=fetch\n",
1294 			PacketLineIn.delimiter(),
1295 			"want " + child.toObjectId().getName() + "\n",
1296 			"ofs-delta\n",
1297 			"done\n",
1298 				PacketLineIn.end());
1299 		pckIn = new PacketLineIn(recvStream);
1300 		assertThat(pckIn.readString(), is("packfile"));
1301 		receivedStats = parsePack(recvStream);
1302 		assertTrue(receivedStats.getNumOfsDelta() != 0);
1303 	}
1304 
1305 	@Test
1306 	public void testV2FetchShallow() throws Exception {
1307 		RevCommit commonParent = remote.commit().message("parent").create();
1308 		RevCommit fooChild = remote.commit().message("x").parent(commonParent).create();
1309 		RevCommit barChild = remote.commit().message("y").parent(commonParent).create();
1310 		remote.update("branch1", barChild);
1311 
1312 		// Without shallow, the server thinks that we have
1313 		// commonParent, so it doesn't send it.
1314 		ByteArrayInputStream recvStream = uploadPackV2(
1315 			"command=fetch\n",
1316 			PacketLineIn.delimiter(),
1317 			"want " + barChild.toObjectId().getName() + "\n",
1318 			"have " + fooChild.toObjectId().getName() + "\n",
1319 			"done\n",
1320 				PacketLineIn.end());
1321 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1322 		assertThat(pckIn.readString(), is("packfile"));
1323 		parsePack(recvStream);
1324 		assertTrue(client.getObjectDatabase().has(barChild.toObjectId()));
1325 		assertFalse(client.getObjectDatabase().has(commonParent.toObjectId()));
1326 
1327 		// With shallow, the server knows that we don't have
1328 		// commonParent, so it sends it.
1329 		recvStream = uploadPackV2(
1330 			"command=fetch\n",
1331 			PacketLineIn.delimiter(),
1332 			"want " + barChild.toObjectId().getName() + "\n",
1333 			"have " + fooChild.toObjectId().getName() + "\n",
1334 			"shallow " + fooChild.toObjectId().getName() + "\n",
1335 			"done\n",
1336 				PacketLineIn.end());
1337 		pckIn = new PacketLineIn(recvStream);
1338 		assertThat(pckIn.readString(), is("packfile"));
1339 		parsePack(recvStream);
1340 		assertTrue(client.getObjectDatabase().has(commonParent.toObjectId()));
1341 	}
1342 
1343 	@Test
1344 	public void testV2FetchDeepenAndDone() throws Exception {
1345 		RevCommit parent = remote.commit().message("parent").create();
1346 		RevCommit child = remote.commit().message("x").parent(parent).create();
1347 		remote.update("branch1", child);
1348 
1349 		// "deepen 1" sends only the child.
1350 		ByteArrayInputStream recvStream = uploadPackV2(
1351 			"command=fetch\n",
1352 			PacketLineIn.delimiter(),
1353 			"want " + child.toObjectId().getName() + "\n",
1354 			"deepen 1\n",
1355 			"done\n",
1356 				PacketLineIn.end());
1357 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1358 		assertThat(pckIn.readString(), is("shallow-info"));
1359 		assertThat(pckIn.readString(), is("shallow " + child.toObjectId().getName()));
1360 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1361 		assertThat(pckIn.readString(), is("packfile"));
1362 		parsePack(recvStream);
1363 		assertTrue(client.getObjectDatabase().has(child.toObjectId()));
1364 		assertFalse(client.getObjectDatabase().has(parent.toObjectId()));
1365 
1366 		// Without that, the parent is sent too.
1367 		recvStream = uploadPackV2(
1368 			"command=fetch\n",
1369 			PacketLineIn.delimiter(),
1370 			"want " + child.toObjectId().getName() + "\n",
1371 			"done\n",
1372 				PacketLineIn.end());
1373 		pckIn = new PacketLineIn(recvStream);
1374 		assertThat(pckIn.readString(), is("packfile"));
1375 		parsePack(recvStream);
1376 		assertTrue(client.getObjectDatabase().has(parent.toObjectId()));
1377 	}
1378 
1379 	@Test
1380 	public void testV2FetchDeepenWithoutDone() throws Exception {
1381 		RevCommit parent = remote.commit().message("parent").create();
1382 		RevCommit child = remote.commit().message("x").parent(parent).create();
1383 		remote.update("branch1", child);
1384 
1385 		ByteArrayInputStream recvStream = uploadPackV2(
1386 			"command=fetch\n",
1387 			PacketLineIn.delimiter(),
1388 			"want " + child.toObjectId().getName() + "\n",
1389 			"deepen 1\n",
1390 				PacketLineIn.end());
1391 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1392 
1393 		// Verify that only the correct section is sent. "shallow-info"
1394 		// is not sent because, according to the specification, it is
1395 		// sent only if a packfile is sent.
1396 		assertThat(pckIn.readString(), is("acknowledgments"));
1397 		assertThat(pckIn.readString(), is("NAK"));
1398 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
1399 	}
1400 
1401 	@Test
1402 	public void testV2FetchShallowSince() throws Exception {
1403 		PersonIdent person = new PersonIdent(remote.getRepository());
1404 
1405 		RevCommit beyondBoundary = remote.commit()
1406 			.committer(new PersonIdent(person, 1510000000, 0)).create();
1407 		RevCommit boundary = remote.commit().parent(beyondBoundary)
1408 			.committer(new PersonIdent(person, 1520000000, 0)).create();
1409 		RevCommit tooOld = remote.commit()
1410 			.committer(new PersonIdent(person, 1500000000, 0)).create();
1411 		RevCommit merge = remote.commit().parent(boundary).parent(tooOld)
1412 			.committer(new PersonIdent(person, 1530000000, 0)).create();
1413 
1414 		remote.update("branch1", merge);
1415 
1416 		// Report that we only have "boundary" as a shallow boundary.
1417 		ByteArrayInputStream recvStream = uploadPackV2(
1418 			"command=fetch\n",
1419 			PacketLineIn.delimiter(),
1420 			"shallow " + boundary.toObjectId().getName() + "\n",
1421 			"deepen-since 1510000\n",
1422 			"want " + merge.toObjectId().getName() + "\n",
1423 			"have " + boundary.toObjectId().getName() + "\n",
1424 			"done\n",
1425 				PacketLineIn.end());
1426 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1427 		assertThat(pckIn.readString(), is("shallow-info"));
1428 
1429 		// "merge" is shallow because one of its parents is committed
1430 		// earlier than the given deepen-since time.
1431 		assertThat(pckIn.readString(), is("shallow " + merge.toObjectId().getName()));
1432 
1433 		// "boundary" is unshallow because its parent committed at or
1434 		// later than the given deepen-since time.
1435 		assertThat(pckIn.readString(), is("unshallow " + boundary.toObjectId().getName()));
1436 
1437 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1438 		assertThat(pckIn.readString(), is("packfile"));
1439 		parsePack(recvStream);
1440 
1441 		// The server does not send this because it is committed
1442 		// earlier than the given deepen-since time.
1443 		assertFalse(client.getObjectDatabase().has(tooOld.toObjectId()));
1444 
1445 		// The server does not send this because the client claims to
1446 		// have it.
1447 		assertFalse(client.getObjectDatabase().has(boundary.toObjectId()));
1448 
1449 		// The server sends both these commits.
1450 		assertTrue(client.getObjectDatabase().has(beyondBoundary.toObjectId()));
1451 		assertTrue(client.getObjectDatabase().has(merge.toObjectId()));
1452 	}
1453 
1454 	@Test
1455 	public void testV2FetchShallowSince_excludedParentWithMultipleChildren() throws Exception {
1456 		PersonIdent person = new PersonIdent(remote.getRepository());
1457 
1458 		RevCommit base = remote.commit()
1459 			.committer(new PersonIdent(person, 1500000000, 0)).create();
1460 		RevCommit child1 = remote.commit().parent(base)
1461 			.committer(new PersonIdent(person, 1510000000, 0)).create();
1462 		RevCommit child2 = remote.commit().parent(base)
1463 			.committer(new PersonIdent(person, 1520000000, 0)).create();
1464 
1465 		remote.update("branch1", child1);
1466 		remote.update("branch2", child2);
1467 
1468 		ByteArrayInputStream recvStream = uploadPackV2(
1469 			"command=fetch\n",
1470 			PacketLineIn.delimiter(),
1471 			"deepen-since 1510000\n",
1472 			"want " + child1.toObjectId().getName() + "\n",
1473 			"want " + child2.toObjectId().getName() + "\n",
1474 			"done\n",
1475 				PacketLineIn.end());
1476 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1477 		assertThat(pckIn.readString(), is("shallow-info"));
1478 
1479 		// "base" is excluded, so its children are shallow.
1480 		assertThat(
1481 			Arrays.asList(pckIn.readString(), pckIn.readString()),
1482 			hasItems(
1483 				"shallow " + child1.toObjectId().getName(),
1484 				"shallow " + child2.toObjectId().getName()));
1485 
1486 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1487 		assertThat(pckIn.readString(), is("packfile"));
1488 		parsePack(recvStream);
1489 
1490 		// Only the children are sent.
1491 		assertFalse(client.getObjectDatabase().has(base.toObjectId()));
1492 		assertTrue(client.getObjectDatabase().has(child1.toObjectId()));
1493 		assertTrue(client.getObjectDatabase().has(child2.toObjectId()));
1494 	}
1495 
1496 	@Test
1497 	public void testV2FetchShallowSince_noCommitsSelected() throws Exception {
1498 		PersonIdent person = new PersonIdent(remote.getRepository());
1499 
1500 		RevCommit tooOld = remote.commit()
1501 				.committer(new PersonIdent(person, 1500000000, 0)).create();
1502 
1503 		remote.update("branch1", tooOld);
1504 
1505 		UploadPackInternalServerErrorException e = assertThrows(
1506 				UploadPackInternalServerErrorException.class,
1507 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
1508 						"deepen-since 1510000\n",
1509 						"want " + tooOld.toObjectId().getName() + "\n",
1510 						"done\n", PacketLineIn.end()));
1511 		assertThat(e.getCause().getMessage(),
1512 				containsString("No commits selected for shallow request"));
1513 	}
1514 
1515 	@Test
1516 	public void testV2FetchDeepenNot() throws Exception {
1517 		RevCommit one = remote.commit().message("one").create();
1518 		RevCommit two = remote.commit().message("two").parent(one).create();
1519 		RevCommit three = remote.commit().message("three").parent(two).create();
1520 		RevCommit side = remote.commit().message("side").parent(one).create();
1521 		RevCommit merge = remote.commit().message("merge")
1522 			.parent(three).parent(side).create();
1523 
1524 		remote.update("branch1", merge);
1525 		remote.update("side", side);
1526 
1527 		// The client is a shallow clone that only has "three", and
1528 		// wants "merge" while excluding "side".
1529 		ByteArrayInputStream recvStream = uploadPackV2(
1530 			"command=fetch\n",
1531 			PacketLineIn.delimiter(),
1532 			"shallow " + three.toObjectId().getName() + "\n",
1533 			"deepen-not side\n",
1534 			"want " + merge.toObjectId().getName() + "\n",
1535 			"have " + three.toObjectId().getName() + "\n",
1536 			"done\n",
1537 				PacketLineIn.end());
1538 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1539 		assertThat(pckIn.readString(), is("shallow-info"));
1540 
1541 		// "merge" is shallow because "side" is excluded by deepen-not.
1542 		// "two" is shallow because "one" (as parent of "side") is excluded by deepen-not.
1543 		assertThat(
1544 			Arrays.asList(pckIn.readString(), pckIn.readString()),
1545 			hasItems(
1546 				"shallow " + merge.toObjectId().getName(),
1547 				"shallow " + two.toObjectId().getName()));
1548 
1549 		// "three" is unshallow because its parent "two" is now available.
1550 		assertThat(pckIn.readString(), is("unshallow " + three.toObjectId().getName()));
1551 
1552 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1553 		assertThat(pckIn.readString(), is("packfile"));
1554 		parsePack(recvStream);
1555 
1556 		// The server does not send these because they are excluded by
1557 		// deepen-not.
1558 		assertFalse(client.getObjectDatabase().has(side.toObjectId()));
1559 		assertFalse(client.getObjectDatabase().has(one.toObjectId()));
1560 
1561 		// The server does not send this because the client claims to
1562 		// have it.
1563 		assertFalse(client.getObjectDatabase().has(three.toObjectId()));
1564 
1565 		// The server sends both these commits.
1566 		assertTrue(client.getObjectDatabase().has(merge.toObjectId()));
1567 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
1568 	}
1569 
1570 	@Test
1571 	public void testV2FetchDeepenNot_excludeDescendantOfWant()
1572 			throws Exception {
1573 		RevCommit one = remote.commit().message("one").create();
1574 		RevCommit two = remote.commit().message("two").parent(one).create();
1575 		RevCommit three = remote.commit().message("three").parent(two).create();
1576 		RevCommit four = remote.commit().message("four").parent(three).create();
1577 
1578 		remote.update("two", two);
1579 		remote.update("four", four);
1580 
1581 		UploadPackInternalServerErrorException e = assertThrows(
1582 				UploadPackInternalServerErrorException.class,
1583 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
1584 						"deepen-not four\n",
1585 						"want " + two.toObjectId().getName() + "\n", "done\n",
1586 						PacketLineIn.end()));
1587 		assertThat(e.getCause().getMessage(),
1588 				containsString("No commits selected for shallow request"));
1589 	}
1590 
1591 	@Test
1592 	public void testV2FetchDeepenNot_supportAnnotatedTags() throws Exception {
1593 		RevCommit one = remote.commit().message("one").create();
1594 		RevCommit two = remote.commit().message("two").parent(one).create();
1595 		RevCommit three = remote.commit().message("three").parent(two).create();
1596 		RevCommit four = remote.commit().message("four").parent(three).create();
1597 		RevTag twoTag = remote.tag("twotag", two);
1598 
1599 		remote.update("refs/tags/twotag", twoTag);
1600 		remote.update("four", four);
1601 
1602 		ByteArrayInputStream recvStream = uploadPackV2(
1603 			"command=fetch\n",
1604 			PacketLineIn.delimiter(),
1605 			"deepen-not twotag\n",
1606 			"want " + four.toObjectId().getName() + "\n",
1607 			"done\n",
1608 				PacketLineIn.end());
1609 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1610 		assertThat(pckIn.readString(), is("shallow-info"));
1611 		assertThat(pckIn.readString(), is("shallow " + three.toObjectId().getName()));
1612 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1613 		assertThat(pckIn.readString(), is("packfile"));
1614 		parsePack(recvStream);
1615 		assertFalse(client.getObjectDatabase().has(one.toObjectId()));
1616 		assertFalse(client.getObjectDatabase().has(two.toObjectId()));
1617 		assertTrue(client.getObjectDatabase().has(three.toObjectId()));
1618 		assertTrue(client.getObjectDatabase().has(four.toObjectId()));
1619 	}
1620 
1621 	@Test
1622 	public void testV2FetchDeepenNot_excludedParentWithMultipleChildren() throws Exception {
1623 		PersonIdent person = new PersonIdent(remote.getRepository());
1624 
1625 		RevCommit base = remote.commit()
1626 			.committer(new PersonIdent(person, 1500000000, 0)).create();
1627 		RevCommit child1 = remote.commit().parent(base)
1628 			.committer(new PersonIdent(person, 1510000000, 0)).create();
1629 		RevCommit child2 = remote.commit().parent(base)
1630 			.committer(new PersonIdent(person, 1520000000, 0)).create();
1631 
1632 		remote.update("base", base);
1633 		remote.update("branch1", child1);
1634 		remote.update("branch2", child2);
1635 
1636 		ByteArrayInputStream recvStream = uploadPackV2(
1637 			"command=fetch\n",
1638 			PacketLineIn.delimiter(),
1639 			"deepen-not base\n",
1640 			"want " + child1.toObjectId().getName() + "\n",
1641 			"want " + child2.toObjectId().getName() + "\n",
1642 			"done\n",
1643 				PacketLineIn.end());
1644 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1645 		assertThat(pckIn.readString(), is("shallow-info"));
1646 
1647 		// "base" is excluded, so its children are shallow.
1648 		assertThat(
1649 			Arrays.asList(pckIn.readString(), pckIn.readString()),
1650 			hasItems(
1651 				"shallow " + child1.toObjectId().getName(),
1652 				"shallow " + child2.toObjectId().getName()));
1653 
1654 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
1655 		assertThat(pckIn.readString(), is("packfile"));
1656 		parsePack(recvStream);
1657 
1658 		// Only the children are sent.
1659 		assertFalse(client.getObjectDatabase().has(base.toObjectId()));
1660 		assertTrue(client.getObjectDatabase().has(child1.toObjectId()));
1661 		assertTrue(client.getObjectDatabase().has(child2.toObjectId()));
1662 	}
1663 
1664 	@Test
1665 	public void testV2FetchUnrecognizedArgument() throws Exception {
1666 		UploadPackInternalServerErrorException e = assertThrows(
1667 				UploadPackInternalServerErrorException.class,
1668 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
1669 						"invalid-argument\n", PacketLineIn.end()));
1670 		assertThat(e.getCause().getMessage(),
1671 				containsString("unexpected invalid-argument"));
1672 	}
1673 
1674 	@Test
1675 	public void testV2FetchServerOptions() throws Exception {
1676 		String[] lines = { "command=fetch\n", "server-option=one\n",
1677 				"server-option=two\n", PacketLineIn.delimiter(),
1678 				PacketLineIn.end() };
1679 
1680 		TestV2Hook testHook = new TestV2Hook();
1681 		uploadPackSetup(TransferConfig.ProtocolVersion.V2.version(),
1682 				(UploadPack up) -> {
1683 					up.setProtocolV2Hook(testHook);
1684 				}, lines);
1685 
1686 		FetchV2Request req = testHook.fetchRequest;
1687 		assertNotNull(req);
1688 		assertEquals(2, req.getServerOptions().size());
1689 		assertThat(req.getServerOptions(), hasItems("one", "two"));
1690 	}
1691 
1692 	@Test
1693 	public void testV2FetchFilter() throws Exception {
1694 		RevBlob big = remote.blob("foobar");
1695 		RevBlob small = remote.blob("fooba");
1696 		RevTree tree = remote.tree(remote.file("1", big),
1697 				remote.file("2", small));
1698 		RevCommit commit = remote.commit(tree);
1699 		remote.update("master", commit);
1700 
1701 		server.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
1702 
1703 		ByteArrayInputStream recvStream = uploadPackV2(
1704 			"command=fetch\n",
1705 			PacketLineIn.delimiter(),
1706 			"want " + commit.toObjectId().getName() + "\n",
1707 			"filter blob:limit=5\n",
1708 			"done\n",
1709 				PacketLineIn.end());
1710 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1711 		assertThat(pckIn.readString(), is("packfile"));
1712 		parsePack(recvStream);
1713 
1714 		assertFalse(client.getObjectDatabase().has(big.toObjectId()));
1715 		assertTrue(client.getObjectDatabase().has(small.toObjectId()));
1716 	}
1717 
1718 	abstract class TreeBuilder {
1719 		abstract void addElements(DirCacheBuilder dcBuilder) throws Exception;
1720 
1721 		RevTree build() throws Exception {
1722 			DirCache dc = DirCache.newInCore();
1723 			DirCacheBuilder dcBuilder = dc.builder();
1724 			addElements(dcBuilder);
1725 			dcBuilder.finish();
1726 			ObjectId id;
1727 			try (ObjectInserter ins =
1728 					remote.getRepository().newObjectInserter()) {
1729 				id = dc.writeTree(ins);
1730 				ins.flush();
1731 			}
1732 			return remote.getRevWalk().parseTree(id);
1733 		}
1734 	}
1735 
1736 	class DeepTreePreparator {
1737 		RevBlob blobLowDepth = remote.blob("lo");
1738 		RevBlob blobHighDepth = remote.blob("hi");
1739 
1740 		RevTree subtree = remote.tree(remote.file("1", blobHighDepth));
1741 		RevTree rootTree = (new TreeBuilder() {
1742 				@Override
1743 				void addElements(DirCacheBuilder dcBuilder) throws Exception {
1744 					dcBuilder.add(remote.file("1", blobLowDepth));
1745 					dcBuilder.addTree(new byte[] {'2'}, DirCacheEntry.STAGE_0,
1746 							remote.getRevWalk().getObjectReader(), subtree);
1747 				}
1748 			}).build();
1749 		RevCommit commit = remote.commit(rootTree);
1750 
1751 		DeepTreePreparator() throws Exception {}
1752 	}
1753 
1754 	private void uploadV2WithTreeDepthFilter(
1755 			long depth, ObjectId... wants) throws Exception {
1756 		server.getConfig().setBoolean("uploadpack", null, "allowfilter", true);
1757 
1758 		List<String> input = new ArrayList<>();
1759 		input.add("command=fetch\n");
1760 		input.add(PacketLineIn.delimiter());
1761 		for (ObjectId want : wants) {
1762 			input.add("want " + want.getName() + "\n");
1763 		}
1764 		input.add("filter tree:" + depth + "\n");
1765 		input.add("done\n");
1766 		input.add(PacketLineIn.end());
1767 		ByteArrayInputStream recvStream =
1768 				uploadPackV2(
1769 						(UploadPack up) -> {up.setRequestPolicy(RequestPolicy.ANY);},
1770 						input.toArray(new String[0]));
1771 		PacketLineIn pckIn = new PacketLineIn(recvStream);
1772 		assertThat(pckIn.readString(), is("packfile"));
1773 		parsePack(recvStream);
1774 	}
1775 
1776 	@Test
1777 	public void testV2FetchFilterTreeDepth0() throws Exception {
1778 		DeepTreePreparator preparator = new DeepTreePreparator();
1779 		remote.update("master", preparator.commit);
1780 
1781 		uploadV2WithTreeDepthFilter(0, preparator.commit.toObjectId());
1782 
1783 		assertFalse(client.getObjectDatabase()
1784 				.has(preparator.rootTree.toObjectId()));
1785 		assertFalse(client.getObjectDatabase()
1786 				.has(preparator.subtree.toObjectId()));
1787 		assertFalse(client.getObjectDatabase()
1788 				.has(preparator.blobLowDepth.toObjectId()));
1789 		assertFalse(client.getObjectDatabase()
1790 				.has(preparator.blobHighDepth.toObjectId()));
1791 		assertEquals(1, stats.getTreesTraversed());
1792 	}
1793 
1794 	@Test
1795 	public void testV2FetchFilterTreeDepth1_serverHasBitmap() throws Exception {
1796 		DeepTreePreparator preparator = new DeepTreePreparator();
1797 		remote.update("master", preparator.commit);
1798 
1799 		// The bitmap should be ignored since we need to track the depth while
1800 		// traversing the trees.
1801 		generateBitmaps(server);
1802 
1803 		uploadV2WithTreeDepthFilter(1, preparator.commit.toObjectId());
1804 
1805 		assertTrue(client.getObjectDatabase()
1806 				.has(preparator.rootTree.toObjectId()));
1807 		assertFalse(client.getObjectDatabase()
1808 				.has(preparator.subtree.toObjectId()));
1809 		assertFalse(client.getObjectDatabase()
1810 				.has(preparator.blobLowDepth.toObjectId()));
1811 		assertFalse(client.getObjectDatabase()
1812 				.has(preparator.blobHighDepth.toObjectId()));
1813 		assertEquals(1, stats.getTreesTraversed());
1814 	}
1815 
1816 	@Test
1817 	public void testV2FetchFilterTreeDepth2() throws Exception {
1818 		DeepTreePreparator preparator = new DeepTreePreparator();
1819 		remote.update("master", preparator.commit);
1820 
1821 		uploadV2WithTreeDepthFilter(2, preparator.commit.toObjectId());
1822 
1823 		assertTrue(client.getObjectDatabase()
1824 				.has(preparator.rootTree.toObjectId()));
1825 		assertTrue(client.getObjectDatabase()
1826 				.has(preparator.subtree.toObjectId()));
1827 		assertTrue(client.getObjectDatabase()
1828 				.has(preparator.blobLowDepth.toObjectId()));
1829 		assertFalse(client.getObjectDatabase()
1830 				.has(preparator.blobHighDepth.toObjectId()));
1831 		assertEquals(2, stats.getTreesTraversed());
1832 	}
1833 
1834 	/**
1835 	 * Creates a commit with the following files:
1836 	 * <pre>
1837 	 * a/x/b/foo
1838 	 * x/b/foo
1839 	 * </pre>
1840 	 * which has an identical tree in two locations: once at / and once at /a
1841 	 */
1842 	class RepeatedSubtreePreparator {
1843 		RevBlob foo = remote.blob("foo");
1844 		RevTree subtree3 = remote.tree(remote.file("foo", foo));
1845 		RevTree subtree2 = (new TreeBuilder() {
1846 			@Override
1847 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1848 				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
1849 						remote.getRevWalk().getObjectReader(), subtree3);
1850 			}
1851 		}).build();
1852 		RevTree subtree1 = (new TreeBuilder() {
1853 			@Override
1854 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1855 				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
1856 						remote.getRevWalk().getObjectReader(), subtree2);
1857 			}
1858 		}).build();
1859 		RevTree rootTree = (new TreeBuilder() {
1860 			@Override
1861 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1862 				dcBuilder.addTree(new byte[] {'a'}, DirCacheEntry.STAGE_0,
1863 						remote.getRevWalk().getObjectReader(), subtree1);
1864 				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
1865 						remote.getRevWalk().getObjectReader(), subtree2);
1866 			}
1867 		}).build();
1868 		RevCommit commit = remote.commit(rootTree);
1869 
1870 		RepeatedSubtreePreparator() throws Exception {}
1871 	}
1872 
1873 	@Test
1874 	public void testV2FetchFilterTreeDepth_iterateOverTreeAtTwoLevels()
1875 			throws Exception {
1876 		// Test tree:<depth> where a tree is iterated to twice - once where a
1877 		// subentry is too deep to be included, and again where the blob inside
1878 		// it is shallow enough to be included.
1879 		RepeatedSubtreePreparator preparator = new RepeatedSubtreePreparator();
1880 		remote.update("master", preparator.commit);
1881 
1882 		uploadV2WithTreeDepthFilter(4, preparator.commit.toObjectId());
1883 
1884 		assertTrue(client.getObjectDatabase()
1885 				.has(preparator.foo.toObjectId()));
1886 	}
1887 
1888 	/**
1889 	 * Creates a commit with the following files:
1890 	 * <pre>
1891 	 * a/x/b/foo
1892 	 * b/u/c/baz
1893 	 * y/x/b/foo
1894 	 * z/v/c/baz
1895 	 * </pre>
1896 	 * which has two pairs of identical trees:
1897 	 * <ul>
1898 	 * <li>one at /a and /y
1899 	 * <li>one at /b/u and /z/v
1900 	 * </ul>
1901 	 * Note that this class defines unique 8 trees (rootTree and subtree1-7)
1902 	 * which means PackStatistics should report having visited 8 trees.
1903 	 */
1904 	class RepeatedSubtreeAtSameLevelPreparator {
1905 		RevBlob foo = remote.blob("foo");
1906 
1907 		/** foo */
1908 		RevTree subtree1 = remote.tree(remote.file("foo", foo));
1909 
1910 		/** b/foo */
1911 		RevTree subtree2 = (new TreeBuilder() {
1912 			@Override
1913 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1914 				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
1915 						remote.getRevWalk().getObjectReader(), subtree1);
1916 			}
1917 		}).build();
1918 
1919 		/** x/b/foo */
1920 		RevTree subtree3 = (new TreeBuilder() {
1921 			@Override
1922 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1923 				dcBuilder.addTree(new byte[] {'x'}, DirCacheEntry.STAGE_0,
1924 						remote.getRevWalk().getObjectReader(), subtree2);
1925 			}
1926 		}).build();
1927 
1928 		RevBlob baz = remote.blob("baz");
1929 
1930 		/** baz */
1931 		RevTree subtree4 = remote.tree(remote.file("baz", baz));
1932 
1933 		/** c/baz */
1934 		RevTree subtree5 = (new TreeBuilder() {
1935 			@Override
1936 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1937 				dcBuilder.addTree(new byte[] {'c'}, DirCacheEntry.STAGE_0,
1938 						remote.getRevWalk().getObjectReader(), subtree4);
1939 			}
1940 		}).build();
1941 
1942 		/** u/c/baz */
1943 		RevTree subtree6 = (new TreeBuilder() {
1944 			@Override
1945 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1946 				dcBuilder.addTree(new byte[] {'u'}, DirCacheEntry.STAGE_0,
1947 						remote.getRevWalk().getObjectReader(), subtree5);
1948 			}
1949 		}).build();
1950 
1951 		/** v/c/baz */
1952 		RevTree subtree7 = (new TreeBuilder() {
1953 			@Override
1954 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1955 				dcBuilder.addTree(new byte[] {'v'}, DirCacheEntry.STAGE_0,
1956 						remote.getRevWalk().getObjectReader(), subtree5);
1957 			}
1958 		}).build();
1959 
1960 		RevTree rootTree = (new TreeBuilder() {
1961 			@Override
1962 			void addElements(DirCacheBuilder dcBuilder) throws Exception {
1963 				dcBuilder.addTree(new byte[] {'a'}, DirCacheEntry.STAGE_0,
1964 						remote.getRevWalk().getObjectReader(), subtree3);
1965 				dcBuilder.addTree(new byte[] {'b'}, DirCacheEntry.STAGE_0,
1966 						remote.getRevWalk().getObjectReader(), subtree6);
1967 				dcBuilder.addTree(new byte[] {'y'}, DirCacheEntry.STAGE_0,
1968 						remote.getRevWalk().getObjectReader(), subtree3);
1969 				dcBuilder.addTree(new byte[] {'z'}, DirCacheEntry.STAGE_0,
1970 						remote.getRevWalk().getObjectReader(), subtree7);
1971 			}
1972 		}).build();
1973 		RevCommit commit = remote.commit(rootTree);
1974 
1975 		RepeatedSubtreeAtSameLevelPreparator() throws Exception {}
1976 	}
1977 
1978 	@Test
1979 	public void testV2FetchFilterTreeDepth_repeatTreeAtSameLevelIncludeFile()
1980 			throws Exception {
1981 		RepeatedSubtreeAtSameLevelPreparator preparator =
1982 				new RepeatedSubtreeAtSameLevelPreparator();
1983 		remote.update("master", preparator.commit);
1984 
1985 		uploadV2WithTreeDepthFilter(5, preparator.commit.toObjectId());
1986 
1987 		assertTrue(client.getObjectDatabase()
1988 				.has(preparator.foo.toObjectId()));
1989 		assertTrue(client.getObjectDatabase()
1990 				.has(preparator.baz.toObjectId()));
1991 		assertEquals(8, stats.getTreesTraversed());
1992 	}
1993 
1994 	@Test
1995 	public void testV2FetchFilterTreeDepth_repeatTreeAtSameLevelExcludeFile()
1996 			throws Exception {
1997 		RepeatedSubtreeAtSameLevelPreparator preparator =
1998 				new RepeatedSubtreeAtSameLevelPreparator();
1999 		remote.update("master", preparator.commit);
2000 
2001 		uploadV2WithTreeDepthFilter(4, preparator.commit.toObjectId());
2002 
2003 		assertFalse(client.getObjectDatabase()
2004 				.has(preparator.foo.toObjectId()));
2005 		assertFalse(client.getObjectDatabase()
2006 				.has(preparator.baz.toObjectId()));
2007 		assertEquals(8, stats.getTreesTraversed());
2008 	}
2009 
2010 	@Test
2011 	public void testWantFilteredObject() throws Exception {
2012 		RepeatedSubtreePreparator preparator = new RepeatedSubtreePreparator();
2013 		remote.update("master", preparator.commit);
2014 
2015 		// Specify wanted blob objects that are deep enough to be filtered. We
2016 		// should still upload them.
2017 		uploadV2WithTreeDepthFilter(
2018 				3,
2019 				preparator.commit.toObjectId(),
2020 				preparator.foo.toObjectId());
2021 		assertTrue(client.getObjectDatabase()
2022 				.has(preparator.foo.toObjectId()));
2023 
2024 		client = newRepo("client");
2025 		// Specify a wanted tree object that is deep enough to be filtered. We
2026 		// should still upload it.
2027 		uploadV2WithTreeDepthFilter(
2028 				2,
2029 				preparator.commit.toObjectId(),
2030 				preparator.subtree3.toObjectId());
2031 		assertTrue(client.getObjectDatabase()
2032 				.has(preparator.foo.toObjectId()));
2033 		assertTrue(client.getObjectDatabase()
2034 				.has(preparator.subtree3.toObjectId()));
2035 	}
2036 
2037 	private void checkV2FetchWhenNotAllowed(String fetchLine, String expectedMessage)
2038 			throws Exception {
2039 		RevCommit commit = remote.commit().message("0").create();
2040 		remote.update("master", commit);
2041 
2042 		UploadPackInternalServerErrorException e = assertThrows(
2043 				UploadPackInternalServerErrorException.class,
2044 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
2045 						"want " + commit.toObjectId().getName() + "\n",
2046 						fetchLine, "done\n", PacketLineIn.end()));
2047 		assertThat(e.getCause().getMessage(),
2048 				containsString(expectedMessage));
2049 	}
2050 
2051 	@Test
2052 	public void testV2FetchFilterWhenNotAllowed() throws Exception {
2053 		checkV2FetchWhenNotAllowed(
2054 			"filter blob:limit=5\n",
2055 			"unexpected filter blob:limit=5");
2056 	}
2057 
2058 	@Test
2059 	public void testV2FetchWantRefIfNotAllowed() throws Exception {
2060 		checkV2FetchWhenNotAllowed(
2061 			"want-ref refs/heads/one\n",
2062 			"unexpected want-ref refs/heads/one");
2063 	}
2064 
2065 	@Test
2066 	public void testV2FetchSidebandAllIfNotAllowed() throws Exception {
2067 		checkV2FetchWhenNotAllowed(
2068 			"sideband-all\n",
2069 			"unexpected sideband-all");
2070 	}
2071 
2072 	@Test
2073 	public void testV2FetchWantRef() throws Exception {
2074 		RevCommit one = remote.commit().message("1").create();
2075 		RevCommit two = remote.commit().message("2").create();
2076 		RevCommit three = remote.commit().message("3").create();
2077 		remote.update("one", one);
2078 		remote.update("two", two);
2079 		remote.update("three", three);
2080 
2081 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2082 
2083 		ByteArrayInputStream recvStream = uploadPackV2(
2084 			"command=fetch\n",
2085 			PacketLineIn.delimiter(),
2086 			"want-ref refs/heads/one\n",
2087 			"want-ref refs/heads/two\n",
2088 			"done\n",
2089 				PacketLineIn.end());
2090 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2091 		assertThat(pckIn.readString(), is("wanted-refs"));
2092 		assertThat(
2093 				Arrays.asList(pckIn.readString(), pckIn.readString()),
2094 				hasItems(
2095 					one.toObjectId().getName() + " refs/heads/one",
2096 					two.toObjectId().getName() + " refs/heads/two"));
2097 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2098 		assertThat(pckIn.readString(), is("packfile"));
2099 		parsePack(recvStream);
2100 
2101 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2102 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
2103 		assertFalse(client.getObjectDatabase().has(three.toObjectId()));
2104 	}
2105 
2106 	@Test
2107 	public void testV2FetchBadWantRef() throws Exception {
2108 		RevCommit one = remote.commit().message("1").create();
2109 		remote.update("one", one);
2110 
2111 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant",
2112 				true);
2113 
2114 		UploadPackInternalServerErrorException e = assertThrows(
2115 				UploadPackInternalServerErrorException.class,
2116 				() -> uploadPackV2("command=fetch\n", PacketLineIn.delimiter(),
2117 						"want-ref refs/heads/one\n",
2118 						"want-ref refs/heads/nonExistentRef\n", "done\n",
2119 						PacketLineIn.end()));
2120 		assertThat(e.getCause().getMessage(),
2121 				containsString("Invalid ref name: refs/heads/nonExistentRef"));
2122 	}
2123 
2124 	@Test
2125 	public void testV2FetchMixedWantRef() throws Exception {
2126 		RevCommit one = remote.commit().message("1").create();
2127 		RevCommit two = remote.commit().message("2").create();
2128 		RevCommit three = remote.commit().message("3").create();
2129 		remote.update("one", one);
2130 		remote.update("two", two);
2131 		remote.update("three", three);
2132 
2133 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2134 
2135 		ByteArrayInputStream recvStream = uploadPackV2(
2136 			"command=fetch\n",
2137 			PacketLineIn.delimiter(),
2138 			"want-ref refs/heads/one\n",
2139 			"want " + two.toObjectId().getName() + "\n",
2140 			"done\n",
2141 				PacketLineIn.end());
2142 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2143 		assertThat(pckIn.readString(), is("wanted-refs"));
2144 		assertThat(
2145 				pckIn.readString(),
2146 				is(one.toObjectId().getName() + " refs/heads/one"));
2147 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2148 		assertThat(pckIn.readString(), is("packfile"));
2149 		parsePack(recvStream);
2150 
2151 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2152 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
2153 		assertFalse(client.getObjectDatabase().has(three.toObjectId()));
2154 	}
2155 
2156 	@Test
2157 	public void testV2FetchWantRefWeAlreadyHave() throws Exception {
2158 		RevCommit one = remote.commit().message("1").create();
2159 		remote.update("one", one);
2160 
2161 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2162 
2163 		ByteArrayInputStream recvStream = uploadPackV2(
2164 			"command=fetch\n",
2165 			PacketLineIn.delimiter(),
2166 			"want-ref refs/heads/one\n",
2167 			"have " + one.toObjectId().getName(),
2168 			"done\n",
2169 				PacketLineIn.end());
2170 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2171 
2172 		// The client still needs to know the hash of the object that
2173 		// refs/heads/one points to, even though it already has the
2174 		// object ...
2175 		assertThat(pckIn.readString(), is("wanted-refs"));
2176 		assertThat(
2177 				pckIn.readString(),
2178 				is(one.toObjectId().getName() + " refs/heads/one"));
2179 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2180 
2181 		// ... but the client does not need the object itself.
2182 		assertThat(pckIn.readString(), is("packfile"));
2183 		parsePack(recvStream);
2184 		assertFalse(client.getObjectDatabase().has(one.toObjectId()));
2185 	}
2186 
2187 	@Test
2188 	public void testV2FetchWantRefAndDeepen() throws Exception {
2189 		RevCommit parent = remote.commit().message("parent").create();
2190 		RevCommit child = remote.commit().message("x").parent(parent).create();
2191 		remote.update("branch1", child);
2192 
2193 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2194 
2195 		ByteArrayInputStream recvStream = uploadPackV2(
2196 			"command=fetch\n",
2197 			PacketLineIn.delimiter(),
2198 			"want-ref refs/heads/branch1\n",
2199 			"deepen 1\n",
2200 			"done\n",
2201 				PacketLineIn.end());
2202 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2203 
2204 		// shallow-info appears first, then wanted-refs.
2205 		assertThat(pckIn.readString(), is("shallow-info"));
2206 		assertThat(pckIn.readString(), is("shallow " + child.toObjectId().getName()));
2207 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2208 		assertThat(pckIn.readString(), is("wanted-refs"));
2209 		assertThat(pckIn.readString(), is(child.toObjectId().getName() + " refs/heads/branch1"));
2210 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2211 		assertThat(pckIn.readString(), is("packfile"));
2212 		parsePack(recvStream);
2213 		assertTrue(client.getObjectDatabase().has(child.toObjectId()));
2214 		assertFalse(client.getObjectDatabase().has(parent.toObjectId()));
2215 	}
2216 
2217 	@Test
2218 	public void testV2FetchMissingShallow() throws Exception {
2219 		RevCommit one = remote.commit().message("1").create();
2220 		RevCommit two = remote.commit().message("2").parent(one).create();
2221 		RevCommit three = remote.commit().message("3").parent(two).create();
2222 		remote.update("three", three);
2223 
2224 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant",
2225 				true);
2226 
2227 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
2228 				PacketLineIn.delimiter(),
2229 				"want-ref refs/heads/three\n",
2230 				"deepen 3",
2231 				"shallow 0123012301230123012301230123012301230123",
2232 				"shallow " + two.getName() + '\n',
2233 				"done\n",
2234 				PacketLineIn.end());
2235 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2236 
2237 		assertThat(pckIn.readString(), is("shallow-info"));
2238 		assertThat(pckIn.readString(),
2239 				is("shallow " + one.toObjectId().getName()));
2240 		assertThat(pckIn.readString(),
2241 				is("unshallow " + two.toObjectId().getName()));
2242 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2243 		assertThat(pckIn.readString(), is("wanted-refs"));
2244 		assertThat(pckIn.readString(),
2245 				is(three.toObjectId().getName() + " refs/heads/three"));
2246 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2247 		assertThat(pckIn.readString(), is("packfile"));
2248 		parsePack(recvStream);
2249 
2250 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2251 		assertTrue(client.getObjectDatabase().has(two.toObjectId()));
2252 		assertTrue(client.getObjectDatabase().has(three.toObjectId()));
2253 	}
2254 
2255 	@Test
2256 	public void testV2FetchSidebandAllNoPackfile() throws Exception {
2257 		RevCommit fooParent = remote.commit().message("x").create();
2258 		RevCommit fooChild = remote.commit().message("x").parent(fooParent).create();
2259 		RevCommit barParent = remote.commit().message("y").create();
2260 		RevCommit barChild = remote.commit().message("y").parent(barParent).create();
2261 		remote.update("branch1", fooChild);
2262 		remote.update("branch2", barChild);
2263 
2264 		server.getConfig().setBoolean("uploadpack", null, "allowsidebandall", true);
2265 
2266 		ByteArrayInputStream recvStream = uploadPackV2(
2267 			"command=fetch\n",
2268 			PacketLineIn.delimiter(),
2269 			"sideband-all\n",
2270 			"want " + fooChild.toObjectId().getName() + "\n",
2271 			"want " + barChild.toObjectId().getName() + "\n",
2272 			"have " + fooParent.toObjectId().getName() + "\n",
2273 			PacketLineIn.end());
2274 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2275 
2276 		assertThat(pckIn.readString(), is("\001acknowledgments"));
2277 		assertThat(pckIn.readString(), is("\001ACK " + fooParent.getName()));
2278 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
2279 	}
2280 
2281 	@Test
2282 	public void testV2FetchSidebandAllPackfile() throws Exception {
2283 		RevCommit commit = remote.commit().message("x").create();
2284 		remote.update("master", commit);
2285 
2286 		server.getConfig().setBoolean("uploadpack", null, "allowsidebandall", true);
2287 
2288 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
2289 				PacketLineIn.delimiter(),
2290 				"want " + commit.getName() + "\n",
2291 				"sideband-all\n",
2292 				"done\n",
2293 				PacketLineIn.end());
2294 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2295 
2296 		String s;
2297 		// When sideband-all is used, object counting happens before
2298 		// "packfile" is written, and object counting outputs progress
2299 		// in sideband 2. Skip all these lines.
2300 		for (s = pckIn.readString(); s.startsWith("\002"); s = pckIn.readString()) {
2301 			// do nothing
2302 		}
2303 		assertThat(s, is("\001packfile"));
2304 		parsePack(recvStream);
2305 	}
2306 
2307 	@Test
2308 	public void testV2FetchPackfileUris() throws Exception {
2309 		// Inside the pack
2310 		RevCommit commit = remote.commit().message("x").create();
2311 		remote.update("master", commit);
2312 		generateBitmaps(server);
2313 
2314 		// Outside the pack
2315 		RevCommit commit2 = remote.commit().message("x").parent(commit).create();
2316 		remote.update("master", commit2);
2317 
2318 		server.getConfig().setBoolean("uploadpack", null, "allowsidebandall", true);
2319 
2320 		ByteArrayInputStream recvStream = uploadPackV2(
2321 			(UploadPack up) -> {
2322 				up.setCachedPackUriProvider(new CachedPackUriProvider() {
2323 					@Override
2324 					public PackInfo getInfo(CachedPack pack,
2325 							Collection<String> protocolsSupported)
2326 							throws IOException {
2327 						assertThat(protocolsSupported, hasItems("https"));
2328 						if (!protocolsSupported.contains("https"))
2329 							return null;
2330 						return new PackInfo("myhash", "myuri", 100);
2331 					}
2332 
2333 				});
2334 			},
2335 			"command=fetch\n",
2336 			PacketLineIn.delimiter(),
2337 			"want " + commit2.getName() + "\n",
2338 			"sideband-all\n",
2339 			"packfile-uris https\n",
2340 			"done\n",
2341 			PacketLineIn.end());
2342 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2343 
2344 		String s;
2345 		// skip all \002 strings
2346 		for (s = pckIn.readString(); s.startsWith("\002"); s = pckIn.readString()) {
2347 			// do nothing
2348 		}
2349 		assertThat(s, is("\001packfile-uris"));
2350 		assertThat(pckIn.readString(), is("\001myhash myuri"));
2351 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2352 		assertThat(pckIn.readString(), is("\001packfile"));
2353 		parsePack(recvStream);
2354 
2355 		assertFalse(client.getObjectDatabase().has(commit.toObjectId()));
2356 		assertTrue(client.getObjectDatabase().has(commit2.toObjectId()));
2357 	}
2358 
2359 	@Test
2360 	public void testGetPeerAgentProtocolV0() throws Exception {
2361 		RevCommit one = remote.commit().message("1").create();
2362 		remote.update("one", one);
2363 
2364 		UploadPack up = new UploadPack(server);
2365 		ByteArrayInputStream send = linesAsInputStream(
2366 				"want " + one.getName() + " agent=JGit-test/1.2.3\n",
2367 				PacketLineIn.end(),
2368 				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n");
2369 
2370 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
2371 		up.upload(send, recv, null);
2372 
2373 		assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.3");
2374 	}
2375 
2376 	@Test
2377 	public void testGetPeerAgentProtocolV2() throws Exception {
2378 		server.getConfig().setString(ConfigConstants.CONFIG_PROTOCOL_SECTION,
2379 				null, ConfigConstants.CONFIG_KEY_VERSION,
2380 				TransferConfig.ProtocolVersion.V2.version());
2381 
2382 		RevCommit one = remote.commit().message("1").create();
2383 		remote.update("one", one);
2384 
2385 		UploadPack up = new UploadPack(server);
2386 		up.setExtraParameters(Sets.of("version=2"));
2387 
2388 		ByteArrayInputStream send = linesAsInputStream(
2389 				"command=fetch\n", "agent=JGit-test/1.2.4\n",
2390 				PacketLineIn.delimiter(), "want " + one.getName() + "\n",
2391 				"have 11cedf1b796d44207da702f7d420684022fc0f09\n", "done\n",
2392 				PacketLineIn.end());
2393 
2394 		ByteArrayOutputStream recv = new ByteArrayOutputStream();
2395 		up.upload(send, recv, null);
2396 
2397 		assertEquals(up.getPeerUserAgent(), "JGit-test/1.2.4");
2398 	}
2399 
2400 	private static class RejectAllRefFilter implements RefFilter {
2401 		@Override
2402 		public Map<String, Ref> filter(Map<String, Ref> refs) {
2403 			return new HashMap<>();
2404 		}
2405 	}
2406 
2407 	@Test
2408 	public void testSingleBranchCloneTagChain() throws Exception {
2409 		RevBlob blob0 = remote.blob("Initial content of first file");
2410 		RevBlob blob1 = remote.blob("Second file content");
2411 		RevCommit commit0 = remote
2412 				.commit(remote.tree(remote.file("prvni.txt", blob0)));
2413 		RevCommit commit1 = remote
2414 				.commit(remote.tree(remote.file("druhy.txt", blob1)), commit0);
2415 		remote.update("master", commit1);
2416 
2417 		RevTag heavyTag1 = remote.tag("commitTagRing", commit0);
2418 		remote.getRevWalk().parseHeaders(heavyTag1);
2419 		RevTag heavyTag2 = remote.tag("middleTagRing", heavyTag1);
2420 		remote.lightweightTag("refTagRing", heavyTag2);
2421 
2422 		UploadPack uploadPack = new UploadPack(remote.getRepository());
2423 
2424 		ByteArrayOutputStream cli = new ByteArrayOutputStream();
2425 		PacketLineOut clientWant = new PacketLineOut(cli);
2426 		clientWant.writeString("want " + commit1.name()
2427 				+ " multi_ack_detailed include-tag thin-pack ofs-delta agent=tempo/pflaska");
2428 		clientWant.end();
2429 		clientWant.writeString("done\n");
2430 
2431 		try (ByteArrayOutputStream serverResponse = new ByteArrayOutputStream()) {
2432 
2433 			uploadPack.setPreUploadHook(new PreUploadHook() {
2434 				@Override
2435 				public void onBeginNegotiateRound(UploadPack up,
2436 						Collection<? extends ObjectId> wants, int cntOffered)
2437 						throws ServiceMayNotContinueException {
2438 					// Do nothing.
2439 				}
2440 
2441 				@Override
2442 				public void onEndNegotiateRound(UploadPack up,
2443 						Collection<? extends ObjectId> wants, int cntCommon,
2444 						int cntNotFound, boolean ready)
2445 						throws ServiceMayNotContinueException {
2446 					// Do nothing.
2447 				}
2448 
2449 				@Override
2450 				public void onSendPack(UploadPack up,
2451 						Collection<? extends ObjectId> wants,
2452 						Collection<? extends ObjectId> haves)
2453 						throws ServiceMayNotContinueException {
2454 					// collect pack data
2455 					serverResponse.reset();
2456 				}
2457 			});
2458 			uploadPack.upload(new ByteArrayInputStream(cli.toByteArray()),
2459 					serverResponse, System.err);
2460 			InputStream packReceived = new ByteArrayInputStream(
2461 					serverResponse.toByteArray());
2462 			PackLock lock = null;
2463 			try (ObjectInserter ins = client.newObjectInserter()) {
2464 				PackParser parser = ins.newPackParser(packReceived);
2465 				parser.setAllowThin(true);
2466 				parser.setLockMessage("receive-tag-chain");
2467 				ProgressMonitor mlc = NullProgressMonitor.INSTANCE;
2468 				lock = parser.parse(mlc, mlc);
2469 				ins.flush();
2470 			} finally {
2471 				if (lock != null) {
2472 					lock.unlock();
2473 				}
2474 			}
2475 			InMemoryRepository.MemObjDatabase objDb = client
2476 					.getObjectDatabase();
2477 			assertTrue(objDb.has(blob0.toObjectId()));
2478 			assertTrue(objDb.has(blob1.toObjectId()));
2479 			assertTrue(objDb.has(commit0.toObjectId()));
2480 			assertTrue(objDb.has(commit1.toObjectId()));
2481 			assertTrue(objDb.has(heavyTag1.toObjectId()));
2482 			assertTrue(objDb.has(heavyTag2.toObjectId()));
2483 		}
2484 	}
2485 
2486 	@Test
2487 	public void testSafeToClearRefsInFetchV0() throws Exception {
2488 		server =
2489 			new RefCallsCountingRepository(
2490 				new DfsRepositoryDescription("server"));
2491 		remote = new TestRepository<>(server);
2492 		RevCommit one = remote.commit().message("1").create();
2493 		remote.update("one", one);
2494 		testProtocol = new TestProtocol<>((Object req, Repository db) -> {
2495 			UploadPack up = new UploadPack(db);
2496 			return up;
2497 		}, null);
2498 		uri = testProtocol.register(ctx, server);
2499 		try (Transport tn = testProtocol.open(uri, client, "server")) {
2500 			tn.fetch(NullProgressMonitor.INSTANCE,
2501 				Collections.singletonList(new RefSpec(one.name())));
2502 		}
2503 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2504 		assertEquals(1, ((RefCallsCountingRepository)server).numRefCalls());
2505 	}
2506 
2507 	@Test
2508 	public void testSafeToClearRefsInFetchV2() throws Exception {
2509 		server =
2510 			new RefCallsCountingRepository(
2511 				new DfsRepositoryDescription("server"));
2512 		remote = new TestRepository<>(server);
2513 		RevCommit one = remote.commit().message("1").create();
2514 		RevCommit two = remote.commit().message("2").create();
2515 		remote.update("one", one);
2516 		remote.update("two", two);
2517 		server.getConfig().setBoolean("uploadpack", null, "allowrefinwant", true);
2518 		ByteArrayInputStream recvStream = uploadPackV2(
2519 			"command=fetch\n",
2520 			PacketLineIn.delimiter(),
2521 			"want-ref refs/heads/one\n",
2522 			"want-ref refs/heads/two\n",
2523 			"done\n",
2524 			PacketLineIn.end());
2525 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2526 		assertThat(pckIn.readString(), is("wanted-refs"));
2527 		assertThat(
2528 			Arrays.asList(pckIn.readString(), pckIn.readString()),
2529 			hasItems(
2530 				one.toObjectId().getName() + " refs/heads/one",
2531 				two.toObjectId().getName() + " refs/heads/two"));
2532 		assertTrue(PacketLineIn.isDelimiter(pckIn.readString()));
2533 		assertThat(pckIn.readString(), is("packfile"));
2534 		parsePack(recvStream);
2535 		assertTrue(client.getObjectDatabase().has(one.toObjectId()));
2536 		assertEquals(1, ((RefCallsCountingRepository)server).numRefCalls());
2537 	}
2538 
2539 	@Test
2540 	public void testNotAdvertisedWantsV1Fetch() throws Exception {
2541 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2542 
2543 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2544 		RevCommit parent = remote
2545 				.commit(remote.tree(remote.file("foo", parentBlob)));
2546 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2547 		RevCommit child = remote
2548 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2549 		remote.update("branch1", child);
2550 
2551 		uploadPackV1("want " + child.toObjectId().getName() + "\n",
2552 				PacketLineIn.end(),
2553 				"have " + parent.toObjectId().getName() + "\n",
2554 				"done\n", PacketLineIn.end());
2555 
2556 		assertEquals(0, stats.getNotAdvertisedWants());
2557 	}
2558 
2559 	@Test
2560 	public void testNotAdvertisedWantsV1FetchRequestPolicyReachableCommit() throws Exception {
2561 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2562 
2563 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2564 		RevCommit parent = remote
2565 				.commit(remote.tree(remote.file("foo", parentBlob)));
2566 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2567 		RevCommit child = remote
2568 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2569 
2570 		remote.update("branch1", child);
2571 
2572 		uploadPackV1((UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
2573 				"want " + parent.toObjectId().getName() + "\n",
2574 				PacketLineIn.end(),
2575 				"done\n", PacketLineIn.end());
2576 
2577 		assertEquals(1, stats.getNotAdvertisedWants());
2578 	}
2579 
2580 	@Test
2581 	public void testNotAdvertisedWantsV2FetchThinPack() throws Exception {
2582 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2583 
2584 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2585 		RevCommit parent = remote
2586 				.commit(remote.tree(remote.file("foo", parentBlob)));
2587 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2588 		RevCommit child = remote
2589 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2590 		remote.update("branch1", child);
2591 
2592 		ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n",
2593 				PacketLineIn.delimiter(),
2594 				"want " + child.toObjectId().getName() + "\n",
2595 				"have " + parent.toObjectId().getName() + "\n", "thin-pack\n",
2596 				"done\n", PacketLineIn.end());
2597 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2598 
2599 		assertThat(pckIn.readString(), is("packfile"));
2600 
2601 		assertEquals(0, stats.getNotAdvertisedWants());
2602 	}
2603 
2604 	@Test
2605 	public void testNotAdvertisedWantsV2FetchRequestPolicyReachableCommit() throws Exception {
2606 		String commonInBlob = "abcdefghijklmnopqrstuvwxyz";
2607 
2608 		RevBlob parentBlob = remote.blob(commonInBlob + "a");
2609 		RevCommit parent = remote
2610 				.commit(remote.tree(remote.file("foo", parentBlob)));
2611 		RevBlob childBlob = remote.blob(commonInBlob + "b");
2612 		RevCommit child = remote
2613 				.commit(remote.tree(remote.file("foo", childBlob)), parent);
2614 
2615 		remote.update("branch1", child);
2616 
2617 		uploadPackV2((UploadPack up) -> {up.setRequestPolicy(RequestPolicy.REACHABLE_COMMIT);},
2618 				"command=fetch\n",
2619 				PacketLineIn.delimiter(),
2620 				"want " + parent.toObjectId().getName() + "\n", "thin-pack\n",
2621 				"done\n", PacketLineIn.end());
2622 
2623 		assertEquals(1, stats.getNotAdvertisedWants());
2624 	}
2625 
2626 	private class RefCallsCountingRepository extends InMemoryRepository {
2627 		private final InMemoryRepository.MemRefDatabase refdb;
2628 		private int numRefCalls;
2629 
2630 		public RefCallsCountingRepository(DfsRepositoryDescription repoDesc) {
2631 			super(repoDesc);
2632 			refdb = new InMemoryRepository.MemRefDatabase() {
2633 				@Override
2634 				public List<Ref> getRefs() throws IOException {
2635 					numRefCalls++;
2636 					return super.getRefs();
2637 				}
2638 			};
2639 		}
2640 
2641 		public int numRefCalls() {
2642 			return numRefCalls;
2643 		}
2644 
2645 		@Override
2646 		public RefDatabase getRefDatabase() {
2647 			return refdb;
2648 		}
2649 	}
2650 
2651 	@Test
2652 	public void testObjectInfo() throws Exception {
2653 		server.getConfig().setBoolean("uploadpack", null, "advertiseobjectinfo",
2654 				true);
2655 
2656 		RevBlob blob1 = remote.blob("foobar");
2657 		RevBlob blob2 = remote.blob("fooba");
2658 		RevTree tree = remote.tree(remote.file("1", blob1),
2659 				remote.file("2", blob2));
2660 		RevCommit commit = remote.commit(tree);
2661 		remote.update("master", commit);
2662 
2663 		TestV2Hook hook = new TestV2Hook();
2664 		ByteArrayInputStream recvStream = uploadPackV2((UploadPack up) -> {
2665 			up.setProtocolV2Hook(hook);
2666 		}, "command=object-info\n", "size",
2667 				"oid " + ObjectId.toString(blob1.getId()),
2668 				"oid " + ObjectId.toString(blob2.getId()), PacketLineIn.end());
2669 		PacketLineIn pckIn = new PacketLineIn(recvStream);
2670 
2671 		assertThat(hook.objectInfoRequest, notNullValue());
2672 		assertThat(pckIn.readString(), is("size"));
2673 		assertThat(pckIn.readString(),
2674 				is(ObjectId.toString(blob1.getId()) + " 6"));
2675 		assertThat(pckIn.readString(),
2676 				is(ObjectId.toString(blob2.getId()) + " 5"));
2677 		assertTrue(PacketLineIn.isEnd(pckIn.readString()));
2678 	}
2679 
2680 	@Test
2681 	public void testObjectInfo_invalidOid() throws Exception {
2682 		server.getConfig().setBoolean("uploadpack", null, "advertiseobjectinfo",
2683 				true);
2684 
2685 		assertThrows(UploadPackInternalServerErrorException.class,
2686 				() -> uploadPackV2("command=object-info\n", "size",
2687 						"oid invalid",
2688 						PacketLineIn.end()));
2689 	}
2690 }