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 org.eclipse.jgit.lib.Constants.CHARSET;
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(CHARSET);
186 boolean prefix = key[key.length - 1] == '/';
187
188 RefCursorImpl i = new RefCursorImpl(refEnd, key, prefix);
189 i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
190 return i;
191 }
192
193
194 @Override
195 public RefCursor byObjectId(AnyObjectId id) throws IOException {
196 initObjIndex();
197 ObjCursorImpl i = new ObjCursorImpl(refEnd, id);
198 if (objIndex != null) {
199 i.initSeek();
200 } else {
201 i.initScan();
202 }
203 return i;
204 }
205
206
207 @Override
208 public LogCursor allLogs() throws IOException {
209 initLogIndex();
210 if (logPosition > 0) {
211 src.adviseSequentialRead(logPosition, logEnd);
212 LogCursorImpl i = new LogCursorImpl(logEnd, null);
213 i.block = readBlock(logPosition, logEnd);
214 return i;
215 }
216 return new EmptyLogCursor();
217 }
218
219
220 @Override
221 public LogCursor seekLog(String refName, long updateIndex)
222 throws IOException {
223 initLogIndex();
224 if (logPosition > 0) {
225 byte[] key = LogEntry.key(refName, updateIndex);
226 byte[] match = refName.getBytes(CHARSET);
227 LogCursorImpl i = new LogCursorImpl(logEnd, match);
228 i.block = seek(LOG_BLOCK_TYPE, key, logIndex, logPosition, logEnd);
229 return i;
230 }
231 return new EmptyLogCursor();
232 }
233
234 private BlockReader seek(byte blockType, byte[] key, BlockReader idx,
235 long startPos, long endPos) throws IOException {
236 if (idx != null) {
237
238 BlockReader block = idx;
239 do {
240 if (block.seekKey(key) > 0) {
241 return null;
242 }
243 long pos = block.readPositionFromIndex();
244 block = readBlock(pos, endPos);
245 } while (block.type() == INDEX_BLOCK_TYPE);
246 block.seekKey(key);
247 return block;
248 }
249 return binarySearch(blockType, key, startPos, endPos);
250 }
251
252 private BlockReader binarySearch(byte blockType, byte[] key,
253 long startPos, long endPos) throws IOException {
254 if (blockSize == 0) {
255 BlockReader b = readBlock(startPos, endPos);
256 if (blockType != b.type()) {
257 return null;
258 }
259 b.seekKey(key);
260 return b;
261 }
262
263 int low = (int) (startPos / blockSize);
264 int end = blocksIn(startPos, endPos);
265 BlockReader block = null;
266 do {
267 int mid = (low + end) >>> 1;
268 block = readBlock(((long) mid) * blockSize, endPos);
269 if (blockType != block.type()) {
270 return null;
271 }
272 int cmp = block.seekKey(key);
273 if (cmp < 0) {
274 end = mid;
275 } else if (cmp == 0) {
276 break;
277 } else {
278 low = mid + 1;
279 }
280 } while (low < end);
281 return block;
282 }
283
284 private void readFileHeader() throws IOException {
285 readHeaderOrFooter(0, FILE_HEADER_LEN);
286 }
287
288 private void readFileFooter() throws IOException {
289 int ftrLen = FILE_FOOTER_LEN;
290 byte[] ftr = readHeaderOrFooter(src.size() - ftrLen, ftrLen);
291
292 CRC32 crc = new CRC32();
293 crc.update(ftr, 0, ftrLen - 4);
294 if (crc.getValue() != NB.decodeUInt32(ftr, ftrLen - 4)) {
295 throw new IOException(JGitText.get().invalidReftableCRC);
296 }
297
298 refIndexPosition = NB.decodeInt64(ftr, 24);
299 long p = NB.decodeInt64(ftr, 32);
300 objPosition = p >>> 5;
301 objIdLen = (int) (p & 0x1f);
302 objIndexPosition = NB.decodeInt64(ftr, 40);
303 logPosition = NB.decodeInt64(ftr, 48);
304 logIndexPosition = NB.decodeInt64(ftr, 56);
305
306 if (refIndexPosition > 0) {
307 refEnd = refIndexPosition;
308 } else if (objPosition > 0) {
309 refEnd = objPosition;
310 } else if (logPosition > 0) {
311 refEnd = logPosition;
312 } else {
313 refEnd = src.size() - ftrLen;
314 }
315
316 if (objPosition > 0) {
317 if (objIndexPosition > 0) {
318 objEnd = objIndexPosition;
319 } else if (logPosition > 0) {
320 objEnd = logPosition;
321 } else {
322 objEnd = src.size() - ftrLen;
323 }
324 }
325
326 if (logPosition > 0) {
327 if (logIndexPosition > 0) {
328 logEnd = logIndexPosition;
329 } else {
330 logEnd = src.size() - ftrLen;
331 }
332 }
333 }
334
335 private byte[] readHeaderOrFooter(long pos, int len) throws IOException {
336 ByteBuffer buf = src.read(pos, len);
337 if (buf.position() != len) {
338 throw new IOException(JGitText.get().shortReadOfBlock);
339 }
340
341 byte[] tmp = new byte[len];
342 buf.flip();
343 buf.get(tmp);
344 if (!isFileHeaderMagic(tmp, 0, len)) {
345 throw new IOException(JGitText.get().invalidReftableFile);
346 }
347
348 int v = NB.decodeInt32(tmp, 4);
349 int version = v >>> 24;
350 if (VERSION_1 != version) {
351 throw new IOException(MessageFormat.format(
352 JGitText.get().unsupportedReftableVersion,
353 Integer.valueOf(version)));
354 }
355 if (blockSize == -1) {
356 blockSize = v & 0xffffff;
357 }
358 minUpdateIndex = NB.decodeInt64(tmp, 8);
359 maxUpdateIndex = NB.decodeInt64(tmp, 16);
360 return tmp;
361 }
362
363 private void initRefIndex() throws IOException {
364 if (refIndexPosition < 0) {
365 readFileFooter();
366 }
367 if (refIndex == null && refIndexPosition > 0) {
368 refIndex = readIndex(refIndexPosition);
369 }
370 }
371
372 private void initObjIndex() throws IOException {
373 if (objIndexPosition < 0) {
374 readFileFooter();
375 }
376 if (objIndex == null && objIndexPosition > 0) {
377 objIndex = readIndex(objIndexPosition);
378 }
379 }
380
381 private void initLogIndex() throws IOException {
382 if (logIndexPosition < 0) {
383 readFileFooter();
384 }
385 if (logIndex == null && logIndexPosition > 0) {
386 logIndex = readIndex(logIndexPosition);
387 }
388 }
389
390 private BlockReader readIndex(long pos) throws IOException {
391 int sz = readBlockLen(pos);
392 BlockReader i = new BlockReader();
393 i.readBlock(src, pos, sz);
394 i.verifyIndex();
395 return i;
396 }
397
398 private int readBlockLen(long pos) throws IOException {
399 int sz = pos == 0 ? FILE_HEADER_LEN + 4 : 4;
400 ByteBuffer tmp = src.read(pos, sz);
401 if (tmp.position() < sz) {
402 throw new IOException(JGitText.get().invalidReftableFile);
403 }
404 byte[] buf;
405 if (tmp.hasArray() && tmp.arrayOffset() == 0) {
406 buf = tmp.array();
407 } else {
408 buf = new byte[sz];
409 tmp.flip();
410 tmp.get(buf);
411 }
412 if (pos == 0 && buf[FILE_HEADER_LEN] == FILE_BLOCK_TYPE) {
413 return FILE_HEADER_LEN;
414 }
415 int p = pos == 0 ? FILE_HEADER_LEN : 0;
416 return decodeBlockLen(NB.decodeInt32(buf, p));
417 }
418
419 private BlockReader readBlock(long pos, long end) throws IOException {
420 if (indexCache != null) {
421 BlockReader b = indexCache.get(pos);
422 if (b != null) {
423 return b;
424 }
425 }
426
427 int sz = blockSize;
428 if (sz == 0) {
429 sz = readBlockLen(pos);
430 } else if (pos + sz > end) {
431 sz = (int) (end - pos);
432 }
433
434 BlockReader b = new BlockReader();
435 b.readBlock(src, pos, sz);
436 if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
437 if (indexCache == null) {
438 indexCache = new LongMap<>();
439 }
440 indexCache.put(pos, b);
441 }
442 return b;
443 }
444
445 private int blocksIn(long pos, long end) {
446 int blocks = (int) ((end - pos) / blockSize);
447 return end % blockSize == 0 ? blocks : (blocks + 1);
448 }
449
450
451
452
453
454
455
456
457 public long size() throws IOException {
458 return src.size();
459 }
460
461
462 @Override
463 public void close() throws IOException {
464 src.close();
465 }
466
467 private class RefCursorImpl extends RefCursor {
468 private final long scanEnd;
469 private final byte[] match;
470 private final boolean prefix;
471
472 private Ref ref;
473 private long updateIndex;
474 BlockReader block;
475
476 RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
477 this.scanEnd = scanEnd;
478 this.match = match;
479 this.prefix = prefix;
480 }
481
482 @Override
483 public boolean next() throws IOException {
484 for (;;) {
485 if (block == null || block.type() != REF_BLOCK_TYPE) {
486 return false;
487 } else if (!block.next()) {
488 long pos = block.endPosition();
489 if (pos >= scanEnd) {
490 return false;
491 }
492 block = readBlock(pos, scanEnd);
493 continue;
494 }
495
496 block.parseKey();
497 if (match != null && !block.match(match, prefix)) {
498 block.skipValue();
499 return false;
500 }
501
502 updateIndex = minUpdateIndex + block.readUpdateIndexDelta();
503 ref = block.readRef();
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 long getUpdateIndex() {
518 return updateIndex;
519 }
520
521 @Override
522 public void close() {
523
524 }
525 }
526
527 private class LogCursorImpl extends LogCursor {
528 private final long scanEnd;
529 private final byte[] match;
530
531 private String refName;
532 private long updateIndex;
533 private ReflogEntry entry;
534 BlockReader block;
535
536 LogCursorImpl(long scanEnd, byte[] match) {
537 this.scanEnd = scanEnd;
538 this.match = match;
539 }
540
541 @Override
542 public boolean next() throws IOException {
543 for (;;) {
544 if (block == null || block.type() != LOG_BLOCK_TYPE) {
545 return false;
546 } else if (!block.next()) {
547 long pos = block.endPosition();
548 if (pos >= scanEnd) {
549 return false;
550 }
551 block = readBlock(pos, scanEnd);
552 continue;
553 }
554
555 block.parseKey();
556 if (match != null && !block.match(match, false)) {
557 block.skipValue();
558 return false;
559 }
560
561 refName = block.name();
562 updateIndex = block.readLogUpdateIndex();
563 entry = block.readLogEntry();
564 if (entry == null && !includeDeletes) {
565 continue;
566 }
567 return true;
568 }
569 }
570
571 @Override
572 public String getRefName() {
573 return refName;
574 }
575
576 @Override
577 public long getUpdateIndex() {
578 return updateIndex;
579 }
580
581 @Override
582 public ReflogEntry getReflogEntry() {
583 return entry;
584 }
585
586 @Override
587 public void close() {
588
589 }
590 }
591
592 static final LongList EMPTY_LONG_LIST = new LongList(0);
593
594 private class ObjCursorImpl extends RefCursor {
595 private final long scanEnd;
596 private final ObjectId match;
597
598 private Ref ref;
599 private long updateIndex;
600 private int listIdx;
601
602 private LongList blockPos;
603 private BlockReader block;
604
605 ObjCursorImpl(long scanEnd, AnyObjectId id) {
606 this.scanEnd = scanEnd;
607 this.match = id.copy();
608 }
609
610 void initSeek() throws IOException {
611 byte[] rawId = new byte[OBJECT_ID_LENGTH];
612 match.copyRawTo(rawId, 0);
613 byte[] key = Arrays.copyOf(rawId, objIdLen);
614
615 BlockReader b = objIndex;
616 do {
617 if (b.seekKey(key) > 0) {
618 blockPos = EMPTY_LONG_LIST;
619 return;
620 }
621 long pos = b.readPositionFromIndex();
622 b = readBlock(pos, objEnd);
623 } while (b.type() == INDEX_BLOCK_TYPE);
624 b.seekKey(key);
625 while (b.next()) {
626 b.parseKey();
627 if (b.match(key, false)) {
628 blockPos = b.readBlockPositionList();
629 if (blockPos == null) {
630 initScan();
631 return;
632 }
633 break;
634 }
635 b.skipValue();
636 }
637 if (blockPos == null) {
638 blockPos = EMPTY_LONG_LIST;
639 }
640 if (blockPos.size() > 0) {
641 long pos = blockPos.get(listIdx++);
642 block = readBlock(pos, scanEnd);
643 }
644 }
645
646 void initScan() throws IOException {
647 block = readBlock(0, scanEnd);
648 }
649
650 @Override
651 public boolean next() throws IOException {
652 for (;;) {
653 if (block == null || block.type() != REF_BLOCK_TYPE) {
654 return false;
655 } else if (!block.next()) {
656 long pos;
657 if (blockPos != null) {
658 if (listIdx >= blockPos.size()) {
659 return false;
660 }
661 pos = blockPos.get(listIdx++);
662 } else {
663 pos = block.endPosition();
664 }
665 if (pos >= scanEnd) {
666 return false;
667 }
668 block = readBlock(pos, scanEnd);
669 continue;
670 }
671
672 block.parseKey();
673 updateIndex = minUpdateIndex + block.readUpdateIndexDelta();
674 ref = block.readRef();
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 long getUpdateIndex() {
690 return updateIndex;
691 }
692
693 @Override
694 public void close() {
695
696 }
697 }
698 }