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