2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
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
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
;
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
;
55 * Support for the Git dircache (aka index file).
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.
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
);
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
,
93 for (int cPos
= 0; cPos
< aLen
&& cPos
< bLen
; cPos
++) {
94 final int cmp
= (aPath
[cPos
] & 0xff) - (bPath
[cPos
] & 0xff);
102 * Create a new in-core index representation and read an index from disk.
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
);
126 * Create a new in-core index representation and read an index from disk.
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.
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.
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
;
174 * Read the index from disk, if it has changed on disk.
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())
190 else if (liveFile
.lastModified() != lastModified
) {
192 final FileInputStream inStream
= new FileInputStream(liveFile
);
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.
212 /** Empty this index, removing all entries. */
213 public void clear() {
215 sortedEntries
= NO_ENTRIES
;
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);
230 throw new CorruptObjectException("Not a DIRC file.");
231 final int ver
= NB
.decodeInt32(hdr
, 4);
233 throw new CorruptObjectException("Unknown DIRC version " + ver
);
234 entryCnt
= NB
.decodeInt32(hdr
, 8);
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)) {
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));
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
)
275 for (int i
= 0; i
< SIG_DIRC
.length
; i
++)
276 if (hdr
[i
] != SIG_DIRC
[i
])
282 * Locate the position a path's entry is at in the index.
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.
288 * If no path matches the entry -(position+1) is returned, where position is
289 * the location it would have gone within the index.
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
) {
300 final byte[] p
= Constants
.encode(path
);
301 return findEntry(p
, p
.length
);
304 int findEntry(final byte[] p
, final int pLen
) {
308 int mid
= (low
+ high
) >> 1;
309 final int cmp
= cmp(p
, pLen
, sortedEntries
[mid
]);
313 while (mid
> 0 && cmp(p
, pLen
, sortedEntries
[mid
- 1]) == 0)
318 } while (low
< high
);
323 * Determine the next index position past all entries with the same name.
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.
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)
347 * Total number of file entries stored in the index.
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
354 * Note that this value counts only <i>files</i>.
356 * @return number of entries available.
357 * @see #getEntry(int)
359 public int getEntryCount() {
364 * Get a specific entry.
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.
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
];