Fix concurrent read / write issue in LockFile on Windows
[jgit.git] / org.eclipse.jgit / src / org / eclipse / jgit / transport / IndexPack.java
blob2daa105c53929fdc1fa0985b0788b5d81df128c0
1 /*
2 * Copyright (C) 2008-2010, Google Inc.
3 * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * and other copyright owners as documented in the project's IP log.
7 * This program and the accompanying materials are made available
8 * under the terms of the Eclipse Distribution License v1.0 which
9 * accompanies this distribution, is reproduced below, and is
10 * available at http://www.eclipse.org/org/documents/edl-v10.php
12 * All rights reserved.
14 * Redistribution and use in source and binary forms, with or
15 * without modification, are permitted provided that the following
16 * conditions are met:
18 * - Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
21 * - Redistributions in binary form must reproduce the above
22 * copyright notice, this list of conditions and the following
23 * disclaimer in the documentation and/or other materials provided
24 * with the distribution.
26 * - Neither the name of the Eclipse Foundation, Inc. nor the
27 * names of its contributors may be used to endorse or promote
28 * products derived from this software without specific prior
29 * written permission.
31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46 package org.eclipse.jgit.transport;
48 import java.io.EOFException;
49 import java.io.File;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.RandomAccessFile;
54 import java.security.MessageDigest;
55 import java.text.MessageFormat;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.List;
59 import java.util.zip.CRC32;
60 import java.util.zip.DataFormatException;
61 import java.util.zip.Deflater;
62 import java.util.zip.Inflater;
64 import org.eclipse.jgit.JGitText;
65 import org.eclipse.jgit.errors.CorruptObjectException;
66 import org.eclipse.jgit.errors.MissingObjectException;
67 import org.eclipse.jgit.lib.AnyObjectId;
68 import org.eclipse.jgit.lib.Constants;
69 import org.eclipse.jgit.lib.CoreConfig;
70 import org.eclipse.jgit.lib.InflaterCache;
71 import org.eclipse.jgit.lib.MutableObjectId;
72 import org.eclipse.jgit.lib.ObjectChecker;
73 import org.eclipse.jgit.lib.ObjectDatabase;
74 import org.eclipse.jgit.lib.ObjectId;
75 import org.eclipse.jgit.lib.ObjectIdSubclassMap;
76 import org.eclipse.jgit.lib.ObjectLoader;
77 import org.eclipse.jgit.lib.ProgressMonitor;
78 import org.eclipse.jgit.lib.Repository;
79 import org.eclipse.jgit.lib.ObjectReader;
80 import org.eclipse.jgit.storage.file.PackIndexWriter;
81 import org.eclipse.jgit.storage.file.PackLock;
82 import org.eclipse.jgit.storage.pack.BinaryDelta;
83 import org.eclipse.jgit.util.NB;
85 /** Indexes Git pack files for local use. */
86 public class IndexPack {
87 /** Progress message when reading raw data from the pack. */
88 public static final String PROGRESS_DOWNLOAD = JGitText.get().receivingObjects;
90 /** Progress message when computing names of delta compressed objects. */
91 public static final String PROGRESS_RESOLVE_DELTA = JGitText.get().resolvingDeltas;
93 /**
94 * Size of the internal stream buffer.
95 * <p>
96 * If callers are going to be supplying IndexPack a BufferedInputStream they
97 * should use this buffer size as the size of the buffer for that
98 * BufferedInputStream, and any other its may be wrapping. This way the
99 * buffers will cascade efficiently and only the IndexPack buffer will be
100 * receiving the bulk of the data stream.
102 public static final int BUFFER_SIZE = 8192;
105 * Create an index pack instance to load a new pack into a repository.
106 * <p>
107 * The received pack data and generated index will be saved to temporary
108 * files within the repository's <code>objects</code> directory. To use the
109 * data contained within them call {@link #renameAndOpenPack()} once the
110 * indexing is complete.
112 * @param db
113 * the repository that will receive the new pack.
114 * @param is
115 * stream to read the pack data from. If the stream is buffered
116 * use {@link #BUFFER_SIZE} as the buffer size for the stream.
117 * @return a new index pack instance.
118 * @throws IOException
119 * a temporary file could not be created.
121 public static IndexPack create(final Repository db, final InputStream is)
122 throws IOException {
123 final String suffix = ".pack";
124 final File objdir = db.getObjectsDirectory();
125 final File tmp = File.createTempFile("incoming_", suffix, objdir);
126 final String n = tmp.getName();
127 final File base;
129 base = new File(objdir, n.substring(0, n.length() - suffix.length()));
130 final IndexPack ip = new IndexPack(db, is, base);
131 ip.setIndexVersion(db.getConfig().get(CoreConfig.KEY)
132 .getPackIndexVersion());
133 return ip;
136 private static enum Source {
137 /** Data is read from the incoming stream. */
138 INPUT,
141 * Data is read from the spooled pack file.
142 * <p>
143 * During streaming, some (or all) data might be saved into the spooled
144 * pack file so it can be randomly accessed later.
146 FILE;
149 private final Repository repo;
152 * Object database used for loading existing objects
154 private final ObjectDatabase objectDatabase;
156 private Inflater inflater;
158 private final MessageDigest objectDigest;
160 private final MutableObjectId tempObjectId;
162 private InputStream in;
164 private byte[] buf;
166 private long bBase;
168 private int bOffset;
170 private int bAvail;
172 private ObjectChecker objCheck;
174 private boolean fixThin;
176 private boolean keepEmpty;
178 private boolean needBaseObjectIds;
180 private int outputVersion;
182 private final File dstPack;
184 private final File dstIdx;
186 private long objectCount;
188 private PackedObjectInfo[] entries;
191 * Every object contained within the incoming pack.
192 * <p>
193 * This is a subset of {@link #entries}, as thin packs can add additional
194 * objects to {@code entries} by copying already existing objects from the
195 * repository onto the end of the thin pack to make it self-contained.
197 private ObjectIdSubclassMap<ObjectId> newObjectIds;
199 private int deltaCount;
201 private int entryCount;
203 private final CRC32 crc = new CRC32();
205 private ObjectIdSubclassMap<DeltaChain> baseById;
208 * Objects referenced by their name from deltas, that aren't in this pack.
209 * <p>
210 * This is the set of objects that were copied onto the end of this pack to
211 * make it complete. These objects were not transmitted by the remote peer,
212 * but instead were assumed to already exist in the local repository.
214 private ObjectIdSubclassMap<ObjectId> baseObjectIds;
216 private LongMap<UnresolvedDelta> baseByPos;
218 private byte[] skipBuffer;
220 private MessageDigest packDigest;
222 private RandomAccessFile packOut;
224 private byte[] packcsum;
226 /** If {@link #fixThin} this is the last byte of the original checksum. */
227 private long originalEOF;
229 private ObjectReader readCurs;
232 * Create a new pack indexer utility.
234 * @param db
235 * @param src
236 * stream to read the pack data from. If the stream is buffered
237 * use {@link #BUFFER_SIZE} as the buffer size for the stream.
238 * @param dstBase
239 * @throws IOException
240 * the output packfile could not be created.
242 public IndexPack(final Repository db, final InputStream src,
243 final File dstBase) throws IOException {
244 repo = db;
245 objectDatabase = db.getObjectDatabase().newCachedDatabase();
246 in = src;
247 inflater = InflaterCache.get();
248 readCurs = objectDatabase.newReader();
249 buf = new byte[BUFFER_SIZE];
250 skipBuffer = new byte[512];
251 objectDigest = Constants.newMessageDigest();
252 tempObjectId = new MutableObjectId();
253 packDigest = Constants.newMessageDigest();
255 if (dstBase != null) {
256 final File dir = dstBase.getParentFile();
257 final String nam = dstBase.getName();
258 dstPack = new File(dir, nam + ".pack");
259 dstIdx = new File(dir, nam + ".idx");
260 packOut = new RandomAccessFile(dstPack, "rw");
261 packOut.setLength(0);
262 } else {
263 dstPack = null;
264 dstIdx = null;
269 * Set the pack index file format version this instance will create.
271 * @param version
272 * the version to write. The special version 0 designates the
273 * oldest (most compatible) format available for the objects.
274 * @see PackIndexWriter
276 public void setIndexVersion(final int version) {
277 outputVersion = version;
281 * Configure this index pack instance to make a thin pack complete.
282 * <p>
283 * Thin packs are sometimes used during network transfers to allow a delta
284 * to be sent without a base object. Such packs are not permitted on disk.
285 * They can be fixed by copying the base object onto the end of the pack.
287 * @param fix
288 * true to enable fixing a thin pack.
290 public void setFixThin(final boolean fix) {
291 fixThin = fix;
295 * Configure this index pack instance to keep an empty pack.
296 * <p>
297 * By default an empty pack (a pack with no objects) is not kept, as doing
298 * so is completely pointless. With no objects in the pack there is no data
299 * stored by it, so the pack is unnecessary.
301 * @param empty true to enable keeping an empty pack.
303 public void setKeepEmpty(final boolean empty) {
304 keepEmpty = empty;
308 * Configure this index pack instance to keep track of new objects.
309 * <p>
310 * By default an index pack doesn't save the new objects that were created
311 * when it was instantiated. Setting this flag to {@code true} allows the
312 * caller to use {@link #getNewObjectIds()} to retrieve that list.
314 * @param b {@code true} to enable keeping track of new objects.
316 public void setNeedNewObjectIds(boolean b) {
317 if (b)
318 newObjectIds = new ObjectIdSubclassMap<ObjectId>();
319 else
320 newObjectIds = null;
323 private boolean needNewObjectIds() {
324 return newObjectIds != null;
328 * Configure this index pack instance to keep track of the objects assumed
329 * for delta bases.
330 * <p>
331 * By default an index pack doesn't save the objects that were used as delta
332 * bases. Setting this flag to {@code true} will allow the caller to
333 * use {@link #getBaseObjectIds()} to retrieve that list.
335 * @param b {@code true} to enable keeping track of delta bases.
337 public void setNeedBaseObjectIds(boolean b) {
338 this.needBaseObjectIds = b;
341 /** @return the new objects that were sent by the user */
342 public ObjectIdSubclassMap<ObjectId> getNewObjectIds() {
343 if (newObjectIds != null)
344 return newObjectIds;
345 return new ObjectIdSubclassMap<ObjectId>();
348 /** @return set of objects the incoming pack assumed for delta purposes */
349 public ObjectIdSubclassMap<ObjectId> getBaseObjectIds() {
350 if (baseObjectIds != null)
351 return baseObjectIds;
352 return new ObjectIdSubclassMap<ObjectId>();
356 * Configure the checker used to validate received objects.
357 * <p>
358 * Usually object checking isn't necessary, as Git implementations only
359 * create valid objects in pack files. However, additional checking may be
360 * useful if processing data from an untrusted source.
362 * @param oc
363 * the checker instance; null to disable object checking.
365 public void setObjectChecker(final ObjectChecker oc) {
366 objCheck = oc;
370 * Configure the checker used to validate received objects.
371 * <p>
372 * Usually object checking isn't necessary, as Git implementations only
373 * create valid objects in pack files. However, additional checking may be
374 * useful if processing data from an untrusted source.
375 * <p>
376 * This is shorthand for:
378 * <pre>
379 * setObjectChecker(on ? new ObjectChecker() : null);
380 * </pre>
382 * @param on
383 * true to enable the default checker; false to disable it.
385 public void setObjectChecking(final boolean on) {
386 setObjectChecker(on ? new ObjectChecker() : null);
390 * Consume data from the input stream until the packfile is indexed.
392 * @param progress
393 * progress feedback
395 * @throws IOException
397 public void index(final ProgressMonitor progress) throws IOException {
398 progress.start(2 /* tasks */);
399 try {
400 try {
401 readPackHeader();
403 entries = new PackedObjectInfo[(int) objectCount];
404 baseById = new ObjectIdSubclassMap<DeltaChain>();
405 baseByPos = new LongMap<UnresolvedDelta>();
407 progress.beginTask(PROGRESS_DOWNLOAD, (int) objectCount);
408 for (int done = 0; done < objectCount; done++) {
409 indexOneObject();
410 progress.update(1);
411 if (progress.isCancelled())
412 throw new IOException(JGitText.get().downloadCancelled);
414 readPackFooter();
415 endInput();
416 progress.endTask();
417 if (deltaCount > 0) {
418 if (packOut == null)
419 throw new IOException(JGitText.get().needPackOut);
420 resolveDeltas(progress);
421 if (entryCount < objectCount) {
422 if (!fixThin) {
423 throw new IOException(MessageFormat.format(
424 JGitText.get().packHasUnresolvedDeltas, (objectCount - entryCount)));
426 fixThinPack(progress);
429 if (packOut != null && (keepEmpty || entryCount > 0))
430 packOut.getChannel().force(true);
432 packDigest = null;
433 baseById = null;
434 baseByPos = null;
436 if (dstIdx != null && (keepEmpty || entryCount > 0))
437 writeIdx();
439 } finally {
440 try {
441 if (readCurs != null)
442 readCurs.release();
443 } finally {
444 readCurs = null;
447 try {
448 InflaterCache.release(inflater);
449 } finally {
450 inflater = null;
451 objectDatabase.close();
454 progress.endTask();
455 if (packOut != null)
456 packOut.close();
459 if (keepEmpty || entryCount > 0) {
460 if (dstPack != null)
461 dstPack.setReadOnly();
462 if (dstIdx != null)
463 dstIdx.setReadOnly();
465 } catch (IOException err) {
466 if (dstPack != null)
467 dstPack.delete();
468 if (dstIdx != null)
469 dstIdx.delete();
470 throw err;
474 private void resolveDeltas(final ProgressMonitor progress)
475 throws IOException {
476 progress.beginTask(PROGRESS_RESOLVE_DELTA, deltaCount);
477 final int last = entryCount;
478 for (int i = 0; i < last; i++) {
479 final int before = entryCount;
480 resolveDeltas(entries[i]);
481 progress.update(entryCount - before);
482 if (progress.isCancelled())
483 throw new IOException(JGitText.get().downloadCancelledDuringIndexing);
485 progress.endTask();
488 private void resolveDeltas(final PackedObjectInfo oe) throws IOException {
489 final int oldCRC = oe.getCRC();
490 if (baseById.get(oe) != null || baseByPos.containsKey(oe.getOffset()))
491 resolveDeltas(oe.getOffset(), oldCRC, Constants.OBJ_BAD, null, oe);
494 private void resolveDeltas(final long pos, final int oldCRC, int type,
495 byte[] data, PackedObjectInfo oe) throws IOException {
496 crc.reset();
497 position(pos);
498 int c = readFrom(Source.FILE);
499 final int typeCode = (c >> 4) & 7;
500 long sz = c & 15;
501 int shift = 4;
502 while ((c & 0x80) != 0) {
503 c = readFrom(Source.FILE);
504 sz += (c & 0x7f) << shift;
505 shift += 7;
508 switch (typeCode) {
509 case Constants.OBJ_COMMIT:
510 case Constants.OBJ_TREE:
511 case Constants.OBJ_BLOB:
512 case Constants.OBJ_TAG:
513 type = typeCode;
514 data = inflateAndReturn(Source.FILE, sz);
515 break;
516 case Constants.OBJ_OFS_DELTA: {
517 c = readFrom(Source.FILE) & 0xff;
518 while ((c & 128) != 0)
519 c = readFrom(Source.FILE) & 0xff;
520 data = BinaryDelta.apply(data, inflateAndReturn(Source.FILE, sz));
521 break;
523 case Constants.OBJ_REF_DELTA: {
524 crc.update(buf, fill(Source.FILE, 20), 20);
525 use(20);
526 data = BinaryDelta.apply(data, inflateAndReturn(Source.FILE, sz));
527 break;
529 default:
530 throw new IOException(MessageFormat.format(JGitText.get().unknownObjectType, typeCode));
533 final int crc32 = (int) crc.getValue();
534 if (oldCRC != crc32)
535 throw new IOException(MessageFormat.format(JGitText.get().corruptionDetectedReReadingAt, pos));
536 if (oe == null) {
537 objectDigest.update(Constants.encodedTypeString(type));
538 objectDigest.update((byte) ' ');
539 objectDigest.update(Constants.encodeASCII(data.length));
540 objectDigest.update((byte) 0);
541 objectDigest.update(data);
542 tempObjectId.fromRaw(objectDigest.digest(), 0);
544 verifySafeObject(tempObjectId, type, data);
545 oe = new PackedObjectInfo(pos, crc32, tempObjectId);
546 addObjectAndTrack(oe);
549 resolveChildDeltas(pos, type, data, oe);
552 private UnresolvedDelta removeBaseById(final AnyObjectId id){
553 final DeltaChain d = baseById.get(id);
554 return d != null ? d.remove() : null;
557 private static UnresolvedDelta reverse(UnresolvedDelta c) {
558 UnresolvedDelta tail = null;
559 while (c != null) {
560 final UnresolvedDelta n = c.next;
561 c.next = tail;
562 tail = c;
563 c = n;
565 return tail;
568 private void resolveChildDeltas(final long pos, int type, byte[] data,
569 PackedObjectInfo oe) throws IOException {
570 UnresolvedDelta a = reverse(removeBaseById(oe));
571 UnresolvedDelta b = reverse(baseByPos.remove(pos));
572 while (a != null && b != null) {
573 if (a.position < b.position) {
574 resolveDeltas(a.position, a.crc, type, data, null);
575 a = a.next;
576 } else {
577 resolveDeltas(b.position, b.crc, type, data, null);
578 b = b.next;
581 resolveChildDeltaChain(type, data, a);
582 resolveChildDeltaChain(type, data, b);
585 private void resolveChildDeltaChain(final int type, final byte[] data,
586 UnresolvedDelta a) throws IOException {
587 while (a != null) {
588 resolveDeltas(a.position, a.crc, type, data, null);
589 a = a.next;
593 private void fixThinPack(final ProgressMonitor progress) throws IOException {
594 growEntries();
596 if (needBaseObjectIds)
597 baseObjectIds = new ObjectIdSubclassMap<ObjectId>();
599 packDigest.reset();
600 originalEOF = packOut.length() - 20;
601 final Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
602 final List<DeltaChain> missing = new ArrayList<DeltaChain>(64);
603 long end = originalEOF;
604 for (final DeltaChain baseId : baseById) {
605 if (baseId.head == null)
606 continue;
607 if (needBaseObjectIds)
608 baseObjectIds.add(baseId);
609 final ObjectLoader ldr;
610 try {
611 ldr = readCurs.open(baseId);
612 } catch (MissingObjectException notFound) {
613 missing.add(baseId);
614 continue;
616 final byte[] data = ldr.getCachedBytes();
617 final int typeCode = ldr.getType();
618 final PackedObjectInfo oe;
620 crc.reset();
621 packOut.seek(end);
622 writeWhole(def, typeCode, data);
623 oe = new PackedObjectInfo(end, (int) crc.getValue(), baseId);
624 entries[entryCount++] = oe;
625 end = packOut.getFilePointer();
627 resolveChildDeltas(oe.getOffset(), typeCode, data, oe);
628 if (progress.isCancelled())
629 throw new IOException(JGitText.get().downloadCancelledDuringIndexing);
631 def.end();
633 for (final DeltaChain base : missing) {
634 if (base.head != null)
635 throw new MissingObjectException(base, "delta base");
638 if (end - originalEOF < 20) {
639 // Ugly corner case; if what we appended on to complete deltas
640 // doesn't completely cover the SHA-1 we have to truncate off
641 // we need to shorten the file, otherwise we will include part
642 // of the old footer as object content.
643 packOut.setLength(end);
646 fixHeaderFooter(packcsum, packDigest.digest());
649 private void writeWhole(final Deflater def, final int typeCode,
650 final byte[] data) throws IOException {
651 int sz = data.length;
652 int hdrlen = 0;
653 buf[hdrlen++] = (byte) ((typeCode << 4) | sz & 15);
654 sz >>>= 4;
655 while (sz > 0) {
656 buf[hdrlen - 1] |= 0x80;
657 buf[hdrlen++] = (byte) (sz & 0x7f);
658 sz >>>= 7;
660 packDigest.update(buf, 0, hdrlen);
661 crc.update(buf, 0, hdrlen);
662 packOut.write(buf, 0, hdrlen);
663 def.reset();
664 def.setInput(data);
665 def.finish();
666 while (!def.finished()) {
667 final int datlen = def.deflate(buf);
668 packDigest.update(buf, 0, datlen);
669 crc.update(buf, 0, datlen);
670 packOut.write(buf, 0, datlen);
674 private void fixHeaderFooter(final byte[] origcsum, final byte[] tailcsum)
675 throws IOException {
676 final MessageDigest origDigest = Constants.newMessageDigest();
677 final MessageDigest tailDigest = Constants.newMessageDigest();
678 long origRemaining = originalEOF;
680 packOut.seek(0);
681 bAvail = 0;
682 bOffset = 0;
683 fill(Source.FILE, 12);
686 final int origCnt = (int) Math.min(bAvail, origRemaining);
687 origDigest.update(buf, 0, origCnt);
688 origRemaining -= origCnt;
689 if (origRemaining == 0)
690 tailDigest.update(buf, origCnt, bAvail - origCnt);
693 NB.encodeInt32(buf, 8, entryCount);
694 packOut.seek(0);
695 packOut.write(buf, 0, 12);
696 packOut.seek(bAvail);
698 packDigest.reset();
699 packDigest.update(buf, 0, bAvail);
700 for (;;) {
701 final int n = packOut.read(buf);
702 if (n < 0)
703 break;
704 if (origRemaining != 0) {
705 final int origCnt = (int) Math.min(n, origRemaining);
706 origDigest.update(buf, 0, origCnt);
707 origRemaining -= origCnt;
708 if (origRemaining == 0)
709 tailDigest.update(buf, origCnt, n - origCnt);
710 } else
711 tailDigest.update(buf, 0, n);
713 packDigest.update(buf, 0, n);
716 if (!Arrays.equals(origDigest.digest(), origcsum)
717 || !Arrays.equals(tailDigest.digest(), tailcsum))
718 throw new IOException(JGitText.get().packCorruptedWhileWritingToFilesystem);
720 packcsum = packDigest.digest();
721 packOut.write(packcsum);
724 private void growEntries() {
725 final PackedObjectInfo[] ne;
727 ne = new PackedObjectInfo[(int) objectCount + baseById.size()];
728 System.arraycopy(entries, 0, ne, 0, entryCount);
729 entries = ne;
732 private void writeIdx() throws IOException {
733 Arrays.sort(entries, 0, entryCount);
734 List<PackedObjectInfo> list = Arrays.asList(entries);
735 if (entryCount < entries.length)
736 list = list.subList(0, entryCount);
738 final FileOutputStream os = new FileOutputStream(dstIdx);
739 try {
740 final PackIndexWriter iw;
741 if (outputVersion <= 0)
742 iw = PackIndexWriter.createOldestPossible(os, list);
743 else
744 iw = PackIndexWriter.createVersion(os, outputVersion);
745 iw.write(list, packcsum);
746 os.getChannel().force(true);
747 } finally {
748 os.close();
752 private void readPackHeader() throws IOException {
753 final int hdrln = Constants.PACK_SIGNATURE.length + 4 + 4;
754 final int p = fill(Source.INPUT, hdrln);
755 for (int k = 0; k < Constants.PACK_SIGNATURE.length; k++)
756 if (buf[p + k] != Constants.PACK_SIGNATURE[k])
757 throw new IOException(JGitText.get().notAPACKFile);
759 final long vers = NB.decodeUInt32(buf, p + 4);
760 if (vers != 2 && vers != 3)
761 throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackVersion, vers));
762 objectCount = NB.decodeUInt32(buf, p + 8);
763 use(hdrln);
766 private void readPackFooter() throws IOException {
767 sync();
768 final byte[] cmpcsum = packDigest.digest();
769 final int c = fill(Source.INPUT, 20);
770 packcsum = new byte[20];
771 System.arraycopy(buf, c, packcsum, 0, 20);
772 use(20);
773 if (packOut != null)
774 packOut.write(packcsum);
776 if (!Arrays.equals(cmpcsum, packcsum))
777 throw new CorruptObjectException(JGitText.get().corruptObjectPackfileChecksumIncorrect);
780 // Cleanup all resources associated with our input parsing.
781 private void endInput() {
782 in = null;
783 skipBuffer = null;
786 // Read one entire object or delta from the input.
787 private void indexOneObject() throws IOException {
788 final long pos = position();
790 crc.reset();
791 int c = readFrom(Source.INPUT);
792 final int typeCode = (c >> 4) & 7;
793 long sz = c & 15;
794 int shift = 4;
795 while ((c & 0x80) != 0) {
796 c = readFrom(Source.INPUT);
797 sz += (c & 0x7f) << shift;
798 shift += 7;
801 switch (typeCode) {
802 case Constants.OBJ_COMMIT:
803 case Constants.OBJ_TREE:
804 case Constants.OBJ_BLOB:
805 case Constants.OBJ_TAG:
806 whole(typeCode, pos, sz);
807 break;
808 case Constants.OBJ_OFS_DELTA: {
809 c = readFrom(Source.INPUT);
810 long ofs = c & 127;
811 while ((c & 128) != 0) {
812 ofs += 1;
813 c = readFrom(Source.INPUT);
814 ofs <<= 7;
815 ofs += (c & 127);
817 final long base = pos - ofs;
818 final UnresolvedDelta n;
819 inflateAndSkip(Source.INPUT, sz);
820 n = new UnresolvedDelta(pos, (int) crc.getValue());
821 n.next = baseByPos.put(base, n);
822 deltaCount++;
823 break;
825 case Constants.OBJ_REF_DELTA: {
826 c = fill(Source.INPUT, 20);
827 crc.update(buf, c, 20);
828 final ObjectId base = ObjectId.fromRaw(buf, c);
829 use(20);
830 DeltaChain r = baseById.get(base);
831 if (r == null) {
832 r = new DeltaChain(base);
833 baseById.add(r);
835 inflateAndSkip(Source.INPUT, sz);
836 r.add(new UnresolvedDelta(pos, (int) crc.getValue()));
837 deltaCount++;
838 break;
840 default:
841 throw new IOException(MessageFormat.format(JGitText.get().unknownObjectType, typeCode));
845 private void whole(final int type, final long pos, final long sz)
846 throws IOException {
847 final byte[] data = inflateAndReturn(Source.INPUT, sz);
848 objectDigest.update(Constants.encodedTypeString(type));
849 objectDigest.update((byte) ' ');
850 objectDigest.update(Constants.encodeASCII(sz));
851 objectDigest.update((byte) 0);
852 objectDigest.update(data);
853 tempObjectId.fromRaw(objectDigest.digest(), 0);
855 verifySafeObject(tempObjectId, type, data);
856 final int crc32 = (int) crc.getValue();
857 addObjectAndTrack(new PackedObjectInfo(pos, crc32, tempObjectId));
860 private void verifySafeObject(final AnyObjectId id, final int type,
861 final byte[] data) throws IOException {
862 if (objCheck != null) {
863 try {
864 objCheck.check(type, data);
865 } catch (CorruptObjectException e) {
866 throw new IOException(MessageFormat.format(JGitText.get().invalidObject
867 , Constants.typeString(type) , id.name() , e.getMessage()));
871 try {
872 final ObjectLoader ldr = readCurs.open(id, type);
873 final byte[] existingData = ldr.getCachedBytes();
874 if (!Arrays.equals(data, existingData)) {
875 throw new IOException(MessageFormat.format(JGitText.get().collisionOn, id.name()));
877 } catch (MissingObjectException notLocal) {
878 // This is OK, we don't have a copy of the object locally
879 // but the API throws when we try to read it as usually its
880 // an error to read something that doesn't exist.
884 // Current position of {@link #bOffset} within the entire file.
885 private long position() {
886 return bBase + bOffset;
889 private void position(final long pos) throws IOException {
890 packOut.seek(pos);
891 bBase = pos;
892 bOffset = 0;
893 bAvail = 0;
896 // Consume exactly one byte from the buffer and return it.
897 private int readFrom(final Source src) throws IOException {
898 if (bAvail == 0)
899 fill(src, 1);
900 bAvail--;
901 final int b = buf[bOffset++] & 0xff;
902 crc.update(b);
903 return b;
906 // Consume cnt bytes from the buffer.
907 private void use(final int cnt) {
908 bOffset += cnt;
909 bAvail -= cnt;
912 // Ensure at least need bytes are available in in {@link #buf}.
913 private int fill(final Source src, final int need) throws IOException {
914 while (bAvail < need) {
915 int next = bOffset + bAvail;
916 int free = buf.length - next;
917 if (free + bAvail < need) {
918 switch(src){
919 case INPUT:
920 sync();
921 break;
922 case FILE:
923 if (bAvail > 0)
924 System.arraycopy(buf, bOffset, buf, 0, bAvail);
925 bOffset = 0;
926 break;
928 next = bAvail;
929 free = buf.length - next;
931 switch(src){
932 case INPUT:
933 next = in.read(buf, next, free);
934 break;
935 case FILE:
936 next = packOut.read(buf, next, free);
937 break;
939 if (next <= 0)
940 throw new EOFException(JGitText.get().packfileIsTruncated);
941 bAvail += next;
943 return bOffset;
946 // Store consumed bytes in {@link #buf} up to {@link #bOffset}.
947 private void sync() throws IOException {
948 packDigest.update(buf, 0, bOffset);
949 if (packOut != null)
950 packOut.write(buf, 0, bOffset);
951 if (bAvail > 0)
952 System.arraycopy(buf, bOffset, buf, 0, bAvail);
953 bBase += bOffset;
954 bOffset = 0;
957 private void inflateAndSkip(final Source src, final long inflatedSize)
958 throws IOException {
959 inflate(src, inflatedSize, skipBuffer, false /* do not keep result */);
962 private byte[] inflateAndReturn(final Source src, final long inflatedSize)
963 throws IOException {
964 final byte[] dst = new byte[(int) inflatedSize];
965 inflate(src, inflatedSize, dst, true /* keep result in dst */);
966 return dst;
969 private void inflate(final Source src, final long inflatedSize,
970 final byte[] dst, final boolean keep) throws IOException {
971 final Inflater inf = inflater;
972 try {
973 int off = 0;
974 long cnt = 0;
975 int p = fill(src, 24);
976 inf.setInput(buf, p, bAvail);
978 for (;;) {
979 int r = inf.inflate(dst, off, dst.length - off);
980 if (r == 0) {
981 if (inf.finished())
982 break;
983 if (inf.needsInput()) {
984 if (p >= 0) {
985 crc.update(buf, p, bAvail);
986 use(bAvail);
988 p = fill(src, 24);
989 inf.setInput(buf, p, bAvail);
990 } else {
991 throw new CorruptObjectException(MessageFormat.format(
992 JGitText.get().packfileCorruptionDetected,
993 JGitText.get().unknownZlibError));
996 cnt += r;
997 if (keep)
998 off += r;
1001 if (cnt != inflatedSize) {
1002 throw new CorruptObjectException(MessageFormat.format(JGitText
1003 .get().packfileCorruptionDetected,
1004 JGitText.get().wrongDecompressedLength));
1007 int left = bAvail - inf.getRemaining();
1008 if (left > 0) {
1009 crc.update(buf, p, left);
1010 use(left);
1012 } catch (DataFormatException dfe) {
1013 throw new CorruptObjectException(MessageFormat.format(JGitText
1014 .get().packfileCorruptionDetected, dfe.getMessage()));
1015 } finally {
1016 inf.reset();
1020 private static class DeltaChain extends ObjectId {
1021 UnresolvedDelta head;
1023 DeltaChain(final AnyObjectId id) {
1024 super(id);
1027 UnresolvedDelta remove() {
1028 final UnresolvedDelta r = head;
1029 if (r != null)
1030 head = null;
1031 return r;
1034 void add(final UnresolvedDelta d) {
1035 d.next = head;
1036 head = d;
1040 private static class UnresolvedDelta {
1041 final long position;
1043 final int crc;
1045 UnresolvedDelta next;
1047 UnresolvedDelta(final long headerOffset, final int crc32) {
1048 position = headerOffset;
1049 crc = crc32;
1054 * Rename the pack to it's final name and location and open it.
1055 * <p>
1056 * If the call completes successfully the repository this IndexPack instance
1057 * was created with will have the objects in the pack available for reading
1058 * and use, without needing to scan for packs.
1060 * @throws IOException
1061 * The pack could not be inserted into the repository's objects
1062 * directory. The pack no longer exists on disk, as it was
1063 * removed prior to throwing the exception to the caller.
1065 public void renameAndOpenPack() throws IOException {
1066 renameAndOpenPack(null);
1070 * Rename the pack to it's final name and location and open it.
1071 * <p>
1072 * If the call completes successfully the repository this IndexPack instance
1073 * was created with will have the objects in the pack available for reading
1074 * and use, without needing to scan for packs.
1076 * @param lockMessage
1077 * message to place in the pack-*.keep file. If null, no lock
1078 * will be created, and this method returns null.
1079 * @return the pack lock object, if lockMessage is not null.
1080 * @throws IOException
1081 * The pack could not be inserted into the repository's objects
1082 * directory. The pack no longer exists on disk, as it was
1083 * removed prior to throwing the exception to the caller.
1085 public PackLock renameAndOpenPack(final String lockMessage)
1086 throws IOException {
1087 if (!keepEmpty && entryCount == 0) {
1088 cleanupTemporaryFiles();
1089 return null;
1092 final MessageDigest d = Constants.newMessageDigest();
1093 final byte[] oeBytes = new byte[Constants.OBJECT_ID_LENGTH];
1094 for (int i = 0; i < entryCount; i++) {
1095 final PackedObjectInfo oe = entries[i];
1096 oe.copyRawTo(oeBytes, 0);
1097 d.update(oeBytes);
1100 final String name = ObjectId.fromRaw(d.digest()).name();
1101 final File packDir = new File(repo.getObjectsDirectory(), "pack");
1102 final File finalPack = new File(packDir, "pack-" + name + ".pack");
1103 final File finalIdx = new File(packDir, "pack-" + name + ".idx");
1104 final PackLock keep = new PackLock(finalPack, repo.getFS());
1106 if (!packDir.exists() && !packDir.mkdir() && !packDir.exists()) {
1107 // The objects/pack directory isn't present, and we are unable
1108 // to create it. There is no way to move this pack in.
1110 cleanupTemporaryFiles();
1111 throw new IOException(MessageFormat.format(JGitText.get().cannotCreateDirectory, packDir.getAbsolutePath()));
1114 if (finalPack.exists()) {
1115 // If the pack is already present we should never replace it.
1117 cleanupTemporaryFiles();
1118 return null;
1121 if (lockMessage != null) {
1122 // If we have a reason to create a keep file for this pack, do
1123 // so, or fail fast and don't put the pack in place.
1125 try {
1126 if (!keep.lock(lockMessage))
1127 throw new IOException(MessageFormat.format(JGitText.get().cannotLockPackIn, finalPack));
1128 } catch (IOException e) {
1129 cleanupTemporaryFiles();
1130 throw e;
1134 if (!dstPack.renameTo(finalPack)) {
1135 cleanupTemporaryFiles();
1136 keep.unlock();
1137 throw new IOException(MessageFormat.format(JGitText.get().cannotMovePackTo, finalPack));
1140 if (!dstIdx.renameTo(finalIdx)) {
1141 cleanupTemporaryFiles();
1142 keep.unlock();
1143 if (!finalPack.delete())
1144 finalPack.deleteOnExit();
1145 throw new IOException(MessageFormat.format(JGitText.get().cannotMoveIndexTo, finalIdx));
1148 try {
1149 repo.openPack(finalPack, finalIdx);
1150 } catch (IOException err) {
1151 keep.unlock();
1152 finalPack.delete();
1153 finalIdx.delete();
1154 throw err;
1157 return lockMessage != null ? keep : null;
1160 private void cleanupTemporaryFiles() {
1161 if (!dstIdx.delete())
1162 dstIdx.deleteOnExit();
1163 if (!dstPack.delete())
1164 dstPack.deleteOnExit();
1167 private void addObjectAndTrack(PackedObjectInfo oe) {
1168 entries[entryCount++] = oe;
1169 if (needNewObjectIds())
1170 newObjectIds.add(oe);