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 		try (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,
123 						Collections.singleton(new RefSpec("+refs/*:refs/*")));
124 				assertEquals(B, src.resolve(R_MASTER));
125 			}
126 
127 			// Now put private stuff into dst.
128 			//
129 			b = d.blob("b");
130 			P = d.commit(d.tree(d.file("b", b)), A);
131 			d.update(R_PRIVATE, P);
132 		}
133 	}
134 
135 	@Test
136 	public void testFilterHidesPrivate() throws Exception {
137 		Map<String, Ref> refs;
138 		try (TransportLocal t = new TransportLocal(src, uriOf(dst),
139 				dst.getDirectory()) {
140 			@Override
141 			ReceivePack createReceivePack(Repository db) {
142 				db.close();
143 				dst.incrementOpen();
144 
145 				final ReceivePack rp = super.createReceivePack(dst);
146 				rp.setAdvertiseRefsHook(new HidePrivateHook());
147 				return rp;
148 			}
149 		}) {
150 			try (PushConnection c = t.openPush()) {
151 				refs = c.getRefsMap();
152 			}
153 		}
154 
155 		assertNotNull(refs);
156 		assertNull("no private", refs.get(R_PRIVATE));
157 		assertNull("no HEAD", refs.get(Constants.HEAD));
158 		assertEquals(1, refs.size());
159 
160 		Ref master = refs.get(R_MASTER);
161 		assertNotNull("has master", master);
162 		assertEquals(B, master.getObjectId());
163 	}
164 
165 	@Test
166 	public void resetsHaves() throws Exception {
167 		AtomicReference<Set<ObjectId>> haves = new AtomicReference<>();
168 		try (TransportLocal t = new TransportLocal(src, uriOf(dst),
169 				dst.getDirectory()) {
170 			@Override
171 			ReceivePack createReceivePack(Repository db) {
172 				dst.incrementOpen();
173 
174 				ReceivePack rp = super.createReceivePack(dst);
175 				rp.setAdvertiseRefsHook(new AdvertiseRefsHook() {
176 					@Override
177 					public void advertiseRefs(BaseReceivePack rp2)
178 							throws ServiceMayNotContinueException {
179 						rp.setAdvertisedRefs(rp.getRepository().getAllRefs(),
180 								null);
181 						new HidePrivateHook().advertiseRefs(rp);
182 						haves.set(rp.getAdvertisedObjects());
183 					}
184 
185 					@Override
186 					public void advertiseRefs(UploadPack uploadPack)
187 							throws ServiceMayNotContinueException {
188 						throw new UnsupportedOperationException();
189 					}
190 				});
191 				return rp;
192 			}
193 		}) {
194 			try (PushConnection c = t.openPush()) {
195 				// Just has to open/close for advertisement.
196 			}
197 		}
198 
199 		assertEquals(1, haves.get().size());
200 		assertTrue(haves.get().contains(B));
201 		assertFalse(haves.get().contains(P));
202 	}
203 
204 	private TransportLocal newTransportLocalWithStrictValidation()
205 			throws Exception {
206 		return new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
207 			@Override
208 			ReceivePack createReceivePack(Repository db) {
209 				db.close();
210 				dst.incrementOpen();
211 
212 				final ReceivePack rp = super.createReceivePack(dst);
213 				rp.setCheckReceivedObjects(true);
214 				rp.setCheckReferencedObjectsAreReachable(true);
215 				rp.setAdvertiseRefsHook(new HidePrivateHook());
216 				return rp;
217 			}
218 		};
219 	}
220 
221 	@Test
222 	public void testSuccess() throws Exception {
223 		// Manually force a delta of an object so we reuse it later.
224 		//
225 		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
226 
227 		packHeader(pack, 2);
228 		pack.write((Constants.OBJ_BLOB) << 4 | 1);
229 		deflate(pack, new byte[] { 'a' });
230 
231 		pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
232 		a.copyRawTo(pack);
233 		deflate(pack, new byte[] { 0x1, 0x1, 0x1, 'b' });
234 
235 		digest(pack);
236 		openPack(pack);
237 
238 		// Verify the only storage of b is our packed delta above.
239 		//
240 		ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase();
241 		assertTrue("has b", od.has(b));
242 		assertFalse("b not loose", od.fileFor(b).exists());
243 
244 		// Now use b but in a different commit than what is hidden.
245 		//
246 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
247 			RevCommit N = s.commit().parent(B).add("q", b).create();
248 			s.update(R_MASTER, N);
249 
250 			// Push this new content to the remote, doing strict validation.
251 			//
252 			PushResult r;
253 			RemoteRefUpdate u = new RemoteRefUpdate( //
254 					src, //
255 					R_MASTER, // src name
256 					R_MASTER, // dst name
257 					false, // do not force update
258 					null, // local tracking branch
259 					null // expected id
260 			);
261 			try (TransportLocal t = newTransportLocalWithStrictValidation()) {
262 				t.setPushThin(true);
263 				r = t.push(PM, Collections.singleton(u));
264 				dst.close();
265 			}
266 
267 			assertNotNull("have result", r);
268 			assertNull("private not advertised", r.getAdvertisedRef(R_PRIVATE));
269 			assertSame("master updated", RemoteRefUpdate.Status.OK,
270 					u.getStatus());
271 			assertEquals(N, dst.resolve(R_MASTER));
272 		}
273 	}
274 
275 	@Test
276 	public void testCreateBranchAtHiddenCommitFails() throws Exception {
277 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
278 		packHeader(pack, 0);
279 		digest(pack);
280 
281 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
282 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
283 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + P.name() + ' '
284 				+ "refs/heads/s" + '\0'
285 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
286 		inPckLine.end();
287 		pack.writeTo(inBuf, PM);
288 
289 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
290 		final ReceivePack rp = new ReceivePack(dst);
291 		rp.setCheckReceivedObjects(true);
292 		rp.setCheckReferencedObjectsAreReachable(true);
293 		rp.setAdvertiseRefsHook(new HidePrivateHook());
294 		try {
295 			receive(rp, inBuf, outBuf);
296 			fail("Expected UnpackException");
297 		} catch (UnpackException failed) {
298 			Throwable err = failed.getCause();
299 			assertTrue(err instanceof MissingObjectException);
300 			MissingObjectException moe = (MissingObjectException) err;
301 			assertEquals(P, moe.getObjectId());
302 		}
303 
304 		final PacketLineIn r = asPacketLineIn(outBuf);
305 		String master = r.readString();
306 		int nul = master.indexOf('\0');
307 		assertTrue("has capability list", nul > 0);
308 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
309 		assertSame(PacketLineIn.END, r.readString());
310 
311 		assertEquals("unpack error Missing commit " + P.name(), r.readString());
312 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
313 		assertSame(PacketLineIn.END, r.readString());
314 	}
315 
316 	private static void receive(final ReceivePack rp,
317 			final TemporaryBuffer.Heap inBuf, final TemporaryBuffer.Heap outBuf)
318 			throws IOException {
319 		rp.receive(new ByteArrayInputStream(inBuf.toByteArray()), outBuf, null);
320 	}
321 
322 	@Test
323 	public void testUsingHiddenDeltaBaseFails() throws Exception {
324 		byte[] delta = { 0x1, 0x1, 0x1, 'c' };
325 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
326 			RevCommit N = s.commit().parent(B)
327 					.add("q",
328 							s.blob(BinaryDelta.apply(
329 									dst.open(b).getCachedBytes(), delta)))
330 					.create();
331 
332 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
333 			packHeader(pack, 3);
334 			copy(pack, src.open(N));
335 			copy(pack, src.open(s.parseBody(N).getTree()));
336 			pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
337 			b.copyRawTo(pack);
338 			deflate(pack, delta);
339 			digest(pack);
340 
341 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
342 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
343 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
344 					+ ' ' + "refs/heads/s" + '\0'
345 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
346 			inPckLine.end();
347 			pack.writeTo(inBuf, PM);
348 
349 			final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
350 			final ReceivePack rp = new ReceivePack(dst);
351 			rp.setCheckReceivedObjects(true);
352 			rp.setCheckReferencedObjectsAreReachable(true);
353 			rp.setAdvertiseRefsHook(new HidePrivateHook());
354 			try {
355 				receive(rp, inBuf, outBuf);
356 				fail("Expected UnpackException");
357 			} catch (UnpackException failed) {
358 				Throwable err = failed.getCause();
359 				assertTrue(err instanceof MissingObjectException);
360 				MissingObjectException moe = (MissingObjectException) err;
361 				assertEquals(b, moe.getObjectId());
362 			}
363 
364 			final PacketLineIn r = asPacketLineIn(outBuf);
365 			String master = r.readString();
366 			int nul = master.indexOf('\0');
367 			assertTrue("has capability list", nul > 0);
368 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
369 			assertSame(PacketLineIn.END, r.readString());
370 
371 			assertEquals("unpack error Missing blob " + b.name(),
372 					r.readString());
373 			assertEquals("ng refs/heads/s n/a (unpacker error)",
374 					r.readString());
375 			assertSame(PacketLineIn.END, r.readString());
376 		}
377 	}
378 
379 	@Test
380 	public void testUsingHiddenCommonBlobFails() throws Exception {
381 		// Try to use the 'b' blob that is hidden.
382 		//
383 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
384 			RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
385 
386 			// But don't include it in the pack.
387 			//
388 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
389 			packHeader(pack, 2);
390 			copy(pack, src.open(N));
391 			copy(pack, src.open(s.parseBody(N).getTree()));
392 			digest(pack);
393 
394 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
395 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
396 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
397 					+ ' ' + "refs/heads/s" + '\0'
398 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
399 			inPckLine.end();
400 			pack.writeTo(inBuf, PM);
401 
402 			final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
403 			final ReceivePack rp = new ReceivePack(dst);
404 			rp.setCheckReceivedObjects(true);
405 			rp.setCheckReferencedObjectsAreReachable(true);
406 			rp.setAdvertiseRefsHook(new HidePrivateHook());
407 			try {
408 				receive(rp, inBuf, outBuf);
409 				fail("Expected UnpackException");
410 			} catch (UnpackException failed) {
411 				Throwable err = failed.getCause();
412 				assertTrue(err instanceof MissingObjectException);
413 				MissingObjectException moe = (MissingObjectException) err;
414 				assertEquals(b, moe.getObjectId());
415 			}
416 
417 			final PacketLineIn r = asPacketLineIn(outBuf);
418 			String master = r.readString();
419 			int nul = master.indexOf('\0');
420 			assertTrue("has capability list", nul > 0);
421 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
422 			assertSame(PacketLineIn.END, r.readString());
423 
424 			assertEquals("unpack error Missing blob " + b.name(),
425 					r.readString());
426 			assertEquals("ng refs/heads/s n/a (unpacker error)",
427 					r.readString());
428 			assertSame(PacketLineIn.END, r.readString());
429 		}
430 	}
431 
432 	@Test
433 	public void testUsingUnknownBlobFails() throws Exception {
434 		// Try to use the 'n' blob that is not on the server.
435 		//
436 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
437 			RevBlob n = s.blob("n");
438 			RevCommit N = s.commit().parent(B).add("q", n).create();
439 
440 			// But don't include it in the pack.
441 			//
442 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
443 			packHeader(pack, 2);
444 			copy(pack, src.open(N));
445 			copy(pack, src.open(s.parseBody(N).getTree()));
446 			digest(pack);
447 
448 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
449 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
450 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
451 					+ ' ' + "refs/heads/s" + '\0'
452 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
453 			inPckLine.end();
454 			pack.writeTo(inBuf, PM);
455 
456 			final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
457 			final ReceivePack rp = new ReceivePack(dst);
458 			rp.setCheckReceivedObjects(true);
459 			rp.setCheckReferencedObjectsAreReachable(true);
460 			rp.setAdvertiseRefsHook(new HidePrivateHook());
461 			try {
462 				receive(rp, inBuf, outBuf);
463 				fail("Expected UnpackException");
464 			} catch (UnpackException failed) {
465 				Throwable err = failed.getCause();
466 				assertTrue(err instanceof MissingObjectException);
467 				MissingObjectException moe = (MissingObjectException) err;
468 				assertEquals(n, moe.getObjectId());
469 			}
470 
471 			final PacketLineIn r = asPacketLineIn(outBuf);
472 			String master = r.readString();
473 			int nul = master.indexOf('\0');
474 			assertTrue("has capability list", nul > 0);
475 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
476 			assertSame(PacketLineIn.END, r.readString());
477 
478 			assertEquals("unpack error Missing blob " + n.name(),
479 					r.readString());
480 			assertEquals("ng refs/heads/s n/a (unpacker error)",
481 					r.readString());
482 			assertSame(PacketLineIn.END, r.readString());
483 		}
484 	}
485 
486 	@Test
487 	public void testIncludesInvalidGitmodules() throws Exception {
488 		final TemporaryBuffer.Heap inBuf = setupSourceRepoInvalidGitmodules();
489 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
490 		final ReceivePack rp = new ReceivePack(dst);
491 		rp.setCheckReceivedObjects(true);
492 		rp.setCheckReferencedObjectsAreReachable(true);
493 		rp.setAdvertiseRefsHook(new HidePrivateHook());
494 		try {
495 			receive(rp, inBuf, outBuf);
496 			fail("Expected UnpackException");
497 		} catch (UnpackException failed) {
498 			Throwable err = failed.getCause();
499 			assertTrue(err instanceof IOException);
500 		}
501 
502 		final PacketLineIn r = asPacketLineIn(outBuf);
503 		String master = r.readString();
504 		int nul = master.indexOf('\0');
505 		assertTrue("has capability list", nul > 0);
506 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
507 		assertSame(PacketLineIn.END, r.readString());
508 
509 		String errorLine = r.readString();
510 		assertTrue(errorLine.startsWith("unpack error"));
511 		assertTrue(errorLine.contains("Invalid submodule URL '-"));
512 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
513 		assertSame(PacketLineIn.END, r.readString());
514 	}
515 
516 	private TemporaryBuffer.Heap setupSourceRepoInvalidGitmodules()
517 			throws IOException, Exception, MissingObjectException {
518 		String fakeGitmodules = new StringBuilder()
519 				.append("[submodule \"test\"]\n")
520 				.append("    path = xlib\n")
521 				.append("    url = https://example.com/repo/xlib.git\n\n")
522 				.append("[submodule \"test2\"]\n")
523 				.append("    path = zlib\n")
524 				.append("    url = -upayload.sh\n")
525 				.toString();
526 
527 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
528 			RevBlob blob = s.blob(fakeGitmodules);
529 			RevCommit N = s.commit().parent(B).add(".gitmodules", blob)
530 					.create();
531 			RevTree t = s.parseBody(N).getTree();
532 
533 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
534 			packHeader(pack, 3);
535 			copy(pack, src.open(N));
536 			copy(pack, src.open(t));
537 			copy(pack, src.open(blob));
538 			digest(pack);
539 
540 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
541 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
542 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
543 					+ ' ' + "refs/heads/s" + '\0'
544 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
545 			inPckLine.end();
546 			pack.writeTo(inBuf, PM);
547 			return inBuf;
548 		}
549 	}
550 
551 	@Test
552 	public void testUsingUnknownTreeFails() throws Exception {
553 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
554 			RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create();
555 			RevTree t = s.parseBody(N).getTree();
556 
557 			// Don't include the tree in the pack.
558 			//
559 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
560 			packHeader(pack, 1);
561 			copy(pack, src.open(N));
562 			digest(pack);
563 
564 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
565 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
566 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
567 					+ ' ' + "refs/heads/s" + '\0'
568 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
569 			inPckLine.end();
570 			pack.writeTo(inBuf, PM);
571 
572 			final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
573 			final ReceivePack rp = new ReceivePack(dst);
574 			rp.setCheckReceivedObjects(true);
575 			rp.setCheckReferencedObjectsAreReachable(true);
576 			rp.setAdvertiseRefsHook(new HidePrivateHook());
577 			try {
578 				receive(rp, inBuf, outBuf);
579 				fail("Expected UnpackException");
580 			} catch (UnpackException failed) {
581 				Throwable err = failed.getCause();
582 				assertTrue(err instanceof MissingObjectException);
583 				MissingObjectException moe = (MissingObjectException) err;
584 				assertEquals(t, moe.getObjectId());
585 			}
586 
587 			final PacketLineIn r = asPacketLineIn(outBuf);
588 			String master = r.readString();
589 			int nul = master.indexOf('\0');
590 			assertTrue("has capability list", nul > 0);
591 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
592 			assertSame(PacketLineIn.END, r.readString());
593 
594 			assertEquals("unpack error Missing tree " + t.name(),
595 					r.readString());
596 			assertEquals("ng refs/heads/s n/a (unpacker error)",
597 					r.readString());
598 			assertSame(PacketLineIn.END, r.readString());
599 		}
600 	}
601 
602 	private static void packHeader(TemporaryBuffer.Heap tinyPack, int cnt)
603 			throws IOException {
604 		final byte[] hdr = new byte[8];
605 		NB.encodeInt32(hdr, 0, 2);
606 		NB.encodeInt32(hdr, 4, cnt);
607 
608 		tinyPack.write(Constants.PACK_SIGNATURE);
609 		tinyPack.write(hdr, 0, 8);
610 	}
611 
612 	private static void copy(TemporaryBuffer.Heap tinyPack, ObjectLoader ldr)
613 			throws IOException {
614 		final byte[] buf = new byte[64];
615 		final byte[] content = ldr.getCachedBytes();
616 		int dataLength = content.length;
617 		int nextLength = dataLength >>> 4;
618 		int size = 0;
619 		buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
620 				| (ldr.getType() << 4) | (dataLength & 0x0F));
621 		dataLength = nextLength;
622 		while (dataLength > 0) {
623 			nextLength >>>= 7;
624 			buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
625 			dataLength = nextLength;
626 		}
627 		tinyPack.write(buf, 0, size);
628 		deflate(tinyPack, content);
629 	}
630 
631 	private static void deflate(TemporaryBuffer.Heap tinyPack,
632 			final byte[] content)
633 			throws IOException {
634 		final Deflater deflater = new Deflater();
635 		final byte[] buf = new byte[128];
636 		deflater.setInput(content, 0, content.length);
637 		deflater.finish();
638 		do {
639 			final int n = deflater.deflate(buf, 0, buf.length);
640 			if (n > 0)
641 				tinyPack.write(buf, 0, n);
642 		} while (!deflater.finished());
643 	}
644 
645 	private static void digest(TemporaryBuffer.Heap buf) throws IOException {
646 		MessageDigest md = Constants.newMessageDigest();
647 		md.update(buf.toByteArray());
648 		buf.write(md.digest());
649 	}
650 
651 	private ObjectInserter inserter;
652 
653 	@After
654 	public void release() {
655 		if (inserter != null) {
656 			inserter.close();
657 		}
658 	}
659 
660 	private void openPack(TemporaryBuffer.Heap buf) throws IOException {
661 		if (inserter == null)
662 			inserter = src.newObjectInserter();
663 
664 		final byte[] raw = buf.toByteArray();
665 		PackParser p = inserter.newPackParser(new ByteArrayInputStream(raw));
666 		p.setAllowThin(true);
667 		p.parse(PM);
668 	}
669 
670 	private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf)
671 			throws IOException {
672 		return new PacketLineIn(new ByteArrayInputStream(buf.toByteArray()));
673 	}
674 
675 	private static final class HidePrivateHook extends AbstractAdvertiseRefsHook {
676 		@Override
677 		public Map<String, Ref> getAdvertisedRefs(Repository r, RevWalk revWalk) {
678 			Map<String, Ref> refs = new HashMap<>(r.getAllRefs());
679 			assertNotNull(refs.remove(R_PRIVATE));
680 			return refs;
681 		}
682 	}
683 
684 	private static URIish uriOf(Repository r) throws URISyntaxException {
685 		return new URIish(r.getDirectory().getAbsolutePath());
686 	}
687 }