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.internal.storage.reftable;
45
46 import static java.lang.Math.log;
47 import static java.nio.charset.StandardCharsets.UTF_8;
48 import static org.eclipse.jgit.internal.storage.reftable.BlockWriter.padBetweenBlocks;
49 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
50 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
51 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_MAGIC;
52 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
53 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
54 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_BLOCK_SIZE;
55 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.MAX_RESTARTS;
56 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.OBJ_BLOCK_TYPE;
57 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
58 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
59 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
60
61 import java.io.IOException;
62 import java.io.OutputStream;
63 import java.text.MessageFormat;
64 import java.util.ArrayList;
65 import java.util.Collection;
66 import java.util.Collections;
67 import java.util.HashSet;
68 import java.util.Iterator;
69 import java.util.List;
70 import java.util.Set;
71 import java.util.zip.CRC32;
72
73 import org.eclipse.jgit.annotations.Nullable;
74 import org.eclipse.jgit.internal.JGitText;
75 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.DeleteLogEntry;
76 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.Entry;
77 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.IndexEntry;
78 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
79 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.ObjEntry;
80 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.RefEntry;
81 import org.eclipse.jgit.lib.AbbreviatedObjectId;
82 import org.eclipse.jgit.lib.AnyObjectId;
83 import org.eclipse.jgit.lib.ObjectId;
84 import org.eclipse.jgit.lib.ObjectIdOwnerMap;
85 import org.eclipse.jgit.lib.ObjectIdSubclassMap;
86 import org.eclipse.jgit.lib.PersonIdent;
87 import org.eclipse.jgit.lib.Ref;
88 import org.eclipse.jgit.util.LongList;
89 import org.eclipse.jgit.util.NB;
90
91
92
93
94
95
96
97
98
99 public class ReftableWriter {
100 private ReftableConfig config;
101 private int refBlockSize;
102 private int logBlockSize;
103 private int restartInterval;
104 private int maxIndexLevels;
105 private boolean alignBlocks;
106 private boolean indexObjects;
107
108 private long minUpdateIndex;
109 private long maxUpdateIndex;
110
111 private ReftableOutputStream out;
112 private ObjectIdSubclassMap<RefList> obj2ref;
113
114 private BlockWriter.Entry lastRef;
115 private BlockWriter.Entry lastLog;
116 private BlockWriter cur;
117 private Section refs;
118 private Section objs;
119 private Section logs;
120 private int objIdLen;
121 private Stats stats;
122
123
124
125
126 public ReftableWriter() {
127 this(new ReftableConfig());
128 lastRef = null;
129 lastLog = null;
130 }
131
132
133
134
135
136
137
138 public ReftableWriter(ReftableConfig cfg) {
139 config = cfg;
140 }
141
142
143
144
145
146
147
148
149 public ReftableWriter setConfig(ReftableConfig cfg) {
150 this.config = cfg != null ? cfg : new ReftableConfig();
151 return this;
152 }
153
154
155
156
157
158
159
160
161
162
163
164 public ReftableWriter setMinUpdateIndex(long min) {
165 minUpdateIndex = min;
166 return this;
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180 public ReftableWriter setMaxUpdateIndex(long max) {
181 maxUpdateIndex = max;
182 return this;
183 }
184
185
186
187
188
189
190
191
192
193
194
195 public ReftableWriter begin(OutputStream os) throws IOException {
196 refBlockSize = config.getRefBlockSize();
197 logBlockSize = config.getLogBlockSize();
198 restartInterval = config.getRestartInterval();
199 maxIndexLevels = config.getMaxIndexLevels();
200 alignBlocks = config.isAlignBlocks();
201 indexObjects = config.isIndexObjects();
202
203 if (refBlockSize <= 0) {
204 refBlockSize = 4 << 10;
205 } else if (refBlockSize > MAX_BLOCK_SIZE) {
206 throw new IllegalArgumentException();
207 }
208 if (logBlockSize <= 0) {
209 logBlockSize = 2 * refBlockSize;
210 }
211 if (restartInterval <= 0) {
212 restartInterval = refBlockSize < (60 << 10) ? 16 : 64;
213 }
214
215 out = new ReftableOutputStream(os, refBlockSize, alignBlocks);
216 refs = new Section(REF_BLOCK_TYPE);
217 if (indexObjects) {
218 obj2ref = new ObjectIdSubclassMap<>();
219 }
220 writeFileHeader();
221 return this;
222 }
223
224
225
226
227
228
229
230
231
232
233 public ReftableWriter sortAndWriteRefs(Collection<Ref> refsToPack)
234 throws IOException {
235 Iterator<RefEntry> itr = refsToPack.stream()
236 .map(r -> new RefEntry(r, maxUpdateIndex - minUpdateIndex))
237 .sorted(Entry::compare)
238 .iterator();
239 while (itr.hasNext()) {
240 RefEntry entry = itr.next();
241 long blockPos = refs.write(entry);
242 indexRef(entry.ref, blockPos);
243 }
244 return this;
245 }
246
247
248
249
250
251
252
253
254
255
256
257 public void writeRef(Ref ref) throws IOException {
258 writeRef(ref, maxUpdateIndex);
259 }
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274 public void writeRef(Ref ref, long updateIndex) throws IOException {
275 if (updateIndex < minUpdateIndex) {
276 throw new IllegalArgumentException();
277 }
278 long d = updateIndex - minUpdateIndex;
279 RefEntry entry = new RefEntry(ref, d);
280 if (lastRef != null && Entry.compare(lastRef, entry) >= 0) {
281 throwIllegalEntry(lastRef, entry);
282 }
283 lastRef = entry;
284
285 long blockPos = refs.write(entry);
286 indexRef(ref, blockPos);
287 }
288
289 private void throwIllegalEntry(Entry last, Entry now) {
290 throw new IllegalArgumentException(MessageFormat.format(
291 JGitText.get().refTableRecordsMustIncrease,
292 new String(last.key, UTF_8), new String(now.key, UTF_8)));
293 }
294
295 private void indexRef(Ref ref, long blockPos) {
296 if (indexObjects && !ref.isSymbolic()) {
297 indexId(ref.getObjectId(), blockPos);
298 indexId(ref.getPeeledObjectId(), blockPos);
299 }
300 }
301
302 private void indexId(ObjectId id, long blockPos) {
303 if (id != null) {
304 RefList l = obj2ref.get(id);
305 if (l == null) {
306 l = new RefList(id);
307 obj2ref.add(l);
308 }
309 l.addBlock(blockPos);
310 }
311 }
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339 public void writeLog(String ref, long updateIndex, PersonIdent who,
340 ObjectId oldId, ObjectId newId, @Nullable String message)
341 throws IOException {
342 String msg = message != null ? message : "";
343 beginLog();
344 LogEntry entry = new LogEntry(ref, updateIndex, who, oldId, newId, msg);
345 if (lastLog != null && Entry.compare(lastLog, entry) >= 0) {
346 throwIllegalEntry(lastLog, entry);
347 }
348 lastLog = entry;
349 logs.write(entry);
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371 public void deleteLog(String ref, long updateIndex) throws IOException {
372 beginLog();
373 logs.write(new DeleteLogEntry(ref, updateIndex));
374 }
375
376 private void beginLog() throws IOException {
377 if (logs == null) {
378 finishRefAndObjSections();
379 out.flushFileHeader();
380 out.setBlockSize(logBlockSize);
381 logs = new Section(LOG_BLOCK_TYPE);
382 }
383 }
384
385
386
387
388
389
390
391
392
393
394
395 public long estimateTotalBytes() {
396 long bytes = out.size();
397 if (bytes == 0) {
398 bytes += FILE_HEADER_LEN;
399 }
400 if (cur != null) {
401 long curBlockPos = out.size();
402 int sz = cur.currentSize();
403 bytes += sz;
404
405 IndexBuilder idx = null;
406 if (cur.blockType() == REF_BLOCK_TYPE) {
407 idx = refs.idx;
408 } else if (cur.blockType() == LOG_BLOCK_TYPE) {
409 idx = logs.idx;
410 }
411 if (idx != null && shouldHaveIndex(idx)) {
412 if (idx == refs.idx) {
413 bytes += out.estimatePadBetweenBlocks(sz);
414 }
415 bytes += idx.estimateBytes(curBlockPos);
416 }
417 }
418 bytes += FILE_FOOTER_LEN;
419 return bytes;
420 }
421
422
423
424
425
426
427
428
429 public ReftableWriter finish() throws IOException {
430 finishRefAndObjSections();
431 finishLogSection();
432 writeFileFooter();
433 out.finishFile();
434
435 stats = new Stats(this, out);
436 out = null;
437 obj2ref = null;
438 cur = null;
439 refs = null;
440 objs = null;
441 logs = null;
442 return this;
443 }
444
445 private void finishRefAndObjSections() throws IOException {
446 if (cur != null && cur.blockType() == REF_BLOCK_TYPE) {
447 refs.finishSectionMaybeWriteIndex();
448 if (indexObjects && !obj2ref.isEmpty() && refs.idx.bytes > 0) {
449 writeObjBlocks();
450 }
451 obj2ref = null;
452 }
453 }
454
455 private void writeObjBlocks() throws IOException {
456 List<RefList> sorted = sortById(obj2ref);
457 obj2ref = null;
458 objIdLen = shortestUniqueAbbreviation(sorted);
459
460 out.padBetweenBlocksToNextBlock();
461 objs = new Section(OBJ_BLOCK_TYPE);
462 objs.entryCnt = sorted.size();
463 for (RefList l : sorted) {
464 objs.write(new ObjEntry(objIdLen, l, l.blockPos));
465 }
466 objs.finishSectionMaybeWriteIndex();
467 }
468
469 private void finishLogSection() throws IOException {
470 if (cur != null && cur.blockType() == LOG_BLOCK_TYPE) {
471 logs.finishSectionMaybeWriteIndex();
472 }
473 }
474
475 private boolean shouldHaveIndex(IndexBuilder idx) {
476 int threshold;
477 if (idx == refs.idx && alignBlocks) {
478 threshold = 4;
479 } else {
480 threshold = 1;
481 }
482 return idx.entries.size() + (cur != null ? 1 : 0) > threshold;
483 }
484
485 private void writeFileHeader() {
486 byte[] hdr = new byte[FILE_HEADER_LEN];
487 encodeHeader(hdr);
488 out.write(hdr, 0, FILE_HEADER_LEN);
489 }
490
491 private void encodeHeader(byte[] hdr) {
492 System.arraycopy(FILE_HEADER_MAGIC, 0, hdr, 0, 4);
493 int bs = alignBlocks ? refBlockSize : 0;
494 NB.encodeInt32(hdr, 4, (VERSION_1 << 24) | bs);
495 NB.encodeInt64(hdr, 8, minUpdateIndex);
496 NB.encodeInt64(hdr, 16, maxUpdateIndex);
497 }
498
499 private void writeFileFooter() {
500 int ftrLen = FILE_FOOTER_LEN;
501 byte[] ftr = new byte[ftrLen];
502 encodeHeader(ftr);
503
504 NB.encodeInt64(ftr, 24, indexPosition(refs));
505 NB.encodeInt64(ftr, 32, (firstBlockPosition(objs) << 5) | objIdLen);
506 NB.encodeInt64(ftr, 40, indexPosition(objs));
507 NB.encodeInt64(ftr, 48, firstBlockPosition(logs));
508 NB.encodeInt64(ftr, 56, indexPosition(logs));
509
510 CRC32 crc = new CRC32();
511 crc.update(ftr, 0, ftrLen - 4);
512 NB.encodeInt32(ftr, ftrLen - 4, (int) crc.getValue());
513
514 out.write(ftr, 0, ftrLen);
515 }
516
517 private static long firstBlockPosition(@Nullable Section s) {
518 return s != null ? s.firstBlockPosition : 0;
519 }
520
521 private static long indexPosition(@Nullable Section s) {
522 return s != null && s.idx != null ? s.idx.rootPosition : 0;
523 }
524
525
526
527
528
529
530 public Stats getStats() {
531 return stats;
532 }
533
534
535 public static class Stats {
536 private final int refBlockSize;
537 private final int logBlockSize;
538 private final int restartInterval;
539
540 private final long minUpdateIndex;
541 private final long maxUpdateIndex;
542
543 private final long refCnt;
544 private final long objCnt;
545 private final int objIdLen;
546 private final long logCnt;
547 private final long refBytes;
548 private final long objBytes;
549 private final long logBytes;
550 private final long paddingUsed;
551 private final long totalBytes;
552
553 private final int refIndexSize;
554 private final int refIndexLevels;
555 private final int objIndexSize;
556 private final int objIndexLevels;
557
558 Stats(ReftableWriter w, ReftableOutputStream o) {
559 refBlockSize = w.refBlockSize;
560 logBlockSize = w.logBlockSize;
561 restartInterval = w.restartInterval;
562
563 minUpdateIndex = w.minUpdateIndex;
564 maxUpdateIndex = w.maxUpdateIndex;
565 paddingUsed = o.paddingUsed();
566 totalBytes = o.size();
567
568 refCnt = w.refs.entryCnt;
569 refBytes = w.refs.bytes;
570
571 objCnt = w.objs != null ? w.objs.entryCnt : 0;
572 objBytes = w.objs != null ? w.objs.bytes : 0;
573 objIdLen = w.objIdLen;
574
575 logCnt = w.logs != null ? w.logs.entryCnt : 0;
576 logBytes = w.logs != null ? w.logs.bytes : 0;
577
578 IndexBuilder refIdx = w.refs.idx;
579 refIndexSize = refIdx.bytes;
580 refIndexLevels = refIdx.levels;
581
582 IndexBuilder objIdx = w.objs != null ? w.objs.idx : null;
583 objIndexSize = objIdx != null ? objIdx.bytes : 0;
584 objIndexLevels = objIdx != null ? objIdx.levels : 0;
585 }
586
587
588 public int refBlockSize() {
589 return refBlockSize;
590 }
591
592
593 public int logBlockSize() {
594 return logBlockSize;
595 }
596
597
598 public int restartInterval() {
599 return restartInterval;
600 }
601
602
603 public long minUpdateIndex() {
604 return minUpdateIndex;
605 }
606
607
608 public long maxUpdateIndex() {
609 return maxUpdateIndex;
610 }
611
612
613 public long refCount() {
614 return refCnt;
615 }
616
617
618 public long objCount() {
619 return objCnt;
620 }
621
622
623 public long logCount() {
624 return logCnt;
625 }
626
627
628 public long refBytes() {
629 return refBytes;
630 }
631
632
633 public long objBytes() {
634 return objBytes;
635 }
636
637
638 public long logBytes() {
639 return logBytes;
640 }
641
642
643 public long totalBytes() {
644 return totalBytes;
645 }
646
647
648 public long paddingBytes() {
649 return paddingUsed;
650 }
651
652
653 public int refIndexSize() {
654 return refIndexSize;
655 }
656
657
658 public int refIndexLevels() {
659 return refIndexLevels;
660 }
661
662
663 public int objIndexSize() {
664 return objIndexSize;
665 }
666
667
668 public int objIndexLevels() {
669 return objIndexLevels;
670 }
671
672
673
674
675
676
677 public int objIdLength() {
678 return objIdLen;
679 }
680 }
681
682 private static List<RefList> sortById(ObjectIdSubclassMap<RefList> m) {
683 List<RefList> s = new ArrayList<>(m.size());
684 for (RefList l : m) {
685 s.add(l);
686 }
687 Collections.sort(s);
688 return s;
689 }
690
691 private static int shortestUniqueAbbreviation(List<RefList> in) {
692
693 int bytes = Math.max(2, (int) (log(in.size()) / log(8)));
694 Set<AbbreviatedObjectId> tmp = new HashSet<>((int) (in.size() * 0.75f));
695 retry: for (;;) {
696 int hexLen = bytes * 2;
697 for (ObjectId id : in) {
698 AbbreviatedObjectId a = id.abbreviate(hexLen);
699 if (!tmp.add(a)) {
700 if (++bytes >= OBJECT_ID_LENGTH) {
701 return OBJECT_ID_LENGTH;
702 }
703 tmp.clear();
704 continue retry;
705 }
706 }
707 return bytes;
708 }
709 }
710
711 private static class RefList extends ObjectIdOwnerMap.Entry {
712 final LongListList.html#LongList">LongList blockPos = new LongList(2);
713
714 RefList(AnyObjectId id) {
715 super(id);
716 }
717
718 void addBlock(long pos) {
719 if (!blockPos.contains(pos)) {
720 blockPos.add(pos);
721 }
722 }
723 }
724
725 private class Section {
726 final IndexBuilder idx;
727 final long firstBlockPosition;
728
729 long entryCnt;
730 long bytes;
731
732 Section(byte keyType) {
733 idx = new IndexBuilder(keyType);
734 firstBlockPosition = out.size();
735 }
736
737 long write(BlockWriter.Entry entry) throws IOException {
738 if (cur == null) {
739 beginBlock(entry);
740 } else if (!cur.tryAdd(entry)) {
741 flushCurBlock();
742 if (cur.padBetweenBlocks()) {
743 out.padBetweenBlocksToNextBlock();
744 }
745 beginBlock(entry);
746 }
747 entryCnt++;
748 return out.size();
749 }
750
751 private void beginBlock(BlockWriter.Entry entry)
752 throws BlockSizeTooSmallException {
753 byte blockType = entry.blockType();
754 int bs = out.bytesAvailableInBlock();
755 cur = new BlockWriter(blockType, idx.keyType, bs, restartInterval);
756 cur.mustAdd(entry);
757 }
758
759 void flushCurBlock() throws IOException {
760 idx.entries.add(new IndexEntry(cur.lastKey(), out.size()));
761 cur.writeTo(out);
762 }
763
764 void finishSectionMaybeWriteIndex() throws IOException {
765 flushCurBlock();
766 cur = null;
767 if (shouldHaveIndex(idx)) {
768 idx.writeIndex();
769 }
770 bytes = out.size() - firstBlockPosition;
771 }
772 }
773
774 private class IndexBuilder {
775 final byte keyType;
776 List<IndexEntry> entries = new ArrayList<>();
777 long rootPosition;
778 int bytes;
779 int levels;
780
781 IndexBuilder(byte kt) {
782 keyType = kt;
783 }
784
785 int estimateBytes(long curBlockPos) {
786 BlockWriter b = new BlockWriter(
787 INDEX_BLOCK_TYPE, keyType,
788 MAX_BLOCK_SIZE,
789 Math.max(restartInterval, entries.size() / MAX_RESTARTS));
790 try {
791 for (Entry e : entries) {
792 b.mustAdd(e);
793 }
794 if (cur != null) {
795 b.mustAdd(new IndexEntry(cur.lastKey(), curBlockPos));
796 }
797 } catch (BlockSizeTooSmallException e) {
798 return b.currentSize();
799 }
800 return b.currentSize();
801 }
802
803 void writeIndex() throws IOException {
804 if (padBetweenBlocks(keyType)) {
805 out.padBetweenBlocksToNextBlock();
806 }
807 long startPos = out.size();
808 writeMultiLevelIndex(entries);
809 bytes = (int) (out.size() - startPos);
810 entries = null;
811 }
812
813 private void writeMultiLevelIndex(List<IndexEntry> keys)
814 throws IOException {
815 levels = 1;
816 while (maxIndexLevels == 0 || levels < maxIndexLevels) {
817 keys = writeOneLevel(keys);
818 if (keys == null) {
819 return;
820 }
821 levels++;
822 }
823
824
825
826 BlockWriter b = new BlockWriter(
827 INDEX_BLOCK_TYPE, keyType,
828 MAX_BLOCK_SIZE,
829 Math.max(restartInterval, keys.size() / MAX_RESTARTS));
830 for (Entry e : keys) {
831 b.mustAdd(e);
832 }
833 rootPosition = out.size();
834 b.writeTo(out);
835 }
836
837 private List<IndexEntry> writeOneLevel(List<IndexEntry> keys)
838 throws IOException {
839 Section thisLevel = new Section(keyType);
840 for (Entry e : keys) {
841 thisLevel.write(e);
842 }
843 if (!thisLevel.idx.entries.isEmpty()) {
844 thisLevel.flushCurBlock();
845 if (cur.padBetweenBlocks()) {
846 out.padBetweenBlocksToNextBlock();
847 }
848 cur = null;
849 return thisLevel.idx.entries;
850 }
851
852
853 rootPosition = out.size();
854 cur.writeTo(out);
855 cur = null;
856 return null;
857 }
858 }
859 }