Implement a new .git/index (aka dircache) read interface
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / dircache / DirCache.java
blob0bc31ec0b263437e398ac074c702fb69efe8143a
1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
21 * written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org.spearce.jgit.dircache;
40 import java.io.BufferedInputStream;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.IOException;
45 import java.nio.ByteBuffer;
46 import java.nio.channels.FileChannel;
47 import java.util.Comparator;
49 import org.spearce.jgit.errors.CorruptObjectException;
50 import org.spearce.jgit.lib.Constants;
51 import org.spearce.jgit.lib.Repository;
52 import org.spearce.jgit.util.NB;
54 /**
55 * Support for the Git dircache (aka index file).
56 * <p>
57 * The index file keeps track of which objects are currently checked out in the
58 * working directory, and the last modified time of those working files. Changes
59 * in the working directory can be detected by comparing the modification times
60 * to the cached modification time within the index file.
61 * <p>
62 * Index files are also used during merges, where the merge happens within the
63 * index file first, and the working directory is updated as a post-merge step.
64 * Conflicts are stored in the index file to allow tool (and human) based
65 * resolutions to be easily performed.
67 public class DirCache {
68 private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
70 private static final int INFO_LEN = DirCacheEntry.INFO_LEN;
72 private static final DirCacheEntry[] NO_ENTRIES = {};
74 static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
75 public int compare(final DirCacheEntry o1, final DirCacheEntry o2) {
76 final int cr = cmp(o1, o2);
77 if (cr != 0)
78 return cr;
79 return o1.getStage() - o2.getStage();
83 static int cmp(final DirCacheEntry a, final DirCacheEntry b) {
84 return cmp(a.path, a.path.length, b);
87 static int cmp(final byte[] aPath, final int aLen, final DirCacheEntry b) {
88 return cmp(aPath, aLen, b.path, b.path.length);
91 static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
92 final int bLen) {
93 for (int cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
94 final int cmp = (aPath[cPos] & 0xff) - (bPath[cPos] & 0xff);
95 if (cmp != 0)
96 return cmp;
98 return aLen - bLen;
102 * Create a new in-core index representation and read an index from disk.
103 * <p>
104 * The new index will be read before it is returned to the caller. Read
105 * failures are reported as exceptions and therefore prevent the method from
106 * returning a partially populated index.
108 * @param indexLocation
109 * location of the index file on disk.
110 * @return a cache representing the contents of the specified index file (if
111 * it exists) or an empty cache if the file does not exist.
112 * @throws IOException
113 * the index file is present but could not be read.
114 * @throws CorruptObjectException
115 * the index file is using a format or extension that this
116 * library does not support.
118 public static DirCache read(final File indexLocation)
119 throws CorruptObjectException, IOException {
120 final DirCache c = new DirCache(indexLocation);
121 c.read();
122 return c;
126 * Create a new in-core index representation and read an index from disk.
127 * <p>
128 * The new index will be read before it is returned to the caller. Read
129 * failures are reported as exceptions and therefore prevent the method from
130 * returning a partially populated index.
132 * @param db
133 * repository the caller wants to read the default index of.
134 * @return a cache representing the contents of the specified index file (if
135 * it exists) or an empty cache if the file does not exist.
136 * @throws IOException
137 * the index file is present but could not be read.
138 * @throws CorruptObjectException
139 * the index file is using a format or extension that this
140 * library does not support.
142 public static DirCache read(final Repository db)
143 throws CorruptObjectException, IOException {
144 return read(new File(db.getDirectory(), "index"));
147 /** Location of the current version of the index file. */
148 private final File liveFile;
150 /** Modification time of the file at the last read/write we did. */
151 private long lastModified;
153 /** Individual file index entries, sorted by path name. */
154 private DirCacheEntry[] sortedEntries;
156 /** Number of positions within {@link #sortedEntries} that are valid. */
157 private int entryCnt;
160 * Create a new in-core index representation.
161 * <p>
162 * The new index will be empty. Callers may wish to read from the on disk
163 * file first with {@link #read()}.
165 * @param indexLocation
166 * location of the index file on disk.
168 public DirCache(final File indexLocation) {
169 liveFile = indexLocation;
170 clear();
174 * Read the index from disk, if it has changed on disk.
175 * <p>
176 * This method tries to avoid loading the index if it has not changed since
177 * the last time we consulted it. A missing index file will be treated as
178 * though it were present but had no file entries in it.
180 * @throws IOException
181 * the index file is present but could not be read. This
182 * DirCache instance may not be populated correctly.
183 * @throws CorruptObjectException
184 * the index file is using a format or extension that this
185 * library does not support.
187 public void read() throws IOException, CorruptObjectException {
188 if (!liveFile.exists())
189 clear();
190 else if (liveFile.lastModified() != lastModified) {
191 try {
192 final FileInputStream inStream = new FileInputStream(liveFile);
193 try {
194 clear();
195 readFrom(inStream);
196 } finally {
197 try {
198 inStream.close();
199 } catch (IOException err2) {
200 // Ignore any close failures.
203 } catch (FileNotFoundException fnfe) {
204 // Someone must have deleted it between our exists test
205 // and actually opening the path. That's fine, its empty.
207 clear();
212 /** Empty this index, removing all entries. */
213 public void clear() {
214 lastModified = 0;
215 sortedEntries = NO_ENTRIES;
216 entryCnt = 0;
219 private void readFrom(final FileInputStream inStream) throws IOException,
220 CorruptObjectException {
221 final FileChannel fd = inStream.getChannel();
222 final long sizeOnDisk = fd.size();
223 final BufferedInputStream in = new BufferedInputStream(inStream);
225 // Read the index header and verify we understand it.
227 final byte[] hdr = new byte[12];
228 NB.readFully(in, hdr, 0, 12);
229 if (!is_DIRC(hdr))
230 throw new CorruptObjectException("Not a DIRC file.");
231 final int ver = NB.decodeInt32(hdr, 4);
232 if (ver != 2)
233 throw new CorruptObjectException("Unknown DIRC version " + ver);
234 entryCnt = NB.decodeInt32(hdr, 8);
235 if (entryCnt < 0)
236 throw new CorruptObjectException("DIRC has too many entries.");
238 // Load the individual file entries.
240 final byte[] infos = new byte[INFO_LEN * entryCnt];
241 sortedEntries = new DirCacheEntry[entryCnt];
242 for (int i = 0; i < entryCnt; i++)
243 sortedEntries[i] = new DirCacheEntry(infos, i * INFO_LEN, in);
244 lastModified = liveFile.lastModified();
246 // After the file entries are index extensions.
248 while (fd.position() - in.available() < sizeOnDisk - 20) {
249 NB.readFully(in, hdr, 0, 8);
250 switch (NB.decodeInt32(hdr, 0)) {
251 default:
252 if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
253 // The extension is optional and is here only as
254 // a performance optimization. Since we do not
255 // understand it, we can safely skip past it.
257 in.skip(NB.decodeInt32(hdr, 4));
258 } else {
259 // The extension is not an optimization and is
260 // _required_ to understand this index format.
261 // Since we did not trap it above we must abort.
263 throw new CorruptObjectException("DIRC extension '"
264 + Constants.CHARSET.decode(
265 ByteBuffer.wrap(hdr, 0, 4)).toString()
266 + "' not supported by this version.");
272 private static boolean is_DIRC(final byte[] hdr) {
273 if (hdr.length < SIG_DIRC.length)
274 return false;
275 for (int i = 0; i < SIG_DIRC.length; i++)
276 if (hdr[i] != SIG_DIRC[i])
277 return false;
278 return true;
282 * Locate the position a path's entry is at in the index.
283 * <p>
284 * If there is at least one entry in the index for this path the position of
285 * the lowest stage is returned. Subsequent stages can be identified by
286 * testing consecutive entries until the path differs.
287 * <p>
288 * If no path matches the entry -(position+1) is returned, where position is
289 * the location it would have gone within the index.
291 * @param path
292 * the path to search for.
293 * @return if >= 0 then the return value is the position of the entry in the
294 * index; pass to {@link #getEntry(int)} to obtain the entry
295 * information. If < 0 the entry does not exist in the index.
297 public int findEntry(final String path) {
298 if (entryCnt == 0)
299 return -1;
300 final byte[] p = Constants.encode(path);
301 return findEntry(p, p.length);
304 int findEntry(final byte[] p, final int pLen) {
305 int low = 0;
306 int high = entryCnt;
307 do {
308 int mid = (low + high) >> 1;
309 final int cmp = cmp(p, pLen, sortedEntries[mid]);
310 if (cmp < 0)
311 high = mid;
312 else if (cmp == 0) {
313 while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
314 mid--;
315 return mid;
316 } else
317 low = mid + 1;
318 } while (low < high);
319 return -(low + 1);
323 * Determine the next index position past all entries with the same name.
324 * <p>
325 * As index entries are sorted by path name, then stage number, this method
326 * advances the supplied position to the first position in the index whose
327 * path name does not match the path name of the supplied position's entry.
329 * @param position
330 * entry position of the path that should be skipped.
331 * @return position of the next entry whose path is after the input.
333 public int nextEntry(final int position) {
334 DirCacheEntry last = sortedEntries[position];
335 int nextIdx = position + 1;
336 while (nextIdx < entryCnt) {
337 final DirCacheEntry next = sortedEntries[nextIdx];
338 if (cmp(last, next) != 0)
339 break;
340 last = next;
341 nextIdx++;
343 return nextIdx;
347 * Total number of file entries stored in the index.
348 * <p>
349 * This count includes unmerged stages for a file entry if the file is
350 * currently conflicted in a merge. This means the total number of entries
351 * in the index may be up to 3 times larger than the number of files in the
352 * working directory.
353 * <p>
354 * Note that this value counts only <i>files</i>.
356 * @return number of entries available.
357 * @see #getEntry(int)
359 public int getEntryCount() {
360 return entryCnt;
364 * Get a specific entry.
366 * @param i
367 * position of the entry to get.
368 * @return the entry at position <code>i</code>.
370 public DirCacheEntry getEntry(final int i) {
371 return sortedEntries[i];
375 * Get a specific entry.
377 * @param path
378 * the path to search for.
379 * @return the entry at position <code>i</code>.
381 public DirCacheEntry getEntry(final String path) {
382 final int i = findEntry(path);
383 return i < 0 ? null : sortedEntries[i];