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  		dst = createBareRepository();
77  
78  		// Fill dst with a some common history.
79  		//
80  		try (TestRepository<Repository> d = new TestRepository<>(dst)) {
81  			a = d.blob("a");
82  			A = d.commit(d.tree(d.file("a", a)));
83  			B = d.commit().parent(A).create();
84  			d.update(R_MASTER, B);
85  
86  			// Clone from dst into src
87  			//
88  			try (Transport t = Transport.open(src, uriOf(dst))) {
89  				t.fetch(PM,
90  						Collections.singleton(new RefSpec("+refs/*:refs/*")));
91  				assertEquals(B, src.resolve(R_MASTER));
92  			}
93  
94  			// Now put private stuff into dst.
95  			//
96  			b = d.blob("b");
97  			P = d.commit(d.tree(d.file("b", b)), A);
98  			d.update(R_PRIVATE, P);
99  		}
100 	}
101 
102 	@Test
103 	public void testFilterHidesPrivate() throws Exception {
104 		Map<String, Ref> refs;
105 		try (TransportLocal t = new TransportLocal(src, uriOf(dst),
106 				dst.getDirectory()) {
107 			@Override
108 			ReceivePack createReceivePack(Repository db) {
109 				db.close();
110 				dst.incrementOpen();
111 
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 				dst.incrementOpen();
140 
141 				ReceivePack rp = super.createReceivePack(dst);
142 				rp.setAdvertiseRefsHook(new AdvertiseRefsHook() {
143 					@Override
144 					public void advertiseRefs(ReceivePack rp2)
145 							throws IOException {
146 						rp.setAdvertisedRefs(rp.getRepository().getAllRefs(),
147 								null);
148 						new HidePrivateHook().advertiseRefs(rp);
149 						haves.set(rp.getAdvertisedObjects());
150 					}
151 
152 					@Override
153 					public void advertiseRefs(UploadPack uploadPack)
154 							throws ServiceMayNotContinueException {
155 						throw new UnsupportedOperationException();
156 					}
157 				});
158 				return rp;
159 			}
160 		}) {
161 			try (PushConnection c = t.openPush()) {
162 				// Just has to open/close for advertisement.
163 			}
164 		}
165 
166 		assertEquals(1, haves.get().size());
167 		assertTrue(haves.get().contains(B));
168 		assertFalse(haves.get().contains(P));
169 	}
170 
171 	private TransportLocal newTransportLocalWithStrictValidation()
172 			throws Exception {
173 		return new TransportLocal(src, uriOf(dst), dst.getDirectory()) {
174 			@Override
175 			ReceivePack createReceivePack(Repository db) {
176 				db.close();
177 				dst.incrementOpen();
178 
179 				final ReceivePack rp = super.createReceivePack(dst);
180 				rp.setCheckReceivedObjects(true);
181 				rp.setCheckReferencedObjectsAreReachable(true);
182 				rp.setAdvertiseRefsHook(new HidePrivateHook());
183 				return rp;
184 			}
185 		};
186 	}
187 
188 	@Test
189 	public void testSuccess() throws Exception {
190 		// Manually force a delta of an object so we reuse it later.
191 		//
192 		TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
193 
194 		packHeader(pack, 2);
195 		pack.write((Constants.OBJ_BLOB) << 4 | 1);
196 		deflate(pack, new byte[] { 'a' });
197 
198 		pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
199 		a.copyRawTo(pack);
200 		deflate(pack, new byte[] { 0x1, 0x1, 0x1, 'b' });
201 
202 		digest(pack);
203 		openPack(pack);
204 
205 		// Verify the only storage of b is our packed delta above.
206 		//
207 		ObjectDirectory od = (ObjectDirectory) src.getObjectDatabase();
208 		assertTrue("has b", od.has(b));
209 		assertFalse("b not loose", od.fileFor(b).exists());
210 
211 		// Now use b but in a different commit than what is hidden.
212 		//
213 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
214 			RevCommit N = s.commit().parent(B).add("q", b).create();
215 			s.update(R_MASTER, N);
216 
217 			// Push this new content to the remote, doing strict validation.
218 			//
219 			PushResult r;
220 			RemoteRefUpdate u = new RemoteRefUpdate( //
221 					src, //
222 					R_MASTER, // src name
223 					R_MASTER, // dst name
224 					false, // do not force update
225 					null, // local tracking branch
226 					null // expected id
227 			);
228 			try (TransportLocal t = newTransportLocalWithStrictValidation()) {
229 				t.setPushThin(true);
230 				r = t.push(PM, Collections.singleton(u));
231 				dst.close();
232 			}
233 
234 			assertNotNull("have result", r);
235 			assertNull("private not advertised", r.getAdvertisedRef(R_PRIVATE));
236 			assertSame("master updated", RemoteRefUpdate.Status.OK,
237 					u.getStatus());
238 			assertEquals(N, dst.resolve(R_MASTER));
239 		}
240 	}
241 
242 	@Test
243 	public void testCreateBranchAtHiddenCommitFails() throws Exception {
244 		final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64);
245 		packHeader(pack, 0);
246 		digest(pack);
247 
248 		final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(256);
249 		final PacketLineOut inPckLine = new PacketLineOut(inBuf);
250 		inPckLine.writeString(ObjectId.zeroId().name() + ' ' + P.name() + ' '
251 				+ "refs/heads/s" + '\0'
252 				+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
253 		inPckLine.end();
254 		pack.writeTo(inBuf, PM);
255 
256 		final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
257 		final ReceivePack rp = new ReceivePack(dst);
258 		rp.setCheckReceivedObjects(true);
259 		rp.setCheckReferencedObjectsAreReachable(true);
260 		rp.setAdvertiseRefsHook(new HidePrivateHook());
261 		try {
262 			receive(rp, inBuf, outBuf);
263 			fail("Expected UnpackException");
264 		} catch (UnpackException failed) {
265 			Throwable err = failed.getCause();
266 			assertTrue(err instanceof MissingObjectException);
267 			MissingObjectException moe = (MissingObjectException) err;
268 			assertEquals(P, moe.getObjectId());
269 		}
270 
271 		final PacketLineIn r = asPacketLineIn(outBuf);
272 		String master = r.readString();
273 		int nul = master.indexOf('\0');
274 		assertTrue("has capability list", nul > 0);
275 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
276 		assertTrue(PacketLineIn.isEnd(r.readString()));
277 
278 		assertEquals("unpack error Missing commit " + P.name(), r.readString());
279 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
280 		assertTrue(PacketLineIn.isEnd(r.readString()));
281 	}
282 
283 	private static void receive(final ReceivePack rp,
284 			final TemporaryBuffer.Heap inBuf, final TemporaryBuffer.Heap outBuf)
285 			throws IOException {
286 		rp.receive(new ByteArrayInputStream(inBuf.toByteArray()), outBuf, null);
287 	}
288 
289 	@Test
290 	public void testUsingHiddenDeltaBaseFails() throws Exception {
291 		byte[] delta = { 0x1, 0x1, 0x1, 'c' };
292 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
293 			RevCommit N = s.commit().parent(B)
294 					.add("q",
295 							s.blob(BinaryDelta.apply(
296 									dst.open(b).getCachedBytes(), delta)))
297 					.create();
298 
299 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
300 			packHeader(pack, 3);
301 			copy(pack, src.open(N));
302 			copy(pack, src.open(s.parseBody(N).getTree()));
303 			pack.write((Constants.OBJ_REF_DELTA) << 4 | 4);
304 			b.copyRawTo(pack);
305 			deflate(pack, delta);
306 			digest(pack);
307 
308 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
309 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
310 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
311 					+ ' ' + "refs/heads/s" + '\0'
312 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
313 			inPckLine.end();
314 			pack.writeTo(inBuf, PM);
315 
316 			final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
317 			final ReceivePack rp = new ReceivePack(dst);
318 			rp.setCheckReceivedObjects(true);
319 			rp.setCheckReferencedObjectsAreReachable(true);
320 			rp.setAdvertiseRefsHook(new HidePrivateHook());
321 			try {
322 				receive(rp, inBuf, outBuf);
323 				fail("Expected UnpackException");
324 			} catch (UnpackException failed) {
325 				Throwable err = failed.getCause();
326 				assertTrue(err instanceof MissingObjectException);
327 				MissingObjectException moe = (MissingObjectException) err;
328 				assertEquals(b, moe.getObjectId());
329 			}
330 
331 			final PacketLineIn r = asPacketLineIn(outBuf);
332 			String master = r.readString();
333 			int nul = master.indexOf('\0');
334 			assertTrue("has capability list", nul > 0);
335 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
336 			assertTrue(PacketLineIn.isEnd(r.readString()));
337 
338 			assertEquals("unpack error Missing blob " + b.name(),
339 					r.readString());
340 			assertEquals("ng refs/heads/s n/a (unpacker error)",
341 					r.readString());
342 			assertTrue(PacketLineIn.isEnd(r.readString()));
343 		}
344 	}
345 
346 	@Test
347 	public void testUsingHiddenCommonBlobFails() throws Exception {
348 		// Try to use the 'b' blob that is hidden.
349 		//
350 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
351 			RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
352 
353 			// But don't include it in the pack.
354 			//
355 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
356 			packHeader(pack, 2);
357 			copy(pack, src.open(N));
358 			copy(pack, src.open(s.parseBody(N).getTree()));
359 			digest(pack);
360 
361 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
362 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
363 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
364 					+ ' ' + "refs/heads/s" + '\0'
365 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
366 			inPckLine.end();
367 			pack.writeTo(inBuf, PM);
368 
369 			final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
370 			final ReceivePack rp = new ReceivePack(dst);
371 			rp.setCheckReceivedObjects(true);
372 			rp.setCheckReferencedObjectsAreReachable(true);
373 			rp.setAdvertiseRefsHook(new HidePrivateHook());
374 			try {
375 				receive(rp, inBuf, outBuf);
376 				fail("Expected UnpackException");
377 			} catch (UnpackException failed) {
378 				Throwable err = failed.getCause();
379 				assertTrue(err instanceof MissingObjectException);
380 				MissingObjectException moe = (MissingObjectException) err;
381 				assertEquals(b, moe.getObjectId());
382 			}
383 
384 			final PacketLineIn r = asPacketLineIn(outBuf);
385 			String master = r.readString();
386 			int nul = master.indexOf('\0');
387 			assertTrue("has capability list", nul > 0);
388 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
389 			assertTrue(PacketLineIn.isEnd(r.readString()));
390 
391 			assertEquals("unpack error Missing blob " + b.name(),
392 					r.readString());
393 			assertEquals("ng refs/heads/s n/a (unpacker error)",
394 					r.readString());
395 			assertTrue(PacketLineIn.isEnd(r.readString()));
396 		}
397 	}
398 
399 	@Test
400 	public void testUsingUnknownBlobFails() throws Exception {
401 		// Try to use the 'n' blob that is not on the server.
402 		//
403 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
404 			RevBlob n = s.blob("n");
405 			RevCommit N = s.commit().parent(B).add("q", n).create();
406 
407 			// But don't include it in the pack.
408 			//
409 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
410 			packHeader(pack, 2);
411 			copy(pack, src.open(N));
412 			copy(pack, src.open(s.parseBody(N).getTree()));
413 			digest(pack);
414 
415 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
416 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
417 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
418 					+ ' ' + "refs/heads/s" + '\0'
419 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
420 			inPckLine.end();
421 			pack.writeTo(inBuf, PM);
422 
423 			final TemporaryBuffer.Heap outBuf = new TemporaryBuffer.Heap(1024);
424 			final ReceivePack rp = new ReceivePack(dst);
425 			rp.setCheckReceivedObjects(true);
426 			rp.setCheckReferencedObjectsAreReachable(true);
427 			rp.setAdvertiseRefsHook(new HidePrivateHook());
428 			try {
429 				receive(rp, inBuf, outBuf);
430 				fail("Expected UnpackException");
431 			} catch (UnpackException failed) {
432 				Throwable err = failed.getCause();
433 				assertTrue(err instanceof MissingObjectException);
434 				MissingObjectException moe = (MissingObjectException) err;
435 				assertEquals(n, moe.getObjectId());
436 			}
437 
438 			final PacketLineIn r = asPacketLineIn(outBuf);
439 			String master = r.readString();
440 			int nul = master.indexOf('\0');
441 			assertTrue("has capability list", nul > 0);
442 			assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
443 			assertTrue(PacketLineIn.isEnd(r.readString()));
444 
445 			assertEquals("unpack error Missing blob " + n.name(),
446 					r.readString());
447 			assertEquals("ng refs/heads/s n/a (unpacker error)",
448 					r.readString());
449 			assertTrue(PacketLineIn.isEnd(r.readString()));
450 		}
451 	}
452 
453 	@Test
454 	public void testIncludesInvalidGitmodules() throws Exception {
455 		final TemporaryBuffer.Heap inBuf = setupSourceRepoInvalidGitmodules();
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 			// Expected
466 		}
467 
468 		final PacketLineIn r = asPacketLineIn(outBuf);
469 		String master = r.readString();
470 		int nul = master.indexOf('\0');
471 		assertTrue("has capability list", nul > 0);
472 		assertEquals(B.name() + ' ' + R_MASTER, master.substring(0, nul));
473 		assertTrue(PacketLineIn.isEnd(r.readString()));
474 
475 		String errorLine = r.readString();
476 		assertTrue(errorLine.startsWith("unpack error"));
477 		assertTrue(errorLine.contains("Invalid submodule URL '-"));
478 		assertEquals("ng refs/heads/s n/a (unpacker error)", r.readString());
479 		assertTrue(PacketLineIn.isEnd(r.readString()));
480 	}
481 
482 	private TemporaryBuffer.Heap setupSourceRepoInvalidGitmodules()
483 			throws IOException, Exception, MissingObjectException {
484 		String fakeGitmodules = new StringBuilder()
485 				.append("[submodule \"test\"]\n")
486 				.append("    path = xlib\n")
487 				.append("    url = https://example.com/repo/xlib.git\n\n")
488 				.append("[submodule \"test2\"]\n")
489 				.append("    path = zlib\n")
490 				.append("    url = -upayload.sh\n")
491 				.toString();
492 
493 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
494 			RevBlob blob = s.blob(fakeGitmodules);
495 			RevCommit N = s.commit().parent(B).add(".gitmodules", blob)
496 					.create();
497 			RevTree t = s.parseBody(N).getTree();
498 
499 			final TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
500 			packHeader(pack, 3);
501 			copy(pack, src.open(N));
502 			copy(pack, src.open(t));
503 			copy(pack, src.open(blob));
504 			digest(pack);
505 
506 			final TemporaryBuffer.Heap inBuf = new TemporaryBuffer.Heap(1024);
507 			final PacketLineOut inPckLine = new PacketLineOut(inBuf);
508 			inPckLine.writeString(ObjectId.zeroId().name() + ' ' + N.name()
509 					+ ' ' + "refs/heads/s" + '\0'
510 					+ BasePackPushConnection.CAPABILITY_REPORT_STATUS);
511 			inPckLine.end();
512 			pack.writeTo(inBuf, PM);
513 			return inBuf;
514 		}
515 	}
516 
517 	@Test
518 	public void testUsingUnknownTreeFails() throws Exception {
519 		try (TestRepository<Repository> s = new TestRepository<>(src)) {
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 }