2 * Copyright (C) 2007 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org
.spearce
.jgit
.fetch
;
19 import java
.io
.BufferedOutputStream
;
20 import java
.io
.EOFException
;
22 import java
.io
.FileOutputStream
;
23 import java
.io
.IOException
;
24 import java
.io
.InputStream
;
25 import java
.io
.RandomAccessFile
;
26 import java
.security
.MessageDigest
;
27 import java
.util
.ArrayList
;
28 import java
.util
.Arrays
;
29 import java
.util
.HashMap
;
30 import java
.util
.zip
.DataFormatException
;
31 import java
.util
.zip
.Inflater
;
33 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
34 import org
.spearce
.jgit
.lib
.BinaryDelta
;
35 import org
.spearce
.jgit
.lib
.Constants
;
36 import org
.spearce
.jgit
.lib
.ObjectId
;
37 import org
.spearce
.jgit
.lib
.ObjectIdMap
;
39 /** Indexes Git pack files for local use. */
40 public class IndexPack
{
41 private static final byte[] SIGNATURE
= { 'P', 'A', 'C', 'K' };
43 private static final int BUFFER_SIZE
= 2048;
45 private static final byte[] COMMIT
;
47 private static final byte[] TREE
;
49 private static final byte[] TAG
;
51 private static final byte[] BLOB
;
54 COMMIT
= Constants
.encodeASCII(Constants
.TYPE_COMMIT
);
55 TREE
= Constants
.encodeASCII(Constants
.TYPE_TREE
);
56 TAG
= Constants
.encodeASCII(Constants
.TYPE_TAG
);
57 BLOB
= Constants
.encodeASCII(Constants
.TYPE_BLOB
);
60 private final Inflater inflater
;
62 private final MessageDigest objectDigest
;
64 private InputStream in
;
74 private final File dstPack
;
76 private final File dstIdx
;
78 private long objectCount
;
80 private ObjectEntry
[] entries
;
82 private int deltaCount
;
84 private int entryCount
;
86 private ObjectIdMap
<ArrayList
<UnresolvedDelta
>> baseById
;
88 private HashMap
<Long
, ArrayList
<UnresolvedDelta
>> baseByPos
;
90 private byte[] objectData
;
92 private MessageDigest packDigest
;
94 private RandomAccessFile packOut
;
96 private byte[] packcsum
;
99 * Create a new pack indexer utility.
103 * @throws IOException
104 * the output packfile could not be created.
106 public IndexPack(final InputStream src
, final File dstBase
)
109 inflater
= new Inflater(false);
110 buf
= new byte[BUFFER_SIZE
];
111 objectData
= new byte[BUFFER_SIZE
];
112 objectDigest
= Constants
.newMessageDigest();
113 packDigest
= Constants
.newMessageDigest();
115 if (dstBase
!= null) {
116 final File dir
= dstBase
.getParentFile();
117 final String nam
= dstBase
.getName();
118 dstPack
= new File(dir
, nam
+ ".pack");
119 dstIdx
= new File(dir
, nam
+ ".idx");
120 packOut
= new RandomAccessFile(dstPack
, "rw");
121 packOut
.setLength(0);
129 * Consume data from the input stream until the packfile is indexed.
131 * @throws IOException
133 public void index() throws IOException
{
138 entries
= new ObjectEntry
[(int) objectCount
];
139 baseById
= new ObjectIdMap
<ArrayList
<UnresolvedDelta
>>();
140 baseByPos
= new HashMap
<Long
, ArrayList
<UnresolvedDelta
>>();
142 for (int done
= 0; done
< objectCount
; done
++)
147 if (deltaCount
> 0) {
149 throw new IOException("need packOut");
151 if (entryCount
< objectCount
)
152 throw new IOException("thin packs aren't supported");
165 } catch (IOException err
) {
174 private void resolveDeltas() throws IOException
{
175 final int last
= entryCount
;
176 for (int i
= 0; i
< last
; i
++)
177 resolveDeltas(entries
[i
]);
180 private void resolveDeltas(final ObjectEntry oe
) throws IOException
{
181 if (baseById
.containsKey(oe
) || baseByPos
.containsKey(new Long(oe
.pos
)))
182 resolveDeltas(oe
.pos
, null, null, oe
);
185 private void resolveDeltas(final long pos
, byte[] type
, byte[] data
,
186 ObjectEntry oe
) throws IOException
{
188 int c
= readFromFile();
189 final int typeCode
= (c
>> 4) & 7;
192 while ((c
& 0x80) != 0) {
194 sz
+= (c
& 0x7f) << shift
;
199 case Constants
.OBJ_COMMIT
:
201 data
= inflateFromFile((int) sz
);
203 case Constants
.OBJ_TREE
:
205 data
= inflateFromFile((int) sz
);
207 case Constants
.OBJ_BLOB
:
209 data
= inflateFromFile((int) sz
);
211 case Constants
.OBJ_TAG
:
213 data
= inflateFromFile((int) sz
);
215 case Constants
.OBJ_OFS_DELTA
: {
216 c
= readFromInput() & 0xff;
217 while ((c
& 128) != 0)
218 c
= readFromInput() & 0xff;
219 data
= BinaryDelta
.apply(data
, inflateFromFile((int) sz
));
222 case Constants
.OBJ_REF_DELTA
: {
225 data
= BinaryDelta
.apply(data
, inflateFromFile((int) sz
));
229 throw new IOException("Unknown object type " + typeCode
+ ".");
233 objectDigest
.update(type
);
234 objectDigest
.update((byte) ' ');
235 objectDigest
.update(Constants
.encodeASCII(data
.length
));
236 objectDigest
.update((byte) 0);
237 objectDigest
.update(data
);
238 oe
= new ObjectEntry(pos
, objectDigest
.digest());
239 entries
[entryCount
++] = oe
;
242 final ArrayList
<UnresolvedDelta
> a
= baseById
.remove(oe
);
243 final ArrayList
<UnresolvedDelta
> b
= baseByPos
.remove(new Long(pos
));
245 if (a
!= null && b
!= null) {
246 while (ai
< a
.size() && bi
< b
.size()) {
247 final UnresolvedDelta ad
= a
.get(ai
);
248 final UnresolvedDelta bd
= b
.get(bi
);
249 if (ad
.position
< bd
.position
) {
250 resolveDeltas(ad
.position
, type
, data
, null);
253 resolveDeltas(bd
.position
, type
, data
, null);
259 while (ai
< a
.size())
260 resolveDeltas(a
.get(ai
++).position
, type
, data
, null);
262 while (bi
< b
.size())
263 resolveDeltas(b
.get(bi
++).position
, type
, data
, null);
266 private void writeIdx() throws IOException
{
267 Arrays
.sort(entries
);
268 final int[] fanout
= new int[256];
269 for (int i
= 0; i
< entryCount
; i
++)
270 fanout
[entries
[i
].getFirstByte() & 0xff]++;
271 for (int i
= 1; i
< 256; i
++)
272 fanout
[i
] += fanout
[i
- 1];
274 final BufferedOutputStream os
= new BufferedOutputStream(
275 new FileOutputStream(dstIdx
), BUFFER_SIZE
);
277 final MessageDigest d
= Constants
.newMessageDigest();
278 for (int i
= 0; i
< 256; i
++)
279 writeUInt32(d
, os
, fanout
[i
]);
280 for (int i
= 0; i
< entryCount
; i
++) {
281 final ObjectEntry oe
= entries
[i
];
282 writeUInt32(d
, os
, oe
.pos
);
283 // System.out.println(oe + " " + oe.pos);
284 os
.write(oe
.getBytes());
285 d
.update(oe
.getBytes());
289 os
.write(d
.digest());
295 private void readPackHeader() throws IOException
{
296 final int hdrln
= SIGNATURE
.length
+ 4 + 4;
297 final int p
= fillFromInput(hdrln
);
298 for (int k
= 0; k
< SIGNATURE
.length
; k
++)
299 if (buf
[p
+ k
] != SIGNATURE
[k
])
300 throw new IOException("Not a PACK file.");
302 final long vers
= readUInt32(buf
, p
+ 4);
303 if (vers
!= 2 && vers
!= 3)
304 throw new IOException("Unsupported pack version " + vers
+ ".");
305 objectCount
= readUInt32(buf
, p
+ 8);
309 private void readPackFooter() throws IOException
{
311 final byte[] cmpcsum
= packDigest
.digest();
312 final int c
= fillFromInput(20);
313 packcsum
= new byte[20];
314 System
.arraycopy(buf
, c
, packcsum
, 0, 20);
317 packOut
.write(packcsum
);
319 if (!Arrays
.equals(cmpcsum
, packcsum
))
320 throw new CorruptObjectException("Packfile checksum incorrect.");
323 // Cleanup all resources associated with our input parsing.
324 private void endInput() {
330 // Read one entire object or delta from the input.
331 private void indexOneObject() throws IOException
{
332 final long pos
= position();
334 int c
= readFromInput();
335 final int typeCode
= (c
>> 4) & 7;
338 while ((c
& 0x80) != 0) {
340 sz
+= (c
& 0x7f) << shift
;
345 case Constants
.OBJ_COMMIT
:
346 whole(COMMIT
, pos
, sz
);
348 case Constants
.OBJ_TREE
:
349 whole(TREE
, pos
, sz
);
351 case Constants
.OBJ_BLOB
:
352 whole(BLOB
, pos
, sz
);
354 case Constants
.OBJ_TAG
:
357 case Constants
.OBJ_OFS_DELTA
: {
358 c
= readFromInput() & 0xff;
360 while ((c
& 128) != 0) {
362 c
= readFromInput() & 0xff;
366 final Long base
= new Long(pos
- ofs
);
367 ArrayList
<UnresolvedDelta
> r
= baseByPos
.get(base
);
369 r
= new ArrayList
<UnresolvedDelta
>(8);
370 baseByPos
.put(base
, r
);
372 r
.add(new UnresolvedDelta(pos
));
374 inflateFromInput(false);
377 case Constants
.OBJ_REF_DELTA
: {
378 c
= fillFromInput(20);
379 final byte[] ref
= new byte[20];
380 System
.arraycopy(buf
, c
, ref
, 0, 20);
382 final ObjectId base
= new ObjectId(ref
);
383 ArrayList
<UnresolvedDelta
> r
= baseById
.get(base
);
385 r
= new ArrayList
<UnresolvedDelta
>(8);
386 baseById
.put(base
, r
);
388 r
.add(new UnresolvedDelta(pos
));
390 inflateFromInput(false);
394 throw new IOException("Unknown object type " + typeCode
+ ".");
398 private void whole(final byte[] type
, final long pos
, final long sz
)
400 objectDigest
.update(type
);
401 objectDigest
.update((byte) ' ');
402 objectDigest
.update(Constants
.encodeASCII(sz
));
403 objectDigest
.update((byte) 0);
404 inflateFromInput(true);
405 entries
[entryCount
++] = new ObjectEntry(pos
, objectDigest
.digest());
408 // Current position of {@link #bOffset} within the entire file.
409 private long position() {
410 return bBase
+ bOffset
;
413 private void position(final long pos
) throws IOException
{
420 // Consume exactly one byte from the buffer and return it.
421 private int readFromInput() throws IOException
{
425 return buf
[bOffset
++] & 0xff;
428 // Consume exactly one byte from the buffer and return it.
429 private int readFromFile() throws IOException
{
433 return buf
[bOffset
++] & 0xff;
436 // Consume cnt bytes from the buffer.
437 private void use(final int cnt
) {
442 // Ensure at least need bytes are available in in {@link #buf}.
443 private int fillFromInput(final int need
) throws IOException
{
444 while (bAvail
< need
) {
445 int next
= bOffset
+ bAvail
;
446 int free
= buf
.length
- next
;
447 if (free
+ bAvail
< need
) {
450 free
= buf
.length
- next
;
452 next
= in
.read(buf
, next
, free
);
454 throw new EOFException("Packfile is truncated.");
460 // Ensure at least need bytes are available in in {@link #buf}.
461 private int fillFromFile() throws IOException
{
463 final int next
= packOut
.read(buf
, 0, buf
.length
);
465 throw new EOFException("Packfile is truncated.");
472 // Store consumed bytes in {@link #buf} up to {@link #bOffset}.
473 private void sync() throws IOException
{
474 packDigest
.update(buf
, 0, bOffset
);
476 packOut
.write(buf
, 0, bOffset
);
478 System
.arraycopy(buf
, bOffset
, buf
, 0, bAvail
);
483 private void inflateFromInput(final boolean digest
) throws IOException
{
484 final Inflater inf
= inflater
;
486 final byte[] dst
= objectData
;
488 while (!inf
.finished()) {
489 if (inf
.needsInput()) {
490 final int p
= fillFromInput(1);
491 inf
.setInput(buf
, p
, bAvail
);
495 int free
= dst
.length
- n
;
498 objectDigest
.update(dst
, 0, n
);
503 n
+= inf
.inflate(dst
, n
, free
);
506 objectDigest
.update(dst
, 0, n
);
507 use(-inf
.getRemaining());
508 } catch (DataFormatException dfe
) {
515 private byte[] inflateFromFile(final int sz
) throws IOException
{
516 final Inflater inf
= inflater
;
518 final byte[] dst
= new byte[sz
];
520 while (!inf
.finished()) {
521 if (inf
.needsInput()) {
522 final int p
= fillFromFile();
523 inf
.setInput(buf
, p
, bAvail
);
526 n
+= inf
.inflate(dst
, n
, sz
- n
);
528 use(-inf
.getRemaining());
530 } catch (DataFormatException dfe
) {
537 private static CorruptObjectException
corrupt(final DataFormatException dfe
) {
538 return new CorruptObjectException("Packfile corruption detected: "
542 private static long readUInt32(final byte[] b
, final int i
) {
543 return (b
[i
+ 0] & 0xff) << 24 | (b
[i
+ 1] & 0xff) << 16
544 | (b
[i
+ 2] & 0xff) << 8 | (b
[i
+ 3] & 0xff);
547 private static void writeUInt32(final MessageDigest m
,
548 final BufferedOutputStream o
, final long i
) throws IOException
{
549 final int a
= ((int) (i
>> 24)) & 0xff;
550 final int b
= ((int) (i
>> 16)) & 0xff;
551 final int c
= ((int) (i
>> 8)) & 0xff;
552 final int d
= ((int) i
) & 0xff;
565 private static class ObjectEntry
extends ObjectId
{
568 ObjectEntry(final long headerOffset
, final byte[] raw
) {
574 private static class UnresolvedDelta
{
577 UnresolvedDelta(final long headerOffset
) {
578 position
= headerOffset
;