1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jgit.internal.storage.file;
14
15 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
16 import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
17
18 import java.io.EOFException;
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InterruptedIOException;
23 import java.io.RandomAccessFile;
24 import java.nio.MappedByteBuffer;
25 import java.nio.channels.FileChannel.MapMode;
26 import java.nio.file.AccessDeniedException;
27 import java.nio.file.NoSuchFileException;
28 import java.text.MessageFormat;
29 import java.time.Instant;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.Comparator;
33 import java.util.Iterator;
34 import java.util.Set;
35 import java.util.concurrent.atomic.AtomicInteger;
36 import java.util.zip.CRC32;
37 import java.util.zip.DataFormatException;
38 import java.util.zip.Inflater;
39
40 import org.eclipse.jgit.annotations.Nullable;
41 import org.eclipse.jgit.errors.CorruptObjectException;
42 import org.eclipse.jgit.errors.LargeObjectException;
43 import org.eclipse.jgit.errors.MissingObjectException;
44 import org.eclipse.jgit.errors.NoPackSignatureException;
45 import org.eclipse.jgit.errors.PackInvalidException;
46 import org.eclipse.jgit.errors.PackMismatchException;
47 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
48 import org.eclipse.jgit.errors.UnpackException;
49 import org.eclipse.jgit.errors.UnsupportedPackIndexVersionException;
50 import org.eclipse.jgit.errors.UnsupportedPackVersionException;
51 import org.eclipse.jgit.internal.JGitText;
52 import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
53 import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
54 import org.eclipse.jgit.lib.AbbreviatedObjectId;
55 import org.eclipse.jgit.lib.AnyObjectId;
56 import org.eclipse.jgit.lib.Constants;
57 import org.eclipse.jgit.lib.ObjectId;
58 import org.eclipse.jgit.lib.ObjectLoader;
59 import org.eclipse.jgit.util.LongList;
60 import org.eclipse.jgit.util.NB;
61 import org.eclipse.jgit.util.RawParseUtils;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65
66
67
68
69
70 public class Pack implements Iterable<PackIndex.MutableEntry> {
71 private static final Logger LOG = LoggerFactory.getLogger(Pack.class);
72
73
74
75
76 public static final Comparator<Pack> SORT = (a, b) -> b.packLastModified
77 .compareTo(a.packLastModified);
78
79 private final PackFile packFile;
80
81 private PackFile keepFile;
82
83 final int hash;
84
85 private RandomAccessFile fd;
86
87
88 private final Object readLock = new Object();
89
90 long length;
91
92 private int activeWindows;
93
94 private int activeCopyRawData;
95
96 Instant packLastModified;
97
98 private PackFileSnapshot fileSnapshot;
99
100 private volatile boolean invalid;
101
102 private volatile Exception invalidatingCause;
103
104 @Nullable
105 private PackFile bitmapIdxFile;
106
107 private AtomicInteger transientErrorCount = new AtomicInteger();
108
109 private byte[] packChecksum;
110
111 private volatile PackIndex loadedIdx;
112
113 private PackReverseIndex reverseIdx;
114
115 private PackBitmapIndex bitmapIdx;
116
117
118
119
120
121
122
123
124 private volatile LongList corruptObjects;
125
126
127
128
129
130
131
132
133
134 public Pack(File packFile, @Nullable PackFile bitmapIdxFile) {
135 this.packFile = new PackFile(packFile);
136 this.fileSnapshot = PackFileSnapshot.save(packFile);
137 this.packLastModified = fileSnapshot.lastModifiedInstant();
138 this.bitmapIdxFile = bitmapIdxFile;
139
140
141
142
143 hash = System.identityHashCode(this) * 31;
144 length = Long.MAX_VALUE;
145 }
146
147 private PackIndex idx() throws IOException {
148 PackIndex idx = loadedIdx;
149 if (idx == null) {
150 synchronized (this) {
151 idx = loadedIdx;
152 if (idx == null) {
153 if (invalid) {
154 throw new PackInvalidException(packFile,
155 invalidatingCause);
156 }
157 try {
158 long start = System.currentTimeMillis();
159 PackFile idxFile = packFile.create(INDEX);
160 idx = PackIndex.open(idxFile);
161 if (LOG.isDebugEnabled()) {
162 LOG.debug(String.format(
163 "Opening pack index %s, size %.3f MB took %d ms",
164 idxFile.getAbsolutePath(),
165 Float.valueOf(idxFile.length()
166 / (1024f * 1024)),
167 Long.valueOf(System.currentTimeMillis()
168 - start)));
169 }
170
171 if (packChecksum == null) {
172 packChecksum = idx.packChecksum;
173 fileSnapshot.setChecksum(
174 ObjectId.fromRaw(packChecksum));
175 } else if (!Arrays.equals(packChecksum,
176 idx.packChecksum)) {
177 throw new PackMismatchException(MessageFormat
178 .format(JGitText.get().packChecksumMismatch,
179 packFile.getPath(),
180 ObjectId.fromRaw(packChecksum)
181 .name(),
182 ObjectId.fromRaw(idx.packChecksum)
183 .name()));
184 }
185 loadedIdx = idx;
186 } catch (InterruptedIOException e) {
187
188
189 throw e;
190 } catch (IOException e) {
191 invalid = true;
192 invalidatingCause = e;
193 throw e;
194 }
195 }
196 }
197 }
198 return idx;
199 }
200
201
202
203
204
205 public PackFile getPackFile() {
206 return packFile;
207 }
208
209
210
211
212
213
214
215 public PackIndex getIndex() throws IOException {
216 return idx();
217 }
218
219
220
221
222
223
224 public String getPackName() {
225 return packFile.getId();
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241 public boolean hasObject(AnyObjectId id) throws IOException {
242 final long offset = idx().findOffset(id);
243 return 0 < offset && !isCorrupt(offset);
244 }
245
246
247
248
249
250
251 public boolean shouldBeKept() {
252 if (keepFile == null) {
253 keepFile = packFile.create(KEEP);
254 }
255 return keepFile.exists();
256 }
257
258
259
260
261
262
263
264
265
266
267
268
269
270 ObjectLoader get(WindowCursor curs, AnyObjectId id)
271 throws IOException {
272 final long offset = idx().findOffset(id);
273 return 0 < offset && !isCorrupt(offset) ? load(curs, offset) : null;
274 }
275
276 void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit)
277 throws IOException {
278 idx().resolve(matches, id, matchLimit);
279 }
280
281
282
283
284 public void close() {
285 WindowCache.purge(this);
286 synchronized (this) {
287 loadedIdx = null;
288 reverseIdx = null;
289 }
290 }
291
292
293
294
295
296
297
298
299
300
301
302
303
304 @Override
305 public Iterator<PackIndex.MutableEntry> iterator() {
306 try {
307 return idx().iterator();
308 } catch (IOException e) {
309 return Collections.<PackIndex.MutableEntry> emptyList().iterator();
310 }
311 }
312
313
314
315
316
317
318
319
320
321 long getObjectCount() throws IOException {
322 return idx().getObjectCount();
323 }
324
325
326
327
328
329
330
331
332
333
334
335 ObjectId findObjectForOffset(long offset) throws IOException {
336 return getReverseIdx().findObject(offset);
337 }
338
339
340
341
342
343
344
345 PackFileSnapshot getFileSnapshot() {
346 return fileSnapshot;
347 }
348
349 AnyObjectId getPackChecksum() {
350 return ObjectId.fromRaw(packChecksum);
351 }
352
353 private final byte[] decompress(final long position, final int sz,
354 final WindowCursor curs) throws IOException, DataFormatException {
355 byte[] dstbuf;
356 try {
357 dstbuf = new byte[sz];
358 } catch (OutOfMemoryError noMemory) {
359
360
361
362
363
364
365
366 return null;
367 }
368
369 if (curs.inflate(this, position, dstbuf, false) != sz)
370 throw new EOFException(MessageFormat.format(
371 JGitText.get().shortCompressedStreamAt,
372 Long.valueOf(position)));
373 return dstbuf;
374 }
375
376 void copyPackAsIs(PackOutputStream out, WindowCursor curs)
377 throws IOException {
378
379 curs.pin(this, 0);
380 curs.copyPackAsIs(this, length, out);
381 }
382
383 final void copyAsIs(PackOutputStream out, LocalObjectToPack src,
384 boolean validate, WindowCursor curs) throws IOException,
385 StoredObjectRepresentationNotAvailableException {
386 beginCopyAsIs();
387 try {
388 copyAsIs2(out, src, validate, curs);
389 } finally {
390 endCopyAsIs();
391 }
392 }
393
394 private void copyAsIs2(PackOutputStream out, LocalObjectToPack src,
395 boolean validate, WindowCursor curs) throws IOException,
396 StoredObjectRepresentationNotAvailableException {
397 final CRC32 crc1 = validate ? new CRC32() : null;
398 final CRC32 crc2 = validate ? new CRC32() : null;
399 final byte[] buf = out.getCopyBuffer();
400
401
402
403 readFully(src.offset, buf, 0, 20, curs);
404 int c = buf[0] & 0xff;
405 final int typeCode = (c >> 4) & 7;
406 long inflatedLength = c & 15;
407 int shift = 4;
408 int headerCnt = 1;
409 while ((c & 0x80) != 0) {
410 c = buf[headerCnt++] & 0xff;
411 inflatedLength += ((long) (c & 0x7f)) << shift;
412 shift += 7;
413 }
414
415 if (typeCode == Constants.OBJ_OFS_DELTA) {
416 do {
417 c = buf[headerCnt++] & 0xff;
418 } while ((c & 128) != 0);
419 if (validate) {
420 assert(crc1 != null && crc2 != null);
421 crc1.update(buf, 0, headerCnt);
422 crc2.update(buf, 0, headerCnt);
423 }
424 } else if (typeCode == Constants.OBJ_REF_DELTA) {
425 if (validate) {
426 assert(crc1 != null && crc2 != null);
427 crc1.update(buf, 0, headerCnt);
428 crc2.update(buf, 0, headerCnt);
429 }
430
431 readFully(src.offset + headerCnt, buf, 0, 20, curs);
432 if (validate) {
433 assert(crc1 != null && crc2 != null);
434 crc1.update(buf, 0, 20);
435 crc2.update(buf, 0, 20);
436 }
437 headerCnt += 20;
438 } else if (validate) {
439 assert(crc1 != null && crc2 != null);
440 crc1.update(buf, 0, headerCnt);
441 crc2.update(buf, 0, headerCnt);
442 }
443
444 final long dataOffset = src.offset + headerCnt;
445 final long dataLength = src.length;
446 final long expectedCRC;
447 final ByteArrayWindow quickCopy;
448
449
450
451
452 try {
453 quickCopy = curs.quickCopy(this, dataOffset, dataLength);
454
455 if (validate && idx().hasCRC32Support()) {
456 assert(crc1 != null);
457
458
459 expectedCRC = idx().findCRC32(src);
460 if (quickCopy != null) {
461 quickCopy.crc32(crc1, dataOffset, (int) dataLength);
462 } else {
463 long pos = dataOffset;
464 long cnt = dataLength;
465 while (cnt > 0) {
466 final int n = (int) Math.min(cnt, buf.length);
467 readFully(pos, buf, 0, n, curs);
468 crc1.update(buf, 0, n);
469 pos += n;
470 cnt -= n;
471 }
472 }
473 if (crc1.getValue() != expectedCRC) {
474 setCorrupt(src.offset);
475 throw new CorruptObjectException(MessageFormat.format(
476 JGitText.get().objectAtHasBadZlibStream,
477 Long.valueOf(src.offset), getPackFile()));
478 }
479 } else if (validate) {
480
481
482
483
484 Inflater inf = curs.inflater();
485 byte[] tmp = new byte[1024];
486 if (quickCopy != null) {
487 quickCopy.check(inf, tmp, dataOffset, (int) dataLength);
488 } else {
489 assert(crc1 != null);
490 long pos = dataOffset;
491 long cnt = dataLength;
492 while (cnt > 0) {
493 final int n = (int) Math.min(cnt, buf.length);
494 readFully(pos, buf, 0, n, curs);
495 crc1.update(buf, 0, n);
496 inf.setInput(buf, 0, n);
497 while (inf.inflate(tmp, 0, tmp.length) > 0)
498 continue;
499 pos += n;
500 cnt -= n;
501 }
502 }
503 if (!inf.finished() || inf.getBytesRead() != dataLength) {
504 setCorrupt(src.offset);
505 throw new EOFException(MessageFormat.format(
506 JGitText.get().shortCompressedStreamAt,
507 Long.valueOf(src.offset)));
508 }
509 assert(crc1 != null);
510 expectedCRC = crc1.getValue();
511 } else {
512 expectedCRC = -1;
513 }
514 } catch (DataFormatException dataFormat) {
515 setCorrupt(src.offset);
516
517 CorruptObjectException corruptObject = new CorruptObjectException(
518 MessageFormat.format(
519 JGitText.get().objectAtHasBadZlibStream,
520 Long.valueOf(src.offset), getPackFile()),
521 dataFormat);
522
523 throw new StoredObjectRepresentationNotAvailableException(
524 corruptObject);
525
526 } catch (IOException ioError) {
527 throw new StoredObjectRepresentationNotAvailableException(ioError);
528 }
529
530 if (quickCopy != null) {
531
532
533
534 out.writeHeader(src, inflatedLength);
535 quickCopy.write(out, dataOffset, (int) dataLength);
536
537 } else if (dataLength <= buf.length) {
538
539
540
541 if (!validate) {
542 long pos = dataOffset;
543 long cnt = dataLength;
544 while (cnt > 0) {
545 final int n = (int) Math.min(cnt, buf.length);
546 readFully(pos, buf, 0, n, curs);
547 pos += n;
548 cnt -= n;
549 }
550 }
551 out.writeHeader(src, inflatedLength);
552 out.write(buf, 0, (int) dataLength);
553 } else {
554
555
556
557
558 out.writeHeader(src, inflatedLength);
559 long pos = dataOffset;
560 long cnt = dataLength;
561 while (cnt > 0) {
562 final int n = (int) Math.min(cnt, buf.length);
563 readFully(pos, buf, 0, n, curs);
564 if (validate) {
565 assert(crc2 != null);
566 crc2.update(buf, 0, n);
567 }
568 out.write(buf, 0, n);
569 pos += n;
570 cnt -= n;
571 }
572 if (validate) {
573 assert(crc2 != null);
574 if (crc2.getValue() != expectedCRC) {
575 throw new CorruptObjectException(MessageFormat.format(
576 JGitText.get().objectAtHasBadZlibStream,
577 Long.valueOf(src.offset), getPackFile()));
578 }
579 }
580 }
581 }
582
583 boolean invalid() {
584 return invalid;
585 }
586
587 void setInvalid() {
588 invalid = true;
589 }
590
591 int incrementTransientErrorCount() {
592 return transientErrorCount.incrementAndGet();
593 }
594
595 void resetTransientErrorCount() {
596 transientErrorCount.set(0);
597 }
598
599 private void readFully(final long position, final byte[] dstbuf,
600 int dstoff, final int cnt, final WindowCursor curs)
601 throws IOException {
602 if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt)
603 throw new EOFException();
604 }
605
606 private synchronized void beginCopyAsIs()
607 throws StoredObjectRepresentationNotAvailableException {
608 if (++activeCopyRawData == 1 && activeWindows == 0) {
609 try {
610 doOpen();
611 } catch (IOException thisPackNotValid) {
612 throw new StoredObjectRepresentationNotAvailableException(
613 thisPackNotValid);
614 }
615 }
616 }
617
618 private synchronized void endCopyAsIs() {
619 if (--activeCopyRawData == 0 && activeWindows == 0)
620 doClose();
621 }
622
623 synchronized boolean beginWindowCache() throws IOException {
624 if (++activeWindows == 1) {
625 if (activeCopyRawData == 0)
626 doOpen();
627 return true;
628 }
629 return false;
630 }
631
632 synchronized boolean endWindowCache() {
633 final boolean r = --activeWindows == 0;
634 if (r && activeCopyRawData == 0)
635 doClose();
636 return r;
637 }
638
639 private void doOpen() throws IOException {
640 if (invalid) {
641 openFail(true, invalidatingCause);
642 throw new PackInvalidException(packFile, invalidatingCause);
643 }
644 try {
645 synchronized (readLock) {
646 fd = new RandomAccessFile(packFile, "r");
647 length = fd.length();
648 onOpenPack();
649 }
650 } catch (InterruptedIOException e) {
651
652 openFail(false, e);
653 throw e;
654 } catch (FileNotFoundException fn) {
655
656
657
658 openFail(!packFile.exists(), fn);
659 throw fn;
660 } catch (EOFException | AccessDeniedException | NoSuchFileException
661 | CorruptObjectException | NoPackSignatureException
662 | PackMismatchException | UnpackException
663 | UnsupportedPackIndexVersionException
664 | UnsupportedPackVersionException pe) {
665
666 openFail(true, pe);
667 throw pe;
668 } catch (IOException | RuntimeException ge) {
669
670
671 openFail(false, ge);
672 throw ge;
673 }
674 }
675
676 private void openFail(boolean invalidate, Exception cause) {
677 activeWindows = 0;
678 activeCopyRawData = 0;
679 invalid = invalidate;
680 invalidatingCause = cause;
681 doClose();
682 }
683
684 private void doClose() {
685 synchronized (readLock) {
686 if (fd != null) {
687 try {
688 fd.close();
689 } catch (IOException err) {
690
691
692
693 }
694 fd = null;
695 }
696 }
697 }
698
699 ByteArrayWindow read(long pos, int size) throws IOException {
700 synchronized (readLock) {
701 if (invalid || fd == null) {
702
703
704
705
706
707 throw new PackInvalidException(packFile, invalidatingCause);
708 }
709 if (length < pos + size)
710 size = (int) (length - pos);
711 final byte[] buf = new byte[size];
712 fd.seek(pos);
713 fd.readFully(buf, 0, size);
714 return new ByteArrayWindow(this, pos, buf);
715 }
716 }
717
718 ByteWindow mmap(long pos, int size) throws IOException {
719 synchronized (readLock) {
720 if (length < pos + size)
721 size = (int) (length - pos);
722
723 MappedByteBuffer map;
724 try {
725 map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
726 } catch (IOException ioe1) {
727
728
729
730
731 System.gc();
732 System.runFinalization();
733 map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
734 }
735
736 if (map.hasArray())
737 return new ByteArrayWindow(this, pos, map.array());
738 return new ByteBufferWindow(this, pos, map);
739 }
740 }
741
742 private void onOpenPack() throws IOException {
743 final PackIndex idx = idx();
744 final byte[] buf = new byte[20];
745
746 fd.seek(0);
747 fd.readFully(buf, 0, 12);
748 if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) {
749 throw new NoPackSignatureException(JGitText.get().notAPACKFile);
750 }
751 final long vers = NB.decodeUInt32(buf, 4);
752 final long packCnt = NB.decodeUInt32(buf, 8);
753 if (vers != 2 && vers != 3) {
754 throw new UnsupportedPackVersionException(vers);
755 }
756
757 if (packCnt != idx.getObjectCount()) {
758 throw new PackMismatchException(MessageFormat.format(
759 JGitText.get().packObjectCountMismatch,
760 Long.valueOf(packCnt), Long.valueOf(idx.getObjectCount()),
761 getPackFile()));
762 }
763
764 fd.seek(length - 20);
765 fd.readFully(buf, 0, 20);
766 if (!Arrays.equals(buf, packChecksum)) {
767 throw new PackMismatchException(MessageFormat.format(
768 JGitText.get().packChecksumMismatch,
769 getPackFile(),
770 ObjectId.fromRaw(buf).name(),
771 ObjectId.fromRaw(idx.packChecksum).name()));
772 }
773 }
774
775 ObjectLoader load(WindowCursor curs, long pos)
776 throws IOException, LargeObjectException {
777 try {
778 final byte[] ib = curs.tempId;
779 Delta delta = null;
780 byte[] data = null;
781 int type = Constants.OBJ_BAD;
782 boolean cached = false;
783
784 SEARCH: for (;;) {
785 readFully(pos, ib, 0, 20, curs);
786 int c = ib[0] & 0xff;
787 final int typeCode = (c >> 4) & 7;
788 long sz = c & 15;
789 int shift = 4;
790 int p = 1;
791 while ((c & 0x80) != 0) {
792 c = ib[p++] & 0xff;
793 sz += ((long) (c & 0x7f)) << shift;
794 shift += 7;
795 }
796
797 switch (typeCode) {
798 case Constants.OBJ_COMMIT:
799 case Constants.OBJ_TREE:
800 case Constants.OBJ_BLOB:
801 case Constants.OBJ_TAG: {
802 if (delta != null || sz < curs.getStreamFileThreshold()) {
803 data = decompress(pos + p, (int) sz, curs);
804 }
805
806 if (delta != null) {
807 type = typeCode;
808 break SEARCH;
809 }
810
811 if (data != null) {
812 return new ObjectLoader.SmallObject(typeCode, data);
813 }
814 return new LargePackedWholeObject(typeCode, sz, pos, p,
815 this, curs.db);
816 }
817
818 case Constants.OBJ_OFS_DELTA: {
819 c = ib[p++] & 0xff;
820 long base = c & 127;
821 while ((c & 128) != 0) {
822 base += 1;
823 c = ib[p++] & 0xff;
824 base <<= 7;
825 base += (c & 127);
826 }
827 base = pos - base;
828 delta = new Delta(delta, pos, (int) sz, p, base);
829 if (sz != delta.deltaSize)
830 break SEARCH;
831
832 DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
833 if (e != null) {
834 type = e.type;
835 data = e.data;
836 cached = true;
837 break SEARCH;
838 }
839 pos = base;
840 continue SEARCH;
841 }
842
843 case Constants.OBJ_REF_DELTA: {
844 readFully(pos + p, ib, 0, 20, curs);
845 long base = findDeltaBase(ObjectId.fromRaw(ib));
846 delta = new Delta(delta, pos, (int) sz, p + 20, base);
847 if (sz != delta.deltaSize)
848 break SEARCH;
849
850 DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
851 if (e != null) {
852 type = e.type;
853 data = e.data;
854 cached = true;
855 break SEARCH;
856 }
857 pos = base;
858 continue SEARCH;
859 }
860
861 default:
862 throw new IOException(MessageFormat.format(
863 JGitText.get().unknownObjectType,
864 Integer.valueOf(typeCode)));
865 }
866 }
867
868
869
870
871 if (data == null)
872 throw new IOException(JGitText.get().inMemoryBufferLimitExceeded);
873
874 assert(delta != null);
875 do {
876
877 if (cached)
878 cached = false;
879 else if (delta.next == null)
880 curs.getDeltaBaseCache().store(this, delta.basePos, data, type);
881
882 pos = delta.deltaPos;
883
884 final byte[] cmds = decompress(pos + delta.hdrLen,
885 delta.deltaSize, curs);
886 if (cmds == null) {
887 data = null;
888 throw new LargeObjectException.OutOfMemory(new OutOfMemoryError());
889 }
890
891 final long sz = BinaryDelta.getResultSize(cmds);
892 if (Integer.MAX_VALUE <= sz)
893 throw new LargeObjectException.ExceedsByteArrayLimit();
894
895 final byte[] result;
896 try {
897 result = new byte[(int) sz];
898 } catch (OutOfMemoryError tooBig) {
899 data = null;
900 throw new LargeObjectException.OutOfMemory(tooBig);
901 }
902
903 BinaryDelta.apply(data, cmds, result);
904 data = result;
905 delta = delta.next;
906 } while (delta != null);
907
908 return new ObjectLoader.SmallObject(type, data);
909
910 } catch (DataFormatException dfe) {
911 throw new CorruptObjectException(
912 MessageFormat.format(
913 JGitText.get().objectAtHasBadZlibStream,
914 Long.valueOf(pos), getPackFile()),
915 dfe);
916 }
917 }
918
919 private long findDeltaBase(ObjectId baseId) throws IOException,
920 MissingObjectException {
921 long ofs = idx().findOffset(baseId);
922 if (ofs < 0)
923 throw new MissingObjectException(baseId,
924 JGitText.get().missingDeltaBase);
925 return ofs;
926 }
927
928 private static class Delta {
929
930 final Delta next;
931
932
933 final long deltaPos;
934
935
936 final int deltaSize;
937
938
939 final int hdrLen;
940
941
942 final long basePos;
943
944 Delta(Delta next, long ofs, int sz, int hdrLen, long baseOffset) {
945 this.next = next;
946 this.deltaPos = ofs;
947 this.deltaSize = sz;
948 this.hdrLen = hdrLen;
949 this.basePos = baseOffset;
950 }
951 }
952
953 byte[] getDeltaHeader(WindowCursor wc, long pos)
954 throws IOException, DataFormatException {
955
956
957
958
959
960 final byte[] hdr = new byte[18];
961 wc.inflate(this, pos, hdr, true );
962 return hdr;
963 }
964
965 int getObjectType(WindowCursor curs, long pos) throws IOException {
966 final byte[] ib = curs.tempId;
967 for (;;) {
968 readFully(pos, ib, 0, 20, curs);
969 int c = ib[0] & 0xff;
970 final int type = (c >> 4) & 7;
971
972 switch (type) {
973 case Constants.OBJ_COMMIT:
974 case Constants.OBJ_TREE:
975 case Constants.OBJ_BLOB:
976 case Constants.OBJ_TAG:
977 return type;
978
979 case Constants.OBJ_OFS_DELTA: {
980 int p = 1;
981 while ((c & 0x80) != 0)
982 c = ib[p++] & 0xff;
983 c = ib[p++] & 0xff;
984 long ofs = c & 127;
985 while ((c & 128) != 0) {
986 ofs += 1;
987 c = ib[p++] & 0xff;
988 ofs <<= 7;
989 ofs += (c & 127);
990 }
991 pos = pos - ofs;
992 continue;
993 }
994
995 case Constants.OBJ_REF_DELTA: {
996 int p = 1;
997 while ((c & 0x80) != 0)
998 c = ib[p++] & 0xff;
999 readFully(pos + p, ib, 0, 20, curs);
1000 pos = findDeltaBase(ObjectId.fromRaw(ib));
1001 continue;
1002 }
1003
1004 default:
1005 throw new IOException(
1006 MessageFormat.format(JGitText.get().unknownObjectType,
1007 Integer.valueOf(type)));
1008 }
1009 }
1010 }
1011
1012 long getObjectSize(WindowCursor curs, AnyObjectId id)
1013 throws IOException {
1014 final long offset = idx().findOffset(id);
1015 return 0 < offset ? getObjectSize(curs, offset) : -1;
1016 }
1017
1018 long getObjectSize(WindowCursor curs, long pos)
1019 throws IOException {
1020 final byte[] ib = curs.tempId;
1021 readFully(pos, ib, 0, 20, curs);
1022 int c = ib[0] & 0xff;
1023 final int type = (c >> 4) & 7;
1024 long sz = c & 15;
1025 int shift = 4;
1026 int p = 1;
1027 while ((c & 0x80) != 0) {
1028 c = ib[p++] & 0xff;
1029 sz += ((long) (c & 0x7f)) << shift;
1030 shift += 7;
1031 }
1032
1033 long deltaAt;
1034 switch (type) {
1035 case Constants.OBJ_COMMIT:
1036 case Constants.OBJ_TREE:
1037 case Constants.OBJ_BLOB:
1038 case Constants.OBJ_TAG:
1039 return sz;
1040
1041 case Constants.OBJ_OFS_DELTA:
1042 c = ib[p++] & 0xff;
1043 while ((c & 128) != 0)
1044 c = ib[p++] & 0xff;
1045 deltaAt = pos + p;
1046 break;
1047
1048 case Constants.OBJ_REF_DELTA:
1049 deltaAt = pos + p + 20;
1050 break;
1051
1052 default:
1053 throw new IOException(MessageFormat.format(
1054 JGitText.get().unknownObjectType, Integer.valueOf(type)));
1055 }
1056
1057 try {
1058 return BinaryDelta.getResultSize(getDeltaHeader(curs, deltaAt));
1059 } catch (DataFormatException e) {
1060 throw new CorruptObjectException(MessageFormat.format(
1061 JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
1062 getPackFile()), e);
1063 }
1064 }
1065
1066 LocalObjectRepresentation representation(final WindowCursor curs,
1067 final AnyObjectId objectId) throws IOException {
1068 final long pos = idx().findOffset(objectId);
1069 if (pos < 0)
1070 return null;
1071
1072 final byte[] ib = curs.tempId;
1073 readFully(pos, ib, 0, 20, curs);
1074 int c = ib[0] & 0xff;
1075 int p = 1;
1076 final int typeCode = (c >> 4) & 7;
1077 while ((c & 0x80) != 0)
1078 c = ib[p++] & 0xff;
1079
1080 long len = (findEndOffset(pos) - pos);
1081 switch (typeCode) {
1082 case Constants.OBJ_COMMIT:
1083 case Constants.OBJ_TREE:
1084 case Constants.OBJ_BLOB:
1085 case Constants.OBJ_TAG:
1086 return LocalObjectRepresentation.newWhole(this, pos, len - p);
1087
1088 case Constants.OBJ_OFS_DELTA: {
1089 c = ib[p++] & 0xff;
1090 long ofs = c & 127;
1091 while ((c & 128) != 0) {
1092 ofs += 1;
1093 c = ib[p++] & 0xff;
1094 ofs <<= 7;
1095 ofs += (c & 127);
1096 }
1097 ofs = pos - ofs;
1098 return LocalObjectRepresentation.newDelta(this, pos, len - p, ofs);
1099 }
1100
1101 case Constants.OBJ_REF_DELTA: {
1102 len -= p;
1103 len -= Constants.OBJECT_ID_LENGTH;
1104 readFully(pos + p, ib, 0, 20, curs);
1105 ObjectId id = ObjectId.fromRaw(ib);
1106 return LocalObjectRepresentation.newDelta(this, pos, len, id);
1107 }
1108
1109 default:
1110 throw new IOException(
1111 MessageFormat.format(JGitText.get().unknownObjectType,
1112 Integer.valueOf(typeCode)));
1113 }
1114 }
1115
1116 private long findEndOffset(long startOffset)
1117 throws IOException, CorruptObjectException {
1118 final long maxOffset = length - 20;
1119 return getReverseIdx().findNextOffset(startOffset, maxOffset);
1120 }
1121
1122 synchronized PackBitmapIndex getBitmapIndex() throws IOException {
1123 if (invalid || bitmapIdxFile == null) {
1124 return null;
1125 }
1126 if (bitmapIdx == null) {
1127 final PackBitmapIndex idx;
1128 try {
1129 idx = PackBitmapIndex.open(bitmapIdxFile, idx(),
1130 getReverseIdx());
1131 } catch (FileNotFoundException e) {
1132
1133
1134
1135 bitmapIdxFile = null;
1136 return null;
1137 }
1138
1139
1140 if (Arrays.equals(packChecksum, idx.packChecksum)) {
1141 bitmapIdx = idx;
1142 } else {
1143 bitmapIdxFile = null;
1144 }
1145 }
1146 return bitmapIdx;
1147 }
1148
1149 private synchronized PackReverseIndex getReverseIdx() throws IOException {
1150 if (reverseIdx == null)
1151 reverseIdx = new PackReverseIndex(idx());
1152 return reverseIdx;
1153 }
1154
1155 private boolean isCorrupt(long offset) {
1156 LongList list = corruptObjects;
1157 if (list == null)
1158 return false;
1159 synchronized (list) {
1160 return list.contains(offset);
1161 }
1162 }
1163
1164 private void setCorrupt(long offset) {
1165 LongList list = corruptObjects;
1166 if (list == null) {
1167 synchronized (readLock) {
1168 list = corruptObjects;
1169 if (list == null) {
1170 list = new LongList();
1171 corruptObjects = list;
1172 }
1173 }
1174 }
1175 synchronized (list) {
1176 list.add(offset);
1177 }
1178 }
1179
1180 @SuppressWarnings("nls")
1181 @Override
1182 public String toString() {
1183 return "Pack [packFileName=" + packFile.getName() + ", length="
1184 + packFile.length() + ", packChecksum="
1185 + ObjectId.fromRaw(packChecksum).name() + "]";
1186 }
1187 }