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