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(
525 corruptObject);
526
527 } catch (IOException ioError) {
528 throw new StoredObjectRepresentationNotAvailableException(ioError);
529 }
530
531 if (quickCopy != null) {
532
533
534
535 out.writeHeader(src, inflatedLength);
536 quickCopy.write(out, dataOffset, (int) dataLength);
537
538 } else if (dataLength <= buf.length) {
539
540
541
542 if (!validate) {
543 long pos = dataOffset;
544 long cnt = dataLength;
545 while (cnt > 0) {
546 final int n = (int) Math.min(cnt, buf.length);
547 readFully(pos, buf, 0, n, curs);
548 pos += n;
549 cnt -= n;
550 }
551 }
552 out.writeHeader(src, inflatedLength);
553 out.write(buf, 0, (int) dataLength);
554 } else {
555
556
557
558
559 out.writeHeader(src, inflatedLength);
560 long pos = dataOffset;
561 long cnt = dataLength;
562 while (cnt > 0) {
563 final int n = (int) Math.min(cnt, buf.length);
564 readFully(pos, buf, 0, n, curs);
565 if (validate) {
566 assert(crc2 != null);
567 crc2.update(buf, 0, n);
568 }
569 out.write(buf, 0, n);
570 pos += n;
571 cnt -= n;
572 }
573 if (validate) {
574 assert(crc2 != null);
575 if (crc2.getValue() != expectedCRC) {
576 throw new CorruptObjectException(MessageFormat.format(
577 JGitText.get().objectAtHasBadZlibStream,
578 Long.valueOf(src.offset), getPackFile()));
579 }
580 }
581 }
582 }
583
584 boolean invalid() {
585 return invalid;
586 }
587
588 void setInvalid() {
589 invalid = true;
590 }
591
592 int incrementTransientErrorCount() {
593 return transientErrorCount.incrementAndGet();
594 }
595
596 void resetTransientErrorCount() {
597 transientErrorCount.set(0);
598 }
599
600 private void readFully(final long position, final byte[] dstbuf,
601 int dstoff, final int cnt, final WindowCursor curs)
602 throws IOException {
603 if (curs.copy(this, position, dstbuf, dstoff, cnt) != cnt)
604 throw new EOFException();
605 }
606
607 private synchronized void beginCopyAsIs(ObjectToPack otp)
608 throws StoredObjectRepresentationNotAvailableException {
609 if (++activeCopyRawData == 1 && activeWindows == 0) {
610 try {
611 doOpen();
612 } catch (IOException thisPackNotValid) {
613 throw new StoredObjectRepresentationNotAvailableException(
614 thisPackNotValid);
615 }
616 }
617 }
618
619 private synchronized void endCopyAsIs() {
620 if (--activeCopyRawData == 0 && activeWindows == 0)
621 doClose();
622 }
623
624 synchronized boolean beginWindowCache() throws IOException {
625 if (++activeWindows == 1) {
626 if (activeCopyRawData == 0)
627 doOpen();
628 return true;
629 }
630 return false;
631 }
632
633 synchronized boolean endWindowCache() {
634 final boolean r = --activeWindows == 0;
635 if (r && activeCopyRawData == 0)
636 doClose();
637 return r;
638 }
639
640 private void doOpen() throws IOException {
641 if (invalid) {
642 openFail(true, invalidatingCause);
643 throw new PackInvalidException(packFile, invalidatingCause);
644 }
645 try {
646 synchronized (readLock) {
647 fd = new RandomAccessFile(packFile, "r");
648 length = fd.length();
649 onOpenPack();
650 }
651 } catch (InterruptedIOException e) {
652
653 openFail(false, e);
654 throw e;
655 } catch (FileNotFoundException fn) {
656
657
658
659 openFail(!packFile.exists(), fn);
660 throw fn;
661 } catch (EOFException | AccessDeniedException | NoSuchFileException
662 | CorruptObjectException | NoPackSignatureException
663 | PackMismatchException | UnpackException
664 | UnsupportedPackIndexVersionException
665 | UnsupportedPackVersionException pe) {
666
667 openFail(true, pe);
668 throw pe;
669 } catch (IOException | RuntimeException ge) {
670
671
672 openFail(false, ge);
673 throw ge;
674 }
675 }
676
677 private void openFail(boolean invalidate, Exception cause) {
678 activeWindows = 0;
679 activeCopyRawData = 0;
680 invalid = invalidate;
681 invalidatingCause = cause;
682 doClose();
683 }
684
685 private void doClose() {
686 synchronized (readLock) {
687 if (fd != null) {
688 try {
689 fd.close();
690 } catch (IOException err) {
691
692
693
694 }
695 fd = null;
696 }
697 }
698 }
699
700 ByteArrayWindow read(long pos, int size) throws IOException {
701 synchronized (readLock) {
702 if (invalid || fd == null) {
703
704
705
706
707
708 throw new PackInvalidException(packFile, invalidatingCause);
709 }
710 if (length < pos + size)
711 size = (int) (length - pos);
712 final byte[] buf = new byte[size];
713 fd.seek(pos);
714 fd.readFully(buf, 0, size);
715 return new ByteArrayWindow(this, pos, buf);
716 }
717 }
718
719 ByteWindow mmap(long pos, int size) throws IOException {
720 synchronized (readLock) {
721 if (length < pos + size)
722 size = (int) (length - pos);
723
724 MappedByteBuffer map;
725 try {
726 map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
727 } catch (IOException ioe1) {
728
729
730
731
732 System.gc();
733 System.runFinalization();
734 map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
735 }
736
737 if (map.hasArray())
738 return new ByteArrayWindow(this, pos, map.array());
739 return new ByteBufferWindow(this, pos, map);
740 }
741 }
742
743 private void onOpenPack() throws IOException {
744 final PackIndex idx = idx();
745 final byte[] buf = new byte[20];
746
747 fd.seek(0);
748 fd.readFully(buf, 0, 12);
749 if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4) {
750 throw new NoPackSignatureException(JGitText.get().notAPACKFile);
751 }
752 final long vers = NB.decodeUInt32(buf, 4);
753 final long packCnt = NB.decodeUInt32(buf, 8);
754 if (vers != 2 && vers != 3) {
755 throw new UnsupportedPackVersionException(vers);
756 }
757
758 if (packCnt != idx.getObjectCount()) {
759 throw new PackMismatchException(MessageFormat.format(
760 JGitText.get().packObjectCountMismatch,
761 Long.valueOf(packCnt), Long.valueOf(idx.getObjectCount()),
762 getPackFile()));
763 }
764
765 fd.seek(length - 20);
766 fd.readFully(buf, 0, 20);
767 if (!Arrays.equals(buf, packChecksum)) {
768 throw new PackMismatchException(MessageFormat.format(
769 JGitText.get().packChecksumMismatch,
770 getPackFile(),
771 ObjectId.fromRaw(buf).name(),
772 ObjectId.fromRaw(idx.packChecksum).name()));
773 }
774 }
775
776 ObjectLoader load(WindowCursor curs, long pos)
777 throws IOException, LargeObjectException {
778 try {
779 final byte[] ib = curs.tempId;
780 Delta delta = null;
781 byte[] data = null;
782 int type = Constants.OBJ_BAD;
783 boolean cached = false;
784
785 SEARCH: for (;;) {
786 readFully(pos, ib, 0, 20, curs);
787 int c = ib[0] & 0xff;
788 final int typeCode = (c >> 4) & 7;
789 long sz = c & 15;
790 int shift = 4;
791 int p = 1;
792 while ((c & 0x80) != 0) {
793 c = ib[p++] & 0xff;
794 sz += ((long) (c & 0x7f)) << shift;
795 shift += 7;
796 }
797
798 switch (typeCode) {
799 case Constants.OBJ_COMMIT:
800 case Constants.OBJ_TREE:
801 case Constants.OBJ_BLOB:
802 case Constants.OBJ_TAG: {
803 if (delta != null || sz < curs.getStreamFileThreshold()) {
804 data = decompress(pos + p, (int) sz, curs);
805 }
806
807 if (delta != null) {
808 type = typeCode;
809 break SEARCH;
810 }
811
812 if (data != null) {
813 return new ObjectLoader.SmallObject(typeCode, data);
814 }
815 return new LargePackedWholeObject(typeCode, sz, pos, p,
816 this, curs.db);
817 }
818
819 case Constants.OBJ_OFS_DELTA: {
820 c = ib[p++] & 0xff;
821 long base = c & 127;
822 while ((c & 128) != 0) {
823 base += 1;
824 c = ib[p++] & 0xff;
825 base <<= 7;
826 base += (c & 127);
827 }
828 base = pos - base;
829 delta = new Delta(delta, pos, (int) sz, p, base);
830 if (sz != delta.deltaSize)
831 break SEARCH;
832
833 DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
834 if (e != null) {
835 type = e.type;
836 data = e.data;
837 cached = true;
838 break SEARCH;
839 }
840 pos = base;
841 continue SEARCH;
842 }
843
844 case Constants.OBJ_REF_DELTA: {
845 readFully(pos + p, ib, 0, 20, curs);
846 long base = findDeltaBase(ObjectId.fromRaw(ib));
847 delta = new Delta(delta, pos, (int) sz, p + 20, base);
848 if (sz != delta.deltaSize)
849 break SEARCH;
850
851 DeltaBaseCache.Entry e = curs.getDeltaBaseCache().get(this, base);
852 if (e != null) {
853 type = e.type;
854 data = e.data;
855 cached = true;
856 break SEARCH;
857 }
858 pos = base;
859 continue SEARCH;
860 }
861
862 default:
863 throw new IOException(MessageFormat.format(
864 JGitText.get().unknownObjectType,
865 Integer.valueOf(typeCode)));
866 }
867 }
868
869
870
871
872 if (data == null)
873 throw new IOException(JGitText.get().inMemoryBufferLimitExceeded);
874
875 assert(delta != null);
876 do {
877
878 if (cached)
879 cached = false;
880 else if (delta.next == null)
881 curs.getDeltaBaseCache().store(this, delta.basePos, data, type);
882
883 pos = delta.deltaPos;
884
885 final byte[] cmds = decompress(pos + delta.hdrLen,
886 delta.deltaSize, curs);
887 if (cmds == null) {
888 data = null;
889 throw new LargeObjectException.OutOfMemory(new OutOfMemoryError());
890 }
891
892 final long sz = BinaryDelta.getResultSize(cmds);
893 if (Integer.MAX_VALUE <= sz)
894 throw new LargeObjectException.ExceedsByteArrayLimit();
895
896 final byte[] result;
897 try {
898 result = new byte[(int) sz];
899 } catch (OutOfMemoryError tooBig) {
900 data = null;
901 throw new LargeObjectException.OutOfMemory(tooBig);
902 }
903
904 BinaryDelta.apply(data, cmds, result);
905 data = result;
906 delta = delta.next;
907 } while (delta != null);
908
909 return new ObjectLoader.SmallObject(type, data);
910
911 } catch (DataFormatException dfe) {
912 throw new CorruptObjectException(
913 MessageFormat.format(
914 JGitText.get().objectAtHasBadZlibStream,
915 Long.valueOf(pos), getPackFile()),
916 dfe);
917 }
918 }
919
920 private long findDeltaBase(ObjectId baseId) throws IOException,
921 MissingObjectException {
922 long ofs = idx().findOffset(baseId);
923 if (ofs < 0)
924 throw new MissingObjectException(baseId,
925 JGitText.get().missingDeltaBase);
926 return ofs;
927 }
928
929 private static class Delta {
930
931 final Delta next;
932
933
934 final long deltaPos;
935
936
937 final int deltaSize;
938
939
940 final int hdrLen;
941
942
943 final long basePos;
944
945 Delta(Delta next, long ofs, int sz, int hdrLen, long baseOffset) {
946 this.next = next;
947 this.deltaPos = ofs;
948 this.deltaSize = sz;
949 this.hdrLen = hdrLen;
950 this.basePos = baseOffset;
951 }
952 }
953
954 byte[] getDeltaHeader(WindowCursor wc, long pos)
955 throws IOException, DataFormatException {
956
957
958
959
960
961 final byte[] hdr = new byte[18];
962 wc.inflate(this, pos, hdr, true );
963 return hdr;
964 }
965
966 int getObjectType(WindowCursor curs, long pos) throws IOException {
967 final byte[] ib = curs.tempId;
968 for (;;) {
969 readFully(pos, ib, 0, 20, curs);
970 int c = ib[0] & 0xff;
971 final int type = (c >> 4) & 7;
972
973 switch (type) {
974 case Constants.OBJ_COMMIT:
975 case Constants.OBJ_TREE:
976 case Constants.OBJ_BLOB:
977 case Constants.OBJ_TAG:
978 return type;
979
980 case Constants.OBJ_OFS_DELTA: {
981 int p = 1;
982 while ((c & 0x80) != 0)
983 c = ib[p++] & 0xff;
984 c = ib[p++] & 0xff;
985 long ofs = c & 127;
986 while ((c & 128) != 0) {
987 ofs += 1;
988 c = ib[p++] & 0xff;
989 ofs <<= 7;
990 ofs += (c & 127);
991 }
992 pos = pos - ofs;
993 continue;
994 }
995
996 case Constants.OBJ_REF_DELTA: {
997 int p = 1;
998 while ((c & 0x80) != 0)
999 c = ib[p++] & 0xff;
1000 readFully(pos + p, ib, 0, 20, curs);
1001 pos = findDeltaBase(ObjectId.fromRaw(ib));
1002 continue;
1003 }
1004
1005 default:
1006 throw new IOException(
1007 MessageFormat.format(JGitText.get().unknownObjectType,
1008 Integer.valueOf(type)));
1009 }
1010 }
1011 }
1012
1013 long getObjectSize(WindowCursor curs, AnyObjectId id)
1014 throws IOException {
1015 final long offset = idx().findOffset(id);
1016 return 0 < offset ? getObjectSize(curs, offset) : -1;
1017 }
1018
1019 long getObjectSize(WindowCursor curs, long pos)
1020 throws IOException {
1021 final byte[] ib = curs.tempId;
1022 readFully(pos, ib, 0, 20, curs);
1023 int c = ib[0] & 0xff;
1024 final int type = (c >> 4) & 7;
1025 long sz = c & 15;
1026 int shift = 4;
1027 int p = 1;
1028 while ((c & 0x80) != 0) {
1029 c = ib[p++] & 0xff;
1030 sz += ((long) (c & 0x7f)) << shift;
1031 shift += 7;
1032 }
1033
1034 long deltaAt;
1035 switch (type) {
1036 case Constants.OBJ_COMMIT:
1037 case Constants.OBJ_TREE:
1038 case Constants.OBJ_BLOB:
1039 case Constants.OBJ_TAG:
1040 return sz;
1041
1042 case Constants.OBJ_OFS_DELTA:
1043 c = ib[p++] & 0xff;
1044 while ((c & 128) != 0)
1045 c = ib[p++] & 0xff;
1046 deltaAt = pos + p;
1047 break;
1048
1049 case Constants.OBJ_REF_DELTA:
1050 deltaAt = pos + p + 20;
1051 break;
1052
1053 default:
1054 throw new IOException(MessageFormat.format(
1055 JGitText.get().unknownObjectType, Integer.valueOf(type)));
1056 }
1057
1058 try {
1059 return BinaryDelta.getResultSize(getDeltaHeader(curs, deltaAt));
1060 } catch (DataFormatException e) {
1061 throw new CorruptObjectException(MessageFormat.format(
1062 JGitText.get().objectAtHasBadZlibStream, Long.valueOf(pos),
1063 getPackFile()), e);
1064 }
1065 }
1066
1067 LocalObjectRepresentation representation(final WindowCursor curs,
1068 final AnyObjectId objectId) throws IOException {
1069 final long pos = idx().findOffset(objectId);
1070 if (pos < 0)
1071 return null;
1072
1073 final byte[] ib = curs.tempId;
1074 readFully(pos, ib, 0, 20, curs);
1075 int c = ib[0] & 0xff;
1076 int p = 1;
1077 final int typeCode = (c >> 4) & 7;
1078 while ((c & 0x80) != 0)
1079 c = ib[p++] & 0xff;
1080
1081 long len = (findEndOffset(pos) - pos);
1082 switch (typeCode) {
1083 case Constants.OBJ_COMMIT:
1084 case Constants.OBJ_TREE:
1085 case Constants.OBJ_BLOB:
1086 case Constants.OBJ_TAG:
1087 return LocalObjectRepresentation.newWhole(this, pos, len - p);
1088
1089 case Constants.OBJ_OFS_DELTA: {
1090 c = ib[p++] & 0xff;
1091 long ofs = c & 127;
1092 while ((c & 128) != 0) {
1093 ofs += 1;
1094 c = ib[p++] & 0xff;
1095 ofs <<= 7;
1096 ofs += (c & 127);
1097 }
1098 ofs = pos - ofs;
1099 return LocalObjectRepresentation.newDelta(this, pos, len - p, ofs);
1100 }
1101
1102 case Constants.OBJ_REF_DELTA: {
1103 len -= p;
1104 len -= Constants.OBJECT_ID_LENGTH;
1105 readFully(pos + p, ib, 0, 20, curs);
1106 ObjectId id = ObjectId.fromRaw(ib);
1107 return LocalObjectRepresentation.newDelta(this, pos, len, id);
1108 }
1109
1110 default:
1111 throw new IOException(
1112 MessageFormat.format(JGitText.get().unknownObjectType,
1113 Integer.valueOf(typeCode)));
1114 }
1115 }
1116
1117 private long findEndOffset(long startOffset)
1118 throws IOException, CorruptObjectException {
1119 final long maxOffset = length - 20;
1120 return getReverseIdx().findNextOffset(startOffset, maxOffset);
1121 }
1122
1123 synchronized PackBitmapIndex getBitmapIndex() throws IOException {
1124 if (invalid || bitmapIdxFile == null) {
1125 return null;
1126 }
1127 if (bitmapIdx == null) {
1128 final PackBitmapIndex idx;
1129 try {
1130 idx = PackBitmapIndex.open(bitmapIdxFile, idx(),
1131 getReverseIdx());
1132 } catch (FileNotFoundException e) {
1133
1134
1135
1136 bitmapIdxFile = null;
1137 return null;
1138 }
1139
1140
1141 if (Arrays.equals(packChecksum, idx.packChecksum)) {
1142 bitmapIdx = idx;
1143 } else {
1144 bitmapIdxFile = null;
1145 }
1146 }
1147 return bitmapIdx;
1148 }
1149
1150 private synchronized PackReverseIndex getReverseIdx() throws IOException {
1151 if (reverseIdx == null)
1152 reverseIdx = new PackReverseIndex(idx());
1153 return reverseIdx;
1154 }
1155
1156 private boolean isCorrupt(long offset) {
1157 LongList list = corruptObjects;
1158 if (list == null)
1159 return false;
1160 synchronized (list) {
1161 return list.contains(offset);
1162 }
1163 }
1164
1165 private void setCorrupt(long offset) {
1166 LongList list = corruptObjects;
1167 if (list == null) {
1168 synchronized (readLock) {
1169 list = corruptObjects;
1170 if (list == null) {
1171 list = new LongList();
1172 corruptObjects = list;
1173 }
1174 }
1175 }
1176 synchronized (list) {
1177 list.add(offset);
1178 }
1179 }
1180
1181 @SuppressWarnings("nls")
1182 @Override
1183 public String toString() {
1184 return "Pack [packFileName=" + packFile.getName() + ", length="
1185 + packFile.length() + ", packChecksum="
1186 + ObjectId.fromRaw(packChecksum).name() + "]";
1187 }
1188 }