Fix DirCache's skip over null byte padding when reading a DIRC file
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / dircache / DirCache.java
blob76657c47b04c4cbff1dc8f8565a1de0529713ed2
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.BufferedOutputStream;
42 import java.io.File;
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.lib.Constants;
55 import org.spearce.jgit.lib.LockFile;
56 import org.spearce.jgit.lib.Repository;
57 import org.spearce.jgit.util.MutableInteger;
58 import org.spearce.jgit.util.NB;
59 import org.spearce.jgit.util.TemporaryBuffer;
61 /**
62 * Support for the Git dircache (aka index file).
63 * <p>
64 * The index file keeps track of which objects are currently checked out in the
65 * working directory, and the last modified time of those working files. Changes
66 * in the working directory can be detected by comparing the modification times
67 * to the cached modification time within the index file.
68 * <p>
69 * Index files are also used during merges, where the merge happens within the
70 * index file first, and the working directory is updated as a post-merge step.
71 * Conflicts are stored in the index file to allow tool (and human) based
72 * resolutions to be easily performed.
74 public class DirCache {
75 private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
77 private static final int EXT_TREE = 0x54524545 /* 'TREE' */;
79 private static final int INFO_LEN = DirCacheEntry.INFO_LEN;
81 private static final DirCacheEntry[] NO_ENTRIES = {};
83 static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
84 public int compare(final DirCacheEntry o1, final DirCacheEntry o2) {
85 final int cr = cmp(o1, o2);
86 if (cr != 0)
87 return cr;
88 return o1.getStage() - o2.getStage();
92 static int cmp(final DirCacheEntry a, final DirCacheEntry b) {
93 return cmp(a.path, a.path.length, b);
96 static int cmp(final byte[] aPath, final int aLen, final DirCacheEntry b) {
97 return cmp(aPath, aLen, b.path, b.path.length);
100 static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
101 final int bLen) {
102 for (int cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
103 final int cmp = (aPath[cPos] & 0xff) - (bPath[cPos] & 0xff);
104 if (cmp != 0)
105 return cmp;
107 return aLen - bLen;
111 * Create a new in-core index representation and read an index from disk.
112 * <p>
113 * The new index will be read before it is returned to the caller. Read
114 * failures are reported as exceptions and therefore prevent the method from
115 * returning a partially populated index.
117 * @param indexLocation
118 * location of the index file on disk.
119 * @return a cache representing the contents of the specified index file (if
120 * it exists) or an empty cache if the file does not exist.
121 * @throws IOException
122 * the index file is present but could not be read.
123 * @throws CorruptObjectException
124 * the index file is using a format or extension that this
125 * library does not support.
127 public static DirCache read(final File indexLocation)
128 throws CorruptObjectException, IOException {
129 final DirCache c = new DirCache(indexLocation);
130 c.read();
131 return c;
135 * Create a new in-core index representation and read an index from disk.
136 * <p>
137 * The new index will be read before it is returned to the caller. Read
138 * failures are reported as exceptions and therefore prevent the method from
139 * returning a partially populated index.
141 * @param db
142 * repository the caller wants to read the default index of.
143 * @return a cache representing the contents of the specified index file (if
144 * it exists) or an empty cache if the file does not exist.
145 * @throws IOException
146 * the index file is present but could not be read.
147 * @throws CorruptObjectException
148 * the index file is using a format or extension that this
149 * library does not support.
151 public static DirCache read(final Repository db)
152 throws CorruptObjectException, IOException {
153 return read(new File(db.getDirectory(), "index"));
157 * Create a new in-core index representation, lock it, and read from disk.
158 * <p>
159 * The new index will be locked and then read before it is returned to the
160 * caller. Read failures are reported as exceptions and therefore prevent
161 * the method from returning a partially populated index. On read failure,
162 * the lock is released.
164 * @param indexLocation
165 * location of the index file on disk.
166 * @return a cache representing the contents of the specified index file (if
167 * it exists) or an empty cache if the file does not exist.
168 * @throws IOException
169 * the index file is present but could not be read, or the lock
170 * could not be obtained.
171 * @throws CorruptObjectException
172 * the index file is using a format or extension that this
173 * library does not support.
175 public static DirCache lock(final File indexLocation)
176 throws CorruptObjectException, IOException {
177 final DirCache c = new DirCache(indexLocation);
178 if (!c.lock())
179 throw new IOException("Cannot lock " + indexLocation);
181 try {
182 c.read();
183 } catch (IOException e) {
184 c.unlock();
185 throw e;
186 } catch (RuntimeException e) {
187 c.unlock();
188 throw e;
189 } catch (Error e) {
190 c.unlock();
191 throw e;
194 return c;
198 * Create a new in-core index representation, lock it, and read from disk.
199 * <p>
200 * The new index will be locked and then read before it is returned to the
201 * caller. Read failures are reported as exceptions and therefore prevent
202 * the method from returning a partially populated index.
204 * @param db
205 * repository the caller wants to read the default index of.
206 * @return a cache representing the contents of the specified index file (if
207 * it exists) or an empty cache if the file does not exist.
208 * @throws IOException
209 * the index file is present but could not be read, or the lock
210 * could not be obtained.
211 * @throws CorruptObjectException
212 * the index file is using a format or extension that this
213 * library does not support.
215 public static DirCache lock(final Repository db)
216 throws CorruptObjectException, IOException {
217 return lock(new File(db.getDirectory(), "index"));
220 /** Location of the current version of the index file. */
221 private final File liveFile;
223 /** Modification time of the file at the last read/write we did. */
224 private long lastModified;
226 /** Individual file index entries, sorted by path name. */
227 private DirCacheEntry[] sortedEntries;
229 /** Number of positions within {@link #sortedEntries} that are valid. */
230 private int entryCnt;
232 /** Cache tree for this index; null if the cache tree is not available. */
233 private DirCacheTree tree;
235 /** Our active lock (if we hold it); null if we don't have it locked. */
236 private LockFile myLock;
239 * Create a new in-core index representation.
240 * <p>
241 * The new index will be empty. Callers may wish to read from the on disk
242 * file first with {@link #read()}.
244 * @param indexLocation
245 * location of the index file on disk.
247 public DirCache(final File indexLocation) {
248 liveFile = indexLocation;
249 clear();
253 * Create a new builder to update this cache.
254 * <p>
255 * Callers should add all entries to the builder, then use
256 * {@link DirCacheBuilder#finish()} to update this instance.
258 * @return a new builder instance for this cache.
260 public DirCacheBuilder builder() {
261 return new DirCacheBuilder(this, entryCnt + 16);
265 * Create a new editor to recreate this cache.
266 * <p>
267 * Callers should add commands to the editor, then use
268 * {@link DirCacheEditor#finish()} to update this instance.
270 * @return a new builder instance for this cache.
272 public DirCacheEditor editor() {
273 return new DirCacheEditor(this, entryCnt + 16);
276 void replace(final DirCacheEntry[] e, final int cnt) {
277 sortedEntries = e;
278 entryCnt = cnt;
279 tree = null;
283 * Read the index from disk, if it has changed on disk.
284 * <p>
285 * This method tries to avoid loading the index if it has not changed since
286 * the last time we consulted it. A missing index file will be treated as
287 * though it were present but had no file entries in it.
289 * @throws IOException
290 * the index file is present but could not be read. This
291 * DirCache instance may not be populated correctly.
292 * @throws CorruptObjectException
293 * the index file is using a format or extension that this
294 * library does not support.
296 public void read() throws IOException, CorruptObjectException {
297 if (!liveFile.exists())
298 clear();
299 else if (liveFile.lastModified() != lastModified) {
300 try {
301 final FileInputStream inStream = new FileInputStream(liveFile);
302 try {
303 clear();
304 readFrom(inStream);
305 } finally {
306 try {
307 inStream.close();
308 } catch (IOException err2) {
309 // Ignore any close failures.
312 } catch (FileNotFoundException fnfe) {
313 // Someone must have deleted it between our exists test
314 // and actually opening the path. That's fine, its empty.
316 clear();
321 /** Empty this index, removing all entries. */
322 public void clear() {
323 lastModified = 0;
324 sortedEntries = NO_ENTRIES;
325 entryCnt = 0;
326 tree = null;
329 private void readFrom(final FileInputStream inStream) throws IOException,
330 CorruptObjectException {
331 final FileChannel fd = inStream.getChannel();
332 final long sizeOnDisk = fd.size();
333 final BufferedInputStream in = new BufferedInputStream(inStream);
335 // Read the index header and verify we understand it.
337 final byte[] hdr = new byte[12];
338 NB.readFully(in, hdr, 0, 12);
339 if (!is_DIRC(hdr))
340 throw new CorruptObjectException("Not a DIRC file.");
341 final int ver = NB.decodeInt32(hdr, 4);
342 if (ver != 2)
343 throw new CorruptObjectException("Unknown DIRC version " + ver);
344 entryCnt = NB.decodeInt32(hdr, 8);
345 if (entryCnt < 0)
346 throw new CorruptObjectException("DIRC has too many entries.");
348 // Load the individual file entries.
350 final byte[] infos = new byte[INFO_LEN * entryCnt];
351 sortedEntries = new DirCacheEntry[entryCnt];
352 for (int i = 0; i < entryCnt; i++)
353 sortedEntries[i] = new DirCacheEntry(infos, i * INFO_LEN, in);
354 lastModified = liveFile.lastModified();
356 // After the file entries are index extensions.
358 while (fd.position() - in.available() < sizeOnDisk - 20) {
359 NB.readFully(in, hdr, 0, 8);
360 switch (NB.decodeInt32(hdr, 0)) {
361 case EXT_TREE: {
362 final byte[] raw = new byte[NB.decodeInt32(hdr, 4)];
363 NB.readFully(in, raw, 0, raw.length);
364 tree = new DirCacheTree(raw, new MutableInteger(), null);
365 break;
367 default:
368 if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
369 // The extension is optional and is here only as
370 // a performance optimization. Since we do not
371 // understand it, we can safely skip past it.
373 NB.skipFully(in, NB.decodeUInt32(hdr, 4));
374 } else {
375 // The extension is not an optimization and is
376 // _required_ to understand this index format.
377 // Since we did not trap it above we must abort.
379 throw new CorruptObjectException("DIRC extension '"
380 + Constants.CHARSET.decode(
381 ByteBuffer.wrap(hdr, 0, 4)).toString()
382 + "' not supported by this version.");
388 private static boolean is_DIRC(final byte[] hdr) {
389 if (hdr.length < SIG_DIRC.length)
390 return false;
391 for (int i = 0; i < SIG_DIRC.length; i++)
392 if (hdr[i] != SIG_DIRC[i])
393 return false;
394 return true;
398 * Try to establish an update lock on the cache file.
400 * @return true if the lock is now held by the caller; false if it is held
401 * by someone else.
402 * @throws IOException
403 * the output file could not be created. The caller does not
404 * hold the lock.
406 public boolean lock() throws IOException {
407 final LockFile tmp = new LockFile(liveFile);
408 if (tmp.lock()) {
409 tmp.setNeedStatInformation(true);
410 myLock = tmp;
411 return true;
413 return false;
417 * Write the entry records from memory to disk.
418 * <p>
419 * The cache must be locked first by calling {@link #lock()} and receiving
420 * true as the return value. Applications are encouraged to lock the index,
421 * then invoke {@link #read()} to ensure the in-memory data is current,
422 * prior to updating the in-memory entries.
423 * <p>
424 * Once written the lock is closed and must be either committed with
425 * {@link #commit()} or rolled back with {@link #unlock()}.
427 * @throws IOException
428 * the output file could not be created. The caller no longer
429 * holds the lock.
431 public void write() throws IOException {
432 final LockFile tmp = myLock;
433 requireLocked(tmp);
434 try {
435 writeTo(new BufferedOutputStream(tmp.getOutputStream()));
436 } catch (IOException err) {
437 tmp.unlock();
438 throw err;
439 } catch (RuntimeException err) {
440 tmp.unlock();
441 throw err;
442 } catch (Error err) {
443 tmp.unlock();
444 throw err;
448 private void writeTo(final OutputStream os) throws IOException {
449 final MessageDigest foot = Constants.newMessageDigest();
450 final DigestOutputStream dos = new DigestOutputStream(os, foot);
452 // Write the header.
454 final byte[] tmp = new byte[128];
455 System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
456 NB.encodeInt32(tmp, 4, /* version */2);
457 NB.encodeInt32(tmp, 8, entryCnt);
458 dos.write(tmp, 0, 12);
460 // Write the individual file entries.
462 if (lastModified <= 0) {
463 // Write a new index, as no entries require smudging.
465 for (int i = 0; i < entryCnt; i++)
466 sortedEntries[i].write(dos);
467 } else {
468 final int smudge_s = (int) (lastModified / 1000);
469 final int smudge_ns = ((int) (lastModified % 1000)) * 1000000;
470 for (int i = 0; i < entryCnt; i++) {
471 final DirCacheEntry e = sortedEntries[i];
472 if (e.mightBeRacilyClean(smudge_s, smudge_ns))
473 e.smudgeRacilyClean();
474 e.write(dos);
478 if (tree != null) {
479 final TemporaryBuffer bb = new TemporaryBuffer();
480 tree.write(tmp, bb);
481 bb.close();
483 NB.encodeInt32(tmp, 0, EXT_TREE);
484 NB.encodeInt32(tmp, 4, (int) bb.length());
485 dos.write(tmp, 0, 8);
486 bb.writeTo(dos, null);
489 os.write(foot.digest());
490 os.close();
494 * Commit this change and release the lock.
495 * <p>
496 * If this method fails (returns false) the lock is still released.
498 * @return true if the commit was successful and the file contains the new
499 * data; false if the commit failed and the file remains with the
500 * old data.
501 * @throws IllegalStateException
502 * the lock is not held.
504 public boolean commit() {
505 final LockFile tmp = myLock;
506 requireLocked(tmp);
507 myLock = null;
508 if (!tmp.commit())
509 return false;
510 lastModified = tmp.getCommitLastModified();
511 return true;
514 private void requireLocked(final LockFile tmp) {
515 if (tmp == null)
516 throw new IllegalStateException("DirCache "
517 + liveFile.getAbsolutePath() + " not locked.");
521 * Unlock this file and abort this change.
522 * <p>
523 * The temporary file (if created) is deleted before returning.
525 public void unlock() {
526 final LockFile tmp = myLock;
527 if (tmp != null) {
528 myLock = null;
529 tmp.unlock();
534 * Locate the position a path's entry is at in the index.
535 * <p>
536 * If there is at least one entry in the index for this path the position of
537 * the lowest stage is returned. Subsequent stages can be identified by
538 * testing consecutive entries until the path differs.
539 * <p>
540 * If no path matches the entry -(position+1) is returned, where position is
541 * the location it would have gone within the index.
543 * @param path
544 * the path to search for.
545 * @return if >= 0 then the return value is the position of the entry in the
546 * index; pass to {@link #getEntry(int)} to obtain the entry
547 * information. If < 0 the entry does not exist in the index.
549 public int findEntry(final String path) {
550 if (entryCnt == 0)
551 return -1;
552 final byte[] p = Constants.encode(path);
553 return findEntry(p, p.length);
556 int findEntry(final byte[] p, final int pLen) {
557 int low = 0;
558 int high = entryCnt;
559 do {
560 int mid = (low + high) >> 1;
561 final int cmp = cmp(p, pLen, sortedEntries[mid]);
562 if (cmp < 0)
563 high = mid;
564 else if (cmp == 0) {
565 while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
566 mid--;
567 return mid;
568 } else
569 low = mid + 1;
570 } while (low < high);
571 return -(low + 1);
575 * Determine the next index position past all entries with the same name.
576 * <p>
577 * As index entries are sorted by path name, then stage number, this method
578 * advances the supplied position to the first position in the index whose
579 * path name does not match the path name of the supplied position's entry.
581 * @param position
582 * entry position of the path that should be skipped.
583 * @return position of the next entry whose path is after the input.
585 public int nextEntry(final int position) {
586 DirCacheEntry last = sortedEntries[position];
587 int nextIdx = position + 1;
588 while (nextIdx < entryCnt) {
589 final DirCacheEntry next = sortedEntries[nextIdx];
590 if (cmp(last, next) != 0)
591 break;
592 last = next;
593 nextIdx++;
595 return nextIdx;
598 int nextEntry(final byte[] p, final int pLen, int nextIdx) {
599 while (nextIdx < entryCnt) {
600 final DirCacheEntry next = sortedEntries[nextIdx];
601 if (!DirCacheTree.peq(p, next.path, pLen))
602 break;
603 nextIdx++;
605 return nextIdx;
609 * Total number of file entries stored in the index.
610 * <p>
611 * This count includes unmerged stages for a file entry if the file is
612 * currently conflicted in a merge. This means the total number of entries
613 * in the index may be up to 3 times larger than the number of files in the
614 * working directory.
615 * <p>
616 * Note that this value counts only <i>files</i>.
618 * @return number of entries available.
619 * @see #getEntry(int)
621 public int getEntryCount() {
622 return entryCnt;
626 * Get a specific entry.
628 * @param i
629 * position of the entry to get.
630 * @return the entry at position <code>i</code>.
632 public DirCacheEntry getEntry(final int i) {
633 return sortedEntries[i];
637 * Get a specific entry.
639 * @param path
640 * the path to search for.
641 * @return the entry at position <code>i</code>.
643 public DirCacheEntry getEntry(final String path) {
644 final int i = findEntry(path);
645 return i < 0 ? null : sortedEntries[i];
649 * Recursively get all entries within a subtree.
651 * @param path
652 * the subtree path to get all entries within.
653 * @return all entries recursively contained within the subtree.
655 public DirCacheEntry[] getEntriesWithin(String path) {
656 if (!path.endsWith("/"))
657 path += "/";
658 final byte[] p = Constants.encode(path);
659 final int pLen = p.length;
661 int eIdx = findEntry(p, pLen);
662 if (eIdx < 0)
663 eIdx = -(eIdx + 1);
664 final int lastIdx = nextEntry(p, pLen, eIdx);
665 final DirCacheEntry[] r = new DirCacheEntry[lastIdx - eIdx];
666 System.arraycopy(sortedEntries, eIdx, r, 0, r.length);
667 return r;
670 void toArray(final int i, final DirCacheEntry[] dst, final int off,
671 final int cnt) {
672 System.arraycopy(sortedEntries, i, dst, off, cnt);
676 * Obtain (or build) the current cache tree structure.
677 * <p>
678 * This method can optionally recreate the cache tree, without flushing the
679 * tree objects themselves to disk.
681 * @param build
682 * if true and the cache tree is not present in the index it will
683 * be generated and returned to the caller.
684 * @return the cache tree; null if there is no current cache tree available
685 * and <code>build</code> was false.
687 public DirCacheTree getCacheTree(final boolean build) {
688 if (build) {
689 if (tree == null)
690 tree = new DirCacheTree();
691 tree.validate(sortedEntries, entryCnt, 0, 0);
693 return tree;