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