Refactor TransportBundle to not be dependent on FileInputStream
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / IndexPack.java
blobbc52896651dde9fe1fd9f9c43edc34d4900ac9f5
1 /*
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
22 * written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org.spearce.jgit.transport;
41 import java.io.EOFException;
42 import java.io.File;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.RandomAccessFile;
47 import java.security.MessageDigest;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.zip.CRC32;
53 import java.util.zip.DataFormatException;
54 import java.util.zip.Deflater;
55 import java.util.zip.Inflater;
57 import org.spearce.jgit.errors.CorruptObjectException;
58 import org.spearce.jgit.errors.MissingObjectException;
59 import org.spearce.jgit.lib.BinaryDelta;
60 import org.spearce.jgit.lib.Constants;
61 import org.spearce.jgit.lib.InflaterCache;
62 import org.spearce.jgit.lib.MutableObjectId;
63 import org.spearce.jgit.lib.ObjectId;
64 import org.spearce.jgit.lib.ObjectIdMap;
65 import org.spearce.jgit.lib.ObjectLoader;
66 import org.spearce.jgit.lib.PackIndexWriter;
67 import org.spearce.jgit.lib.ProgressMonitor;
68 import org.spearce.jgit.lib.Repository;
69 import org.spearce.jgit.util.NB;
71 /** Indexes Git pack files for local use. */
72 public class IndexPack {
73 /** Progress message when reading raw data from the pack. */
74 public static final String PROGRESS_DOWNLOAD = "Receiving objects";
76 /** Progress message when computing names of delta compressed objects. */
77 public static final String PROGRESS_RESOLVE_DELTA = "Resolving deltas";
79 /**
80 * Size of the internal stream buffer.
81 * <p>
82 * If callers are going to be supplying IndexPack a BufferedInputStream they
83 * should use this buffer size as the size of the buffer for that
84 * BufferedInputStream, and any other its may be wrapping. This way the
85 * buffers will cascade efficiently and only the IndexPack buffer will be
86 * receiving the bulk of the data stream.
88 public static final int BUFFER_SIZE = 8192;
90 /**
91 * Create an index pack instance to load a new pack into a repository.
92 * <p>
93 * The received pack data and generated index will be saved to temporary
94 * files within the repository's <code>objects</code> directory. To use the
95 * data contained within them call {@link #renameAndOpenPack()} once the
96 * indexing is complete.
98 * @param db
99 * the repository that will receive the new pack.
100 * @param is
101 * stream to read the pack data from. If the stream is buffered
102 * use {@link #BUFFER_SIZE} as the buffer size for the stream.
103 * @return a new index pack instance.
104 * @throws IOException
105 * a temporary file could not be created.
107 public static IndexPack create(final Repository db, final InputStream is)
108 throws IOException {
109 final String suffix = ".pack";
110 final File objdir = db.getObjectsDirectory();
111 final File tmp = File.createTempFile("incoming_", suffix, objdir);
112 final String n = tmp.getName();
113 final File base;
115 base = new File(objdir, n.substring(0, n.length() - suffix.length()));
116 final IndexPack ip = new IndexPack(db, is, base);
117 ip.setIndexVersion(db.getConfig().getCore().getPackIndexVersion());
118 return ip;
121 private final Repository repo;
123 private Inflater inflater;
125 private final MessageDigest objectDigest;
127 private final MutableObjectId tempObjectId;
129 private InputStream in;
131 private byte[] buf;
133 private long bBase;
135 private int bOffset;
137 private int bAvail;
139 private boolean fixThin;
141 private int outputVersion;
143 private final File dstPack;
145 private final File dstIdx;
147 private long objectCount;
149 private PackedObjectInfo[] entries;
151 private int deltaCount;
153 private int entryCount;
155 private final CRC32 crc = new CRC32();
157 private ObjectIdMap<ArrayList<UnresolvedDelta>> baseById;
159 private HashMap<Long, ArrayList<UnresolvedDelta>> baseByPos;
161 private byte[] objectData;
163 private MessageDigest packDigest;
165 private RandomAccessFile packOut;
167 private byte[] packcsum;
169 /** If {@link #fixThin} this is the last byte of the original checksum. */
170 private long originalEOF;
173 * Create a new pack indexer utility.
175 * @param db
176 * @param src
177 * stream to read the pack data from. If the stream is buffered
178 * use {@link #BUFFER_SIZE} as the buffer size for the stream.
179 * @param dstBase
180 * @throws IOException
181 * the output packfile could not be created.
183 public IndexPack(final Repository db, final InputStream src,
184 final File dstBase) throws IOException {
185 repo = db;
186 in = src;
187 inflater = InflaterCache.get();
188 buf = new byte[BUFFER_SIZE];
189 objectData = new byte[BUFFER_SIZE];
190 objectDigest = Constants.newMessageDigest();
191 tempObjectId = new MutableObjectId();
192 packDigest = Constants.newMessageDigest();
194 if (dstBase != null) {
195 final File dir = dstBase.getParentFile();
196 final String nam = dstBase.getName();
197 dstPack = new File(dir, nam + ".pack");
198 dstIdx = new File(dir, nam + ".idx");
199 packOut = new RandomAccessFile(dstPack, "rw");
200 packOut.setLength(0);
201 } else {
202 dstPack = null;
203 dstIdx = null;
208 * Set the pack index file format version this instance will create.
210 * @param version
211 * the version to write. The special version 0 designates the
212 * oldest (most compatible) format available for the objects.
213 * @see PackIndexWriter
215 public void setIndexVersion(final int version) {
216 outputVersion = version;
220 * Configure this index pack instance to make a thin pack complete.
221 * <p>
222 * Thin packs are sometimes used during network transfers to allow a delta
223 * to be sent without a base object. Such packs are not permitted on disk.
224 * They can be fixed by copying the base object onto the end of the pack.
226 * @param fix
227 * true to enable fixing a thin pack.
229 public void setFixThin(final boolean fix) {
230 fixThin = fix;
234 * Consume data from the input stream until the packfile is indexed.
236 * @param progress
237 * progress feedback
239 * @throws IOException
241 public void index(final ProgressMonitor progress) throws IOException {
242 progress.start(2 /* tasks */);
243 try {
244 try {
245 readPackHeader();
247 entries = new PackedObjectInfo[(int) objectCount];
248 baseById = new ObjectIdMap<ArrayList<UnresolvedDelta>>();
249 baseByPos = new HashMap<Long, ArrayList<UnresolvedDelta>>();
251 progress.beginTask(PROGRESS_DOWNLOAD, (int) objectCount);
252 for (int done = 0; done < objectCount; done++) {
253 indexOneObject();
254 progress.update(1);
255 if (progress.isCancelled())
256 throw new IOException("Download cancelled");
258 readPackFooter();
259 endInput();
260 progress.endTask();
261 if (deltaCount > 0) {
262 if (packOut == null)
263 throw new IOException("need packOut");
264 resolveDeltas(progress);
265 if (entryCount < objectCount) {
266 if (!fixThin) {
267 throw new IOException("pack has "
268 + (objectCount - entryCount)
269 + " unresolved deltas");
271 fixThinPack(progress);
274 if (packOut != null)
275 packOut.getChannel().force(true);
277 packDigest = null;
278 baseById = null;
279 baseByPos = null;
281 if (dstIdx != null)
282 writeIdx();
284 } finally {
285 final Inflater inf = inflater;
286 inflater = null;
287 InflaterCache.release(inf);
289 progress.endTask();
290 if (packOut != null)
291 packOut.close();
294 if (dstPack != null)
295 dstPack.setReadOnly();
296 if (dstIdx != null)
297 dstIdx.setReadOnly();
298 } catch (IOException err) {
299 if (dstPack != null)
300 dstPack.delete();
301 if (dstIdx != null)
302 dstIdx.delete();
303 throw err;
307 private void resolveDeltas(final ProgressMonitor progress)
308 throws IOException {
309 progress.beginTask(PROGRESS_RESOLVE_DELTA, deltaCount);
310 final int last = entryCount;
311 for (int i = 0; i < last; i++) {
312 final int before = entryCount;
313 resolveDeltas(entries[i]);
314 progress.update(entryCount - before);
315 if (progress.isCancelled())
316 throw new IOException("Download cancelled during indexing");
318 progress.endTask();
321 private void resolveDeltas(final PackedObjectInfo oe) throws IOException {
322 final int oldCRC = oe.getCRC();
323 if (baseById.containsKey(oe)
324 || baseByPos.containsKey(new Long(oe.getOffset())))
325 resolveDeltas(oe.getOffset(), oldCRC, Constants.OBJ_BAD, null, oe);
328 private void resolveDeltas(final long pos, final int oldCRC, int type,
329 byte[] data, PackedObjectInfo oe) throws IOException {
330 crc.reset();
331 position(pos);
332 int c = readFromFile();
333 final int typeCode = (c >> 4) & 7;
334 long sz = c & 15;
335 int shift = 4;
336 while ((c & 0x80) != 0) {
337 c = readFromFile();
338 sz += (c & 0x7f) << shift;
339 shift += 7;
342 switch (typeCode) {
343 case Constants.OBJ_COMMIT:
344 case Constants.OBJ_TREE:
345 case Constants.OBJ_BLOB:
346 case Constants.OBJ_TAG:
347 type = typeCode;
348 data = inflateFromFile((int) sz);
349 break;
350 case Constants.OBJ_OFS_DELTA: {
351 c = readFromFile() & 0xff;
352 while ((c & 128) != 0)
353 c = readFromFile() & 0xff;
354 data = BinaryDelta.apply(data, inflateFromFile((int) sz));
355 break;
357 case Constants.OBJ_REF_DELTA: {
358 crc.update(buf, fillFromFile(20), 20);
359 use(20);
360 data = BinaryDelta.apply(data, inflateFromFile((int) sz));
361 break;
363 default:
364 throw new IOException("Unknown object type " + typeCode + ".");
367 final int crc32 = (int) crc.getValue();
368 if (oldCRC != crc32)
369 throw new IOException("Corruption detected re-reading at " + pos);
370 if (oe == null) {
371 objectDigest.update(Constants.encodedTypeString(type));
372 objectDigest.update((byte) ' ');
373 objectDigest.update(Constants.encodeASCII(data.length));
374 objectDigest.update((byte) 0);
375 objectDigest.update(data);
376 tempObjectId.fromRaw(objectDigest.digest(), 0);
378 oe = new PackedObjectInfo(pos, crc32, tempObjectId);
379 entries[entryCount++] = oe;
382 resolveChildDeltas(pos, type, data, oe);
385 private void resolveChildDeltas(final long pos, int type, byte[] data,
386 PackedObjectInfo oe) throws IOException {
387 final ArrayList<UnresolvedDelta> a = baseById.remove(oe);
388 final ArrayList<UnresolvedDelta> b = baseByPos.remove(new Long(pos));
389 int ai = 0, bi = 0;
390 if (a != null && b != null) {
391 while (ai < a.size() && bi < b.size()) {
392 final UnresolvedDelta ad = a.get(ai);
393 final UnresolvedDelta bd = b.get(bi);
394 if (ad.position < bd.position) {
395 resolveDeltas(ad.position, ad.crc, type, data, null);
396 ai++;
397 } else {
398 resolveDeltas(bd.position, bd.crc, type, data, null);
399 bi++;
403 if (a != null)
404 while (ai < a.size()) {
405 final UnresolvedDelta ad = a.get(ai++);
406 resolveDeltas(ad.position, ad.crc, type, data, null);
408 if (b != null)
409 while (bi < b.size()) {
410 final UnresolvedDelta bd = b.get(bi++);
411 resolveDeltas(bd.position, bd.crc, type, data, null);
415 private void fixThinPack(final ProgressMonitor progress) throws IOException {
416 growEntries();
418 packDigest.reset();
419 originalEOF = packOut.length() - 20;
420 final Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
421 long end = originalEOF;
422 for (final ObjectId baseId : new ArrayList<ObjectId>(baseById.keySet())) {
423 final ObjectLoader ldr = repo.openObject(baseId);
424 if (ldr == null)
425 continue;
426 final byte[] data = ldr.getBytes();
427 final int typeCode = ldr.getType();
428 final PackedObjectInfo oe;
430 crc.reset();
431 packOut.seek(end);
432 writeWhole(def, typeCode, data);
433 oe = new PackedObjectInfo(end, (int) crc.getValue(), baseId);
434 entries[entryCount++] = oe;
435 end = packOut.getFilePointer();
437 resolveChildDeltas(oe.getOffset(), typeCode, data, oe);
438 if (progress.isCancelled())
439 throw new IOException("Download cancelled during indexing");
441 def.end();
443 if (!baseById.isEmpty()) {
444 final ObjectId need = baseById.keySet().iterator().next();
445 throw new MissingObjectException(need, "delta base");
448 fixHeaderFooter(packcsum, packDigest.digest());
451 private void writeWhole(final Deflater def, final int typeCode,
452 final byte[] data) throws IOException {
453 int sz = data.length;
454 int hdrlen = 0;
455 buf[hdrlen++] = (byte) ((typeCode << 4) | sz & 15);
456 sz >>>= 4;
457 while (sz > 0) {
458 buf[hdrlen - 1] |= 0x80;
459 buf[hdrlen++] = (byte) (sz & 0x7f);
460 sz >>>= 7;
462 packDigest.update(buf, 0, hdrlen);
463 crc.update(buf, 0, hdrlen);
464 packOut.write(buf, 0, hdrlen);
465 def.reset();
466 def.setInput(data);
467 def.finish();
468 while (!def.finished()) {
469 final int datlen = def.deflate(buf);
470 packDigest.update(buf, 0, datlen);
471 crc.update(buf, 0, datlen);
472 packOut.write(buf, 0, datlen);
476 private void fixHeaderFooter(final byte[] origcsum, final byte[] tailcsum)
477 throws IOException {
478 final MessageDigest origDigest = Constants.newMessageDigest();
479 final MessageDigest tailDigest = Constants.newMessageDigest();
480 long origRemaining = originalEOF;
482 packOut.seek(0);
483 bAvail = 0;
484 bOffset = 0;
485 fillFromFile(12);
488 final int origCnt = (int) Math.min(bAvail, origRemaining);
489 origDigest.update(buf, 0, origCnt);
490 origRemaining -= origCnt;
491 if (origRemaining == 0)
492 tailDigest.update(buf, origCnt, bAvail - origCnt);
495 NB.encodeInt32(buf, 8, entryCount);
496 packOut.seek(0);
497 packOut.write(buf, 0, 12);
498 packOut.seek(bAvail);
500 packDigest.reset();
501 packDigest.update(buf, 0, bAvail);
502 for (;;) {
503 final int n = packOut.read(buf);
504 if (n < 0)
505 break;
506 if (origRemaining != 0) {
507 final int origCnt = (int) Math.min(n, origRemaining);
508 origDigest.update(buf, 0, origCnt);
509 origRemaining -= origCnt;
510 if (origRemaining == 0)
511 tailDigest.update(buf, origCnt, n - origCnt);
512 } else
513 tailDigest.update(buf, 0, n);
515 packDigest.update(buf, 0, n);
518 if (!Arrays.equals(origDigest.digest(), origcsum)
519 || !Arrays.equals(tailDigest.digest(), tailcsum))
520 throw new IOException("Pack corrupted while writing to filesystem");
522 packcsum = packDigest.digest();
523 packOut.write(packcsum);
526 private void growEntries() {
527 final PackedObjectInfo[] ne;
529 ne = new PackedObjectInfo[(int) objectCount + baseById.size()];
530 System.arraycopy(entries, 0, ne, 0, entryCount);
531 entries = ne;
534 private void writeIdx() throws IOException {
535 Arrays.sort(entries, 0, entryCount);
536 List<PackedObjectInfo> list = Arrays.asList(entries);
537 if (entryCount < entries.length)
538 list = list.subList(0, entryCount);
540 final FileOutputStream os = new FileOutputStream(dstIdx);
541 try {
542 final PackIndexWriter iw;
543 if (outputVersion <= 0)
544 iw = PackIndexWriter.createOldestPossible(os, list);
545 else
546 iw = PackIndexWriter.createVersion(os, outputVersion);
547 iw.write(list, packcsum);
548 os.getChannel().force(true);
549 } finally {
550 os.close();
554 private void readPackHeader() throws IOException {
555 final int hdrln = Constants.PACK_SIGNATURE.length + 4 + 4;
556 final int p = fillFromInput(hdrln);
557 for (int k = 0; k < Constants.PACK_SIGNATURE.length; k++)
558 if (buf[p + k] != Constants.PACK_SIGNATURE[k])
559 throw new IOException("Not a PACK file.");
561 final long vers = NB.decodeUInt32(buf, p + 4);
562 if (vers != 2 && vers != 3)
563 throw new IOException("Unsupported pack version " + vers + ".");
564 objectCount = NB.decodeUInt32(buf, p + 8);
565 use(hdrln);
568 private void readPackFooter() throws IOException {
569 sync();
570 final byte[] cmpcsum = packDigest.digest();
571 final int c = fillFromInput(20);
572 packcsum = new byte[20];
573 System.arraycopy(buf, c, packcsum, 0, 20);
574 use(20);
575 if (packOut != null)
576 packOut.write(packcsum);
578 if (!Arrays.equals(cmpcsum, packcsum))
579 throw new CorruptObjectException("Packfile checksum incorrect.");
582 // Cleanup all resources associated with our input parsing.
583 private void endInput() {
584 in = null;
585 objectData = null;
588 // Read one entire object or delta from the input.
589 private void indexOneObject() throws IOException {
590 final long pos = position();
592 crc.reset();
593 int c = readFromInput();
594 final int typeCode = (c >> 4) & 7;
595 long sz = c & 15;
596 int shift = 4;
597 while ((c & 0x80) != 0) {
598 c = readFromInput();
599 sz += (c & 0x7f) << shift;
600 shift += 7;
603 switch (typeCode) {
604 case Constants.OBJ_COMMIT:
605 case Constants.OBJ_TREE:
606 case Constants.OBJ_BLOB:
607 case Constants.OBJ_TAG:
608 whole(typeCode, pos, sz);
609 break;
610 case Constants.OBJ_OFS_DELTA: {
611 c = readFromInput();
612 long ofs = c & 127;
613 while ((c & 128) != 0) {
614 ofs += 1;
615 c = readFromInput();
616 ofs <<= 7;
617 ofs += (c & 127);
619 final Long base = new Long(pos - ofs);
620 ArrayList<UnresolvedDelta> r = baseByPos.get(base);
621 if (r == null) {
622 r = new ArrayList<UnresolvedDelta>(8);
623 baseByPos.put(base, r);
625 inflateFromInput(false);
626 r.add(new UnresolvedDelta(pos, (int) crc.getValue()));
627 deltaCount++;
628 break;
630 case Constants.OBJ_REF_DELTA: {
631 c = fillFromInput(20);
632 crc.update(buf, c, 20);
633 final ObjectId base = ObjectId.fromRaw(buf, c);
634 use(20);
635 ArrayList<UnresolvedDelta> r = baseById.get(base);
636 if (r == null) {
637 r = new ArrayList<UnresolvedDelta>(8);
638 baseById.put(base, r);
640 inflateFromInput(false);
641 r.add(new UnresolvedDelta(pos, (int) crc.getValue()));
642 deltaCount++;
643 break;
645 default:
646 throw new IOException("Unknown object type " + typeCode + ".");
650 private void whole(final int type, final long pos, final long sz)
651 throws IOException {
652 objectDigest.update(Constants.encodedTypeString(type));
653 objectDigest.update((byte) ' ');
654 objectDigest.update(Constants.encodeASCII(sz));
655 objectDigest.update((byte) 0);
656 inflateFromInput(true);
657 tempObjectId.fromRaw(objectDigest.digest(), 0);
659 final int crc32 = (int) crc.getValue();
660 entries[entryCount++] = new PackedObjectInfo(pos, crc32, tempObjectId);
663 // Current position of {@link #bOffset} within the entire file.
664 private long position() {
665 return bBase + bOffset;
668 private void position(final long pos) throws IOException {
669 packOut.seek(pos);
670 bBase = pos;
671 bOffset = 0;
672 bAvail = 0;
675 // Consume exactly one byte from the buffer and return it.
676 private int readFromInput() throws IOException {
677 if (bAvail == 0)
678 fillFromInput(1);
679 bAvail--;
680 final int b = buf[bOffset++] & 0xff;
681 crc.update(b);
682 return b;
685 // Consume exactly one byte from the buffer and return it.
686 private int readFromFile() throws IOException {
687 if (bAvail == 0)
688 fillFromFile(1);
689 bAvail--;
690 final int b = buf[bOffset++] & 0xff;
691 crc.update(b);
692 return b;
695 // Consume cnt bytes from the buffer.
696 private void use(final int cnt) {
697 bOffset += cnt;
698 bAvail -= cnt;
701 // Ensure at least need bytes are available in in {@link #buf}.
702 private int fillFromInput(final int need) throws IOException {
703 while (bAvail < need) {
704 int next = bOffset + bAvail;
705 int free = buf.length - next;
706 if (free + bAvail < need) {
707 sync();
708 next = bAvail;
709 free = buf.length - next;
711 next = in.read(buf, next, free);
712 if (next <= 0)
713 throw new EOFException("Packfile is truncated.");
714 bAvail += next;
716 return bOffset;
719 // Ensure at least need bytes are available in in {@link #buf}.
720 private int fillFromFile(final int need) throws IOException {
721 if (bAvail < need) {
722 int next = bOffset + bAvail;
723 int free = buf.length - next;
724 if (free + bAvail < need) {
725 if (bAvail > 0)
726 System.arraycopy(buf, bOffset, buf, 0, bAvail);
727 bOffset = 0;
728 next = bAvail;
729 free = buf.length - next;
731 next = packOut.read(buf, next, free);
732 if (next <= 0)
733 throw new EOFException("Packfile is truncated.");
734 bAvail += next;
736 return bOffset;
739 // Store consumed bytes in {@link #buf} up to {@link #bOffset}.
740 private void sync() throws IOException {
741 packDigest.update(buf, 0, bOffset);
742 if (packOut != null)
743 packOut.write(buf, 0, bOffset);
744 if (bAvail > 0)
745 System.arraycopy(buf, bOffset, buf, 0, bAvail);
746 bBase += bOffset;
747 bOffset = 0;
750 private void inflateFromInput(final boolean digest) throws IOException {
751 final Inflater inf = inflater;
752 try {
753 final byte[] dst = objectData;
754 int n = 0;
755 int p = -1;
756 while (!inf.finished()) {
757 if (inf.needsInput()) {
758 if (p >= 0) {
759 crc.update(buf, p, bAvail);
760 use(bAvail);
762 p = fillFromInput(1);
763 inf.setInput(buf, p, bAvail);
766 int free = dst.length - n;
767 if (free < 8) {
768 if (digest)
769 objectDigest.update(dst, 0, n);
770 n = 0;
771 free = dst.length;
774 n += inf.inflate(dst, n, free);
776 if (digest)
777 objectDigest.update(dst, 0, n);
778 n = bAvail - inf.getRemaining();
779 if (n > 0) {
780 crc.update(buf, p, n);
781 use(n);
783 } catch (DataFormatException dfe) {
784 throw corrupt(dfe);
785 } finally {
786 inf.reset();
790 private byte[] inflateFromFile(final int sz) throws IOException {
791 final Inflater inf = inflater;
792 try {
793 final byte[] dst = new byte[sz];
794 int n = 0;
795 int p = -1;
796 while (!inf.finished()) {
797 if (inf.needsInput()) {
798 if (p >= 0) {
799 crc.update(buf, p, bAvail);
800 use(bAvail);
802 p = fillFromFile(1);
803 inf.setInput(buf, p, bAvail);
805 n += inf.inflate(dst, n, sz - n);
807 n = bAvail - inf.getRemaining();
808 if (n > 0) {
809 crc.update(buf, p, n);
810 use(n);
812 return dst;
813 } catch (DataFormatException dfe) {
814 throw corrupt(dfe);
815 } finally {
816 inf.reset();
820 private static CorruptObjectException corrupt(final DataFormatException dfe) {
821 return new CorruptObjectException("Packfile corruption detected: "
822 + dfe.getMessage());
825 private static class UnresolvedDelta {
826 final long position;
828 final int crc;
830 UnresolvedDelta(final long headerOffset, final int crc32) {
831 position = headerOffset;
832 crc = crc32;
837 * Rename the pack to it's final name and location and open it.
838 * <p>
839 * If the call completes successfully the repository this IndexPack instance
840 * was created with will have the objects in the pack available for reading
841 * and use, without needing to scan for packs.
843 * @throws IOException
844 * The pack could not be inserted into the repository's objects
845 * directory. The pack no longer exists on disk, as it was
846 * removed prior to throwing the exception to the caller.
848 public void renameAndOpenPack() throws IOException {
849 final MessageDigest d = Constants.newMessageDigest();
850 final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
851 for (int i = 0; i < entryCount; i++) {
852 final PackedObjectInfo oe = entries[i];
853 oe.copyRawTo(oeBytes, 0);
854 d.update(oeBytes);
857 final String name = ObjectId.fromRaw(d.digest()).name();
858 final File packDir = new File(repo.getObjectsDirectory(), "pack");
859 final File finalPack = new File(packDir, "pack-" + name + ".pack");
860 final File finalIdx = new File(packDir, "pack-" + name + ".idx");
862 if (finalPack.exists()) {
863 // If the pack is already present we should never replace it.
865 cleanupTemporaryFiles();
866 return;
869 if (!dstPack.renameTo(finalPack)) {
870 cleanupTemporaryFiles();
871 throw new IOException("Cannot move pack to " + finalPack);
874 if (!dstIdx.renameTo(finalIdx)) {
875 cleanupTemporaryFiles();
876 if (!finalPack.delete())
877 finalPack.deleteOnExit();
878 throw new IOException("Cannot move index to " + finalIdx);
881 try {
882 repo.openPack(finalPack, finalIdx);
883 } catch (IOException err) {
884 finalPack.delete();
885 finalIdx.delete();
886 throw err;
890 private void cleanupTemporaryFiles() {
891 if (!dstIdx.delete())
892 dstIdx.deleteOnExit();
893 if (!dstPack.delete())
894 dstPack.deleteOnExit();