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.internal.storage.reftable.BlockReader.decodeBlockLen;
15 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_BLOCK_TYPE;
16 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_FOOTER_LEN;
17 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
18 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
19 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
20 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.REF_BLOCK_TYPE;
21 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.VERSION_1;
22 import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.isFileHeaderMagic;
23 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
24
25 import java.io.IOException;
26 import java.nio.ByteBuffer;
27 import java.text.MessageFormat;
28 import java.util.Arrays;
29 import java.util.zip.CRC32;
30
31 import org.eclipse.jgit.internal.JGitText;
32 import org.eclipse.jgit.internal.storage.io.BlockSource;
33 import org.eclipse.jgit.internal.storage.reftable.BlockWriter.LogEntry;
34 import org.eclipse.jgit.lib.AnyObjectId;
35 import org.eclipse.jgit.lib.ObjectId;
36 import org.eclipse.jgit.lib.Ref;
37 import org.eclipse.jgit.lib.ReflogEntry;
38 import org.eclipse.jgit.util.LongList;
39 import org.eclipse.jgit.util.LongMap;
40 import org.eclipse.jgit.util.NB;
41
42
43
44
45
46
47
48 public class ReftableReader extends Reftable implements AutoCloseable {
49 private final BlockSource src;
50
51 private int blockSize = -1;
52 private long minUpdateIndex;
53 private long maxUpdateIndex;
54
55 private long refEnd;
56 private long objPosition;
57 private long objEnd;
58 private long logPosition;
59 private long logEnd;
60 private int objIdLen;
61
62 private long refIndexPosition = -1;
63 private long objIndexPosition = -1;
64 private long logIndexPosition = -1;
65
66 private BlockReader refIndex;
67 private BlockReader objIndex;
68 private BlockReader logIndex;
69 private LongMap<BlockReader> indexCache;
70
71
72
73
74
75
76
77 public ReftableReader(BlockSource src) {
78 this.src = src;
79 }
80
81
82
83
84
85
86
87
88
89
90
91 public int blockSize() throws IOException {
92 if (blockSize == -1) {
93 readFileHeader();
94 }
95 return blockSize;
96 }
97
98 @Override
99 public boolean hasObjectMap() throws IOException {
100 if (objIndexPosition == -1) {
101 readFileFooter();
102 }
103
104
105 return (objPosition > 0 || refEnd == 24 || refIndexPosition == 0);
106 }
107
108
109
110
111 @Override
112 public long minUpdateIndex() throws IOException {
113 if (blockSize == -1) {
114 readFileHeader();
115 }
116 return minUpdateIndex;
117 }
118
119
120
121
122 @Override
123 public long maxUpdateIndex() throws IOException {
124 if (blockSize == -1) {
125 readFileHeader();
126 }
127 return maxUpdateIndex;
128 }
129
130
131 @Override
132 public RefCursor allRefs() throws IOException {
133 if (blockSize == -1) {
134 readFileHeader();
135 }
136
137 if (refEnd == 0) {
138 readFileFooter();
139 }
140 src.adviseSequentialRead(0, refEnd);
141
142 RefCursorImpl i = new RefCursorImpl(refEnd, null, false);
143 i.block = readBlock(0, refEnd);
144 return i;
145 }
146
147
148 @Override
149 public RefCursor seekRef(String refName) throws IOException {
150 initRefIndex();
151
152 byte[] key = refName.getBytes(UTF_8);
153 RefCursorImpl i = new RefCursorImpl(refEnd, key, false);
154 i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
155 return i;
156 }
157
158
159 @Override
160 public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
161 initRefIndex();
162
163 byte[] key = prefix.getBytes(UTF_8);
164 RefCursorImpl i = new RefCursorImpl(refEnd, key, true);
165 i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
166 return i;
167 }
168
169
170 @Override
171 public RefCursor byObjectId(AnyObjectId id) throws IOException {
172 initObjIndex();
173 ObjCursorImpl i = new ObjCursorImpl(refEnd, id);
174 if (objIndex != null) {
175 i.initSeek();
176 } else {
177 i.initScan();
178 }
179 return i;
180 }
181
182
183 @Override
184 public LogCursor allLogs() throws IOException {
185 initLogIndex();
186 if (logPosition > 0) {
187 src.adviseSequentialRead(logPosition, logEnd);
188 LogCursorImpl i = new LogCursorImpl(logEnd, null);
189 i.block = readBlock(logPosition, logEnd);
190 return i;
191 }
192 return new EmptyLogCursor();
193 }
194
195
196 @Override
197 public LogCursor seekLog(String refName, long updateIndex)
198 throws IOException {
199 initLogIndex();
200 if (logPosition > 0) {
201 byte[] key = LogEntry.key(refName, updateIndex);
202 byte[] match = refName.getBytes(UTF_8);
203 LogCursorImpl i = new LogCursorImpl(logEnd, match);
204 i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
205 return i;
206 }
207 return new EmptyLogCursor();
208 }
209
210 private BlockReadere/jgit/internal/storage/reftable/BlockReader.html#BlockReader">BlockReader seek(byte blockType, byte[] key, BlockReader idx,
211 long startPos, long endPos) throws IOException {
212 if (idx != null) {
213
214 BlockReader block = idx;
215 do {
216 if (block.seekKey(key) > 0) {
217 return null;
218 }
219 long pos = block.readPositionFromIndex();
220 block = readBlock(pos, endPos);
221 } while (block.type() == INDEX_BLOCK_TYPE);
222 block.seekKey(key);
223 return block;
224 }
225 if (blockType == LOG_BLOCK_TYPE) {
226
227
228 BlockReader block = readBlock(startPos, endPos);
229
230 for (;;) {
231 if (block == null || block.type() != LOG_BLOCK_TYPE) {
232 return null;
233 }
234
235 int result = block.seekKey(key);
236 if (result <= 0) {
237
238
239
240
241 return block;
242 }
243
244 long pos = block.endPosition();
245 if (pos >= endPos) {
246 return null;
247 }
248 block = readBlock(pos, endPos);
249 }
250 }
251 return binarySearch(blockType, key, startPos, endPos);
252 }
253
254 private BlockReader binarySearch(byte blockType, byte[] key,
255 long startPos, long endPos) throws IOException {
256 if (blockSize == 0) {
257 BlockReader b = readBlock(startPos, endPos);
258 if (blockType != b.type()) {
259 return null;
260 }
261 b.seekKey(key);
262 return b;
263 }
264
265 int low = (int) (startPos / blockSize);
266 int end = blocksIn(startPos, endPos);
267 BlockReader block = null;
268 do {
269 int mid = (low + end) >>> 1;
270 block = readBlock(((long) mid) * blockSize, endPos);
271 if (blockType != block.type()) {
272 return null;
273 }
274 int cmp = block.seekKey(key);
275 if (cmp < 0) {
276 end = mid;
277 } else if (cmp == 0) {
278 break;
279 } else {
280 low = mid + 1;
281 }
282 } while (low < end);
283 return block;
284 }
285
286 private void readFileHeader() throws IOException {
287 readHeaderOrFooter(0, FILE_HEADER_LEN);
288 }
289
290 private void readFileFooter() throws IOException {
291 int ftrLen = FILE_FOOTER_LEN;
292 byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen);
293
294 CRC32 crc = new CRC32();
295 crc.update(ftr, 0, ftrLen - 4);
296 if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) {
297 throw new IOException(JGitText.get().invalidReftableCRC);
298 }
299
300 refIndexPosition = NB.decodeInt64(ftr, 24);
301 long p = NB.decodeInt64(ftr, 32);
302 objPosition = p >>> 5;
303 objIdLen = (int) (p & 0x1f);
304 objIndexPosition = NB.decodeInt64(ftr, 40);
305 logPosition = NB.decodeInt64(ftr, 48);
306 logIndexPosition = NB.decodeInt64(ftr, 56);
307
308 if (refIndexPosition > 0) {
309 refEnd = refIndexPosition;
310 } else if (objPosition > 0) {
311 refEnd = objPosition;
312 } else if (logPosition > 0) {
313 refEnd = logPosition;
314 } else {
315 refEnd = src.size() - ftrLen;
316 }
317
318 if (objPosition > 0) {
319 if (objIndexPosition > 0) {
320 objEnd = objIndexPosition;
321 } else if (logPosition > 0) {
322 objEnd = logPosition;
323 } else {
324 objEnd = src.size() - ftrLen;
325 }
326 }
327
328 if (logPosition > 0) {
329 if (logIndexPosition > 0) {
330 logEnd = logIndexPosition;
331 } else {
332 logEnd = src.size() - ftrLen;
333 }
334 }
335 }
336
337 private byte[] readHeaderOrFooter(long pos, int len) throws IOException {
338 ByteBuffer buf = src.read(pos, len);
339 if (buf.position() != len) {
340 throw new IOException(JGitText.get().shortReadOfBlock);
341 }
342
343 byte[] tmp = new byte[len];
344 buf.flip();
345 buf.get(tmp);
346 if (!isFileHeaderMagic(tmp, 0, len)) {
347 throw new IOException(JGitText.get().invalidReftableFile);
348 }
349
350 int v = NB.decodeInt32(tmp, 4);
351 int version = v >>> 24;
352 if (VERSION_1 != version) {
353 throw new IOException(MessageFormat.format(
354 JGitText.get().unsupportedReftableVersion,
355 Integer.valueOf(version)));
356 }
357 if (blockSize == -1) {
358 blockSize = v & 0xffffff;
359 }
360 minUpdateIndex = NB.decodeInt64(tmp, 8);
361 maxUpdateIndex = NB.decodeInt64(tmp, 16);
362 return tmp;
363 }
364
365 private void initRefIndex() throws IOException {
366 if (refIndexPosition < 0) {
367 readFileFooter();
368 }
369 if (refIndex == null && refIndexPosition > 0) {
370 refIndex = readIndex(refIndexPosition);
371 }
372 }
373
374 private void initObjIndex() throws IOException {
375 if (objIndexPosition < 0) {
376 readFileFooter();
377 }
378 if (objIndex == null && objIndexPosition > 0) {
379 objIndex = readIndex(objIndexPosition);
380 }
381 }
382
383 private void initLogIndex() throws IOException {
384 if (logIndexPosition < 0) {
385 readFileFooter();
386 }
387 if (logIndex == null && logIndexPosition > 0) {
388 logIndex = readIndex(logIndexPosition);
389 }
390 }
391
392 private BlockReader readIndex(long pos) throws IOException {
393 int sz = readBlockLen(pos);
394 BlockReader i = new BlockReader();
395 i.readBlock(src, pos, sz);
396 i.verifyIndex();
397 return i;
398 }
399
400 private int readBlockLen(long pos) throws IOException {
401 int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4;
402 ByteBuffer tmp = src.read(pos, sz);
403 if (tmp.position() < sz) {
404 throw new IOException(JGitText.get().invalidReftableFile);
405 }
406 byte[] buf;
407 if (tmp.hasArray() && tmp.arrayOffset() == 0) {
408 buf = tmp.array();
409 } else {
410 buf = new byte[sz];
411 tmp.flip();
412 tmp.get(buf);
413 }
414 if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) {
415 return FILE_HEADER_LEN;
416 }
417 int p = pos == 0 ? FILE_HEADER_LEN : 0;
418 return decodeBlockLen(NB.decodeInt32(buf, p));
419 }
420
421 private BlockReader readBlock(long pos, long end) throws IOException {
422 if (indexCache != null) {
423 BlockReader b = indexCache.get(pos);
424 if (b != null) {
425 return b;
426 }
427 }
428
429 int sz = blockSize;
430 if (sz == 0) {
431 sz = readBlockLen(pos);
432 } else if (pos + sz > end) {
433 sz = (int) (end - pos);
434 }
435
436 BlockReader b = new BlockReader();
437 b.readBlock(src, pos, sz);
438 if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
439 if (indexCache == null) {
440 indexCache = new LongMap<>();
441 }
442 indexCache.put(pos, b);
443 }
444 return b;
445 }
446
447 private int blocksIn(long pos, long end) {
448 int blocks = (int) ((end - pos) / blockSize);
449 return end % blockSize == 0 ? blocks : (blocks + 1);
450 }
451
452
453
454
455
456
457
458
459 public long size() throws IOException {
460 return src.size();
461 }
462
463
464 @Override
465 public void close() throws IOException {
466 src.close();
467 }
468
469 private class RefCursorImpl extends RefCursor {
470 private final long scanEnd;
471 private final byte[] match;
472 private final boolean prefix;
473
474 private Ref ref;
475 BlockReader block;
476
477 RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
478 this.scanEnd = scanEnd;
479 this.match = match;
480 this.prefix = prefix;
481 }
482
483 @Override
484 public boolean next() throws IOException {
485 for (;;) {
486 if (block == null || block.type() != REF_BLOCK_TYPE) {
487 return false;
488 } else if (!block.next()) {
489 long pos = block.endPosition();
490 if (pos >= scanEnd) {
491 return false;
492 }
493 block = readBlock(pos, scanEnd);
494 continue;
495 }
496
497 block.parseKey();
498 if (match != null && !block.match(match, prefix)) {
499 block.skipValue();
500 return false;
501 }
502
503 ref = block.readRef(minUpdateIndex);
504 if (!includeDeletes && wasDeleted()) {
505 continue;
506 }
507 return true;
508 }
509 }
510
511 @Override
512 public Ref getRef() {
513 return ref;
514 }
515
516 @Override
517 public void close() {
518
519 }
520 }
521
522 private class LogCursorImpl extends LogCursor {
523 private final long scanEnd;
524 private final byte[] match;
525
526 private String refName;
527 private long updateIndex;
528 private ReflogEntry entry;
529 BlockReader block;
530
531
532
533
534
535
536
537
538
539 LogCursorImpl(long scanEnd, byte[] match) {
540 this.scanEnd = scanEnd;
541 this.match = match;
542 }
543
544 @Override
545 public boolean next() throws IOException {
546 for (;;) {
547 if (block == null || block.type() != LOG_BLOCK_TYPE) {
548 return false;
549 } else if (!block.next()) {
550 long pos = block.endPosition();
551 if (pos >= scanEnd) {
552 return false;
553 }
554 block = readBlock(pos, scanEnd);
555 continue;
556 }
557
558 block.parseKey();
559 if (match != null && !block.match(match, false)) {
560 block.skipValue();
561 return false;
562 }
563
564 refName = block.name();
565 updateIndex = block.readLogUpdateIndex();
566 entry = block.readLogEntry();
567 if (entry == null && !includeDeletes) {
568 continue;
569 }
570 return true;
571 }
572 }
573
574 @Override
575 public String getRefName() {
576 return refName;
577 }
578
579 @Override
580 public long getUpdateIndex() {
581 return updateIndex;
582 }
583
584 @Override
585 public ReflogEntry getReflogEntry() {
586 return entry;
587 }
588
589 @Override
590 public void close() {
591
592 }
593 }
594
595 static final LongListml#LongList">LongList EMPTY_LONG_LIST = new LongList(0);
596
597 private class ObjCursorImpl extends RefCursor {
598 private final long scanEnd;
599 private final ObjectId match;
600
601 private Ref ref;
602 private int listIdx;
603
604 private LongList blockPos;
605 private BlockReader block;
606
607 ObjCursorImpl(long scanEnd, AnyObjectId id) {
608 this.scanEnd = scanEnd;
609 this.match = id.copy();
610 }
611
612 void initSeek() throws IOException {
613 byte[] rawId = new byte[OBJECT_ID_LENGTH];
614 match.copyRawTo(rawId, 0);
615 byte[] key = Arrays.copyOf(rawId, objIdLen);
616
617 BlockReader b = objIndex;
618 do {
619 if (b.seekKey(key) > 0) {
620 blockPos = EMPTY_LONG_LIST;
621 return;
622 }
623 long pos = b.readPositionFromIndex();
624 b = readBlock(pos, objEnd);
625 } while (b.type() == INDEX_BLOCK_TYPE);
626 b.seekKey(key);
627 while (b.next()) {
628 b.parseKey();
629 if (b.match(key, false)) {
630 blockPos = b.readBlockPositionList();
631 if (blockPos == null) {
632 initScan();
633 return;
634 }
635 break;
636 }
637 b.skipValue();
638 }
639 if (blockPos == null) {
640 blockPos = EMPTY_LONG_LIST;
641 }
642 if (blockPos.size() > 0) {
643 long pos = blockPos.get(listIdx++);
644 block = readBlock(pos, scanEnd);
645 }
646 }
647
648 void initScan() throws IOException {
649 block = readBlock(0, scanEnd);
650 }
651
652 @Override
653 public boolean next() throws IOException {
654 for (;;) {
655 if (block == null || block.type() != REF_BLOCK_TYPE) {
656 return false;
657 } else if (!block.next()) {
658 long pos;
659 if (blockPos != null) {
660 if (listIdx >= blockPos.size()) {
661 return false;
662 }
663 pos = blockPos.get(listIdx++);
664 } else {
665 pos = block.endPosition();
666 }
667 if (pos >= scanEnd) {
668 return false;
669 }
670 block = readBlock(pos, scanEnd);
671 continue;
672 }
673
674 block.parseKey();
675 ref = block.readRef(minUpdateIndex);
676 ObjectId id = ref.getObjectId();
677 if (id != null && match.equals(id)
678 && (includeDeletes || !wasDeleted())) {
679 return true;
680 }
681 }
682 }
683
684 @Override
685 public Ref getRef() {
686 return ref;
687 }
688
689 @Override
690 public void close() {
691
692 }
693 }
694 }