1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.reftable;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
14 import static org.eclipse.jgit.lib.Constants.HEAD;
15 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
16 import static org.eclipse.jgit.lib.Constants.R_HEADS;
17 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
18 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
19 import static org.hamcrest.CoreMatchers.containsString;
20 import static org.hamcrest.MatcherAssert.assertThat;
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.assertSame;
26 import static org.junit.Assert.assertThrows;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.concurrent.locks.ReentrantLock;
38 import java.util.stream.Collectors;
39
40 import org.eclipse.jgit.internal.JGitText;
41 import org.eclipse.jgit.internal.storage.io.BlockSource;
42 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats;
43 import org.eclipse.jgit.lib.ObjectId;
44 import org.eclipse.jgit.lib.ObjectIdRef;
45 import org.eclipse.jgit.lib.PersonIdent;
46 import org.eclipse.jgit.lib.Ref;
47 import org.eclipse.jgit.lib.ReflogEntry;
48 import org.eclipse.jgit.lib.SymbolicRef;
49 import org.hamcrest.Matchers;
50 import org.junit.Test;
51
52 public class ReftableTest {
53 private static final byte[] LAST_UTF8_CHAR = new byte[] {
54 (byte)0x10,
55 (byte)0xFF,
56 (byte)0xFF};
57
58 private static final String MASTER = "refs/heads/master";
59 private static final String NEXT = "refs/heads/next";
60 private static final String AFTER_NEXT = "refs/heads/nextnext";
61 private static final String LAST = "refs/heads/nextnextnext";
62 private static final String NOT_REF_HEADS = "refs/zzz/zzz";
63 private static final String V1_0 = "refs/tags/v1.0";
64
65 private Stats stats;
66
67 @Test
68 public void emptyTable() throws IOException {
69 byte[] table = write();
70 assertEquals(92 , table.length);
71 assertEquals('R', table[0]);
72 assertEquals('E', table[1]);
73 assertEquals('F', table[2]);
74 assertEquals('T', table[3]);
75 assertEquals(0x01, table[4]);
76 assertTrue(ReftableConstants.isFileHeaderMagic(table, 0, 8));
77 assertTrue(ReftableConstants.isFileHeaderMagic(table, 24, 92));
78
79 Reftable t = read(table);
80 try (RefCursor rc = t.allRefs()) {
81 assertFalse(rc.next());
82 }
83 try (RefCursor rc = t.seekRef(HEAD)) {
84 assertFalse(rc.next());
85 }
86 try (RefCursor rc = t.seekRefsWithPrefix(R_HEADS)) {
87 assertFalse(rc.next());
88 }
89 try (LogCursor rc = t.allLogs()) {
90 assertFalse(rc.next());
91 }
92 }
93
94 @Test
95 public void emptyVirtualTableFromRefs() throws IOException {
96 Reftable t = Reftable.from(Collections.emptyList());
97 try (RefCursor rc = t.allRefs()) {
98 assertFalse(rc.next());
99 }
100 try (RefCursor rc = t.seekRef(HEAD)) {
101 assertFalse(rc.next());
102 }
103 try (LogCursor rc = t.allLogs()) {
104 assertFalse(rc.next());
105 }
106 }
107
108 @Test
109 public void estimateCurrentBytesOneRef() throws IOException {
110 Ref exp = ref(MASTER, 1);
111 int expBytes = 24 + 4 + 5 + 4 + MASTER.length() + 20 + 68;
112
113 byte[] table;
114 ReftableConfig cfg = new ReftableConfig();
115 cfg.setIndexObjects(false);
116 try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
117 ReftableWriter writer = new ReftableWriter(buf).setConfig(cfg);
118 writer.begin();
119 assertEquals(92, writer.estimateTotalBytes());
120 writer.writeRef(exp);
121 assertEquals(expBytes, writer.estimateTotalBytes());
122 writer.finish();
123 table = buf.toByteArray();
124 }
125 assertEquals(expBytes, table.length);
126 }
127
128 @Test
129 public void estimateCurrentBytesWithIndex() throws IOException {
130 List<Ref> refs = new ArrayList<>();
131 for (int i = 1; i <= 5670; i++) {
132 @SuppressWarnings("boxing")
133 Ref ref = ref(String.format("refs/heads/%04d", i), i);
134 refs.add(ref);
135 }
136
137 ReftableConfig cfg = new ReftableConfig();
138 cfg.setIndexObjects(false);
139 cfg.setMaxIndexLevels(1);
140
141 int expBytes = 147860;
142 byte[] table;
143 try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
144 ReftableWriter writer = new ReftableWriter(buf).setConfig(cfg);
145 writer.begin();
146 writer.sortAndWriteRefs(refs);
147 assertEquals(expBytes, writer.estimateTotalBytes());
148 writer.finish();
149 stats = writer.getStats();
150 table = buf.toByteArray();
151 }
152 assertEquals(1, stats.refIndexLevels());
153 assertEquals(expBytes, table.length);
154 }
155
156 @Test
157 public void hasObjMapRefs() throws IOException {
158 ArrayList<Ref> refs = new ArrayList<>();
159 refs.add(ref(MASTER, 1));
160 byte[] table = write(refs);
161 ReftableReader t = read(table);
162 assertTrue(t.hasObjectMap());
163 }
164
165 @Test
166 public void hasObjMapRefsSmallTable() throws IOException {
167 ArrayList<Ref> refs = new ArrayList<>();
168 ReftableConfig cfg = new ReftableConfig();
169 cfg.setIndexObjects(false);
170 refs.add(ref(MASTER, 1));
171 byte[] table = write(refs);
172 ReftableReader t = read(table);
173 assertTrue(t.hasObjectMap());
174 }
175
176 @Test
177 public void hasObjLogs() throws IOException {
178 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
179 String msg = "test";
180 ReftableConfig cfg = new ReftableConfig();
181 cfg.setIndexObjects(false);
182
183 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
184 ReftableWriter writer = new ReftableWriter(buffer)
185 .setMinUpdateIndex(1)
186 .setConfig(cfg)
187 .setMaxUpdateIndex(1)
188 .begin();
189
190 writer.writeLog("master", 1, who, ObjectId.zeroId(), id(1), msg);
191 writer.finish();
192 byte[] table = buffer.toByteArray();
193
194 ReftableReader t = read(table);
195 assertTrue(t.hasObjectMap());
196 }
197
198 @Test
199 public void hasObjMapRefsNoIndexObjects() throws IOException {
200 ArrayList<Ref> refs = new ArrayList<>();
201 ReftableConfig cfg = new ReftableConfig();
202 cfg.setIndexObjects(false);
203 cfg.setRefBlockSize(256);
204 cfg.setAlignBlocks(true);
205
206
207 int N = 256 * 5 / 25;
208 for (int i= 0; i < N; i++) {
209 @SuppressWarnings("boxing")
210 Ref ref = ref(String.format("%02d/xxxxxxxxxx", i), i);
211 refs.add(ref);
212 }
213 byte[] table = write(refs, cfg);
214
215 ReftableReader t = read(table);
216 assertFalse(t.hasObjectMap());
217 }
218
219 @Test
220 public void oneIdRef() throws IOException {
221 Ref exp = ref(MASTER, 1);
222 byte[] table = write(exp);
223 assertEquals(24 + 4 + 5 + 4 + MASTER.length() + 20 + 68, table.length);
224
225 ReftableReader t = read(table);
226 try (RefCursor rc = t.allRefs()) {
227 assertTrue(rc.next());
228 Ref act = rc.getRef();
229 assertNotNull(act);
230 assertEquals(PACKED, act.getStorage());
231 assertTrue(act.isPeeled());
232 assertFalse(act.isSymbolic());
233 assertEquals(exp.getName(), act.getName());
234 assertEquals(exp.getObjectId(), act.getObjectId());
235 assertEquals(0, act.getUpdateIndex());
236 assertNull(act.getPeeledObjectId());
237 assertFalse(rc.wasDeleted());
238 assertFalse(rc.next());
239 }
240 try (RefCursor rc = t.seekRef(MASTER)) {
241 assertTrue(rc.next());
242 Ref act = rc.getRef();
243 assertNotNull(act);
244 assertEquals(exp.getName(), act.getName());
245 assertEquals(0, act.getUpdateIndex());
246 assertFalse(rc.next());
247 }
248 }
249
250 @Test
251 public void oneTagRef() throws IOException {
252 Ref exp = tag(V1_0, 1, 2);
253 byte[] table = write(exp);
254 assertEquals(24 + 4 + 5 + 3 + V1_0.length() + 40 + 68, table.length);
255
256 ReftableReader t = read(table);
257 try (RefCursor rc = t.allRefs()) {
258 assertTrue(rc.next());
259 Ref act = rc.getRef();
260 assertNotNull(act);
261 assertEquals(PACKED, act.getStorage());
262 assertTrue(act.isPeeled());
263 assertFalse(act.isSymbolic());
264 assertEquals(exp.getName(), act.getName());
265 assertEquals(exp.getObjectId(), act.getObjectId());
266 assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId());
267 assertEquals(0, act.getUpdateIndex());
268 }
269 }
270
271 @Test
272 public void oneSymbolicRef() throws IOException {
273 Ref exp = sym(HEAD, MASTER);
274 byte[] table = write(exp);
275 assertEquals(
276 24 + 4 + 5 + 2 + HEAD.length() + 2 + MASTER.length() + 68,
277 table.length);
278
279 ReftableReader t = read(table);
280 try (RefCursor rc = t.allRefs()) {
281 assertTrue(rc.next());
282 Ref act = rc.getRef();
283 assertNotNull(act);
284 assertTrue(act.isSymbolic());
285 assertEquals(exp.getName(), act.getName());
286 assertNotNull(act.getLeaf());
287 assertEquals(MASTER, act.getTarget().getName());
288 assertNull(act.getObjectId());
289 assertEquals(0, act.getUpdateIndex());
290 }
291 }
292
293 @Test
294 public void resolveSymbolicRef() throws IOException {
295 Reftable t = read(write(
296 sym(HEAD, "refs/heads/tmp"),
297 sym("refs/heads/tmp", MASTER),
298 ref(MASTER, 1)));
299
300 Ref head = t.exactRef(HEAD);
301 assertNull(head.getObjectId());
302 assertEquals("refs/heads/tmp", head.getTarget().getName());
303 assertEquals(0, head.getUpdateIndex());
304
305 head = t.resolve(head);
306 assertNotNull(head);
307 assertEquals(id(1), head.getObjectId());
308 assertEquals(0, head.getUpdateIndex());
309
310 Ref master = t.exactRef(MASTER);
311 assertNotNull(master);
312 assertSame(master, t.resolve(master));
313 assertEquals(0, master.getUpdateIndex());
314 }
315
316 @Test
317 public void failDeepChainOfSymbolicRef() throws IOException {
318 Reftable t = read(write(
319 sym(HEAD, "refs/heads/1"),
320 sym("refs/heads/1", "refs/heads/2"),
321 sym("refs/heads/2", "refs/heads/3"),
322 sym("refs/heads/3", "refs/heads/4"),
323 sym("refs/heads/4", "refs/heads/5"),
324 sym("refs/heads/5", MASTER),
325 ref(MASTER, 1)));
326
327 Ref head = t.exactRef(HEAD);
328 assertNull(head.getObjectId());
329 assertNull(t.resolve(head));
330 }
331
332 @Test
333 public void oneDeletedRef() throws IOException {
334 String name = "refs/heads/gone";
335 Ref exp = newRef(name);
336 byte[] table = write(exp);
337 assertEquals(24 + 4 + 5 + 3 + name.length() + 68, table.length);
338
339 ReftableReader t = read(table);
340 try (RefCursor rc = t.allRefs()) {
341 assertFalse(rc.next());
342 }
343
344 t.setIncludeDeletes(true);
345 try (RefCursor rc = t.allRefs()) {
346 assertTrue(rc.next());
347 Ref act = rc.getRef();
348 assertNotNull(act);
349 assertFalse(act.isSymbolic());
350 assertEquals(name, act.getName());
351 assertEquals(NEW, act.getStorage());
352 assertNull(act.getObjectId());
353 assertTrue(rc.wasDeleted());
354 }
355 }
356
357 @Test
358 public void seekNotFound() throws IOException {
359 Ref exp = ref(MASTER, 1);
360 ReftableReader t = read(write(exp));
361 try (RefCursor rc = t.seekRef("refs/heads/a")) {
362 assertFalse(rc.next());
363 }
364 try (RefCursor rc = t.seekRef("refs/heads/n")) {
365 assertFalse(rc.next());
366 }
367 }
368
369 @Test
370 public void namespaceNotFound() throws IOException {
371 Ref exp = ref(MASTER, 1);
372 ReftableReader t = read(write(exp));
373 try (RefCursor rc = t.seekRefsWithPrefix("refs/changes/")) {
374 assertFalse(rc.next());
375 }
376 try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
377 assertFalse(rc.next());
378 }
379 }
380
381 @Test
382 public void namespaceHeads() throws IOException {
383 Ref master = ref(MASTER, 1);
384 Ref next = ref(NEXT, 2);
385 Ref v1 = tag(V1_0, 3, 4);
386
387 ReftableReader t = read(write(master, next, v1));
388 try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
389 assertTrue(rc.next());
390 assertEquals(V1_0, rc.getRef().getName());
391 assertEquals(0, rc.getRef().getUpdateIndex());
392 assertFalse(rc.next());
393 }
394 try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
395 assertTrue(rc.next());
396 assertEquals(MASTER, rc.getRef().getName());
397 assertEquals(0, rc.getRef().getUpdateIndex());
398
399 assertTrue(rc.next());
400 assertEquals(NEXT, rc.getRef().getName());
401 assertEquals(0, rc.getRef().getUpdateIndex());
402
403 assertFalse(rc.next());
404 }
405 }
406
407 @Test
408 public void seekPastRefWithRefCursor() throws IOException {
409 Ref exp = ref(MASTER, 1);
410 Ref next = ref(NEXT, 2);
411 Ref afterNext = ref(AFTER_NEXT, 3);
412 Ref afterNextNext = ref(LAST, 4);
413 ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
414 try (RefCursor rc = t.seekRefsWithPrefix("")) {
415 assertTrue(rc.next());
416 assertEquals(MASTER, rc.getRef().getName());
417
418 rc.seekPastPrefix("refs/heads/next/");
419
420 assertTrue(rc.next());
421 assertEquals(AFTER_NEXT, rc.getRef().getName());
422 assertTrue(rc.next());
423 assertEquals(LAST, rc.getRef().getName());
424
425 assertFalse(rc.next());
426 }
427 }
428
429 @Test
430 public void seekPastToNonExistentPrefixToTheMiddle() throws IOException {
431 Ref exp = ref(MASTER, 1);
432 Ref next = ref(NEXT, 2);
433 Ref afterNext = ref(AFTER_NEXT, 3);
434 Ref afterNextNext = ref(LAST, 4);
435 ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
436 try (RefCursor rc = t.seekRefsWithPrefix("")) {
437 rc.seekPastPrefix("refs/heads/master_non_existent");
438
439 assertTrue(rc.next());
440 assertEquals(NEXT, rc.getRef().getName());
441
442 assertTrue(rc.next());
443 assertEquals(AFTER_NEXT, rc.getRef().getName());
444
445 assertTrue(rc.next());
446 assertEquals(LAST, rc.getRef().getName());
447
448 assertFalse(rc.next());
449 }
450 }
451
452 @Test
453 public void seekPastToNonExistentPrefixToTheEnd() throws IOException {
454 Ref exp = ref(MASTER, 1);
455 Ref next = ref(NEXT, 2);
456 Ref afterNext = ref(AFTER_NEXT, 3);
457 Ref afterNextNext = ref(LAST, 4);
458 ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
459 try (RefCursor rc = t.seekRefsWithPrefix("")) {
460 rc.seekPastPrefix("refs/heads/nextnon_existent_end");
461 assertFalse(rc.next());
462 }
463 }
464
465 @Test
466 public void seekPastWithSeekRefsWithPrefix() throws IOException {
467 Ref exp = ref(MASTER, 1);
468 Ref next = ref(NEXT, 2);
469 Ref afterNext = ref(AFTER_NEXT, 3);
470 Ref afterNextNext = ref(LAST, 4);
471 Ref notRefsHeads = ref(NOT_REF_HEADS, 5);
472 ReftableReader t = read(write(exp, next, afterNext, afterNextNext, notRefsHeads));
473 try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
474 rc.seekPastPrefix("refs/heads/next/");
475 assertTrue(rc.next());
476 assertEquals(AFTER_NEXT, rc.getRef().getName());
477 assertTrue(rc.next());
478 assertEquals(LAST, rc.getRef().getName());
479
480
481
482 assertFalse(rc.next());
483 }
484 }
485
486 @Test
487 public void seekPastWithLotsOfRefs() throws IOException {
488 Ref[] refs = new Ref[500];
489 for (int i = 1; i <= 500; i++) {
490 refs[i - 1] = ref(String.format("refs/%d", Integer.valueOf(i)), i);
491 }
492 ReftableReader t = read(write(refs));
493 try (RefCursor rc = t.allRefs()) {
494 rc.seekPastPrefix("refs/3");
495 assertTrue(rc.next());
496 assertEquals("refs/4", rc.getRef().getName());
497 assertTrue(rc.next());
498 assertEquals("refs/40", rc.getRef().getName());
499
500 rc.seekPastPrefix("refs/8");
501 assertTrue(rc.next());
502 assertEquals("refs/9", rc.getRef().getName());
503 assertTrue(rc.next());
504 assertEquals("refs/90", rc.getRef().getName());
505 assertTrue(rc.next());
506 assertEquals("refs/91", rc.getRef().getName());
507 }
508 }
509
510 @Test
511 public void seekPastManyTimes() throws IOException {
512 Ref exp = ref(MASTER, 1);
513 Ref next = ref(NEXT, 2);
514 Ref afterNext = ref(AFTER_NEXT, 3);
515 Ref afterNextNext = ref(LAST, 4);
516 ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
517
518 try (RefCursor rc = t.seekRefsWithPrefix("")) {
519 rc.seekPastPrefix("refs/heads/master");
520 rc.seekPastPrefix("refs/heads/next");
521 rc.seekPastPrefix("refs/heads/nextnext");
522 rc.seekPastPrefix("refs/heads/nextnextnext");
523 assertFalse(rc.next());
524 }
525 }
526
527 @Test
528 public void seekPastOnEmptyTable() throws IOException {
529 ReftableReader t = read(write());
530 try (RefCursor rc = t.seekRefsWithPrefix("")) {
531 rc.seekPastPrefix("refs/");
532 assertFalse(rc.next());
533 }
534 }
535
536 @Test
537 public void indexScan() throws IOException {
538 List<Ref> refs = new ArrayList<>();
539 for (int i = 1; i <= 5670; i++) {
540 @SuppressWarnings("boxing")
541 Ref ref = ref(String.format("refs/heads/%04d", i), i);
542 refs.add(ref);
543 }
544
545 byte[] table = write(refs);
546 assertTrue(stats.refIndexLevels() > 0);
547 assertTrue(stats.refIndexSize() > 0);
548 assertScan(refs, read(table));
549 }
550
551 @Test
552 public void indexSeek() throws IOException {
553 List<Ref> refs = new ArrayList<>();
554 for (int i = 1; i <= 5670; i++) {
555 @SuppressWarnings("boxing")
556 Ref ref = ref(String.format("refs/heads/%04d", i), i);
557 refs.add(ref);
558 }
559
560 byte[] table = write(refs);
561 assertTrue(stats.refIndexLevels() > 0);
562 assertTrue(stats.refIndexSize() > 0);
563 assertSeek(refs, read(table));
564 }
565
566 @Test
567 public void noIndexScan() throws IOException {
568 List<Ref> refs = new ArrayList<>();
569 for (int i = 1; i <= 567; i++) {
570 @SuppressWarnings("boxing")
571 Ref ref = ref(String.format("refs/heads/%03d", i), i);
572 refs.add(ref);
573 }
574
575 byte[] table = write(refs);
576 assertEquals(0, stats.refIndexLevels());
577 assertEquals(0, stats.refIndexSize());
578 assertEquals(table.length, stats.totalBytes());
579 assertScan(refs, read(table));
580 }
581
582 @Test
583 public void noIndexSeek() throws IOException {
584 List<Ref> refs = new ArrayList<>();
585 for (int i = 1; i <= 567; i++) {
586 @SuppressWarnings("boxing")
587 Ref ref = ref(String.format("refs/heads/%03d", i), i);
588 refs.add(ref);
589 }
590
591 byte[] table = write(refs);
592 assertEquals(0, stats.refIndexLevels());
593 assertSeek(refs, read(table));
594 }
595
596 @Test
597 public void invalidRefWriteOrderSortAndWrite() {
598 Ref master = ref(MASTER, 1);
599 ReftableWriter writer = new ReftableWriter(new ByteArrayOutputStream())
600 .setMinUpdateIndex(1)
601 .setMaxUpdateIndex(1)
602 .begin();
603
604 List<Ref> refs = new ArrayList<>();
605 refs.add(master);
606 refs.add(master);
607
608 IllegalArgumentException e = assertThrows(
609 IllegalArgumentException.class,
610 () -> writer.sortAndWriteRefs(refs));
611 assertThat(e.getMessage(), containsString("records must be increasing"));
612 }
613
614 @Test
615 public void invalidReflogWriteOrderUpdateIndex() throws IOException {
616 ReftableWriter writer = new ReftableWriter(new ByteArrayOutputStream())
617 .setMinUpdateIndex(1)
618 .setMaxUpdateIndex(2)
619 .begin();
620 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
621 String msg = "test";
622
623 writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
624 IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
625 () -> writer.writeLog(
626 MASTER, 2, who, ObjectId.zeroId(), id(2), msg));
627 assertThat(e.getMessage(), containsString("records must be increasing"));
628 }
629
630 @Test
631 public void invalidReflogWriteOrderName() throws IOException {
632 ReftableWriter writer = new ReftableWriter(new ByteArrayOutputStream())
633 .setMinUpdateIndex(1)
634 .setMaxUpdateIndex(1)
635 .begin();
636 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
637 String msg = "test";
638
639 writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(1), msg);
640 IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
641 () -> writer.writeLog(
642 MASTER, 1, who, ObjectId.zeroId(), id(2), msg));
643 assertThat(e.getMessage(), containsString("records must be increasing"));
644 }
645
646 @Test
647 public void withReflog() throws IOException {
648 Ref master = ref(MASTER, 1);
649 Ref next = ref(NEXT, 2);
650 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
651 String msg = "test";
652
653 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
654 ReftableWriter writer = new ReftableWriter(buffer)
655 .setMinUpdateIndex(1)
656 .setMaxUpdateIndex(1)
657 .begin();
658
659 writer.writeRef(master);
660 writer.writeRef(next);
661
662 writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
663 writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
664
665 writer.finish();
666 byte[] table = buffer.toByteArray();
667 assertEquals(247, table.length);
668
669 ReftableReader t = read(table);
670 try (RefCursor rc = t.allRefs()) {
671 assertTrue(rc.next());
672 assertEquals(MASTER, rc.getRef().getName());
673 assertEquals(id(1), rc.getRef().getObjectId());
674 assertEquals(1, rc.getRef().getUpdateIndex());
675
676 assertTrue(rc.next());
677 assertEquals(NEXT, rc.getRef().getName());
678 assertEquals(id(2), rc.getRef().getObjectId());
679 assertEquals(1, rc.getRef().getUpdateIndex());
680 assertFalse(rc.next());
681 }
682 try (LogCursor lc = t.allLogs()) {
683 assertTrue(lc.next());
684 assertEquals(MASTER, lc.getRefName());
685 assertEquals(1, lc.getUpdateIndex());
686 assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
687 assertEquals(id(1), lc.getReflogEntry().getNewId());
688 assertEquals(who, lc.getReflogEntry().getWho());
689 assertEquals(msg, lc.getReflogEntry().getComment());
690
691 assertTrue(lc.next());
692 assertEquals(NEXT, lc.getRefName());
693 assertEquals(1, lc.getUpdateIndex());
694 assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
695 assertEquals(id(2), lc.getReflogEntry().getNewId());
696 assertEquals(who, lc.getReflogEntry().getWho());
697 assertEquals(msg, lc.getReflogEntry().getComment());
698
699 assertFalse(lc.next());
700 }
701 }
702
703 @Test
704 public void reflogReader() throws IOException {
705 Ref master = ref(MASTER, 1);
706 Ref next = ref(NEXT, 2);
707
708 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
709 ReftableWriter writer = new ReftableWriter(buffer).setMinUpdateIndex(1)
710 .setMaxUpdateIndex(1).begin();
711
712 writer.writeRef(master);
713 writer.writeRef(next);
714
715 PersonIdent who1 = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
716 writer.writeLog(MASTER, 3, who1, ObjectId.zeroId(), id(1), "1");
717 PersonIdent who2 = new PersonIdent("Log", "Ger", 1500079710, -8 * 60);
718 writer.writeLog(MASTER, 2, who2, id(1), id(2), "2");
719 PersonIdent who3 = new PersonIdent("Log", "Ger", 1500079711, -8 * 60);
720 writer.writeLog(MASTER, 1, who3, id(2), id(3), "3");
721
722 writer.finish();
723 byte[] table = buffer.toByteArray();
724
725 ReentrantLock lock = new ReentrantLock();
726 ReftableReader t = read(table);
727 ReftableReflogReader rlr = new ReftableReflogReader(lock, t, MASTER);
728
729 assertEquals(rlr.getLastEntry().getWho(), who1);
730 List<PersonIdent> all = rlr.getReverseEntries().stream()
731 .map(x -> x.getWho()).collect(Collectors.toList());
732 Matchers.contains(all, who3, who2, who1);
733
734 assertEquals(rlr.getReverseEntry(1).getWho(), who2);
735
736 List<ReflogEntry> reverse2 = rlr.getReverseEntries(2);
737 Matchers.contains(reverse2, who3, who2);
738
739 List<PersonIdent> more = rlr.getReverseEntries(4).stream()
740 .map(x -> x.getWho()).collect(Collectors.toList());
741 assertEquals(all, more);
742 }
743
744 @Test
745 public void allRefs() throws IOException {
746 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
747 ReftableConfig cfg = new ReftableConfig();
748 cfg.setRefBlockSize(1024);
749 cfg.setLogBlockSize(1024);
750 cfg.setAlignBlocks(true);
751 ReftableWriter writer = new ReftableWriter(buffer)
752 .setMinUpdateIndex(1)
753 .setMaxUpdateIndex(1)
754 .setConfig(cfg)
755 .begin();
756 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
757
758
759 List<String> names = new ArrayList<>();
760 for (int i = 0; i < 4; i++) {
761 @SuppressWarnings("boxing")
762 String name = new String(new char[220]).replace("\0", String.format("%c", i + 'a'));
763 names.add(name);
764 writer.writeRef(ref(name, i));
765 }
766
767
768 writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), "msg");
769 writer.finish();
770 byte[] table = buffer.toByteArray();
771
772 ReftableReader t = read(table);
773 RefCursor c = t.allRefs();
774
775 int j = 0;
776 while (c.next()) {
777 assertEquals(names.get(j), c.getRef().getName());
778 j++;
779 }
780 }
781
782
783 @Test
784 public void reflogSeek() throws IOException {
785 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
786 String msg = "test";
787 String msgNext = "test next";
788
789 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
790 ReftableWriter writer = new ReftableWriter(buffer)
791 .setMinUpdateIndex(1)
792 .setMaxUpdateIndex(1)
793 .begin();
794
795 writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
796 writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msgNext);
797
798 writer.finish();
799 byte[] table = buffer.toByteArray();
800
801 ReftableReader t = read(table);
802 try (LogCursor c = t.seekLog(MASTER, Long.MAX_VALUE)) {
803 assertTrue(c.next());
804 assertEquals(c.getReflogEntry().getComment(), msg);
805 }
806 try (LogCursor c = t.seekLog(MASTER, 0)) {
807 assertFalse(c.next());
808 }
809 try (LogCursor c = t.seekLog(MASTER, 1)) {
810 assertTrue(c.next());
811 assertEquals(c.getUpdateIndex(), 1);
812 assertEquals(c.getReflogEntry().getComment(), msg);
813 }
814 try (LogCursor c = t.seekLog(NEXT, Long.MAX_VALUE)) {
815 assertTrue(c.next());
816 assertEquals(c.getReflogEntry().getComment(), msgNext);
817 }
818 try (LogCursor c = t.seekLog(NEXT, 0)) {
819 assertFalse(c.next());
820 }
821 try (LogCursor c = t.seekLog(NEXT, 1)) {
822 assertTrue(c.next());
823 assertEquals(c.getUpdateIndex(), 1);
824 assertEquals(c.getReflogEntry().getComment(), msgNext);
825 }
826 }
827
828 @Test
829 public void reflogSeekPrefix() throws IOException {
830 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
831
832 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
833 ReftableWriter writer = new ReftableWriter(buffer)
834 .setMinUpdateIndex(1)
835 .setMaxUpdateIndex(1)
836 .begin();
837
838 writer.writeLog("branchname", 1, who, ObjectId.zeroId(), id(1), "branchname");
839
840 writer.finish();
841 byte[] table = buffer.toByteArray();
842
843 ReftableReader t = read(table);
844 try (LogCursor c = t.seekLog("branch", Long.MAX_VALUE)) {
845
846
847 assertFalse(c.next());
848 }
849 }
850
851 @Test
852 public void onlyReflog() throws IOException {
853 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
854 String msg = "test";
855
856 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
857 ReftableWriter writer = new ReftableWriter(buffer)
858 .setMinUpdateIndex(1)
859 .setMaxUpdateIndex(1)
860 .begin();
861 writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
862 writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(2), msg);
863 writer.finish();
864 byte[] table = buffer.toByteArray();
865 stats = writer.getStats();
866 assertEquals(170, table.length);
867 assertEquals(0, stats.refCount());
868 assertEquals(0, stats.refBytes());
869 assertEquals(0, stats.refIndexLevels());
870
871 ReftableReader t = read(table);
872 try (RefCursor rc = t.allRefs()) {
873 assertFalse(rc.next());
874 }
875 try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
876 assertFalse(rc.next());
877 }
878 try (LogCursor lc = t.allLogs()) {
879 assertTrue(lc.next());
880 assertEquals(MASTER, lc.getRefName());
881 assertEquals(1, lc.getUpdateIndex());
882 assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
883 assertEquals(id(1), lc.getReflogEntry().getNewId());
884 assertEquals(who, lc.getReflogEntry().getWho());
885
886 assertEquals(who.toExternalString(), lc.getReflogEntry().getWho().toExternalString());
887 assertEquals(msg, lc.getReflogEntry().getComment());
888
889 assertTrue(lc.next());
890 assertEquals(NEXT, lc.getRefName());
891 assertEquals(1, lc.getUpdateIndex());
892 assertEquals(ObjectId.zeroId(), lc.getReflogEntry().getOldId());
893 assertEquals(id(2), lc.getReflogEntry().getNewId());
894 assertEquals(who, lc.getReflogEntry().getWho());
895 assertEquals(msg, lc.getReflogEntry().getComment());
896
897 assertFalse(lc.next());
898 }
899 }
900
901 @Test
902 public void logScan() throws IOException {
903 ReftableConfig cfg = new ReftableConfig();
904 cfg.setRefBlockSize(256);
905 cfg.setLogBlockSize(2048);
906
907 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
908 ReftableWriter writer = new ReftableWriter(cfg, buffer);
909 writer.setMinUpdateIndex(1).setMaxUpdateIndex(1).begin();
910
911 List<Ref> refs = new ArrayList<>();
912 for (int i = 1; i <= 5670; i++) {
913 @SuppressWarnings("boxing")
914 Ref ref = ref(String.format("refs/heads/%04d", i), i);
915 refs.add(ref);
916 writer.writeRef(ref);
917 }
918
919 PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
920 for (Ref ref : refs) {
921 writer.writeLog(ref.getName(), 1, who,
922 ObjectId.zeroId(), ref.getObjectId(),
923 "create " + ref.getName());
924 }
925 writer.finish();
926 stats = writer.getStats();
927 assertTrue(stats.logBytes() > 4096);
928 byte[] table = buffer.toByteArray();
929
930 ReftableReader t = read(table);
931 try (LogCursor lc = t.allLogs()) {
932 for (Ref exp : refs) {
933 assertTrue("has " + exp.getName(), lc.next());
934 assertEquals(exp.getName(), lc.getRefName());
935 ReflogEntry entry = lc.getReflogEntry();
936 assertNotNull(entry);
937 assertEquals(who, entry.getWho());
938 assertEquals(ObjectId.zeroId(), entry.getOldId());
939 assertEquals(exp.getObjectId(), entry.getNewId());
940 assertEquals("create " + exp.getName(), entry.getComment());
941 }
942 assertFalse(lc.next());
943 }
944 }
945
946 @Test
947 public void byObjectIdOneRefNoIndex() throws IOException {
948 List<Ref> refs = new ArrayList<>();
949 for (int i = 1; i <= 200; i++) {
950 @SuppressWarnings("boxing")
951 Ref ref = ref(String.format("refs/heads/%02d", i), i);
952 refs.add(ref);
953 }
954 refs.add(ref("refs/heads/master", 100));
955
956 ReftableReader t = read(write(refs));
957 assertEquals(0, stats.objIndexSize());
958
959 try (RefCursor rc = t.byObjectId(id(42))) {
960 assertTrue("has 42", rc.next());
961 assertEquals("refs/heads/42", rc.getRef().getName());
962 assertEquals(id(42), rc.getRef().getObjectId());
963 assertEquals(0, rc.getRef().getUpdateIndex());
964 assertFalse(rc.next());
965 }
966 try (RefCursor rc = t.byObjectId(id(100))) {
967 assertTrue("has 100", rc.next());
968 assertEquals("refs/heads/100", rc.getRef().getName());
969 assertEquals(id(100), rc.getRef().getObjectId());
970
971 assertTrue("has master", rc.next());
972 assertEquals("refs/heads/master", rc.getRef().getName());
973 assertEquals(id(100), rc.getRef().getObjectId());
974 assertEquals(0, rc.getRef().getUpdateIndex());
975
976 assertFalse(rc.next());
977 }
978 }
979
980 @Test
981 public void byObjectIdOneRefWithIndex() throws IOException {
982 List<Ref> refs = new ArrayList<>();
983 for (int i = 1; i <= 5200; i++) {
984 @SuppressWarnings("boxing")
985 Ref ref = ref(String.format("refs/heads/%02d", i), i);
986 refs.add(ref);
987 }
988 refs.add(ref("refs/heads/master", 100));
989
990 ReftableReader t = read(write(refs));
991 assertTrue(stats.objIndexSize() > 0);
992
993 try (RefCursor rc = t.byObjectId(id(42))) {
994 assertTrue("has 42", rc.next());
995 assertEquals("refs/heads/42", rc.getRef().getName());
996 assertEquals(id(42), rc.getRef().getObjectId());
997 assertEquals(0, rc.getRef().getUpdateIndex());
998 assertFalse(rc.next());
999 }
1000 try (RefCursor rc = t.byObjectId(id(100))) {
1001 assertTrue("has 100", rc.next());
1002 assertEquals("refs/heads/100", rc.getRef().getName());
1003 assertEquals(id(100), rc.getRef().getObjectId());
1004
1005 assertTrue("has master", rc.next());
1006 assertEquals("refs/heads/master", rc.getRef().getName());
1007 assertEquals(id(100), rc.getRef().getObjectId());
1008 assertEquals(0, rc.getRef().getUpdateIndex());
1009
1010 assertFalse(rc.next());
1011 }
1012 }
1013
1014 @Test
1015 public void byObjectIdSkipPastPrefix() throws IOException {
1016 ReftableReader t = read(write());
1017 try (RefCursor rc = t.byObjectId(id(2))) {
1018 assertThrows(UnsupportedOperationException.class, () -> rc.seekPastPrefix("refs/heads/"));
1019 }
1020 }
1021
1022 @Test
1023 public void unpeeledDoesNotWrite() {
1024 try {
1025 write(new ObjectIdRef.Unpeeled(PACKED, MASTER, id(1)));
1026 fail("expected IOException");
1027 } catch (IOException e) {
1028 assertEquals(JGitText.get().peeledRefIsRequired, e.getMessage());
1029 }
1030 }
1031
1032 @Test
1033 public void skipPastRefWithLastUTF8() throws IOException {
1034 ReftableReader t = read(write(ref(String.format("refs/heads/%sbla", new String(LAST_UTF8_CHAR
1035 , UTF_8)), 1)));
1036
1037 try (RefCursor rc = t.allRefs()) {
1038 rc.seekPastPrefix("refs/heads/");
1039 assertFalse(rc.next());
1040 }
1041 }
1042
1043
1044 @Test
1045 public void nameTooLongDoesNotWrite() throws IOException {
1046 try {
1047 ReftableConfig cfg = new ReftableConfig();
1048 cfg.setRefBlockSize(64);
1049
1050 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1051 ReftableWriter writer = new ReftableWriter(cfg, buffer).begin();
1052 writer.writeRef(ref("refs/heads/i-am-not-a-teapot", 1));
1053 writer.finish();
1054 fail("expected BlockSizeTooSmallException");
1055 } catch (BlockSizeTooSmallException e) {
1056 assertEquals(85, e.getMinimumBlockSize());
1057 }
1058 }
1059
1060 @Test
1061 public void badCrc32() throws IOException {
1062 byte[] table = write();
1063 table[table.length - 1] = 0x42;
1064
1065 try {
1066 read(table).seekRef(HEAD);
1067 fail("expected IOException");
1068 } catch (IOException e) {
1069 assertEquals(JGitText.get().invalidReftableCRC, e.getMessage());
1070 }
1071 }
1072
1073 private static void assertScan(List<Ref> refs, Reftable t)
1074 throws IOException {
1075 try (RefCursor rc = t.allRefs()) {
1076 for (Ref exp : refs) {
1077 assertTrue("has " + exp.getName(), rc.next());
1078 Ref act = rc.getRef();
1079 assertEquals(exp.getName(), act.getName());
1080 assertEquals(exp.getObjectId(), act.getObjectId());
1081 assertEquals(0, rc.getRef().getUpdateIndex());
1082 }
1083 assertFalse(rc.next());
1084 }
1085 }
1086
1087 private static void assertSeek(List<Ref> refs, Reftable t)
1088 throws IOException {
1089 for (Ref exp : refs) {
1090 try (RefCursor rc = t.seekRef(exp.getName())) {
1091 assertTrue("has " + exp.getName(), rc.next());
1092 Ref act = rc.getRef();
1093 assertEquals(exp.getName(), act.getName());
1094 assertEquals(exp.getObjectId(), act.getObjectId());
1095 assertEquals(0, rc.getRef().getUpdateIndex());
1096 assertFalse(rc.next());
1097 }
1098 }
1099 }
1100
1101 private static Ref ref(String name, int id) {
1102 return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
1103 }
1104
1105 private static Ref tag(String name, int id1, int id2) {
1106 return new ObjectIdRef.PeeledTag(PACKED, name, id(id1), id(id2));
1107 }
1108
1109 private static Ref sym(String name, String target) {
1110 return new SymbolicRef(name, newRef(target));
1111 }
1112
1113 private static Ref newRef(String name) {
1114 return new ObjectIdRef.Unpeeled(NEW, name, null);
1115 }
1116
1117 private static ObjectId id(int i) {
1118 byte[] buf = new byte[OBJECT_ID_LENGTH];
1119 buf[0] = (byte) (i & 0xff);
1120 buf[1] = (byte) ((i >>> 8) & 0xff);
1121 buf[2] = (byte) ((i >>> 16) & 0xff);
1122 buf[3] = (byte) (i >>> 24);
1123 return ObjectId.fromRaw(buf);
1124 }
1125
1126 private static ReftableReader read(byte[] table) {
1127 return new ReftableReader(BlockSource.from(table));
1128 }
1129
1130 private byte[] write(Ref... refs) throws IOException {
1131 return write(Arrays.asList(refs));
1132 }
1133
1134 private byte[] write(Collection<Ref> refs) throws IOException {
1135 return write(refs, new ReftableConfig());
1136 }
1137
1138 private byte[] write(Collection<Ref> refs, ReftableConfig cfg) throws IOException {
1139 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1140 stats = new ReftableWriter(buffer)
1141 .setConfig(cfg)
1142 .begin()
1143 .sortAndWriteRefs(refs)
1144 .finish()
1145 .getStats();
1146 return buffer.toByteArray();
1147 }
1148 }