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