1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
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
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
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
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
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
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
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
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,
249 R_MASTER,
250 false,
251 null,
252 null
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
370
371 TestRepository<Repository> s = new TestRepository<>(src);
372 RevCommit N = s.commit().parent(B).add("q", s.blob("b")).create();
373
374
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
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
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
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 }