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