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