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