DirCache.java

  1. /*
  2.  * Copyright (C) 2008, 2010, Google Inc.
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4.  * Copyright (C) 2011, 2020, Matthias Sohn <matthias.sohn@sap.com> and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */

  12. package org.eclipse.jgit.dircache;

  13. import static java.nio.charset.StandardCharsets.ISO_8859_1;

  14. import java.io.BufferedInputStream;
  15. import java.io.BufferedOutputStream;
  16. import java.io.EOFException;
  17. import java.io.File;
  18. import java.io.FileNotFoundException;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.security.DigestOutputStream;
  23. import java.security.MessageDigest;
  24. import java.text.MessageFormat;
  25. import java.time.Instant;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Comparator;
  29. import java.util.List;

  30. import org.eclipse.jgit.errors.CorruptObjectException;
  31. import org.eclipse.jgit.errors.IndexReadException;
  32. import org.eclipse.jgit.errors.LockFailedException;
  33. import org.eclipse.jgit.errors.UnmergedPathException;
  34. import org.eclipse.jgit.events.IndexChangedEvent;
  35. import org.eclipse.jgit.events.IndexChangedListener;
  36. import org.eclipse.jgit.internal.JGitText;
  37. import org.eclipse.jgit.internal.storage.file.FileSnapshot;
  38. import org.eclipse.jgit.internal.storage.file.LockFile;
  39. import org.eclipse.jgit.lib.AnyObjectId;
  40. import org.eclipse.jgit.lib.Config;
  41. import org.eclipse.jgit.lib.Config.ConfigEnum;
  42. import org.eclipse.jgit.lib.ConfigConstants;
  43. import org.eclipse.jgit.lib.Constants;
  44. import org.eclipse.jgit.lib.ObjectId;
  45. import org.eclipse.jgit.lib.ObjectInserter;
  46. import org.eclipse.jgit.lib.ObjectReader;
  47. import org.eclipse.jgit.lib.Repository;
  48. import org.eclipse.jgit.treewalk.FileTreeIterator;
  49. import org.eclipse.jgit.treewalk.TreeWalk;
  50. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  51. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
  52. import org.eclipse.jgit.util.FS;
  53. import org.eclipse.jgit.util.IO;
  54. import org.eclipse.jgit.util.MutableInteger;
  55. import org.eclipse.jgit.util.NB;
  56. import org.eclipse.jgit.util.TemporaryBuffer;
  57. import org.eclipse.jgit.util.io.SilentFileInputStream;

  58. /**
  59.  * Support for the Git dircache (aka index file).
  60.  * <p>
  61.  * The index file keeps track of which objects are currently checked out in the
  62.  * working directory, and the last modified time of those working files. Changes
  63.  * in the working directory can be detected by comparing the modification times
  64.  * to the cached modification time within the index file.
  65.  * <p>
  66.  * Index files are also used during merges, where the merge happens within the
  67.  * index file first, and the working directory is updated as a post-merge step.
  68.  * Conflicts are stored in the index file to allow tool (and human) based
  69.  * resolutions to be easily performed.
  70.  */
  71. public class DirCache {
  72.     private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };

  73.     private static final int EXT_TREE = 0x54524545 /* 'TREE' */;

  74.     private static final DirCacheEntry[] NO_ENTRIES = {};

  75.     private static final byte[] NO_CHECKSUM = {};

  76.     static final Comparator<DirCacheEntry> ENT_CMP = (DirCacheEntry o1,
  77.             DirCacheEntry o2) -> {
  78.         final int cr = cmp(o1, o2);
  79.         if (cr != 0)
  80.             return cr;
  81.         return o1.getStage() - o2.getStage();
  82.     };

  83.     static int cmp(DirCacheEntry a, DirCacheEntry b) {
  84.         return cmp(a.path, a.path.length, b);
  85.     }

  86.     static int cmp(byte[] aPath, int aLen, DirCacheEntry b) {
  87.         return cmp(aPath, aLen, b.path, b.path.length);
  88.     }

  89.     static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
  90.             final int bLen) {
  91.         for (int cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
  92.             final int cmp = (aPath[cPos] & 0xff) - (bPath[cPos] & 0xff);
  93.             if (cmp != 0)
  94.                 return cmp;
  95.         }
  96.         return aLen - bLen;
  97.     }

  98.     /**
  99.      * Create a new empty index which is never stored on disk.
  100.      *
  101.      * @return an empty cache which has no backing store file. The cache may not
  102.      *         be read or written, but it may be queried and updated (in
  103.      *         memory).
  104.      */
  105.     public static DirCache newInCore() {
  106.         return new DirCache(null, null);
  107.     }

  108.     /**
  109.      * Create a new in memory index read from the contents of a tree.
  110.      *
  111.      * @param reader
  112.      *            reader to access the tree objects from a repository.
  113.      * @param treeId
  114.      *            tree to read. Must identify a tree, not a tree-ish.
  115.      * @return a new cache which has no backing store file, but contains the
  116.      *         contents of {@code treeId}.
  117.      * @throws java.io.IOException
  118.      *             one or more trees not available from the ObjectReader.
  119.      * @since 4.2
  120.      */
  121.     public static DirCache read(ObjectReader reader, AnyObjectId treeId)
  122.             throws IOException {
  123.         DirCache d = newInCore();
  124.         DirCacheBuilder b = d.builder();
  125.         b.addTree(null, DirCacheEntry.STAGE_0, reader, treeId);
  126.         b.finish();
  127.         return d;
  128.     }

  129.     /**
  130.      * Create a new in-core index representation and read an index from disk.
  131.      * <p>
  132.      * The new index will be read before it is returned to the caller. Read
  133.      * failures are reported as exceptions and therefore prevent the method from
  134.      * returning a partially populated index.
  135.      *
  136.      * @param repository
  137.      *            repository containing the index to read
  138.      * @return a cache representing the contents of the specified index file (if
  139.      *         it exists) or an empty cache if the file does not exist.
  140.      * @throws java.io.IOException
  141.      *             the index file is present but could not be read.
  142.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  143.      *             the index file is using a format or extension that this
  144.      *             library does not support.
  145.      */
  146.     public static DirCache read(Repository repository)
  147.             throws CorruptObjectException, IOException {
  148.         final DirCache c = read(repository.getIndexFile(), repository.getFS());
  149.         c.repository = repository;
  150.         return c;
  151.     }

  152.     /**
  153.      * Create a new in-core index representation and read an index from disk.
  154.      * <p>
  155.      * The new index will be read before it is returned to the caller. Read
  156.      * failures are reported as exceptions and therefore prevent the method from
  157.      * returning a partially populated index.
  158.      *
  159.      * @param indexLocation
  160.      *            location of the index file on disk.
  161.      * @param fs
  162.      *            the file system abstraction which will be necessary to perform
  163.      *            certain file system operations.
  164.      * @return a cache representing the contents of the specified index file (if
  165.      *         it exists) or an empty cache if the file does not exist.
  166.      * @throws java.io.IOException
  167.      *             the index file is present but could not be read.
  168.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  169.      *             the index file is using a format or extension that this
  170.      *             library does not support.
  171.      */
  172.     public static DirCache read(File indexLocation, FS fs)
  173.             throws CorruptObjectException, IOException {
  174.         final DirCache c = new DirCache(indexLocation, fs);
  175.         c.read();
  176.         return c;
  177.     }

  178.     /**
  179.      * Create a new in-core index representation, lock it, and read from disk.
  180.      * <p>
  181.      * The new index will be locked and then read before it is returned to the
  182.      * caller. Read failures are reported as exceptions and therefore prevent
  183.      * the method from returning a partially populated index. On read failure,
  184.      * the lock is released.
  185.      *
  186.      * @param indexLocation
  187.      *            location of the index file on disk.
  188.      * @param fs
  189.      *            the file system abstraction which will be necessary to perform
  190.      *            certain file system operations.
  191.      * @return a cache representing the contents of the specified index file (if
  192.      *         it exists) or an empty cache if the file does not exist.
  193.      * @throws java.io.IOException
  194.      *             the index file is present but could not be read, or the lock
  195.      *             could not be obtained.
  196.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  197.      *             the index file is using a format or extension that this
  198.      *             library does not support.
  199.      */
  200.     public static DirCache lock(File indexLocation, FS fs)
  201.             throws CorruptObjectException, IOException {
  202.         final DirCache c = new DirCache(indexLocation, fs);
  203.         if (!c.lock())
  204.             throw new LockFailedException(indexLocation);

  205.         try {
  206.             c.read();
  207.         } catch (IOException | RuntimeException | Error e) {
  208.             c.unlock();
  209.             throw e;
  210.         }

  211.         return c;
  212.     }

  213.     /**
  214.      * Create a new in-core index representation, lock it, and read from disk.
  215.      * <p>
  216.      * The new index will be locked and then read before it is returned to the
  217.      * caller. Read failures are reported as exceptions and therefore prevent
  218.      * the method from returning a partially populated index. On read failure,
  219.      * the lock is released.
  220.      *
  221.      * @param repository
  222.      *            repository containing the index to lock and read
  223.      * @param indexChangedListener
  224.      *            listener to be informed when DirCache is committed
  225.      * @return a cache representing the contents of the specified index file (if
  226.      *         it exists) or an empty cache if the file does not exist.
  227.      * @throws java.io.IOException
  228.      *             the index file is present but could not be read, or the lock
  229.      *             could not be obtained.
  230.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  231.      *             the index file is using a format or extension that this
  232.      *             library does not support.
  233.      * @since 2.0
  234.      */
  235.     public static DirCache lock(final Repository repository,
  236.             final IndexChangedListener indexChangedListener)
  237.             throws CorruptObjectException, IOException {
  238.         DirCache c = lock(repository.getIndexFile(), repository.getFS(),
  239.                 indexChangedListener);
  240.         c.repository = repository;
  241.         return c;
  242.     }

  243.     /**
  244.      * Create a new in-core index representation, lock it, and read from disk.
  245.      * <p>
  246.      * The new index will be locked and then read before it is returned to the
  247.      * caller. Read failures are reported as exceptions and therefore prevent
  248.      * the method from returning a partially populated index. On read failure,
  249.      * the lock is released.
  250.      *
  251.      * @param indexLocation
  252.      *            location of the index file on disk.
  253.      * @param fs
  254.      *            the file system abstraction which will be necessary to perform
  255.      *            certain file system operations.
  256.      * @param indexChangedListener
  257.      *            listener to be informed when DirCache is committed
  258.      * @return a cache representing the contents of the specified index file (if
  259.      *         it exists) or an empty cache if the file does not exist.
  260.      * @throws java.io.IOException
  261.      *             the index file is present but could not be read, or the lock
  262.      *             could not be obtained.
  263.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  264.      *             the index file is using a format or extension that this
  265.      *             library does not support.
  266.      */
  267.     public static DirCache lock(final File indexLocation, final FS fs,
  268.             IndexChangedListener indexChangedListener)
  269.             throws CorruptObjectException,
  270.             IOException {
  271.         DirCache c = lock(indexLocation, fs);
  272.         c.registerIndexChangedListener(indexChangedListener);
  273.         return c;
  274.     }

  275.     /** Location of the current version of the index file. */
  276.     private final File liveFile;

  277.     /** Individual file index entries, sorted by path name. */
  278.     private DirCacheEntry[] sortedEntries;

  279.     /** Number of positions within {@link #sortedEntries} that are valid. */
  280.     private int entryCnt;

  281.     /** Cache tree for this index; null if the cache tree is not available. */
  282.     private DirCacheTree tree;

  283.     /** Our active lock (if we hold it); null if we don't have it locked. */
  284.     private LockFile myLock;

  285.     /** Keep track of whether the index has changed or not */
  286.     private FileSnapshot snapshot;

  287.     /** index checksum when index was read from disk */
  288.     private byte[] readIndexChecksum;

  289.     /** index checksum when index was written to disk */
  290.     private byte[] writeIndexChecksum;

  291.     /** listener to be informed on commit */
  292.     private IndexChangedListener indexChangedListener;

  293.     /** Repository containing this index */
  294.     private Repository repository;

  295.     /** If we read this index from disk, the original format. */
  296.     private DirCacheVersion version;

  297.     /**
  298.      * Create a new in-core index representation.
  299.      * <p>
  300.      * The new index will be empty. Callers may wish to read from the on disk
  301.      * file first with {@link #read()}.
  302.      *
  303.      * @param indexLocation
  304.      *            location of the index file on disk.
  305.      * @param fs
  306.      *            the file system abstraction which will be necessary to perform
  307.      *            certain file system operations.
  308.      */
  309.     public DirCache(File indexLocation, FS fs) {
  310.         liveFile = indexLocation;
  311.         clear();
  312.     }

  313.     /**
  314.      * Create a new builder to update this cache.
  315.      * <p>
  316.      * Callers should add all entries to the builder, then use
  317.      * {@link org.eclipse.jgit.dircache.DirCacheBuilder#finish()} to update this
  318.      * instance.
  319.      *
  320.      * @return a new builder instance for this cache.
  321.      */
  322.     public DirCacheBuilder builder() {
  323.         return new DirCacheBuilder(this, entryCnt + 16);
  324.     }

  325.     /**
  326.      * Create a new editor to recreate this cache.
  327.      * <p>
  328.      * Callers should add commands to the editor, then use
  329.      * {@link org.eclipse.jgit.dircache.DirCacheEditor#finish()} to update this
  330.      * instance.
  331.      *
  332.      * @return a new builder instance for this cache.
  333.      */
  334.     public DirCacheEditor editor() {
  335.         return new DirCacheEditor(this, entryCnt + 16);
  336.     }

  337.     DirCacheVersion getVersion() {
  338.         return version;
  339.     }

  340.     void replace(DirCacheEntry[] e, int cnt) {
  341.         sortedEntries = e;
  342.         entryCnt = cnt;
  343.         tree = null;
  344.     }

  345.     /**
  346.      * Read the index from disk, if it has changed on disk.
  347.      * <p>
  348.      * This method tries to avoid loading the index if it has not changed since
  349.      * the last time we consulted it. A missing index file will be treated as
  350.      * though it were present but had no file entries in it.
  351.      *
  352.      * @throws java.io.IOException
  353.      *             the index file is present but could not be read. This
  354.      *             DirCache instance may not be populated correctly.
  355.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  356.      *             the index file is using a format or extension that this
  357.      *             library does not support.
  358.      */
  359.     public void read() throws IOException, CorruptObjectException {
  360.         if (liveFile == null)
  361.             throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
  362.         if (!liveFile.exists())
  363.             clear();
  364.         else if (snapshot == null || snapshot.isModified(liveFile)) {
  365.             try (SilentFileInputStream inStream = new SilentFileInputStream(
  366.                     liveFile)) {
  367.                 clear();
  368.                 readFrom(inStream);
  369.             } catch (FileNotFoundException fnfe) {
  370.                 if (liveFile.exists()) {
  371.                     // Panic: the index file exists but we can't read it
  372.                     throw new IndexReadException(
  373.                             MessageFormat.format(JGitText.get().cannotReadIndex,
  374.                                     liveFile.getAbsolutePath(), fnfe));
  375.                 }
  376.                 // Someone must have deleted it between our exists test
  377.                 // and actually opening the path. That's fine, its empty.
  378.                 //
  379.                 clear();
  380.             }
  381.             snapshot = FileSnapshot.save(liveFile);
  382.         }
  383.     }

  384.     /**
  385.      * Whether the memory state differs from the index file
  386.      *
  387.      * @return {@code true} if the memory state differs from the index file
  388.      * @throws java.io.IOException
  389.      */
  390.     public boolean isOutdated() throws IOException {
  391.         if (liveFile == null || !liveFile.exists())
  392.             return false;
  393.         return snapshot == null || snapshot.isModified(liveFile);
  394.     }

  395.     /**
  396.      * Empty this index, removing all entries.
  397.      */
  398.     public void clear() {
  399.         snapshot = null;
  400.         sortedEntries = NO_ENTRIES;
  401.         entryCnt = 0;
  402.         tree = null;
  403.         readIndexChecksum = NO_CHECKSUM;
  404.     }

  405.     private void readFrom(InputStream inStream) throws IOException,
  406.             CorruptObjectException {
  407.         final BufferedInputStream in = new BufferedInputStream(inStream);
  408.         final MessageDigest md = Constants.newMessageDigest();

  409.         // Read the index header and verify we understand it.
  410.         //
  411.         final byte[] hdr = new byte[20];
  412.         IO.readFully(in, hdr, 0, 12);
  413.         md.update(hdr, 0, 12);
  414.         if (!is_DIRC(hdr))
  415.             throw new CorruptObjectException(JGitText.get().notADIRCFile);
  416.         int versionCode = NB.decodeInt32(hdr, 4);
  417.         DirCacheVersion ver = DirCacheVersion.fromInt(versionCode);
  418.         if (ver == null) {
  419.             throw new CorruptObjectException(
  420.                     MessageFormat.format(JGitText.get().unknownDIRCVersion,
  421.                             Integer.valueOf(versionCode)));
  422.         }
  423.         boolean extended = false;
  424.         switch (ver) {
  425.         case DIRC_VERSION_MINIMUM:
  426.             break;
  427.         case DIRC_VERSION_EXTENDED:
  428.         case DIRC_VERSION_PATHCOMPRESS:
  429.             extended = true;
  430.             break;
  431.         default:
  432.             throw new CorruptObjectException(MessageFormat
  433.                     .format(JGitText.get().unknownDIRCVersion, ver));
  434.         }
  435.         version = ver;
  436.         entryCnt = NB.decodeInt32(hdr, 8);
  437.         if (entryCnt < 0)
  438.             throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);

  439.         snapshot = FileSnapshot.save(liveFile);
  440.         Instant smudge = snapshot.lastModifiedInstant();

  441.         // Load the individual file entries.
  442.         //
  443.         final int infoLength = DirCacheEntry.getMaximumInfoLength(extended);
  444.         final byte[] infos = new byte[infoLength * entryCnt];
  445.         sortedEntries = new DirCacheEntry[entryCnt];

  446.         final MutableInteger infoAt = new MutableInteger();
  447.         for (int i = 0; i < entryCnt; i++) {
  448.             sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge,
  449.                     version, i == 0 ? null : sortedEntries[i - 1]);
  450.         }

  451.         // After the file entries are index extensions, and then a footer.
  452.         //
  453.         for (;;) {
  454.             in.mark(21);
  455.             IO.readFully(in, hdr, 0, 20);
  456.             if (in.read() < 0) {
  457.                 // No extensions present; the file ended where we expected.
  458.                 //
  459.                 break;
  460.             }

  461.             in.reset();
  462.             md.update(hdr, 0, 8);
  463.             IO.skipFully(in, 8);

  464.             long sz = NB.decodeUInt32(hdr, 4);
  465.             switch (NB.decodeInt32(hdr, 0)) {
  466.             case EXT_TREE: {
  467.                 if (Integer.MAX_VALUE < sz) {
  468.                     throw new CorruptObjectException(MessageFormat.format(
  469.                             JGitText.get().DIRCExtensionIsTooLargeAt,
  470.                             formatExtensionName(hdr), Long.valueOf(sz)));
  471.                 }
  472.                 final byte[] raw = new byte[(int) sz];
  473.                 IO.readFully(in, raw, 0, raw.length);
  474.                 md.update(raw, 0, raw.length);
  475.                 tree = new DirCacheTree(raw, new MutableInteger(), null);
  476.                 break;
  477.             }
  478.             default:
  479.                 if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
  480.                     // The extension is optional and is here only as
  481.                     // a performance optimization. Since we do not
  482.                     // understand it, we can safely skip past it, after
  483.                     // we include its data in our checksum.
  484.                     //
  485.                     skipOptionalExtension(in, md, hdr, sz);
  486.                 } else {
  487.                     // The extension is not an optimization and is
  488.                     // _required_ to understand this index format.
  489.                     // Since we did not trap it above we must abort.
  490.                     //
  491.                     throw new CorruptObjectException(MessageFormat.format(JGitText.get().DIRCExtensionNotSupportedByThisVersion
  492.                             , formatExtensionName(hdr)));
  493.                 }
  494.             }
  495.         }

  496.         readIndexChecksum = md.digest();
  497.         if (!Arrays.equals(readIndexChecksum, hdr)) {
  498.             throw new CorruptObjectException(JGitText.get().DIRCChecksumMismatch);
  499.         }
  500.     }

  501.     private void skipOptionalExtension(final InputStream in,
  502.             final MessageDigest md, final byte[] hdr, long sz)
  503.             throws IOException {
  504.         final byte[] b = new byte[4096];
  505.         while (0 < sz) {
  506.             int n = in.read(b, 0, (int) Math.min(b.length, sz));
  507.             if (n < 0) {
  508.                 throw new EOFException(
  509.                         MessageFormat.format(
  510.                                 JGitText.get().shortReadOfOptionalDIRCExtensionExpectedAnotherBytes,
  511.                                 formatExtensionName(hdr), Long.valueOf(sz)));
  512.             }
  513.             md.update(b, 0, n);
  514.             sz -= n;
  515.         }
  516.     }

  517.     private static String formatExtensionName(byte[] hdr) {
  518.         return "'" + new String(hdr, 0, 4, ISO_8859_1) + "'"; //$NON-NLS-1$ //$NON-NLS-2$
  519.     }

  520.     private static boolean is_DIRC(byte[] hdr) {
  521.         if (hdr.length < SIG_DIRC.length)
  522.             return false;
  523.         for (int i = 0; i < SIG_DIRC.length; i++)
  524.             if (hdr[i] != SIG_DIRC[i])
  525.                 return false;
  526.         return true;
  527.     }

  528.     /**
  529.      * Try to establish an update lock on the cache file.
  530.      *
  531.      * @return true if the lock is now held by the caller; false if it is held
  532.      *         by someone else.
  533.      * @throws java.io.IOException
  534.      *             the output file could not be created. The caller does not
  535.      *             hold the lock.
  536.      */
  537.     public boolean lock() throws IOException {
  538.         if (liveFile == null)
  539.             throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
  540.         final LockFile tmp = new LockFile(liveFile);
  541.         if (tmp.lock()) {
  542.             tmp.setNeedStatInformation(true);
  543.             myLock = tmp;
  544.             return true;
  545.         }
  546.         return false;
  547.     }

  548.     /**
  549.      * Write the entry records from memory to disk.
  550.      * <p>
  551.      * The cache must be locked first by calling {@link #lock()} and receiving
  552.      * true as the return value. Applications are encouraged to lock the index,
  553.      * then invoke {@link #read()} to ensure the in-memory data is current,
  554.      * prior to updating the in-memory entries.
  555.      * <p>
  556.      * Once written the lock is closed and must be either committed with
  557.      * {@link #commit()} or rolled back with {@link #unlock()}.
  558.      *
  559.      * @throws java.io.IOException
  560.      *             the output file could not be created. The caller no longer
  561.      *             holds the lock.
  562.      */
  563.     public void write() throws IOException {
  564.         final LockFile tmp = myLock;
  565.         requireLocked(tmp);
  566.         try (OutputStream o = tmp.getOutputStream();
  567.                 OutputStream bo = new BufferedOutputStream(o)) {
  568.             writeTo(liveFile.getParentFile(), bo);
  569.         } catch (IOException | RuntimeException | Error err) {
  570.             tmp.unlock();
  571.             throw err;
  572.         }
  573.     }

  574.     void writeTo(File dir, OutputStream os) throws IOException {
  575.         final MessageDigest foot = Constants.newMessageDigest();
  576.         final DigestOutputStream dos = new DigestOutputStream(os, foot);

  577.         if (version == null && this.repository != null) {
  578.             // A new DirCache is being written.
  579.             DirCacheConfig config = repository.getConfig()
  580.                     .get(DirCacheConfig::new);
  581.             version = config.getIndexVersion();
  582.         }
  583.         if (version == null
  584.                 || version == DirCacheVersion.DIRC_VERSION_MINIMUM) {
  585.             version = DirCacheVersion.DIRC_VERSION_MINIMUM;
  586.             for (int i = 0; i < entryCnt; i++) {
  587.                 if (sortedEntries[i].isExtended()) {
  588.                     version = DirCacheVersion.DIRC_VERSION_EXTENDED;
  589.                     break;
  590.                 }
  591.             }
  592.         }

  593.         // Write the header.
  594.         //
  595.         final byte[] tmp = new byte[128];
  596.         System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
  597.         NB.encodeInt32(tmp, 4, version.getVersionCode());
  598.         NB.encodeInt32(tmp, 8, entryCnt);
  599.         dos.write(tmp, 0, 12);

  600.         // Write the individual file entries.

  601.         Instant smudge;
  602.         if (myLock != null) {
  603.             // For new files we need to smudge the index entry
  604.             // if they have been modified "now". Ideally we'd
  605.             // want the timestamp when we're done writing the index,
  606.             // so we use the current timestamp as a approximation.
  607.             myLock.createCommitSnapshot();
  608.             snapshot = myLock.getCommitSnapshot();
  609.             smudge = snapshot.lastModifiedInstant();
  610.         } else {
  611.             // Used in unit tests only
  612.             smudge = Instant.EPOCH;
  613.         }

  614.         // Check if tree is non-null here since calling updateSmudgedEntries
  615.         // will automatically build it via creating a DirCacheIterator
  616.         final boolean writeTree = tree != null;

  617.         if (repository != null && entryCnt > 0)
  618.             updateSmudgedEntries();

  619.         for (int i = 0; i < entryCnt; i++) {
  620.             final DirCacheEntry e = sortedEntries[i];
  621.             if (e.mightBeRacilyClean(smudge)) {
  622.                 e.smudgeRacilyClean();
  623.             }
  624.             e.write(dos, version, i == 0 ? null : sortedEntries[i - 1]);
  625.         }

  626.         if (writeTree) {
  627.             @SuppressWarnings("resource") // Explicitly closed in try block, and
  628.                                             // destroyed in finally
  629.             TemporaryBuffer bb = new TemporaryBuffer.LocalFile(dir, 5 << 20);
  630.             try {
  631.                 tree.write(tmp, bb);
  632.                 bb.close();

  633.                 NB.encodeInt32(tmp, 0, EXT_TREE);
  634.                 NB.encodeInt32(tmp, 4, (int) bb.length());
  635.                 dos.write(tmp, 0, 8);
  636.                 bb.writeTo(dos, null);
  637.             } finally {
  638.                 bb.destroy();
  639.             }
  640.         }
  641.         writeIndexChecksum = foot.digest();
  642.         os.write(writeIndexChecksum);
  643.         os.close();
  644.     }

  645.     /**
  646.      * Commit this change and release the lock.
  647.      * <p>
  648.      * If this method fails (returns false) the lock is still released.
  649.      *
  650.      * @return true if the commit was successful and the file contains the new
  651.      *         data; false if the commit failed and the file remains with the
  652.      *         old data.
  653.      * @throws java.lang.IllegalStateException
  654.      *             the lock is not held.
  655.      */
  656.     public boolean commit() {
  657.         final LockFile tmp = myLock;
  658.         requireLocked(tmp);
  659.         myLock = null;
  660.         if (!tmp.commit()) {
  661.             return false;
  662.         }
  663.         snapshot = tmp.getCommitSnapshot();
  664.         if (indexChangedListener != null
  665.                 && !Arrays.equals(readIndexChecksum, writeIndexChecksum)) {
  666.             indexChangedListener.onIndexChanged(new IndexChangedEvent(true));
  667.         }
  668.         return true;
  669.     }

  670.     private void requireLocked(LockFile tmp) {
  671.         if (liveFile == null)
  672.             throw new IllegalStateException(JGitText.get().dirCacheIsNotLocked);
  673.         if (tmp == null)
  674.             throw new IllegalStateException(MessageFormat.format(JGitText.get().dirCacheFileIsNotLocked
  675.                     , liveFile.getAbsolutePath()));
  676.     }

  677.     /**
  678.      * Unlock this file and abort this change.
  679.      * <p>
  680.      * The temporary file (if created) is deleted before returning.
  681.      */
  682.     public void unlock() {
  683.         final LockFile tmp = myLock;
  684.         if (tmp != null) {
  685.             myLock = null;
  686.             tmp.unlock();
  687.         }
  688.     }

  689.     /**
  690.      * Locate the position a path's entry is at in the index. For details refer
  691.      * to #findEntry(byte[], int).
  692.      *
  693.      * @param path
  694.      *            the path to search for.
  695.      * @return if &gt;= 0 then the return value is the position of the entry in
  696.      *         the index; pass to {@link #getEntry(int)} to obtain the entry
  697.      *         information. If &lt; 0 the entry does not exist in the index.
  698.      */
  699.     public int findEntry(String path) {
  700.         final byte[] p = Constants.encode(path);
  701.         return findEntry(p, p.length);
  702.     }

  703.     /**
  704.      * Locate the position a path's entry is at in the index.
  705.      * <p>
  706.      * If there is at least one entry in the index for this path the position of
  707.      * the lowest stage is returned. Subsequent stages can be identified by
  708.      * testing consecutive entries until the path differs.
  709.      * <p>
  710.      * If no path matches the entry -(position+1) is returned, where position is
  711.      * the location it would have gone within the index.
  712.      *
  713.      * @param p
  714.      *            the byte array starting with the path to search for.
  715.      * @param pLen
  716.      *            the length of the path in bytes
  717.      * @return if &gt;= 0 then the return value is the position of the entry in
  718.      *         the index; pass to {@link #getEntry(int)} to obtain the entry
  719.      *         information. If &lt; 0 the entry does not exist in the index.
  720.      * @since 3.4
  721.      */
  722.     public int findEntry(byte[] p, int pLen) {
  723.         return findEntry(0, p, pLen);
  724.     }

  725.     int findEntry(int low, byte[] p, int pLen) {
  726.         int high = entryCnt;
  727.         while (low < high) {
  728.             int mid = (low + high) >>> 1;
  729.             final int cmp = cmp(p, pLen, sortedEntries[mid]);
  730.             if (cmp < 0)
  731.                 high = mid;
  732.             else if (cmp == 0) {
  733.                 while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
  734.                     mid--;
  735.                 return mid;
  736.             } else
  737.                 low = mid + 1;
  738.         }
  739.         return -(low + 1);
  740.     }

  741.     /**
  742.      * Determine the next index position past all entries with the same name.
  743.      * <p>
  744.      * As index entries are sorted by path name, then stage number, this method
  745.      * advances the supplied position to the first position in the index whose
  746.      * path name does not match the path name of the supplied position's entry.
  747.      *
  748.      * @param position
  749.      *            entry position of the path that should be skipped.
  750.      * @return position of the next entry whose path is after the input.
  751.      */
  752.     public int nextEntry(int position) {
  753.         DirCacheEntry last = sortedEntries[position];
  754.         int nextIdx = position + 1;
  755.         while (nextIdx < entryCnt) {
  756.             final DirCacheEntry next = sortedEntries[nextIdx];
  757.             if (cmp(last, next) != 0)
  758.                 break;
  759.             last = next;
  760.             nextIdx++;
  761.         }
  762.         return nextIdx;
  763.     }

  764.     int nextEntry(byte[] p, int pLen, int nextIdx) {
  765.         while (nextIdx < entryCnt) {
  766.             final DirCacheEntry next = sortedEntries[nextIdx];
  767.             if (!DirCacheTree.peq(p, next.path, pLen))
  768.                 break;
  769.             nextIdx++;
  770.         }
  771.         return nextIdx;
  772.     }

  773.     /**
  774.      * Total number of file entries stored in the index.
  775.      * <p>
  776.      * This count includes unmerged stages for a file entry if the file is
  777.      * currently conflicted in a merge. This means the total number of entries
  778.      * in the index may be up to 3 times larger than the number of files in the
  779.      * working directory.
  780.      * <p>
  781.      * Note that this value counts only <i>files</i>.
  782.      *
  783.      * @return number of entries available.
  784.      * @see #getEntry(int)
  785.      */
  786.     public int getEntryCount() {
  787.         return entryCnt;
  788.     }

  789.     /**
  790.      * Get a specific entry.
  791.      *
  792.      * @param i
  793.      *            position of the entry to get.
  794.      * @return the entry at position <code>i</code>.
  795.      */
  796.     public DirCacheEntry getEntry(int i) {
  797.         return sortedEntries[i];
  798.     }

  799.     /**
  800.      * Get a specific entry.
  801.      *
  802.      * @param path
  803.      *            the path to search for.
  804.      * @return the entry for the given <code>path</code>.
  805.      */
  806.     public DirCacheEntry getEntry(String path) {
  807.         final int i = findEntry(path);
  808.         return i < 0 ? null : sortedEntries[i];
  809.     }

  810.     /**
  811.      * Recursively get all entries within a subtree.
  812.      *
  813.      * @param path
  814.      *            the subtree path to get all entries within.
  815.      * @return all entries recursively contained within the subtree.
  816.      */
  817.     public DirCacheEntry[] getEntriesWithin(String path) {
  818.         if (path.length() == 0) {
  819.             DirCacheEntry[] r = new DirCacheEntry[entryCnt];
  820.             System.arraycopy(sortedEntries, 0, r, 0, entryCnt);
  821.             return r;
  822.         }
  823.         if (!path.endsWith("/")) //$NON-NLS-1$
  824.             path += "/"; //$NON-NLS-1$
  825.         final byte[] p = Constants.encode(path);
  826.         final int pLen = p.length;

  827.         int eIdx = findEntry(p, pLen);
  828.         if (eIdx < 0)
  829.             eIdx = -(eIdx + 1);
  830.         final int lastIdx = nextEntry(p, pLen, eIdx);
  831.         final DirCacheEntry[] r = new DirCacheEntry[lastIdx - eIdx];
  832.         System.arraycopy(sortedEntries, eIdx, r, 0, r.length);
  833.         return r;
  834.     }

  835.     void toArray(final int i, final DirCacheEntry[] dst, final int off,
  836.             final int cnt) {
  837.         System.arraycopy(sortedEntries, i, dst, off, cnt);
  838.     }

  839.     /**
  840.      * Obtain (or build) the current cache tree structure.
  841.      * <p>
  842.      * This method can optionally recreate the cache tree, without flushing the
  843.      * tree objects themselves to disk.
  844.      *
  845.      * @param build
  846.      *            if true and the cache tree is not present in the index it will
  847.      *            be generated and returned to the caller.
  848.      * @return the cache tree; null if there is no current cache tree available
  849.      *         and <code>build</code> was false.
  850.      */
  851.     public DirCacheTree getCacheTree(boolean build) {
  852.         if (build) {
  853.             if (tree == null)
  854.                 tree = new DirCacheTree();
  855.             tree.validate(sortedEntries, entryCnt, 0, 0);
  856.         }
  857.         return tree;
  858.     }

  859.     /**
  860.      * Write all index trees to the object store, returning the root tree.
  861.      *
  862.      * @param ow
  863.      *            the writer to use when serializing to the store. The caller is
  864.      *            responsible for flushing the inserter before trying to use the
  865.      *            returned tree identity.
  866.      * @return identity for the root tree.
  867.      * @throws org.eclipse.jgit.errors.UnmergedPathException
  868.      *             one or more paths contain higher-order stages (stage &gt; 0),
  869.      *             which cannot be stored in a tree object.
  870.      * @throws java.lang.IllegalStateException
  871.      *             one or more paths contain an invalid mode which should never
  872.      *             appear in a tree object.
  873.      * @throws java.io.IOException
  874.      *             an unexpected error occurred writing to the object store.
  875.      */
  876.     public ObjectId writeTree(ObjectInserter ow)
  877.             throws UnmergedPathException, IOException {
  878.         return getCacheTree(true).writeTree(sortedEntries, 0, 0, ow);
  879.     }

  880.     /**
  881.      * Tells whether this index contains unmerged paths.
  882.      *
  883.      * @return {@code true} if this index contains unmerged paths. Means: at
  884.      *         least one entry is of a stage different from 0. {@code false}
  885.      *         will be returned if all entries are of stage 0.
  886.      */
  887.     public boolean hasUnmergedPaths() {
  888.         for (int i = 0; i < entryCnt; i++) {
  889.             if (sortedEntries[i].getStage() > 0) {
  890.                 return true;
  891.             }
  892.         }
  893.         return false;
  894.     }

  895.     private void registerIndexChangedListener(IndexChangedListener listener) {
  896.         this.indexChangedListener = listener;
  897.     }

  898.     /**
  899.      * Update any smudged entries with information from the working tree.
  900.      *
  901.      * @throws IOException
  902.      */
  903.     private void updateSmudgedEntries() throws IOException {
  904.         List<String> paths = new ArrayList<>(128);
  905.         try (TreeWalk walk = new TreeWalk(repository)) {
  906.             walk.setOperationType(OperationType.CHECKIN_OP);
  907.             for (int i = 0; i < entryCnt; i++)
  908.                 if (sortedEntries[i].isSmudged())
  909.                     paths.add(sortedEntries[i].getPathString());
  910.             if (paths.isEmpty())
  911.                 return;
  912.             walk.setFilter(PathFilterGroup.createFromStrings(paths));

  913.             DirCacheIterator iIter = new DirCacheIterator(this);
  914.             FileTreeIterator fIter = new FileTreeIterator(repository);
  915.             walk.addTree(iIter);
  916.             walk.addTree(fIter);
  917.             fIter.setDirCacheIterator(walk, 0);
  918.             walk.setRecursive(true);
  919.             while (walk.next()) {
  920.                 iIter = walk.getTree(0, DirCacheIterator.class);
  921.                 if (iIter == null)
  922.                     continue;
  923.                 fIter = walk.getTree(1, FileTreeIterator.class);
  924.                 if (fIter == null)
  925.                     continue;
  926.                 DirCacheEntry entry = iIter.getDirCacheEntry();
  927.                 if (entry.isSmudged() && iIter.idEqual(fIter)) {
  928.                     entry.setLength(fIter.getEntryLength());
  929.                     entry.setLastModified(fIter.getEntryLastModifiedInstant());
  930.                 }
  931.             }
  932.         }
  933.     }

  934.     enum DirCacheVersion implements ConfigEnum {

  935.         /** Minimum index version on-disk format that we support. */
  936.         DIRC_VERSION_MINIMUM(2),
  937.         /** Version 3 supports extended flags. */
  938.         DIRC_VERSION_EXTENDED(3),
  939.         /**
  940.          * Version 4 adds very simple "path compression": it strips out the
  941.          * common prefix between the last entry written and the current entry.
  942.          * Instead of writing two entries with paths "foo/bar/baz/a.txt" and
  943.          * "foo/bar/baz/b.txt" it only writes "b.txt" for the second entry.
  944.          * <p>
  945.          * It is also <em>not</em> padded.
  946.          * </p>
  947.          */
  948.         DIRC_VERSION_PATHCOMPRESS(4);

  949.         private final int version;

  950.         private DirCacheVersion(int versionCode) {
  951.             this.version = versionCode;
  952.         }

  953.         public int getVersionCode() {
  954.             return version;
  955.         }

  956.         @Override
  957.         public String toConfigValue() {
  958.             return Integer.toString(version);
  959.         }

  960.         @Override
  961.         public boolean matchConfigValue(String in) {
  962.             try {
  963.                 return version == Integer.parseInt(in);
  964.             } catch (NumberFormatException e) {
  965.                 return false;
  966.             }
  967.         }

  968.         public static DirCacheVersion fromInt(int val) {
  969.             for (DirCacheVersion v : DirCacheVersion.values()) {
  970.                 if (val == v.getVersionCode()) {
  971.                     return v;
  972.                 }
  973.             }
  974.             return null;
  975.         }
  976.     }

  977.     private static class DirCacheConfig {

  978.         private final DirCacheVersion indexVersion;

  979.         public DirCacheConfig(Config cfg) {
  980.             boolean manyFiles = cfg.getBoolean(
  981.                     ConfigConstants.CONFIG_FEATURE_SECTION,
  982.                     ConfigConstants.CONFIG_KEY_MANYFILES, false);
  983.             indexVersion = cfg.getEnum(DirCacheVersion.values(),
  984.                     ConfigConstants.CONFIG_INDEX_SECTION, null,
  985.                     ConfigConstants.CONFIG_KEY_VERSION,
  986.                     manyFiles ? DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
  987.                             : DirCacheVersion.DIRC_VERSION_EXTENDED);
  988.         }

  989.         public DirCacheVersion getIndexVersion() {
  990.             return indexVersion;
  991.         }

  992.     }
  993. }