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