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