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