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