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