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