View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertFalse;
48  import static org.junit.Assert.assertNotNull;
49  import static org.junit.Assert.assertNull;
50  import static org.junit.Assert.assertSame;
51  import static org.junit.Assert.assertTrue;
52  import static org.junit.Assert.fail;
53  
54  import java.io.ByteArrayInputStream;
55  import java.io.IOException;
56  import java.net.URISyntaxException;
57  import java.security.MessageDigest;
58  import java.util.Collections;
59  import java.util.HashMap;
60  import java.util.Map;
61  import java.util.Set;
62  import java.util.concurrent.atomic.AtomicReference;
63  import java.util.zip.Deflater;
64  
65  import org.eclipse.jgit.errors.MissingObjectException;
66  import org.eclipse.jgit.errors.UnpackException;
67  import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
68  import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
69  import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
70  import org.eclipse.jgit.junit.TestRepository;
71  import org.eclipse.jgit.lib.Constants;
72  import org.eclipse.jgit.lib.NullProgressMonitor;
73  import org.eclipse.jgit.lib.ObjectId;
74  import org.eclipse.jgit.lib.ObjectInserter;
75  import org.eclipse.jgit.lib.ObjectLoader;
76  import org.eclipse.jgit.lib.Ref;
77  import org.eclipse.jgit.lib.Repository;
78  import org.eclipse.jgit.revwalk.RevBlob;
79  import org.eclipse.jgit.revwalk.RevCommit;
80  import org.eclipse.jgit.revwalk.RevTree;
81  import org.eclipse.jgit.revwalk.RevWalk;
82  import org.eclipse.jgit.util.NB;
83  import org.eclipse.jgit.util.TemporaryBuffer;
84  import org.junit.After;
85  import org.junit.Before;
86  import org.junit.Test;
87  
88  public class ReceivePackAdvertiseRefsHookTest extends LocalDiskRepositoryTestCase {
89  	private static final NullProgressMonitor PM = NullProgressMonitor.INSTANCE;
90  
91  	private static final String R_MASTER = Constants.R_HEADS + Constants.MASTER;
92  
93  	private static final String R_PRIVATE = Constants.R_HEADS + "private";
94  
95  	private Repository src;
96  
97  	private Repository dst;
98  
99  	private RevCommit A, B, P;
100 
101 	private RevBlob a, b;
102 
103 	@Override
104 	@Before
105 	public void setUp() throws Exception {
106 		super.setUp();
107 
108 		src = createBareRepository();
109 		dst = createBareRepository();
110 
111 		// Fill dst with a some common history.
112 		//
113 		TestRepository<Repository> d = new TestRepository<>(dst);
114 		a = d.blob("a");
115 		A = d.commit(d.tree(d.file("a", a)));
116 		B = d.commit().parent(A).create();
117 		d.update(R_MASTER, B);
118 
119 		// Clone from dst into src
120 		//
121 		try (Transport t = Transport.open(src, uriOf(dst))) {
122 			t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*")));
123 			assertEquals(B, src.resolve(R_MASTER));
124 		}
125 
126 		// Now put private stuff into dst.
127 		//
128 		b = d.blob("b");
129 		P = d.commit(d.tree(d.file("b", b)), A);
130 		d.update(R_PRIVATE, P);
131 	}
132 
133 	@Test
134 	public void testFilterHidesPrivate() throws Exception {
135 		Map<String, Ref> refs;
136 		try (TransportLocal t = new TransportLocal(src, uriOf(dst),
137 				dst.getDirectory()) {
138 			@Override
139 			ReceivePack createReceivePack(Repository db) {
140 				db.close();
141 				dst.incrementOpen();
142 
143 				final ReceivePack rp = super.createReceivePack(dst);
144 				rp.setAdvertiseRefsHook(new HidePrivateHook());
145 				return rp;
146 			}
147 		}) {
148 			try (PushConnection c = t.openPush()) {
149 				refs = c.getRefsMap();
150 			}
151 		}
152 
153 		assertNotNull(refs);
154 		assertNull("no private", refs.get(R_PRIVATE));
155 		assertNull("no HEAD", refs.get(Constants.HEAD));
156 		assertEquals(1, refs.size());
157 
158 		Ref master = refs.get(R_MASTER);
159 		assertNotNull("has master", master);
160 		assertEquals(B, master.getObjectId());
161 	}
162 
163 	@Test
164 	public void resetsHaves() throws Exception {
165 		AtomicReference<Set<ObjectId>> haves = new AtomicReference<>();
166 		try (TransportLocal t = new TransportLocal(src, uriOf(dst),
167 				dst.getDirectory()) {
168 			@Override
169 			ReceivePack createReceivePack(Repository db) {
170 				dst.incrementOpen();
171 
172 				ReceivePack rp = super.createReceivePack(dst);
173 				rp.setAdvertiseRefsHook(new AdvertiseRefsHook() {
174 					@Override
175 					public void advertiseRefs(BaseReceivePack rp2)
176 							throws ServiceMayNotContinueException {
177 						rp.setAdvertisedRefs(rp.getRepository().getAllRefs(),
178 								null);
179 						new HidePrivateHook().advertiseRefs(rp);
180 						haves.set(rp.getAdvertisedObjects());
181 					}
182 
183 					@Override
184 					public void advertiseRefs(UploadPack uploadPack)
185 							throws ServiceMayNotContinueException {
186 						throw new UnsupportedOperationException();
187 					}
188 				});
189 				return rp;
190 			}
191 		}) {
192 			try (PushConnection c = t.openPush()) {
193 				// Just has to open/close for advertisement.
194 			}
195 		}
196 
197 		assertEquals(1, haves.get().size());
198 		assertTrue(haves.get().contains(B));
199 		assertFalse(haves.get().contains(P));
200 	}
201 
202 	private TransportLocal newTransportLocalWithStrictValidation()
203 			throws Exception {
204 		return new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
205 			@Override
206 			ReceivePack createReceivePack(Repository db) {
207 				db.close();
208 				dst.incrementOpen();
209 
210 				final ReceivePack rp = super.createReceivePack(dst);
211 				rp.setCheckReceivedObjects(true);
212 				rp.setCheckReferencedObjectsAreReachable(true);
213 				rp.setAdvertiseRefsHook(new HidePrivateHook());
214 				return rp;
215 			}
216 		};
217 	}
218 
219 	@Test
220 	public void testSuccess() throws Exception {
221 		// Manually force a delta of an object so we reuse it later.
222 		//
223 		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
224 
225 		packHeader(pack, 2);
226 		pack.write((Constants.OBJ_BLOB) << 4 | 1);
227 		deflate(pack, new byte[] { 'a' });
228 
229 		pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
230 		a.copyRawTo(pack);
231 		deflate(pack, new byte[] { 0x1, 0x1, 0x1, 'b' });
232 
233 		digest(pack);
234 		openPack(pack);
235 
236 		// Verify the only storage of b is our packed delta above.
237 		//
238 		ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase();
239 		assertTrue("has b", src.hasObject(b));
240 		assertFalse("b not loose", od.fileFor(b).exists());
241 
242 		// Now use b but in a different commit than what is hidden.
243 		//
244 		TestRepository<Repository> s = new TestRepository<>(src);
245 		RevCommit N = s.commit().parent(B).add("q", b).create();
246 		s.update(R_MASTER, N);
247 
248 		// Push this new content to the remote, doing strict validation.
249 		//
250 		PushResult r;
251 		RemoteRefUpdate u = new RemoteRefUpdate( //
252 				src, //
253 				R_MASTER, // src name
254 				R_MASTER, // dst name
255 				false, // do not force update
256 				null, // local tracking branch
257 				null // expected id
258 		);
259 		try (TransportLocal t = newTransportLocalWithStrictValidation()) {
260 			t.setPushThin(true);
261 			r = t.push(PM, Collections.singleton(u));
262 			dst.close();
263 		}
264 
265 		assertNotNull("have result", r);
266 		assertNull("private not advertised", r.getAdvertisedRef(R_PRIVATE));
267 		assertSame("master updated", RemoteRefUpdate.Status.OK, u.getStatus());
268 		assertEquals(N, dst.resolve(R_MASTER));
269 	}
270 
271 	@Test
272 	public void testCreateBranchAtHiddenCommitFails() throws Exception {
273 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
274 		packHeader(pack, 0);
275 		digest(pack);
276 
277 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
278 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
279 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + P.name() + ' '
280 				+ "refs/heads/s" + '\0'
281 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
282 		inPckLine.end();
283 		pack.writeTo(inBuf, PM);
284 
285 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
286 		final ReceivePack rp = new ReceivePack(dst);
287 		rp.setCheckReceivedObjects(true);
288 		rp.setCheckReferencedObjectsAreReachable(true);
289 		rp.setAdvertiseRefsHook(new HidePrivateHook());
290 		try {
291 			receive(rp, inBuf, outBuf);
292 			fail("Expected UnpackException");
293 		} catch (UnpackException failed) {
294 			Throwable err = failed.getCause();
295 			assertTrue(err instanceof MissingObjectException);
296 			MissingObjectException moe = (MissingObjectException) err;
297 			assertEquals(P, moe.getObjectId());
298 		}
299 
300 		final PacketLineIn r = asPacketLineIn(outBuf);
301 		String master = r.readString();
302 		int nul = master.indexOf('\0');
303 		assertTrue("has capability list", nul > 0);
304 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
305 		assertSame(PacketLineIn.END, r.readString());
306 
307 		assertEquals("unpack error Missing commit " + P.name(), r.readString());
308 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
309 		assertSame(PacketLineIn.END, r.readString());
310 	}
311 
312 	private static void receive(final ReceivePack rp,
313 			final TemporaryBuffer.Heap inBuf, final TemporaryBuffer.Heap outBuf)
314 			throws IOException {
315 		rp.receive(new ByteArrayInputStream(inBuf.toByteArray()), outBuf, null);
316 	}
317 
318 	@Test
319 	public void testUsingHiddenDeltaBaseFails() throws Exception {
320 		byte[] delta = { 0x1, 0x1, 0x1, 'c' };
321 		TestRepository<Repository> s = new TestRepository<>(src);
322 		RevCommit N = s.commit().parent(B).add("q",
323 				s.blob(BinaryDelta.apply(dst.open(b).getCachedBytes(), delta)))
324 				.create();
325 
326 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
327 		packHeader(pack, 3);
328 		copy(pack, src.open(N));
329 		copy(pack, src.open(s.parseBody(N).getTree()));
330 		pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
331 		b.copyRawTo(pack);
332 		deflate(pack, delta);
333 		digest(pack);
334 
335 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
336 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
337 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
338 				+ "refs/heads/s" + '\0'
339 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
340 		inPckLine.end();
341 		pack.writeTo(inBuf, PM);
342 
343 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
344 		final ReceivePack rp = new ReceivePack(dst);
345 		rp.setCheckReceivedObjects(true);
346 		rp.setCheckReferencedObjectsAreReachable(true);
347 		rp.setAdvertiseRefsHook(new HidePrivateHook());
348 		try {
349 			receive(rp, inBuf, outBuf);
350 			fail("Expected UnpackException");
351 		} catch (UnpackException failed) {
352 			Throwable err = failed.getCause();
353 			assertTrue(err instanceof MissingObjectException);
354 			MissingObjectException moe = (MissingObjectException) err;
355 			assertEquals(b, moe.getObjectId());
356 		}
357 
358 		final PacketLineIn r = asPacketLineIn(outBuf);
359 		String master = r.readString();
360 		int nul = master.indexOf('\0');
361 		assertTrue("has capability list", nul > 0);
362 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
363 		assertSame(PacketLineIn.END, r.readString());
364 
365 		assertEquals("unpack error Missing blob " + b.name(), r.readString());
366 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
367 		assertSame(PacketLineIn.END, r.readString());
368 	}
369 
370 	@Test
371 	public void testUsingHiddenCommonBlobFails() throws Exception {
372 		// Try to use the 'b' blob that is hidden.
373 		//
374 		TestRepository<Repository> s = new TestRepository<>(src);
375 		RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
376 
377 		// But don't include it in the pack.
378 		//
379 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
380 		packHeader(pack, 2);
381 		copy(pack, src.open(N));
382 		copy(pack,src.open(s.parseBody(N).getTree()));
383 		digest(pack);
384 
385 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
386 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
387 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
388 				+ "refs/heads/s" + '\0'
389 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
390 		inPckLine.end();
391 		pack.writeTo(inBuf, PM);
392 
393 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
394 		final ReceivePack rp = new ReceivePack(dst);
395 		rp.setCheckReceivedObjects(true);
396 		rp.setCheckReferencedObjectsAreReachable(true);
397 		rp.setAdvertiseRefsHook(new HidePrivateHook());
398 		try {
399 			receive(rp, inBuf, outBuf);
400 			fail("Expected UnpackException");
401 		} catch (UnpackException failed) {
402 			Throwable err = failed.getCause();
403 			assertTrue(err instanceof MissingObjectException);
404 			MissingObjectException moe = (MissingObjectException) err;
405 			assertEquals(b, moe.getObjectId());
406 		}
407 
408 		final PacketLineIn r = asPacketLineIn(outBuf);
409 		String master = r.readString();
410 		int nul = master.indexOf('\0');
411 		assertTrue("has capability list", nul > 0);
412 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
413 		assertSame(PacketLineIn.END, r.readString());
414 
415 		assertEquals("unpack error Missing blob " + b.name(), r.readString());
416 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
417 		assertSame(PacketLineIn.END, r.readString());
418 	}
419 
420 	@Test
421 	public void testUsingUnknownBlobFails() throws Exception {
422 		// Try to use the 'n' blob that is not on the server.
423 		//
424 		TestRepository<Repository> s = new TestRepository<>(src);
425 		RevBlob n = s.blob("n");
426 		RevCommit N = s.commit().parent(B).add("q", n).create();
427 
428 		// But don't include it in the pack.
429 		//
430 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
431 		packHeader(pack, 2);
432 		copy(pack, src.open(N));
433 		copy(pack,src.open(s.parseBody(N).getTree()));
434 		digest(pack);
435 
436 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
437 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
438 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
439 				+ "refs/heads/s" + '\0'
440 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
441 		inPckLine.end();
442 		pack.writeTo(inBuf, PM);
443 
444 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
445 		final ReceivePack rp = new ReceivePack(dst);
446 		rp.setCheckReceivedObjects(true);
447 		rp.setCheckReferencedObjectsAreReachable(true);
448 		rp.setAdvertiseRefsHook(new HidePrivateHook());
449 		try {
450 			receive(rp, inBuf, outBuf);
451 			fail("Expected UnpackException");
452 		} catch (UnpackException failed) {
453 			Throwable err = failed.getCause();
454 			assertTrue(err instanceof MissingObjectException);
455 			MissingObjectException moe = (MissingObjectException) err;
456 			assertEquals(n, moe.getObjectId());
457 		}
458 
459 		final PacketLineIn r = asPacketLineIn(outBuf);
460 		String master = r.readString();
461 		int nul = master.indexOf('\0');
462 		assertTrue("has capability list", nul > 0);
463 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
464 		assertSame(PacketLineIn.END, r.readString());
465 
466 		assertEquals("unpack error Missing blob " + n.name(), r.readString());
467 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
468 		assertSame(PacketLineIn.END, r.readString());
469 	}
470 
471 	@Test
472 	public void testIncludesInvalidGitmodules() throws Exception {
473 		final TemporaryBuffer.Heap inBuf = setupSourceRepoInvalidGitmodules();
474 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
475 		final ReceivePack rp = new ReceivePack(dst);
476 		rp.setCheckReceivedObjects(true);
477 		rp.setCheckReferencedObjectsAreReachable(true);
478 		rp.setAdvertiseRefsHook(new HidePrivateHook());
479 		try {
480 			receive(rp, inBuf, outBuf);
481 			fail("Expected UnpackException");
482 		} catch (UnpackException failed) {
483 			Throwable err = failed.getCause();
484 			assertTrue(err instanceof IOException);
485 		}
486 
487 		final PacketLineIn r = asPacketLineIn(outBuf);
488 		String master = r.readString();
489 		int nul = master.indexOf('\0');
490 		assertTrue("has capability list", nul > 0);
491 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
492 		assertSame(PacketLineIn.END, r.readString());
493 
494 		String errorLine = r.readString();
495 		assertTrue(errorLine.startsWith("unpack error"));
496 		assertTrue(errorLine.contains("Invalid submodule URL '-"));
497 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
498 		assertSame(PacketLineIn.END, r.readString());
499 	}
500 
501 	private TemporaryBuffer.Heap setupSourceRepoInvalidGitmodules()
502 			throws IOException, Exception, MissingObjectException {
503 		String fakeGitmodules = new StringBuilder()
504 				.append("[submodule \"test\"]\n")
505 				.append("    path = xlib\n")
506 				.append("    url = https://example.com/repo/xlib.git\n\n")
507 				.append("[submodule \"test2\"]\n")
508 				.append("    path = zlib\n")
509 				.append("    url = -upayload.sh\n")
510 				.toString();
511 
512 		TestRepository<Repository> s = new TestRepository<>(src);
513 		RevBlob blob = s.blob(fakeGitmodules);
514 		RevCommit N = s.commit().parent(B)
515 				.add(".gitmodules", blob).create();
516 		RevTree t = s.parseBody(N).getTree();
517 
518 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
519 		packHeader(pack, 3);
520 		copy(pack, src.open(N));
521 		copy(pack, src.open(t));
522 		copy(pack, src.open(blob));
523 		digest(pack);
524 
525 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
526 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
527 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
528 				+ "refs/heads/s" + '\0'
529 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
530 		inPckLine.end();
531 		pack.writeTo(inBuf, PM);
532 		return inBuf;
533 	}
534 
535 	@Test
536 	public void testUsingUnknownTreeFails() throws Exception {
537 		TestRepository<Repository> s = new TestRepository<>(src);
538 		RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create();
539 		RevTree t = s.parseBody(N).getTree();
540 
541 		// Don't include the tree in the pack.
542 		//
543 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
544 		packHeader(pack, 1);
545 		copy(pack, src.open(N));
546 		digest(pack);
547 
548 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
549 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
550 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
551 				+ "refs/heads/s" + '\0'
552 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
553 		inPckLine.end();
554 		pack.writeTo(inBuf, PM);
555 
556 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
557 		final ReceivePack rp = new ReceivePack(dst);
558 		rp.setCheckReceivedObjects(true);
559 		rp.setCheckReferencedObjectsAreReachable(true);
560 		rp.setAdvertiseRefsHook(new HidePrivateHook());
561 		try {
562 			receive(rp, inBuf, outBuf);
563 			fail("Expected UnpackException");
564 		} catch (UnpackException failed) {
565 			Throwable err = failed.getCause();
566 			assertTrue(err instanceof MissingObjectException);
567 			MissingObjectException moe = (MissingObjectException) err;
568 			assertEquals(t, moe.getObjectId());
569 		}
570 
571 		final PacketLineIn r = asPacketLineIn(outBuf);
572 		String master = r.readString();
573 		int nul = master.indexOf('\0');
574 		assertTrue("has capability list", nul > 0);
575 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
576 		assertSame(PacketLineIn.END, r.readString());
577 
578 		assertEquals("unpack error Missing tree " + t.name(), r.readString());
579 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
580 		assertSame(PacketLineIn.END, r.readString());
581 	}
582 
583 	private static void packHeader(TemporaryBuffer.Heap tinyPack, int cnt)
584 			throws IOException {
585 		final byte[] hdr = new byte[8];
586 		NB.encodeInt32(hdr, 0, 2);
587 		NB.encodeInt32(hdr, 4, cnt);
588 
589 		tinyPack.write(Constants.PACK_SIGNATURE);
590 		tinyPack.write(hdr, 0, 8);
591 	}
592 
593 	private static void copy(TemporaryBuffer.Heap tinyPack, ObjectLoader ldr)
594 			throws IOException {
595 		final byte[] buf = new byte[64];
596 		final byte[] content = ldr.getCachedBytes();
597 		int dataLength = content.length;
598 		int nextLength = dataLength >>> 4;
599 		int size = 0;
600 		buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
601 				| (ldr.getType() << 4) | (dataLength & 0x0F));
602 		dataLength = nextLength;
603 		while (dataLength > 0) {
604 			nextLength >>>= 7;
605 			buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
606 			dataLength = nextLength;
607 		}
608 		tinyPack.write(buf, 0, size);
609 		deflate(tinyPack, content);
610 	}
611 
612 	private static void deflate(TemporaryBuffer.Heap tinyPack,
613 			final byte[] content)
614 			throws IOException {
615 		final Deflater deflater = new Deflater();
616 		final byte[] buf = new byte[128];
617 		deflater.setInput(content, 0, content.length);
618 		deflater.finish();
619 		do {
620 			final int n = deflater.deflate(buf, 0, buf.length);
621 			if (n > 0)
622 				tinyPack.write(buf, 0, n);
623 		} while (!deflater.finished());
624 	}
625 
626 	private static void digest(TemporaryBuffer.Heap buf) throws IOException {
627 		MessageDigest md = Constants.newMessageDigest();
628 		md.update(buf.toByteArray());
629 		buf.write(md.digest());
630 	}
631 
632 	private ObjectInserter inserter;
633 
634 	@After
635 	public void release() {
636 		if (inserter != null) {
637 			inserter.close();
638 		}
639 	}
640 
641 	private void openPack(TemporaryBuffer.Heap buf) throws IOException {
642 		if (inserter == null)
643 			inserter = src.newObjectInserter();
644 
645 		final byte[] raw = buf.toByteArray();
646 		PackParser p = inserter.newPackParser(new ByteArrayInputStream(raw));
647 		p.setAllowThin(true);
648 		p.parse(PM);
649 	}
650 
651 	private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf)
652 			throws IOException {
653 		return new PacketLineIn(new ByteArrayInputStream(buf.toByteArray()));
654 	}
655 
656 	private static final class HidePrivateHook extends AbstractAdvertiseRefsHook {
657 		@Override
658 		public Map<String, Ref> getAdvertisedRefs(Repository r, RevWalk revWalk) {
659 			Map<String, Ref> refs = new HashMap<>(r.getAllRefs());
660 			assertNotNull(refs.remove(R_PRIVATE));
661 			return refs;
662 		}
663 	}
664 
665 	private static URIish uriOf(Repository r) throws URISyntaxException {
666 		return new URIish(r.getDirectory().getAbsolutePath());
667 	}
668 }