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
;
41 import java
.io
.BufferedOutputStream
;
43 import java
.io
.FileInputStream
;
44 import java
.io
.FileNotFoundException
;
45 import java
.io
.IOException
;
46 import java
.io
.OutputStream
;
47 import java
.nio
.ByteBuffer
;
48 import java
.nio
.channels
.FileChannel
;
49 import java
.security
.DigestOutputStream
;
50 import java
.security
.MessageDigest
;
51 import java
.util
.Comparator
;
53 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
54 import org
.spearce
.jgit
.errors
.UnmergedPathException
;
55 import org
.spearce
.jgit
.lib
.Constants
;
56 import org
.spearce
.jgit
.lib
.LockFile
;
57 import org
.spearce
.jgit
.lib
.ObjectId
;
58 import org
.spearce
.jgit
.lib
.ObjectWriter
;
59 import org
.spearce
.jgit
.lib
.Repository
;
60 import org
.spearce
.jgit
.util
.MutableInteger
;
61 import org
.spearce
.jgit
.util
.NB
;
62 import org
.spearce
.jgit
.util
.TemporaryBuffer
;
65 * Support for the Git dircache (aka index file).
67 * The index file keeps track of which objects are currently checked out in the
68 * working directory, and the last modified time of those working files. Changes
69 * in the working directory can be detected by comparing the modification times
70 * to the cached modification time within the index file.
72 * Index files are also used during merges, where the merge happens within the
73 * index file first, and the working directory is updated as a post-merge step.
74 * Conflicts are stored in the index file to allow tool (and human) based
75 * resolutions to be easily performed.
77 public class DirCache
{
78 private static final byte[] SIG_DIRC
= { 'D', 'I', 'R', 'C' };
80 private static final int EXT_TREE
= 0x54524545 /* 'TREE' */;
82 private static final int INFO_LEN
= DirCacheEntry
.INFO_LEN
;
84 private static final DirCacheEntry
[] NO_ENTRIES
= {};
86 static final Comparator
<DirCacheEntry
> ENT_CMP
= new Comparator
<DirCacheEntry
>() {
87 public int compare(final DirCacheEntry o1
, final DirCacheEntry o2
) {
88 final int cr
= cmp(o1
, o2
);
91 return o1
.getStage() - o2
.getStage();
95 static int cmp(final DirCacheEntry a
, final DirCacheEntry b
) {
96 return cmp(a
.path
, a
.path
.length
, b
);
99 static int cmp(final byte[] aPath
, final int aLen
, final DirCacheEntry b
) {
100 return cmp(aPath
, aLen
, b
.path
, b
.path
.length
);
103 static int cmp(final byte[] aPath
, final int aLen
, final byte[] bPath
,
105 for (int cPos
= 0; cPos
< aLen
&& cPos
< bLen
; cPos
++) {
106 final int cmp
= (aPath
[cPos
] & 0xff) - (bPath
[cPos
] & 0xff);
114 * Create a new empty index which is never stored on disk.
116 * @return an empty cache which has no backing store file. The cache may not
117 * be read or written, but it may be queried and updated (in
120 public static DirCache
newInCore() {
121 return new DirCache(null);
125 * Create a new in-core index representation and read an index from disk.
127 * The new index will be read before it is returned to the caller. Read
128 * failures are reported as exceptions and therefore prevent the method from
129 * returning a partially populated index.
131 * @param indexLocation
132 * location of the index file on disk.
133 * @return a cache representing the contents of the specified index file (if
134 * it exists) or an empty cache if the file does not exist.
135 * @throws IOException
136 * the index file is present but could not be read.
137 * @throws CorruptObjectException
138 * the index file is using a format or extension that this
139 * library does not support.
141 public static DirCache
read(final File indexLocation
)
142 throws CorruptObjectException
, IOException
{
143 final DirCache c
= new DirCache(indexLocation
);
149 * Create a new in-core index representation and read an index from disk.
151 * The new index will be read before it is returned to the caller. Read
152 * failures are reported as exceptions and therefore prevent the method from
153 * returning a partially populated index.
156 * repository the caller wants to read the default index of.
157 * @return a cache representing the contents of the specified index file (if
158 * it exists) or an empty cache if the file does not exist.
159 * @throws IOException
160 * the index file is present but could not be read.
161 * @throws CorruptObjectException
162 * the index file is using a format or extension that this
163 * library does not support.
165 public static DirCache
read(final Repository db
)
166 throws CorruptObjectException
, IOException
{
167 return read(new File(db
.getDirectory(), "index"));
171 * Create a new in-core index representation, lock it, and read from disk.
173 * The new index will be locked and then read before it is returned to the
174 * caller. Read failures are reported as exceptions and therefore prevent
175 * the method from returning a partially populated index. On read failure,
176 * the lock is released.
178 * @param indexLocation
179 * location of the index file on disk.
180 * @return a cache representing the contents of the specified index file (if
181 * it exists) or an empty cache if the file does not exist.
182 * @throws IOException
183 * the index file is present but could not be read, or the lock
184 * could not be obtained.
185 * @throws CorruptObjectException
186 * the index file is using a format or extension that this
187 * library does not support.
189 public static DirCache
lock(final File indexLocation
)
190 throws CorruptObjectException
, IOException
{
191 final DirCache c
= new DirCache(indexLocation
);
193 throw new IOException("Cannot lock " + indexLocation
);
197 } catch (IOException e
) {
200 } catch (RuntimeException e
) {
212 * Create a new in-core index representation, lock it, and read from disk.
214 * The new index will be locked and then read before it is returned to the
215 * caller. Read failures are reported as exceptions and therefore prevent
216 * the method from returning a partially populated index.
219 * repository the caller wants to read the default index of.
220 * @return a cache representing the contents of the specified index file (if
221 * it exists) or an empty cache if the file does not exist.
222 * @throws IOException
223 * the index file is present but could not be read, or the lock
224 * could not be obtained.
225 * @throws CorruptObjectException
226 * the index file is using a format or extension that this
227 * library does not support.
229 public static DirCache
lock(final Repository db
)
230 throws CorruptObjectException
, IOException
{
231 return lock(new File(db
.getDirectory(), "index"));
234 /** Location of the current version of the index file. */
235 private final File liveFile
;
237 /** Modification time of the file at the last read/write we did. */
238 private long lastModified
;
240 /** Individual file index entries, sorted by path name. */
241 private DirCacheEntry
[] sortedEntries
;
243 /** Number of positions within {@link #sortedEntries} that are valid. */
244 private int entryCnt
;
246 /** Cache tree for this index; null if the cache tree is not available. */
247 private DirCacheTree tree
;
249 /** Our active lock (if we hold it); null if we don't have it locked. */
250 private LockFile myLock
;
253 * Create a new in-core index representation.
255 * The new index will be empty. Callers may wish to read from the on disk
256 * file first with {@link #read()}.
258 * @param indexLocation
259 * location of the index file on disk.
261 public DirCache(final File indexLocation
) {
262 liveFile
= indexLocation
;
267 * Create a new builder to update this cache.
269 * Callers should add all entries to the builder, then use
270 * {@link DirCacheBuilder#finish()} to update this instance.
272 * @return a new builder instance for this cache.
274 public DirCacheBuilder
builder() {
275 return new DirCacheBuilder(this, entryCnt
+ 16);
279 * Create a new editor to recreate this cache.
281 * Callers should add commands to the editor, then use
282 * {@link DirCacheEditor#finish()} to update this instance.
284 * @return a new builder instance for this cache.
286 public DirCacheEditor
editor() {
287 return new DirCacheEditor(this, entryCnt
+ 16);
290 void replace(final DirCacheEntry
[] e
, final int cnt
) {
297 * Read the index from disk, if it has changed on disk.
299 * This method tries to avoid loading the index if it has not changed since
300 * the last time we consulted it. A missing index file will be treated as
301 * though it were present but had no file entries in it.
303 * @throws IOException
304 * the index file is present but could not be read. This
305 * DirCache instance may not be populated correctly.
306 * @throws CorruptObjectException
307 * the index file is using a format or extension that this
308 * library does not support.
310 public void read() throws IOException
, CorruptObjectException
{
311 if (liveFile
== null)
312 throw new IOException("DirCache does not have a backing file");
313 if (!liveFile
.exists())
315 else if (liveFile
.lastModified() != lastModified
) {
317 final FileInputStream inStream
= new FileInputStream(liveFile
);
324 } catch (IOException err2
) {
325 // Ignore any close failures.
328 } catch (FileNotFoundException fnfe
) {
329 // Someone must have deleted it between our exists test
330 // and actually opening the path. That's fine, its empty.
337 /** Empty this index, removing all entries. */
338 public void clear() {
340 sortedEntries
= NO_ENTRIES
;
345 private void readFrom(final FileInputStream inStream
) throws IOException
,
346 CorruptObjectException
{
347 final FileChannel fd
= inStream
.getChannel();
348 final long sizeOnDisk
= fd
.size();
349 final BufferedInputStream in
= new BufferedInputStream(inStream
);
351 // Read the index header and verify we understand it.
353 final byte[] hdr
= new byte[12];
354 NB
.readFully(in
, hdr
, 0, 12);
356 throw new CorruptObjectException("Not a DIRC file.");
357 final int ver
= NB
.decodeInt32(hdr
, 4);
359 throw new CorruptObjectException("Unknown DIRC version " + ver
);
360 entryCnt
= NB
.decodeInt32(hdr
, 8);
362 throw new CorruptObjectException("DIRC has too many entries.");
364 // Load the individual file entries.
366 final byte[] infos
= new byte[INFO_LEN
* entryCnt
];
367 sortedEntries
= new DirCacheEntry
[entryCnt
];
368 for (int i
= 0; i
< entryCnt
; i
++)
369 sortedEntries
[i
] = new DirCacheEntry(infos
, i
* INFO_LEN
, in
);
370 lastModified
= liveFile
.lastModified();
372 // After the file entries are index extensions.
374 while (fd
.position() - in
.available() < sizeOnDisk
- 20) {
375 NB
.readFully(in
, hdr
, 0, 8);
376 switch (NB
.decodeInt32(hdr
, 0)) {
378 final byte[] raw
= new byte[NB
.decodeInt32(hdr
, 4)];
379 NB
.readFully(in
, raw
, 0, raw
.length
);
380 tree
= new DirCacheTree(raw
, new MutableInteger(), null);
384 if (hdr
[0] >= 'A' && hdr
[0] <= 'Z') {
385 // The extension is optional and is here only as
386 // a performance optimization. Since we do not
387 // understand it, we can safely skip past it.
389 NB
.skipFully(in
, NB
.decodeUInt32(hdr
, 4));
391 // The extension is not an optimization and is
392 // _required_ to understand this index format.
393 // Since we did not trap it above we must abort.
395 throw new CorruptObjectException("DIRC extension '"
396 + Constants
.CHARSET
.decode(
397 ByteBuffer
.wrap(hdr
, 0, 4)).toString()
398 + "' not supported by this version.");
404 private static boolean is_DIRC(final byte[] hdr
) {
405 if (hdr
.length
< SIG_DIRC
.length
)
407 for (int i
= 0; i
< SIG_DIRC
.length
; i
++)
408 if (hdr
[i
] != SIG_DIRC
[i
])
414 * Try to establish an update lock on the cache file.
416 * @return true if the lock is now held by the caller; false if it is held
418 * @throws IOException
419 * the output file could not be created. The caller does not
422 public boolean lock() throws IOException
{
423 if (liveFile
== null)
424 throw new IOException("DirCache does not have a backing file");
425 final LockFile tmp
= new LockFile(liveFile
);
427 tmp
.setNeedStatInformation(true);
435 * Write the entry records from memory to disk.
437 * The cache must be locked first by calling {@link #lock()} and receiving
438 * true as the return value. Applications are encouraged to lock the index,
439 * then invoke {@link #read()} to ensure the in-memory data is current,
440 * prior to updating the in-memory entries.
442 * Once written the lock is closed and must be either committed with
443 * {@link #commit()} or rolled back with {@link #unlock()}.
445 * @throws IOException
446 * the output file could not be created. The caller no longer
449 public void write() throws IOException
{
450 final LockFile tmp
= myLock
;
453 writeTo(new BufferedOutputStream(tmp
.getOutputStream()));
454 } catch (IOException err
) {
457 } catch (RuntimeException err
) {
460 } catch (Error err
) {
466 private void writeTo(final OutputStream os
) throws IOException
{
467 final MessageDigest foot
= Constants
.newMessageDigest();
468 final DigestOutputStream dos
= new DigestOutputStream(os
, foot
);
472 final byte[] tmp
= new byte[128];
473 System
.arraycopy(SIG_DIRC
, 0, tmp
, 0, SIG_DIRC
.length
);
474 NB
.encodeInt32(tmp
, 4, /* version */2);
475 NB
.encodeInt32(tmp
, 8, entryCnt
);
476 dos
.write(tmp
, 0, 12);
478 // Write the individual file entries.
480 if (lastModified
<= 0) {
481 // Write a new index, as no entries require smudging.
483 for (int i
= 0; i
< entryCnt
; i
++)
484 sortedEntries
[i
].write(dos
);
486 final int smudge_s
= (int) (lastModified
/ 1000);
487 final int smudge_ns
= ((int) (lastModified
% 1000)) * 1000000;
488 for (int i
= 0; i
< entryCnt
; i
++) {
489 final DirCacheEntry e
= sortedEntries
[i
];
490 if (e
.mightBeRacilyClean(smudge_s
, smudge_ns
))
491 e
.smudgeRacilyClean();
497 final TemporaryBuffer bb
= new TemporaryBuffer();
501 NB
.encodeInt32(tmp
, 0, EXT_TREE
);
502 NB
.encodeInt32(tmp
, 4, (int) bb
.length());
503 dos
.write(tmp
, 0, 8);
504 bb
.writeTo(dos
, null);
507 os
.write(foot
.digest());
512 * Commit this change and release the lock.
514 * If this method fails (returns false) the lock is still released.
516 * @return true if the commit was successful and the file contains the new
517 * data; false if the commit failed and the file remains with the
519 * @throws IllegalStateException
520 * the lock is not held.
522 public boolean commit() {
523 final LockFile tmp
= myLock
;
528 lastModified
= tmp
.getCommitLastModified();
532 private void requireLocked(final LockFile tmp
) {
533 if (liveFile
== null)
534 throw new IllegalStateException("DirCache is not locked");
536 throw new IllegalStateException("DirCache "
537 + liveFile
.getAbsolutePath() + " not locked.");
541 * Unlock this file and abort this change.
543 * The temporary file (if created) is deleted before returning.
545 public void unlock() {
546 final LockFile tmp
= myLock
;
554 * Locate the position a path's entry is at in the index.
556 * If there is at least one entry in the index for this path the position of
557 * the lowest stage is returned. Subsequent stages can be identified by
558 * testing consecutive entries until the path differs.
560 * If no path matches the entry -(position+1) is returned, where position is
561 * the location it would have gone within the index.
564 * the path to search for.
565 * @return if >= 0 then the return value is the position of the entry in the
566 * index; pass to {@link #getEntry(int)} to obtain the entry
567 * information. If < 0 the entry does not exist in the index.
569 public int findEntry(final String path
) {
572 final byte[] p
= Constants
.encode(path
);
573 return findEntry(p
, p
.length
);
576 int findEntry(final byte[] p
, final int pLen
) {
580 int mid
= (low
+ high
) >> 1;
581 final int cmp
= cmp(p
, pLen
, sortedEntries
[mid
]);
585 while (mid
> 0 && cmp(p
, pLen
, sortedEntries
[mid
- 1]) == 0)
590 } while (low
< high
);
595 * Determine the next index position past all entries with the same name.
597 * As index entries are sorted by path name, then stage number, this method
598 * advances the supplied position to the first position in the index whose
599 * path name does not match the path name of the supplied position's entry.
602 * entry position of the path that should be skipped.
603 * @return position of the next entry whose path is after the input.
605 public int nextEntry(final int position
) {
606 DirCacheEntry last
= sortedEntries
[position
];
607 int nextIdx
= position
+ 1;
608 while (nextIdx
< entryCnt
) {
609 final DirCacheEntry next
= sortedEntries
[nextIdx
];
610 if (cmp(last
, next
) != 0)
618 int nextEntry(final byte[] p
, final int pLen
, int nextIdx
) {
619 while (nextIdx
< entryCnt
) {
620 final DirCacheEntry next
= sortedEntries
[nextIdx
];
621 if (!DirCacheTree
.peq(p
, next
.path
, pLen
))
629 * Total number of file entries stored in the index.
631 * This count includes unmerged stages for a file entry if the file is
632 * currently conflicted in a merge. This means the total number of entries
633 * in the index may be up to 3 times larger than the number of files in the
636 * Note that this value counts only <i>files</i>.
638 * @return number of entries available.
639 * @see #getEntry(int)
641 public int getEntryCount() {
646 * Get a specific entry.
649 * position of the entry to get.
650 * @return the entry at position <code>i</code>.
652 public DirCacheEntry
getEntry(final int i
) {
653 return sortedEntries
[i
];
657 * Get a specific entry.
660 * the path to search for.
661 * @return the entry at position <code>i</code>.
663 public DirCacheEntry
getEntry(final String path
) {
664 final int i
= findEntry(path
);
665 return i
< 0 ?
null : sortedEntries
[i
];
669 * Recursively get all entries within a subtree.
672 * the subtree path to get all entries within.
673 * @return all entries recursively contained within the subtree.
675 public DirCacheEntry
[] getEntriesWithin(String path
) {
676 if (!path
.endsWith("/"))
678 final byte[] p
= Constants
.encode(path
);
679 final int pLen
= p
.length
;
681 int eIdx
= findEntry(p
, pLen
);
684 final int lastIdx
= nextEntry(p
, pLen
, eIdx
);
685 final DirCacheEntry
[] r
= new DirCacheEntry
[lastIdx
- eIdx
];
686 System
.arraycopy(sortedEntries
, eIdx
, r
, 0, r
.length
);
690 void toArray(final int i
, final DirCacheEntry
[] dst
, final int off
,
692 System
.arraycopy(sortedEntries
, i
, dst
, off
, cnt
);
696 * Obtain (or build) the current cache tree structure.
698 * This method can optionally recreate the cache tree, without flushing the
699 * tree objects themselves to disk.
702 * if true and the cache tree is not present in the index it will
703 * be generated and returned to the caller.
704 * @return the cache tree; null if there is no current cache tree available
705 * and <code>build</code> was false.
707 public DirCacheTree
getCacheTree(final boolean build
) {
710 tree
= new DirCacheTree();
711 tree
.validate(sortedEntries
, entryCnt
, 0, 0);
717 * Write all index trees to the object store, returning the root tree.
720 * the writer to use when serializing to the store.
721 * @return identity for the root tree.
722 * @throws UnmergedPathException
723 * one or more paths contain higher-order stages (stage > 0),
724 * which cannot be stored in a tree object.
725 * @throws IOException
726 * an unexpected error occurred writing to the object store.
728 public ObjectId
writeTree(final ObjectWriter ow
)
729 throws UnmergedPathException
, IOException
{
730 return getCacheTree(true).writeTree(sortedEntries
, 0, 0, ow
);