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(final 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 	@Test
203 	public void testSuccess() throws Exception {
204 		// Manually force a delta of an object so we reuse it later.
205 		//
206 		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
207 
208 		packHeader(pack, 2);
209 		pack.write((Constants.OBJ_BLOB) << 4 | 1);
210 		deflate(pack, new byte[] { 'a' });
211 
212 		pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
213 		a.copyRawTo(pack);
214 		deflate(pack, new byte[] { 0x1, 0x1, 0x1, 'b' });
215 
216 		digest(pack);
217 		openPack(pack);
218 
219 		// Verify the only storage of b is our packed delta above.
220 		//
221 		ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase();
222 		assertTrue("has b", src.hasObject(b));
223 		assertFalse("b not loose", od.fileFor(b).exists());
224 
225 		// Now use b but in a different commit than what is hidden.
226 		//
227 		TestRepository<Repository> s = new TestRepository<>(src);
228 		RevCommit N = s.commit().parent(B).add("q", b).create();
229 		s.update(R_MASTER, N);
230 
231 		// Push this new content to the remote, doing strict validation.
232 		//
233 		TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
234 			@Override
235 			ReceivePack createReceivePack(final Repository db) {
236 				db.close();
237 				dst.incrementOpen();
238 
239 				final ReceivePack rp = super.createReceivePack(dst);
240 				rp.setCheckReceivedObjects(true);
241 				rp.setCheckReferencedObjectsAreReachable(true);
242 				rp.setAdvertiseRefsHook(new HidePrivateHook());
243 				return rp;
244 			}
245 		};
246 		RemoteRefUpdate u = new RemoteRefUpdate( //
247 				src, //
248 				R_MASTER, // src name
249 				R_MASTER, // dst name
250 				false, // do not force update
251 				null, // local tracking branch
252 				null // expected id
253 		);
254 		PushResult r;
255 		try {
256 			t.setPushThin(true);
257 			r = t.push(PM, Collections.singleton(u));
258 		} finally {
259 			t.close();
260 		}
261 
262 		assertNotNull("have result", r);
263 		assertNull("private not advertised", r.getAdvertisedRef(R_PRIVATE));
264 		assertSame("master updated", RemoteRefUpdate.Status.OK, u.getStatus());
265 		assertEquals(N, dst.resolve(R_MASTER));
266 	}
267 
268 	@Test
269 	public void testCreateBranchAtHiddenCommitFails() throws Exception {
270 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
271 		packHeader(pack, 0);
272 		digest(pack);
273 
274 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
275 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
276 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + P.name() + ' '
277 				+ "refs/heads/s" + '\0'
278 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
279 		inPckLine.end();
280 		pack.writeTo(inBuf, PM);
281 
282 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
283 		final ReceivePack rp = new ReceivePack(dst);
284 		rp.setCheckReceivedObjects(true);
285 		rp.setCheckReferencedObjectsAreReachable(true);
286 		rp.setAdvertiseRefsHook(new HidePrivateHook());
287 		try {
288 			receive(rp, inBuf, outBuf);
289 			fail("Expected UnpackException");
290 		} catch (UnpackException failed) {
291 			Throwable err = failed.getCause();
292 			assertTrue(err instanceof MissingObjectException);
293 			MissingObjectException moe = (MissingObjectException) err;
294 			assertEquals(P, moe.getObjectId());
295 		}
296 
297 		final PacketLineIn r = asPacketLineIn(outBuf);
298 		String master = r.readString();
299 		int nul = master.indexOf('\0');
300 		assertTrue("has capability list", nul > 0);
301 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
302 		assertSame(PacketLineIn.END, r.readString());
303 
304 		assertEquals("unpack error Missing commit " + P.name(), r.readString());
305 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
306 		assertSame(PacketLineIn.END, r.readString());
307 	}
308 
309 	private static void receive(final ReceivePack rp,
310 			final TemporaryBuffer.Heap inBuf, final TemporaryBuffer.Heap outBuf)
311 			throws IOException {
312 		rp.receive(new ByteArrayInputStream(inBuf.toByteArray()), outBuf, null);
313 	}
314 
315 	@Test
316 	public void testUsingHiddenDeltaBaseFails() throws Exception {
317 		byte[] delta = { 0x1, 0x1, 0x1, 'c' };
318 		TestRepository<Repository> s = new TestRepository<>(src);
319 		RevCommit N = s.commit().parent(B).add("q",
320 				s.blob(BinaryDelta.apply(dst.open(b).getCachedBytes(), delta)))
321 				.create();
322 
323 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
324 		packHeader(pack, 3);
325 		copy(pack, src.open(N));
326 		copy(pack, src.open(s.parseBody(N).getTree()));
327 		pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
328 		b.copyRawTo(pack);
329 		deflate(pack, delta);
330 		digest(pack);
331 
332 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
333 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
334 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
335 				+ "refs/heads/s" + '\0'
336 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
337 		inPckLine.end();
338 		pack.writeTo(inBuf, PM);
339 
340 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
341 		final ReceivePack rp = new ReceivePack(dst);
342 		rp.setCheckReceivedObjects(true);
343 		rp.setCheckReferencedObjectsAreReachable(true);
344 		rp.setAdvertiseRefsHook(new HidePrivateHook());
345 		try {
346 			receive(rp, inBuf, outBuf);
347 			fail("Expected UnpackException");
348 		} catch (UnpackException failed) {
349 			Throwable err = failed.getCause();
350 			assertTrue(err instanceof MissingObjectException);
351 			MissingObjectException moe = (MissingObjectException) err;
352 			assertEquals(b, moe.getObjectId());
353 		}
354 
355 		final PacketLineIn r = asPacketLineIn(outBuf);
356 		String master = r.readString();
357 		int nul = master.indexOf('\0');
358 		assertTrue("has capability list", nul > 0);
359 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
360 		assertSame(PacketLineIn.END, r.readString());
361 
362 		assertEquals("unpack error Missing blob " + b.name(), r.readString());
363 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
364 		assertSame(PacketLineIn.END, r.readString());
365 	}
366 
367 	@Test
368 	public void testUsingHiddenCommonBlobFails() throws Exception {
369 		// Try to use the 'b' blob that is hidden.
370 		//
371 		TestRepository<Repository> s = new TestRepository<>(src);
372 		RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
373 
374 		// But don't include it in the pack.
375 		//
376 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
377 		packHeader(pack, 2);
378 		copy(pack, src.open(N));
379 		copy(pack,src.open(s.parseBody(N).getTree()));
380 		digest(pack);
381 
382 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
383 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
384 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
385 				+ "refs/heads/s" + '\0'
386 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
387 		inPckLine.end();
388 		pack.writeTo(inBuf, PM);
389 
390 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
391 		final ReceivePack rp = new ReceivePack(dst);
392 		rp.setCheckReceivedObjects(true);
393 		rp.setCheckReferencedObjectsAreReachable(true);
394 		rp.setAdvertiseRefsHook(new HidePrivateHook());
395 		try {
396 			receive(rp, inBuf, outBuf);
397 			fail("Expected UnpackException");
398 		} catch (UnpackException failed) {
399 			Throwable err = failed.getCause();
400 			assertTrue(err instanceof MissingObjectException);
401 			MissingObjectException moe = (MissingObjectException) err;
402 			assertEquals(b, moe.getObjectId());
403 		}
404 
405 		final PacketLineIn r = asPacketLineIn(outBuf);
406 		String master = r.readString();
407 		int nul = master.indexOf('\0');
408 		assertTrue("has capability list", nul > 0);
409 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
410 		assertSame(PacketLineIn.END, r.readString());
411 
412 		assertEquals("unpack error Missing blob " + b.name(), r.readString());
413 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
414 		assertSame(PacketLineIn.END, r.readString());
415 	}
416 
417 	@Test
418 	public void testUsingUnknownBlobFails() throws Exception {
419 		// Try to use the 'n' blob that is not on the server.
420 		//
421 		TestRepository<Repository> s = new TestRepository<>(src);
422 		RevBlob n = s.blob("n");
423 		RevCommit N = s.commit().parent(B).add("q", n).create();
424 
425 		// But don't include it in the pack.
426 		//
427 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
428 		packHeader(pack, 2);
429 		copy(pack, src.open(N));
430 		copy(pack,src.open(s.parseBody(N).getTree()));
431 		digest(pack);
432 
433 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
434 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
435 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
436 				+ "refs/heads/s" + '\0'
437 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
438 		inPckLine.end();
439 		pack.writeTo(inBuf, PM);
440 
441 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
442 		final ReceivePack rp = new ReceivePack(dst);
443 		rp.setCheckReceivedObjects(true);
444 		rp.setCheckReferencedObjectsAreReachable(true);
445 		rp.setAdvertiseRefsHook(new HidePrivateHook());
446 		try {
447 			receive(rp, inBuf, outBuf);
448 			fail("Expected UnpackException");
449 		} catch (UnpackException failed) {
450 			Throwable err = failed.getCause();
451 			assertTrue(err instanceof MissingObjectException);
452 			MissingObjectException moe = (MissingObjectException) err;
453 			assertEquals(n, moe.getObjectId());
454 		}
455 
456 		final PacketLineIn r = asPacketLineIn(outBuf);
457 		String master = r.readString();
458 		int nul = master.indexOf('\0');
459 		assertTrue("has capability list", nul > 0);
460 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
461 		assertSame(PacketLineIn.END, r.readString());
462 
463 		assertEquals("unpack error Missing blob " + n.name(), r.readString());
464 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
465 		assertSame(PacketLineIn.END, r.readString());
466 	}
467 
468 	@Test
469 	public void testUsingUnknownTreeFails() throws Exception {
470 		TestRepository<Repository> s = new TestRepository<>(src);
471 		RevCommit N = s.commit().parent(B).add("q", s.blob("a")).create();
472 		RevTree t = s.parseBody(N).getTree();
473 
474 		// Don't include the tree in the pack.
475 		//
476 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
477 		packHeader(pack, 1);
478 		copy(pack, src.open(N));
479 		digest(pack);
480 
481 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
482 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
483 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name() + ' '
484 				+ "refs/heads/s" + '\0'
485 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
486 		inPckLine.end();
487 		pack.writeTo(inBuf, PM);
488 
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 MissingObjectException);
500 			MissingObjectException moe = (MissingObjectException) err;
501 			assertEquals(t, moe.getObjectId());
502 		}
503 
504 		final PacketLineIn r = asPacketLineIn(outBuf);
505 		String master = r.readString();
506 		int nul = master.indexOf('\0');
507 		assertTrue("has capability list", nul > 0);
508 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
509 		assertSame(PacketLineIn.END, r.readString());
510 
511 		assertEquals("unpack error Missing tree " + t.name(), r.readString());
512 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
513 		assertSame(PacketLineIn.END, r.readString());
514 	}
515 
516 	private static void packHeader(TemporaryBuffer.Heap tinyPack, int cnt)
517 			throws IOException {
518 		final byte[] hdr = new byte[8];
519 		NB.encodeInt32(hdr, 0, 2);
520 		NB.encodeInt32(hdr, 4, cnt);
521 
522 		tinyPack.write(Constants.PACK_SIGNATURE);
523 		tinyPack.write(hdr, 0, 8);
524 	}
525 
526 	private static void copy(TemporaryBuffer.Heap tinyPack, ObjectLoader ldr)
527 			throws IOException {
528 		final byte[] buf = new byte[64];
529 		final byte[] content = ldr.getCachedBytes();
530 		int dataLength = content.length;
531 		int nextLength = dataLength >>> 4;
532 		int size = 0;
533 		buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00)
534 				| (ldr.getType() << 4) | (dataLength & 0x0F));
535 		dataLength = nextLength;
536 		while (dataLength > 0) {
537 			nextLength >>>= 7;
538 			buf[size++] = (byte) ((nextLength > 0 ? 0x80 : 0x00) | (dataLength & 0x7F));
539 			dataLength = nextLength;
540 		}
541 		tinyPack.write(buf, 0, size);
542 		deflate(tinyPack, content);
543 	}
544 
545 	private static void deflate(TemporaryBuffer.Heap tinyPack,
546 			final byte[] content)
547 			throws IOException {
548 		final Deflater deflater = new Deflater();
549 		final byte[] buf = new byte[128];
550 		deflater.setInput(content, 0, content.length);
551 		deflater.finish();
552 		do {
553 			final int n = deflater.deflate(buf, 0, buf.length);
554 			if (n > 0)
555 				tinyPack.write(buf, 0, n);
556 		} while (!deflater.finished());
557 	}
558 
559 	private static void digest(TemporaryBuffer.Heap buf) throws IOException {
560 		MessageDigest md = Constants.newMessageDigest();
561 		md.update(buf.toByteArray());
562 		buf.write(md.digest());
563 	}
564 
565 	private ObjectInserter inserter;
566 
567 	@After
568 	public void release() {
569 		if (inserter != null) {
570 			inserter.close();
571 		}
572 	}
573 
574 	private void openPack(TemporaryBuffer.Heap buf) throws IOException {
575 		if (inserter == null)
576 			inserter = src.newObjectInserter();
577 
578 		final byte[] raw = buf.toByteArray();
579 		PackParser p = inserter.newPackParser(new ByteArrayInputStream(raw));
580 		p.setAllowThin(true);
581 		p.parse(PM);
582 	}
583 
584 	private static PacketLineIn asPacketLineIn(TemporaryBuffer.Heap buf)
585 			throws IOException {
586 		return new PacketLineIn(new ByteArrayInputStream(buf.toByteArray()));
587 	}
588 
589 	private static final class HidePrivateHook extends AbstractAdvertiseRefsHook {
590 		@Override
591 		public Map<String, Ref> getAdvertisedRefs(Repository r, RevWalk revWalk) {
592 			Map<String, Ref> refs = new HashMap<>(r.getAllRefs());
593 			assertNotNull(refs.remove(R_PRIVATE));
594 			return refs;
595 		}
596 	}
597 
598 	private static URIish uriOf(Repository r) throws URISyntaxException {
599 		return new URIish(r.getDirectory().getAbsolutePath());
600 	}
601 }