Cache pack index fully
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / lib / PackFile.java
blobfa206fdab10783977673ee807565e6cf31546b32
1 /*
2 * Copyright (C) 2006 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.lib;
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.zip.DataFormatException;
23 import org.spearce.jgit.errors.CorruptObjectException;
25 public class PackFile {
26 private static final int IDX_HDR_LEN = 256 * 4;
28 private static final byte[] SIGNATURE = { 'P', 'A', 'C', 'K' };
30 private final Repository repo;
32 private final WindowedFile pack;
34 private final WindowedFile idx;
35 private byte[][] idxdata;
37 private long objectCnt;
39 public PackFile(final Repository parentRepo, final File packFile)
40 throws IOException {
41 repo = parentRepo;
42 // FIXME window size and mmap type should be configurable
43 pack = new WindowedFile(repo.getWindowCache(), packFile,
44 64 * 1024 * 1024, true);
45 try {
46 readPackHeader();
48 final String name = packFile.getName();
49 final int dot = name.lastIndexOf('.');
50 final File idxFile = new File(packFile.getParentFile(), name
51 .substring(0, dot)
52 + ".idx");
53 // FIXME window size and mmap type should be configurable
54 idx = new WindowedFile(new WindowCache(8*1024*1024,1), idxFile, 8*1024*1024, true);
55 try {
56 readIndexHeader();
57 } catch (IOException ioe) {
58 try {
59 idx.close();
60 } catch (IOException err2) {
61 // ignore
63 throw ioe;
65 } catch (IOException ioe) {
66 try {
67 pack.close();
68 } catch (IOException err2) {
69 // Ignore this
71 throw ioe;
75 ObjectLoader resolveBase(final long ofs) throws IOException {
76 return reader(ofs);
79 /**
80 * Determine if an object is contained within the pack file.
81 * <p>
82 * For performance reasons only the index file is searched; the main pack
83 * content is ignored entirely.
84 * </p>
86 * @param id
87 * the object to look for. Must not be null.
88 * @return true if the object is in this pack; false otherwise.
90 public boolean hasObject(final ObjectId id) {
91 return findOffset(id) != -1;
94 /**
95 * Get an object from this pack.
96 * <p>
97 * For performance reasons the caller is responsible for supplying a
98 * temporary buffer of at least {@link Constants#OBJECT_ID_LENGTH} bytes for
99 * use during searching. If an object loader is returned this temporary
100 * buffer becomes the property of the object loader and must not be
101 * overwritten by the caller. If no object loader is returned then the
102 * temporary buffer remains the property of the caller and may be given to a
103 * different pack file to continue searching for the needed object.
104 * </p>
106 * @param id
107 * the object to obtain from the pack. Must not be null.
108 * @return the object loader for the requested object if it is contained in
109 * this pack; null if the object was not found.
110 * @throws IOException
111 * the pack file or the index could not be read.
113 public PackedObjectLoader get(final ObjectId id)
114 throws IOException {
115 final long offset = findOffset(id);
116 if (offset == -1)
117 return null;
118 final PackedObjectLoader objReader = reader(offset);
119 objReader.setId(id);
120 return objReader;
123 public void close() throws IOException {
124 pack.close();
125 idx.close();
128 byte[] decompress(final long position, final int totalSize)
129 throws DataFormatException, IOException {
130 final byte[] dstbuf = new byte[totalSize];
131 pack.readCompressed(position, dstbuf);
132 return dstbuf;
135 private void readPackHeader() throws IOException {
136 long position = 0;
137 final byte[] sig = new byte[SIGNATURE.length];
138 final byte[] intbuf = new byte[4];
139 final long vers;
141 if (pack.read(position, sig) != SIGNATURE.length)
142 throw new IOException("Not a PACK file.");
143 for (int k = 0; k < SIGNATURE.length; k++) {
144 if (sig[k] != SIGNATURE[k])
145 throw new IOException("Not a PACK file.");
147 position += SIGNATURE.length;
149 vers = pack.readUInt32(position, intbuf);
150 if (vers != 2 && vers != 3)
151 throw new IOException("Unsupported pack version " + vers + ".");
152 position += 4;
154 objectCnt = pack.readUInt32(position, intbuf);
157 private void readIndexHeader() throws CorruptObjectException, IOException {
158 if (idx.length() != (IDX_HDR_LEN + (24 * objectCnt) + (2 * Constants.OBJECT_ID_LENGTH)))
159 throw new CorruptObjectException("Invalid pack index");
161 final long[] idxHeader = new long[256]; // really unsigned 32-bit...
162 final byte[] intbuf = new byte[4];
163 for (int k = 0; k < idxHeader.length; k++)
164 idxHeader[k] = idx.readUInt32(k * 4, intbuf);
165 idxdata = new byte[idxHeader.length][];
166 for (int k = 0; k < idxHeader.length; k++) {
167 int n;
168 if (k == 0) {
169 n = (int)(idxHeader[k]);
170 } else {
171 n = (int)(idxHeader[k]-idxHeader[k-1]);
173 if (n > 0) {
174 idxdata[k] = new byte[n * (Constants.OBJECT_ID_LENGTH + 4)];
175 int off = (int) ((k == 0) ? 0 : idxHeader[k-1] * (Constants.OBJECT_ID_LENGTH + 4));
176 idx.read(off + IDX_HDR_LEN, idxdata[k]);
181 private PackedObjectLoader reader(final long objOffset)
182 throws IOException {
183 long pos = objOffset;
184 int p = 0;
185 final byte[] ib = new byte[Constants.OBJECT_ID_LENGTH];
186 pack.readFully(pos, ib);
187 int c = ib[p++] & 0xff;
188 final int typeCode = (c >> 4) & 7;
189 long dataSize = c & 15;
190 int shift = 4;
191 while ((c & 0x80) != 0) {
192 c = ib[p++] & 0xff;
193 dataSize += (c & 0x7f) << shift;
194 shift += 7;
196 pos += p;
198 switch (typeCode) {
199 case Constants.OBJ_COMMIT:
200 return whole(Constants.TYPE_COMMIT, pos, dataSize);
201 case Constants.OBJ_TREE:
202 return whole(Constants.TYPE_TREE, pos, dataSize);
203 case Constants.OBJ_BLOB:
204 return whole(Constants.TYPE_BLOB, pos, dataSize);
205 case Constants.OBJ_TAG:
206 return whole(Constants.TYPE_TAG, pos, dataSize);
207 case Constants.OBJ_OFS_DELTA: {
208 pack.readFully(pos, ib);
209 p = 0;
210 c = ib[p++] & 0xff;
211 long ofs = c & 127;
212 while ((c & 128) != 0) {
213 ofs += 1;
214 c = ib[p++] & 0xff;
215 ofs <<= 7;
216 ofs += (c & 127);
218 return new DeltaOfsPackedObjectLoader(this, pos + p,
219 (int) dataSize, objOffset - ofs);
221 case Constants.OBJ_REF_DELTA: {
222 pack.readFully(pos, ib);
223 return new DeltaRefPackedObjectLoader(this, pos + ib.length,
224 (int) dataSize, new ObjectId(ib));
226 default:
227 throw new IOException("Unknown object type " + typeCode + ".");
231 private final WholePackedObjectLoader whole(final String type,
232 final long pos, final long size) {
233 return new WholePackedObjectLoader(this, pos, type, (int) size);
236 private long findOffset(final ObjectId objId) {
237 final int levelOne = objId.getFirstByte();
238 byte[] data = idxdata[levelOne];
239 if (data == null)
240 return -1;
241 long high = data.length / (4 + Constants.OBJECT_ID_LENGTH);
242 long low = 0;
243 do {
244 final long mid = (low + high) / 2;
245 final long pos = ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4;
246 final int cmp = objId.compareTo(data, pos);
247 if (cmp < 0)
248 high = mid;
249 else if (cmp == 0) {
250 int b0 = data[(int)pos-4] & 0xff;
251 int b1 = data[(int)pos-3] & 0xff;
252 int b2 = data[(int)pos-2] & 0xff;
253 int b3 = data[(int)pos-1] & 0xff;
254 return (((long)b0) << 24) | ( b1 << 16 ) | ( b2 << 8 ) | (b3);
255 } else
256 low = mid + 1;
257 } while (low < high);
258 return -1;