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