Switch jgit library to the EDL (3-clause BSD)
[jgit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / IndexPack.java
blob0b5c962839a8d7a29461e9ad526e2c53493d3015
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.BufferedOutputStream;
42 import java.io.EOFException;
43 import java.io.File;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.RandomAccessFile;
48 import java.security.MessageDigest;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.HashMap;
52 import java.util.zip.DataFormatException;
53 import java.util.zip.Deflater;
54 import java.util.zip.Inflater;
56 import org.spearce.jgit.errors.CorruptObjectException;
57 import org.spearce.jgit.lib.AnyObjectId;
58 import org.spearce.jgit.lib.BinaryDelta;
59 import org.spearce.jgit.lib.Constants;
60 import org.spearce.jgit.lib.InflaterCache;
61 import org.spearce.jgit.lib.MutableObjectId;
62 import org.spearce.jgit.lib.ObjectId;
63 import org.spearce.jgit.lib.ObjectIdMap;
64 import org.spearce.jgit.lib.ObjectLoader;
65 import org.spearce.jgit.lib.ProgressMonitor;
66 import org.spearce.jgit.lib.Repository;
67 import org.spearce.jgit.util.NB;
69 /** Indexes Git pack files for local use. */
70 public class IndexPack {
71 /** Progress message when reading raw data from the pack. */
72 public static final String PROGRESS_DOWNLOAD = "Receiving objects";
74 /** Progress message when computing names of delta compressed objects. */
75 public static final String PROGRESS_RESOLVE_DELTA = "Resolving deltas";
77 private static final byte[] SIGNATURE = { 'P', 'A', 'C', 'K' };
79 private static final int BUFFER_SIZE = 2048;
81 /**
82 * Create an index pack instance to load a new pack into a repository.
83 * <p>
84 * The received pack data and generated index will be saved to temporary
85 * files within the repository's <code>objects</code> directory. To use
86 * the data contained within them call {@link #renameAndOpenPack()} once the
87 * indexing is complete.
89 * @param db
90 * the repository that will receive the new pack.
91 * @param is
92 * stream to read the pack data from.
93 * @return a new index pack instance.
94 * @throws IOException
95 * a temporary file could not be created.
97 public static IndexPack create(final Repository db, final InputStream is)
98 throws IOException {
99 final String suffix = ".pack";
100 final File objdir = db.getObjectsDirectory();
101 final File tmp = File.createTempFile("incoming_", suffix, objdir);
102 final String n = tmp.getName();
103 final File base;
105 base = new File(objdir, n.substring(0, n.length() - suffix.length()));
106 return new IndexPack(db, is, base);
109 private final Repository repo;
111 private Inflater inflater;
113 private final MessageDigest objectDigest;
115 private final MutableObjectId tempObjectId;
117 private InputStream in;
119 private byte[] buf;
121 private long bBase;
123 private int bOffset;
125 private int bAvail;
127 private boolean fixThin;
129 private final File dstPack;
131 private final File dstIdx;
133 private long objectCount;
135 private ObjectEntry[] entries;
137 private int deltaCount;
139 private int entryCount;
141 private ObjectIdMap<ArrayList<UnresolvedDelta>> baseById;
143 private HashMap<Long, ArrayList<UnresolvedDelta>> baseByPos;
145 private byte[] objectData;
147 private MessageDigest packDigest;
149 private RandomAccessFile packOut;
151 private byte[] packcsum;
154 * Create a new pack indexer utility.
156 * @param db
157 * @param src
158 * @param dstBase
159 * @throws IOException
160 * the output packfile could not be created.
162 public IndexPack(final Repository db, final InputStream src,
163 final File dstBase) throws IOException {
164 repo = db;
165 in = src;
166 inflater = InflaterCache.get();
167 buf = new byte[BUFFER_SIZE];
168 objectData = new byte[BUFFER_SIZE];
169 objectDigest = Constants.newMessageDigest();
170 tempObjectId = new MutableObjectId();
171 packDigest = Constants.newMessageDigest();
173 if (dstBase != null) {
174 final File dir = dstBase.getParentFile();
175 final String nam = dstBase.getName();
176 dstPack = new File(dir, nam + ".pack");
177 dstIdx = new File(dir, nam + ".idx");
178 packOut = new RandomAccessFile(dstPack, "rw");
179 packOut.setLength(0);
180 } else {
181 dstPack = null;
182 dstIdx = null;
187 * Configure this index pack instance to make a thin pack complete.
188 * <p>
189 * Thin packs are sometimes used during network transfers to allow a delta
190 * to be sent without a base object. Such packs are not permitted on disk.
191 * They can be fixed by copying the base object onto the end of the pack.
193 * @param fix
194 * true to enable fixing a thin pack.
196 public void setFixThin(final boolean fix) {
197 fixThin = fix;
201 * Consume data from the input stream until the packfile is indexed.
203 * @param progress
204 * progress feedback
206 * @throws IOException
208 public void index(final ProgressMonitor progress) throws IOException {
209 progress.start(2 /* tasks */);
210 try {
211 try {
212 readPackHeader();
214 entries = new ObjectEntry[(int) objectCount];
215 baseById = new ObjectIdMap<ArrayList<UnresolvedDelta>>();
216 baseByPos = new HashMap<Long, ArrayList<UnresolvedDelta>>();
218 progress.beginTask(PROGRESS_DOWNLOAD, (int) objectCount);
219 for (int done = 0; done < objectCount; done++) {
220 indexOneObject();
221 progress.update(1);
222 if (progress.isCancelled())
223 throw new IOException("Download cancelled");
225 readPackFooter();
226 endInput();
227 progress.endTask();
228 if (deltaCount > 0) {
229 if (packOut == null)
230 throw new IOException("need packOut");
231 resolveDeltas(progress);
232 if (entryCount < objectCount) {
233 if (!fixThin) {
234 throw new IOException("pack has "
235 + (objectCount - entryCount)
236 + " unresolved deltas");
238 fixThinPack(progress);
242 packDigest = null;
243 baseById = null;
244 baseByPos = null;
246 if (dstIdx != null)
247 writeIdx();
249 } finally {
250 final Inflater inf = inflater;
251 inflater = null;
252 InflaterCache.release(inf);
254 progress.endTask();
255 if (packOut != null)
256 packOut.close();
259 if (dstPack != null)
260 dstPack.setReadOnly();
261 if (dstIdx != null)
262 dstIdx.setReadOnly();
263 } catch (IOException err) {
264 if (dstPack != null)
265 dstPack.delete();
266 if (dstIdx != null)
267 dstIdx.delete();
268 throw err;
272 private void resolveDeltas(final ProgressMonitor progress)
273 throws IOException {
274 progress.beginTask(PROGRESS_RESOLVE_DELTA, deltaCount);
275 final int last = entryCount;
276 for (int i = 0; i < last; i++) {
277 final int before = entryCount;
278 resolveDeltas(entries[i]);
279 progress.update(entryCount - before);
280 if (progress.isCancelled())
281 throw new IOException("Download cancelled during indexing");
283 progress.endTask();
286 private void resolveDeltas(final ObjectEntry oe) throws IOException {
287 if (baseById.containsKey(oe) || baseByPos.containsKey(new Long(oe.pos)))
288 resolveDeltas(oe.pos, Constants.OBJ_BAD, null, oe);
291 private void resolveDeltas(final long pos, int type, byte[] data,
292 ObjectEntry oe) throws IOException {
293 position(pos);
294 int c = readFromFile();
295 final int typeCode = (c >> 4) & 7;
296 long sz = c & 15;
297 int shift = 4;
298 while ((c & 0x80) != 0) {
299 c = readFromFile();
300 sz += (c & 0x7f) << shift;
301 shift += 7;
304 switch (typeCode) {
305 case Constants.OBJ_COMMIT:
306 case Constants.OBJ_TREE:
307 case Constants.OBJ_BLOB:
308 case Constants.OBJ_TAG:
309 type = typeCode;
310 data = inflateFromFile((int) sz);
311 break;
312 case Constants.OBJ_OFS_DELTA: {
313 c = readFromInput() & 0xff;
314 while ((c & 128) != 0)
315 c = readFromInput() & 0xff;
316 data = BinaryDelta.apply(data, inflateFromFile((int) sz));
317 break;
319 case Constants.OBJ_REF_DELTA: {
320 fillFromInput(20);
321 use(20);
322 data = BinaryDelta.apply(data, inflateFromFile((int) sz));
323 break;
325 default:
326 throw new IOException("Unknown object type " + typeCode + ".");
329 if (oe == null) {
330 objectDigest.update(Constants.encodedTypeString(type));
331 objectDigest.update((byte) ' ');
332 objectDigest.update(Constants.encodeASCII(data.length));
333 objectDigest.update((byte) 0);
334 objectDigest.update(data);
335 tempObjectId.fromRaw(objectDigest.digest(), 0);
336 oe = new ObjectEntry(pos, tempObjectId);
337 entries[entryCount++] = oe;
340 resolveChildDeltas(pos, type, data, oe);
343 private void resolveChildDeltas(final long pos, int type, byte[] data,
344 ObjectEntry oe) throws IOException {
345 final ArrayList<UnresolvedDelta> a = baseById.remove(oe);
346 final ArrayList<UnresolvedDelta> b = baseByPos.remove(new Long(pos));
347 int ai = 0, bi = 0;
348 if (a != null && b != null) {
349 while (ai < a.size() && bi < b.size()) {
350 final UnresolvedDelta ad = a.get(ai);
351 final UnresolvedDelta bd = b.get(bi);
352 if (ad.position < bd.position) {
353 resolveDeltas(ad.position, type, data, null);
354 ai++;
355 } else {
356 resolveDeltas(bd.position, type, data, null);
357 bi++;
361 if (a != null)
362 while (ai < a.size())
363 resolveDeltas(a.get(ai++).position, type, data, null);
364 if (b != null)
365 while (bi < b.size())
366 resolveDeltas(b.get(bi++).position, type, data, null);
369 private void fixThinPack(final ProgressMonitor progress) throws IOException {
370 growEntries();
372 final Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
373 long end = packOut.length() - 20;
374 while (!baseById.isEmpty()) {
375 final ObjectId baseId = baseById.keySet().iterator().next();
376 final ObjectLoader ldr = repo.openObject(baseId);
377 final byte[] data = ldr.getBytes();
378 final int typeCode = ldr.getType();
379 final ObjectEntry oe;
381 oe = new ObjectEntry(end, baseId);
382 entries[entryCount++] = oe;
383 packOut.seek(end);
384 writeWhole(def, typeCode, data);
385 end = packOut.getFilePointer();
387 resolveChildDeltas(oe.pos, typeCode, data, oe);
388 if (progress.isCancelled())
389 throw new IOException("Download cancelled during indexing");
391 def.end();
393 fixHeaderFooter();
396 private void writeWhole(final Deflater def, final int typeCode,
397 final byte[] data) throws IOException {
398 int sz = data.length;
399 int hdrlen = 0;
400 buf[hdrlen++] = (byte) ((typeCode << 4) | sz & 15);
401 sz >>>= 4;
402 while (sz > 0) {
403 buf[hdrlen - 1] |= 0x80;
404 buf[hdrlen++] = (byte) (sz & 0x7f);
405 sz >>>= 7;
407 packOut.write(buf, 0, hdrlen);
408 def.reset();
409 def.setInput(data);
410 def.finish();
411 while (!def.finished())
412 packOut.write(buf, 0, def.deflate(buf));
415 private void fixHeaderFooter() throws IOException {
416 packOut.seek(0);
417 if (packOut.read(buf, 0, 12) != 12)
418 throw new IOException("Cannot re-read pack header to fix count");
419 NB.encodeInt32(buf, 8, entryCount);
420 packOut.seek(0);
421 packOut.write(buf, 0, 12);
423 packDigest.reset();
424 packDigest.update(buf, 0, 12);
425 for (;;) {
426 final int n = packOut.read(buf);
427 if (n < 0)
428 break;
429 packDigest.update(buf, 0, n);
432 packcsum = packDigest.digest();
433 packOut.write(packcsum);
436 private void growEntries() {
437 final ObjectEntry[] ne;
439 ne = new ObjectEntry[(int) objectCount + baseById.size()];
440 System.arraycopy(entries, 0, ne, 0, entryCount);
441 entries = ne;
444 private void writeIdx() throws IOException {
445 Arrays.sort(entries, 0, entryCount);
446 final int[] fanout = new int[256];
447 for (int i = 0; i < entryCount; i++)
448 fanout[entries[i].getFirstByte() & 0xff]++;
449 for (int i = 1; i < 256; i++)
450 fanout[i] += fanout[i - 1];
452 final BufferedOutputStream os = new BufferedOutputStream(
453 new FileOutputStream(dstIdx), BUFFER_SIZE);
454 try {
455 final byte[] rawoe = new byte[4 + Constants.OBJECT_ID_LENGTH];
456 final MessageDigest d = Constants.newMessageDigest();
457 for (int i = 0; i < 256; i++) {
458 NB.encodeInt32(rawoe, 0, fanout[i]);
459 os.write(rawoe, 0, 4);
460 d.update(rawoe, 0, 4);
462 for (int i = 0; i < entryCount; i++) {
463 final ObjectEntry oe = entries[i];
464 if (oe.pos >>> 1 > Integer.MAX_VALUE)
465 throw new IOException("Pack too large for index version 1");
466 NB.encodeInt32(rawoe, 0, (int) oe.pos);
467 oe.copyRawTo(rawoe, 4);
468 os.write(rawoe);
469 d.update(rawoe);
471 os.write(packcsum);
472 d.update(packcsum);
473 os.write(d.digest());
474 } finally {
475 os.close();
479 private void readPackHeader() throws IOException {
480 final int hdrln = SIGNATURE.length + 4 + 4;
481 final int p = fillFromInput(hdrln);
482 for (int k = 0; k < SIGNATURE.length; k++)
483 if (buf[p + k] != SIGNATURE[k])
484 throw new IOException("Not a PACK file.");
486 final long vers = NB.decodeUInt32(buf, p + 4);
487 if (vers != 2 && vers != 3)
488 throw new IOException("Unsupported pack version " + vers + ".");
489 objectCount = NB.decodeUInt32(buf, p + 8);
490 use(hdrln);
493 private void readPackFooter() throws IOException {
494 sync();
495 final byte[] cmpcsum = packDigest.digest();
496 final int c = fillFromInput(20);
497 packcsum = new byte[20];
498 System.arraycopy(buf, c, packcsum, 0, 20);
499 use(20);
500 if (packOut != null)
501 packOut.write(packcsum);
503 if (!Arrays.equals(cmpcsum, packcsum))
504 throw new CorruptObjectException("Packfile checksum incorrect.");
507 // Cleanup all resources associated with our input parsing.
508 private void endInput() {
509 in = null;
510 objectData = null;
513 // Read one entire object or delta from the input.
514 private void indexOneObject() throws IOException {
515 final long pos = position();
517 int c = readFromInput();
518 final int typeCode = (c >> 4) & 7;
519 long sz = c & 15;
520 int shift = 4;
521 while ((c & 0x80) != 0) {
522 c = readFromInput();
523 sz += (c & 0x7f) << shift;
524 shift += 7;
527 switch (typeCode) {
528 case Constants.OBJ_COMMIT:
529 case Constants.OBJ_TREE:
530 case Constants.OBJ_BLOB:
531 case Constants.OBJ_TAG:
532 whole(typeCode, pos, sz);
533 break;
534 case Constants.OBJ_OFS_DELTA: {
535 c = readFromInput() & 0xff;
536 long ofs = c & 127;
537 while ((c & 128) != 0) {
538 ofs += 1;
539 c = readFromInput() & 0xff;
540 ofs <<= 7;
541 ofs += (c & 127);
543 final Long base = new Long(pos - ofs);
544 ArrayList<UnresolvedDelta> r = baseByPos.get(base);
545 if (r == null) {
546 r = new ArrayList<UnresolvedDelta>(8);
547 baseByPos.put(base, r);
549 r.add(new UnresolvedDelta(pos));
550 deltaCount++;
551 inflateFromInput(false);
552 break;
554 case Constants.OBJ_REF_DELTA: {
555 c = fillFromInput(20);
556 final byte[] ref = new byte[20];
557 System.arraycopy(buf, c, ref, 0, 20);
558 use(20);
559 final ObjectId base = ObjectId.fromRaw(ref);
560 ArrayList<UnresolvedDelta> r = baseById.get(base);
561 if (r == null) {
562 r = new ArrayList<UnresolvedDelta>(8);
563 baseById.put(base, r);
565 r.add(new UnresolvedDelta(pos));
566 deltaCount++;
567 inflateFromInput(false);
568 break;
570 default:
571 throw new IOException("Unknown object type " + typeCode + ".");
575 private void whole(final int type, final long pos, final long sz)
576 throws IOException {
577 objectDigest.update(Constants.encodedTypeString(type));
578 objectDigest.update((byte) ' ');
579 objectDigest.update(Constants.encodeASCII(sz));
580 objectDigest.update((byte) 0);
581 inflateFromInput(true);
582 tempObjectId.fromRaw(objectDigest.digest(), 0);
583 entries[entryCount++] = new ObjectEntry(pos, tempObjectId);
586 // Current position of {@link #bOffset} within the entire file.
587 private long position() {
588 return bBase + bOffset;
591 private void position(final long pos) throws IOException {
592 packOut.seek(pos);
593 bBase = pos;
594 bOffset = 0;
595 bAvail = 0;
598 // Consume exactly one byte from the buffer and return it.
599 private int readFromInput() throws IOException {
600 if (bAvail == 0)
601 fillFromInput(1);
602 bAvail--;
603 return buf[bOffset++] & 0xff;
606 // Consume exactly one byte from the buffer and return it.
607 private int readFromFile() throws IOException {
608 if (bAvail == 0)
609 fillFromFile();
610 bAvail--;
611 return buf[bOffset++] & 0xff;
614 // Consume cnt bytes from the buffer.
615 private void use(final int cnt) {
616 bOffset += cnt;
617 bAvail -= cnt;
620 // Ensure at least need bytes are available in in {@link #buf}.
621 private int fillFromInput(final int need) throws IOException {
622 while (bAvail < need) {
623 int next = bOffset + bAvail;
624 int free = buf.length - next;
625 if (free + bAvail < need) {
626 sync();
627 next = bAvail;
628 free = buf.length - next;
630 next = in.read(buf, next, free);
631 if (next <= 0)
632 throw new EOFException("Packfile is truncated.");
633 bAvail += next;
635 return bOffset;
638 // Ensure at least need bytes are available in in {@link #buf}.
639 private int fillFromFile() throws IOException {
640 if (bAvail == 0) {
641 final int next = packOut.read(buf, 0, buf.length);
642 if (next <= 0)
643 throw new EOFException("Packfile is truncated.");
644 bAvail = next;
645 bOffset = 0;
647 return bOffset;
650 // Store consumed bytes in {@link #buf} up to {@link #bOffset}.
651 private void sync() throws IOException {
652 packDigest.update(buf, 0, bOffset);
653 if (packOut != null)
654 packOut.write(buf, 0, bOffset);
655 if (bAvail > 0)
656 System.arraycopy(buf, bOffset, buf, 0, bAvail);
657 bBase += bOffset;
658 bOffset = 0;
661 private void inflateFromInput(final boolean digest) throws IOException {
662 final Inflater inf = inflater;
663 try {
664 final byte[] dst = objectData;
665 int n = 0;
666 while (!inf.finished()) {
667 if (inf.needsInput()) {
668 final int p = fillFromInput(1);
669 inf.setInput(buf, p, bAvail);
670 use(bAvail);
673 int free = dst.length - n;
674 if (free < 8) {
675 if (digest)
676 objectDigest.update(dst, 0, n);
677 n = 0;
678 free = dst.length;
681 n += inf.inflate(dst, n, free);
683 if (digest)
684 objectDigest.update(dst, 0, n);
685 use(-inf.getRemaining());
686 } catch (DataFormatException dfe) {
687 throw corrupt(dfe);
688 } finally {
689 inf.reset();
693 private byte[] inflateFromFile(final int sz) throws IOException {
694 final Inflater inf = inflater;
695 try {
696 final byte[] dst = new byte[sz];
697 int n = 0;
698 while (!inf.finished()) {
699 if (inf.needsInput()) {
700 final int p = fillFromFile();
701 inf.setInput(buf, p, bAvail);
702 use(bAvail);
704 n += inf.inflate(dst, n, sz - n);
706 use(-inf.getRemaining());
707 return dst;
708 } catch (DataFormatException dfe) {
709 throw corrupt(dfe);
710 } finally {
711 inf.reset();
715 private static CorruptObjectException corrupt(final DataFormatException dfe) {
716 return new CorruptObjectException("Packfile corruption detected: "
717 + dfe.getMessage());
720 private static class ObjectEntry extends ObjectId {
721 final long pos;
723 ObjectEntry(final long headerOffset, final AnyObjectId id) {
724 super(id);
725 pos = headerOffset;
729 private static class UnresolvedDelta {
730 final long position;
732 UnresolvedDelta(final long headerOffset) {
733 position = headerOffset;
738 * Rename the pack to it's final name and location and open it.
739 * <p>
740 * If the call completes successfully the repository this IndexPack instance
741 * was created with will have the objects in the pack available for reading
742 * and use, without needing to scan for packs.
744 * @throws IOException
745 * The pack could not be inserted into the repository's objects
746 * directory. The pack no longer exists on disk, as it was
747 * removed prior to throwing the exception to the caller.
749 public void renameAndOpenPack() throws IOException {
750 final MessageDigest d = Constants.newMessageDigest();
751 final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
752 for (int i = 0; i < entryCount; i++) {
753 final ObjectEntry oe = entries[i];
754 oe.copyRawTo(oeBytes, 0);
755 d.update(oeBytes);
758 final ObjectId name = ObjectId.fromRaw(d.digest());
759 final File packDir = new File(repo.getObjectsDirectory(), "pack");
760 final File finalPack = new File(packDir, "pack-" + name + ".pack");
761 final File finalIdx = new File(packDir, "pack-" + name + ".idx");
763 if (finalPack.exists()) {
764 // If the pack is already present we should never replace it.
766 cleanupTemporaryFiles();
767 return;
770 if (!dstPack.renameTo(finalPack)) {
771 cleanupTemporaryFiles();
772 throw new IOException("Cannot move pack to " + finalPack);
775 if (!dstIdx.renameTo(finalIdx)) {
776 cleanupTemporaryFiles();
777 if (!finalPack.delete())
778 finalPack.deleteOnExit();
779 throw new IOException("Cannot move index to " + finalIdx);
782 try {
783 repo.openPack(finalPack, finalIdx);
784 } catch (IOException err) {
785 finalPack.delete();
786 finalIdx.delete();
787 throw err;
791 private void cleanupTemporaryFiles() {
792 if (!dstIdx.delete())
793 dstIdx.deleteOnExit();
794 if (!dstPack.delete())
795 dstPack.deleteOnExit();