2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
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
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
.lib
;
41 import java
.io
.EOFException
;
43 import java
.io
.IOException
;
44 import java
.io
.OutputStream
;
45 import java
.util
.Iterator
;
46 import java
.util
.zip
.CRC32
;
47 import java
.util
.zip
.CheckedOutputStream
;
48 import java
.util
.zip
.DataFormatException
;
50 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
51 import org
.spearce
.jgit
.util
.NB
;
54 * A Git version 2 pack file representation. A pack file contains Git objects in
55 * delta packed format yielding high compression of lots of object where some
56 * objects are similar.
58 public class PackFile
implements Iterable
<PackIndex
.MutableEntry
> {
59 private final WindowedFile pack
;
61 private final PackIndex idx
;
63 private PackReverseIndex reverseIdx
;
66 * Construct a reader for an existing, pre-indexed packfile.
69 * Git repository holding this pack file
71 * path of the <code>.idx</code> file listing the contents.
73 * path of the <code>.pack</code> file holding the data.
75 * the index file cannot be accessed at this time.
77 public PackFile(final Repository parentRepo
, final File idxFile
,
78 final File packFile
) throws IOException
{
79 pack
= new WindowedFile(packFile
) {
81 protected void onOpen() throws IOException
{
86 idx
= PackIndex
.open(idxFile
);
87 } catch (IOException ioe
) {
92 final PackedObjectLoader
resolveBase(final WindowCursor curs
, final long ofs
)
94 return reader(curs
, ofs
);
98 * Determine if an object is contained within the pack file.
100 * For performance reasons only the index file is searched; the main pack
101 * content is ignored entirely.
105 * the object to look for. Must not be null.
106 * @return true if the object is in this pack; false otherwise.
108 public boolean hasObject(final AnyObjectId id
) {
109 return idx
.hasObject(id
);
113 * Get an object from this pack.
116 * the object to obtain from the pack. Must not be null.
117 * @return the object loader for the requested object if it is contained in
118 * this pack; null if the object was not found.
119 * @throws IOException
120 * the pack file or the index could not be read.
122 public PackedObjectLoader
get(final AnyObjectId id
) throws IOException
{
123 final WindowCursor wc
= new WindowCursor();
132 * Get an object from this pack.
135 * temporary working space associated with the calling thread.
137 * the object to obtain from the pack. Must not be null.
138 * @return the object loader for the requested object if it is contained in
139 * this pack; null if the object was not found.
140 * @throws IOException
141 * the pack file or the index could not be read.
143 public PackedObjectLoader
get(final WindowCursor curs
, final AnyObjectId id
)
145 final long offset
= idx
.findOffset(id
);
148 final PackedObjectLoader objReader
= reader(curs
, offset
);
149 objReader
.setId(id
.toObjectId());
154 * Close the resources utilized by this repository
156 public void close() {
157 UnpackedObjectCache
.purge(pack
);
162 * Provide iterator over entries in associated pack index, that should also
163 * exist in this pack file. Objects returned by such iterator are mutable
166 * Iterator returns objects in SHA-1 lexicographical order.
169 * @return iterator over entries of associated pack index
171 * @see PackIndex#iterator()
173 public Iterator
<PackIndex
.MutableEntry
> iterator() {
174 return idx
.iterator();
178 * Obtain the total number of objects available in this pack. This method
179 * relies on pack index, giving number of effectively available objects.
181 * @return number of objects in index of this pack, likewise in this pack
183 long getObjectCount() {
184 return idx
.getObjectCount();
188 * Search for object id with the specified start offset in associated pack
192 * start offset of object to find
193 * @return object id for this offset, or null if no object was found
195 ObjectId
findObjectForOffset(final long offset
) {
196 return getReverseIdx().findObject(offset
);
199 final UnpackedObjectCache
.Entry
readCache(final long position
) {
200 return UnpackedObjectCache
.get(pack
, position
);
203 final void saveCache(final long position
, final byte[] data
, final int type
) {
204 UnpackedObjectCache
.store(pack
, position
, data
, type
);
207 final byte[] decompress(final long position
, final int totalSize
,
208 final WindowCursor curs
) throws DataFormatException
, IOException
{
209 final byte[] dstbuf
= new byte[totalSize
];
210 pack
.readCompressed(position
, dstbuf
, curs
);
214 final void copyRawData(final PackedObjectLoader loader
,
215 final OutputStream out
, final byte buf
[]) throws IOException
{
216 final long objectOffset
= loader
.objectOffset
;
217 final long dataOffset
= loader
.dataOffset
;
218 final int cnt
= (int) (findEndOffset(objectOffset
) - dataOffset
);
219 final WindowCursor curs
= loader
.curs
;
221 if (idx
.hasCRC32Support()) {
222 final CRC32 crc
= new CRC32();
223 int headerCnt
= (int) (dataOffset
- objectOffset
);
224 while (headerCnt
> 0) {
225 int toRead
= Math
.min(headerCnt
, buf
.length
);
226 int read
= pack
.read(objectOffset
, buf
, 0, toRead
, curs
);
228 throw new EOFException();
229 crc
.update(buf
, 0, read
);
232 final CheckedOutputStream crcOut
= new CheckedOutputStream(out
, crc
);
233 pack
.copyToStream(dataOffset
, buf
, cnt
, crcOut
, curs
);
234 final long computed
= crc
.getValue();
237 if (loader
.hasComputedId())
240 id
= findObjectForOffset(objectOffset
);
241 final long expected
= idx
.findCRC32(id
);
242 if (computed
!= expected
)
243 throw new CorruptObjectException(id
,
244 "Possible data corruption - CRC32 of raw pack data (object offset "
246 + ") mismatch CRC32 from pack index");
248 pack
.copyToStream(dataOffset
, buf
, cnt
, out
, curs
);
250 // read to verify against Adler32 zlib checksum
251 loader
.getCachedBytes();
255 boolean supportsFastCopyRawData() {
256 return idx
.hasCRC32Support();
260 private void readPackHeader() throws IOException
{
261 final WindowCursor curs
= new WindowCursor();
263 final byte[] sig
= new byte[Constants
.PACK_SIGNATURE
.length
];
264 final byte[] intbuf
= new byte[4];
267 if (pack
.read(position
, sig
, curs
) != Constants
.PACK_SIGNATURE
.length
)
268 throw new IOException("Not a PACK file.");
269 for (int k
= 0; k
< Constants
.PACK_SIGNATURE
.length
; k
++) {
270 if (sig
[k
] != Constants
.PACK_SIGNATURE
[k
])
271 throw new IOException("Not a PACK file.");
273 position
+= Constants
.PACK_SIGNATURE
.length
;
275 pack
.readFully(position
, intbuf
, curs
);
276 vers
= NB
.decodeUInt32(intbuf
, 0);
277 if (vers
!= 2 && vers
!= 3)
278 throw new IOException("Unsupported pack version " + vers
+ ".");
281 pack
.readFully(position
, intbuf
, curs
);
282 final long objectCnt
= NB
.decodeUInt32(intbuf
, 0);
283 if (idx
.getObjectCount() != objectCnt
)
284 throw new IOException("Pack index"
285 + " object count mismatch; expected " + objectCnt
286 + " found " + idx
.getObjectCount() + ": " + pack
.getName());
289 private PackedObjectLoader
reader(final WindowCursor curs
,
290 final long objOffset
) throws IOException
{
291 long pos
= objOffset
;
293 final byte[] ib
= curs
.tempId
;
294 pack
.readFully(pos
, ib
, curs
);
295 int c
= ib
[p
++] & 0xff;
296 final int typeCode
= (c
>> 4) & 7;
297 long dataSize
= c
& 15;
299 while ((c
& 0x80) != 0) {
301 dataSize
+= (c
& 0x7f) << shift
;
307 case Constants
.OBJ_COMMIT
:
308 case Constants
.OBJ_TREE
:
309 case Constants
.OBJ_BLOB
:
310 case Constants
.OBJ_TAG
:
311 return new WholePackedObjectLoader(curs
, this, pos
, objOffset
,
312 typeCode
, (int) dataSize
);
314 case Constants
.OBJ_OFS_DELTA
: {
315 pack
.readFully(pos
, ib
, curs
);
319 while ((c
& 128) != 0) {
325 return new DeltaOfsPackedObjectLoader(curs
, this, pos
+ p
,
326 objOffset
, (int) dataSize
, objOffset
- ofs
);
328 case Constants
.OBJ_REF_DELTA
: {
329 pack
.readFully(pos
, ib
, curs
);
330 return new DeltaRefPackedObjectLoader(curs
, this, pos
+ ib
.length
,
331 objOffset
, (int) dataSize
, ObjectId
.fromRaw(ib
));
334 throw new IOException("Unknown object type " + typeCode
+ ".");
338 private long findEndOffset(final long startOffset
)
339 throws CorruptObjectException
{
340 final long maxOffset
= pack
.length() - Constants
.OBJECT_ID_LENGTH
;
341 return getReverseIdx().findNextOffset(startOffset
, maxOffset
);
344 private synchronized PackReverseIndex
getReverseIdx() {
345 if (reverseIdx
== null)
346 reverseIdx
= new PackReverseIndex(idx
);