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
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
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
;
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
;
94 * Size of the internal stream buffer.
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.
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.
113 * the repository that will receive the new pack.
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
)
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();
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());
136 private static enum Source
{
137 /** Data is read from the incoming stream. */
141 * Data is read from the spooled pack file.
143 * During streaming, some (or all) data might be saved into the spooled
144 * pack file so it can be randomly accessed later.
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
;
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.
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.
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.
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.
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
{
245 objectDatabase
= db
.getObjectDatabase().newCachedDatabase();
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);
269 * Set the pack index file format version this instance will create.
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.
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.
288 * true to enable fixing a thin pack.
290 public void setFixThin(final boolean fix
) {
295 * Configure this index pack instance to keep an empty pack.
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
) {
308 * Configure this index pack instance to keep track of new objects.
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
) {
318 newObjectIds
= new ObjectIdSubclassMap
<ObjectId
>();
323 private boolean needNewObjectIds() {
324 return newObjectIds
!= null;
328 * Configure this index pack instance to keep track of the objects assumed
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)
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.
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.
363 * the checker instance; null to disable object checking.
365 public void setObjectChecker(final ObjectChecker oc
) {
370 * Configure the checker used to validate received objects.
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.
376 * This is shorthand for:
379 * setObjectChecker(on ? new ObjectChecker() : null);
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.
395 * @throws IOException
397 public void index(final ProgressMonitor progress
) throws IOException
{
398 progress
.start(2 /* tasks */);
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
++) {
411 if (progress
.isCancelled())
412 throw new IOException(JGitText
.get().downloadCancelled
);
417 if (deltaCount
> 0) {
419 throw new IOException(JGitText
.get().needPackOut
);
420 resolveDeltas(progress
);
421 if (entryCount
< objectCount
) {
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);
436 if (dstIdx
!= null && (keepEmpty
|| entryCount
> 0))
441 if (readCurs
!= null)
448 InflaterCache
.release(inflater
);
451 objectDatabase
.close();
459 if (keepEmpty
|| entryCount
> 0) {
461 dstPack
.setReadOnly();
463 dstIdx
.setReadOnly();
465 } catch (IOException err
) {
474 private void resolveDeltas(final ProgressMonitor progress
)
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
);
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
{
498 int c
= readFrom(Source
.FILE
);
499 final int typeCode
= (c
>> 4) & 7;
502 while ((c
& 0x80) != 0) {
503 c
= readFrom(Source
.FILE
);
504 sz
+= (c
& 0x7f) << shift
;
509 case Constants
.OBJ_COMMIT
:
510 case Constants
.OBJ_TREE
:
511 case Constants
.OBJ_BLOB
:
512 case Constants
.OBJ_TAG
:
514 data
= inflateAndReturn(Source
.FILE
, sz
);
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
));
523 case Constants
.OBJ_REF_DELTA
: {
524 crc
.update(buf
, fill(Source
.FILE
, 20), 20);
526 data
= BinaryDelta
.apply(data
, inflateAndReturn(Source
.FILE
, sz
));
530 throw new IOException(MessageFormat
.format(JGitText
.get().unknownObjectType
, typeCode
));
533 final int crc32
= (int) crc
.getValue();
535 throw new IOException(MessageFormat
.format(JGitText
.get().corruptionDetectedReReadingAt
, pos
));
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;
560 final UnresolvedDelta n
= c
.next
;
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);
577 resolveDeltas(b
.position
, b
.crc
, type
, data
, null);
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
{
588 resolveDeltas(a
.position
, a
.crc
, type
, data
, null);
593 private void fixThinPack(final ProgressMonitor progress
) throws IOException
{
596 if (needBaseObjectIds
)
597 baseObjectIds
= new ObjectIdSubclassMap
<ObjectId
>();
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)
607 if (needBaseObjectIds
)
608 baseObjectIds
.add(baseId
);
609 final ObjectLoader ldr
;
611 ldr
= readCurs
.open(baseId
);
612 } catch (MissingObjectException notFound
) {
616 final byte[] data
= ldr
.getCachedBytes();
617 final int typeCode
= ldr
.getType();
618 final PackedObjectInfo oe
;
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
);
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
;
653 buf
[hdrlen
++] = (byte) ((typeCode
<< 4) | sz
& 15);
656 buf
[hdrlen
- 1] |= 0x80;
657 buf
[hdrlen
++] = (byte) (sz
& 0x7f);
660 packDigest
.update(buf
, 0, hdrlen
);
661 crc
.update(buf
, 0, hdrlen
);
662 packOut
.write(buf
, 0, hdrlen
);
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
)
676 final MessageDigest origDigest
= Constants
.newMessageDigest();
677 final MessageDigest tailDigest
= Constants
.newMessageDigest();
678 long origRemaining
= originalEOF
;
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
);
695 packOut
.write(buf
, 0, 12);
696 packOut
.seek(bAvail
);
699 packDigest
.update(buf
, 0, bAvail
);
701 final int n
= packOut
.read(buf
);
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
);
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
);
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
);
740 final PackIndexWriter iw
;
741 if (outputVersion
<= 0)
742 iw
= PackIndexWriter
.createOldestPossible(os
, list
);
744 iw
= PackIndexWriter
.createVersion(os
, outputVersion
);
745 iw
.write(list
, packcsum
);
746 os
.getChannel().force(true);
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);
766 private void readPackFooter() throws IOException
{
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);
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() {
786 // Read one entire object or delta from the input.
787 private void indexOneObject() throws IOException
{
788 final long pos
= position();
791 int c
= readFrom(Source
.INPUT
);
792 final int typeCode
= (c
>> 4) & 7;
795 while ((c
& 0x80) != 0) {
796 c
= readFrom(Source
.INPUT
);
797 sz
+= (c
& 0x7f) << shift
;
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
);
808 case Constants
.OBJ_OFS_DELTA
: {
809 c
= readFrom(Source
.INPUT
);
811 while ((c
& 128) != 0) {
813 c
= readFrom(Source
.INPUT
);
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
);
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
);
830 DeltaChain r
= baseById
.get(base
);
832 r
= new DeltaChain(base
);
835 inflateAndSkip(Source
.INPUT
, sz
);
836 r
.add(new UnresolvedDelta(pos
, (int) crc
.getValue()));
841 throw new IOException(MessageFormat
.format(JGitText
.get().unknownObjectType
, typeCode
));
845 private void whole(final int type
, final long pos
, final long sz
)
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) {
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()));
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
{
896 // Consume exactly one byte from the buffer and return it.
897 private int readFrom(final Source src
) throws IOException
{
901 final int b
= buf
[bOffset
++] & 0xff;
906 // Consume cnt bytes from the buffer.
907 private void use(final int 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
) {
924 System
.arraycopy(buf
, bOffset
, buf
, 0, bAvail
);
929 free
= buf
.length
- next
;
933 next
= in
.read(buf
, next
, free
);
936 next
= packOut
.read(buf
, next
, free
);
940 throw new EOFException(JGitText
.get().packfileIsTruncated
);
946 // Store consumed bytes in {@link #buf} up to {@link #bOffset}.
947 private void sync() throws IOException
{
948 packDigest
.update(buf
, 0, bOffset
);
950 packOut
.write(buf
, 0, bOffset
);
952 System
.arraycopy(buf
, bOffset
, buf
, 0, bAvail
);
957 private void inflateAndSkip(final Source src
, final long inflatedSize
)
959 inflate(src
, inflatedSize
, skipBuffer
, false /* do not keep result */);
962 private byte[] inflateAndReturn(final Source src
, final long inflatedSize
)
964 final byte[] dst
= new byte[(int) inflatedSize
];
965 inflate(src
, inflatedSize
, dst
, true /* keep result in 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
;
975 int p
= fill(src
, 24);
976 inf
.setInput(buf
, p
, bAvail
);
979 int r
= inf
.inflate(dst
, off
, dst
.length
- off
);
983 if (inf
.needsInput()) {
985 crc
.update(buf
, p
, bAvail
);
989 inf
.setInput(buf
, p
, bAvail
);
991 throw new CorruptObjectException(MessageFormat
.format(
992 JGitText
.get().packfileCorruptionDetected
,
993 JGitText
.get().unknownZlibError
));
1001 if (cnt
!= inflatedSize
) {
1002 throw new CorruptObjectException(MessageFormat
.format(JGitText
1003 .get().packfileCorruptionDetected
,
1004 JGitText
.get().wrongDecompressedLength
));
1007 int left
= bAvail
- inf
.getRemaining();
1009 crc
.update(buf
, p
, left
);
1012 } catch (DataFormatException dfe
) {
1013 throw new CorruptObjectException(MessageFormat
.format(JGitText
1014 .get().packfileCorruptionDetected
, dfe
.getMessage()));
1020 private static class DeltaChain
extends ObjectId
{
1021 UnresolvedDelta head
;
1023 DeltaChain(final AnyObjectId id
) {
1027 UnresolvedDelta
remove() {
1028 final UnresolvedDelta r
= head
;
1034 void add(final UnresolvedDelta d
) {
1040 private static class UnresolvedDelta
{
1041 final long position
;
1045 UnresolvedDelta next
;
1047 UnresolvedDelta(final long headerOffset
, final int crc32
) {
1048 position
= headerOffset
;
1054 * Rename the pack to it's final name and location and open it.
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.
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();
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);
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();
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.
1126 if (!keep
.lock(lockMessage
))
1127 throw new IOException(MessageFormat
.format(JGitText
.get().cannotLockPackIn
, finalPack
));
1128 } catch (IOException e
) {
1129 cleanupTemporaryFiles();
1134 if (!dstPack
.renameTo(finalPack
)) {
1135 cleanupTemporaryFiles();
1137 throw new IOException(MessageFormat
.format(JGitText
.get().cannotMovePackTo
, finalPack
));
1140 if (!dstIdx
.renameTo(finalIdx
)) {
1141 cleanupTemporaryFiles();
1143 if (!finalPack
.delete())
1144 finalPack
.deleteOnExit();
1145 throw new IOException(MessageFormat
.format(JGitText
.get().cannotMoveIndexTo
, finalIdx
));
1149 repo
.openPack(finalPack
, finalIdx
);
1150 } catch (IOException 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
);