DirCacheEntry.java

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

  13. package org.eclipse.jgit.dircache;

  14. import static java.nio.charset.StandardCharsets.UTF_8;

  15. import java.io.ByteArrayOutputStream;
  16. import java.io.EOFException;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.io.OutputStream;
  20. import java.nio.ByteBuffer;
  21. import java.security.MessageDigest;
  22. import java.text.MessageFormat;
  23. import java.time.Instant;
  24. import java.util.Arrays;

  25. import org.eclipse.jgit.dircache.DirCache.DirCacheVersion;
  26. import org.eclipse.jgit.errors.CorruptObjectException;
  27. import org.eclipse.jgit.internal.JGitText;
  28. import org.eclipse.jgit.lib.AnyObjectId;
  29. import org.eclipse.jgit.lib.Constants;
  30. import org.eclipse.jgit.lib.FileMode;
  31. import org.eclipse.jgit.lib.ObjectId;
  32. import org.eclipse.jgit.util.IO;
  33. import org.eclipse.jgit.util.MutableInteger;
  34. import org.eclipse.jgit.util.NB;
  35. import org.eclipse.jgit.util.SystemReader;

  36. /**
  37.  * A single file (or stage of a file) in a
  38.  * {@link org.eclipse.jgit.dircache.DirCache}.
  39.  * <p>
  40.  * An entry represents exactly one stage of a file. If a file path is unmerged
  41.  * then multiple DirCacheEntry instances may appear for the same path name.
  42.  */
  43. public class DirCacheEntry {
  44.     private static final byte[] nullpad = new byte[8];

  45.     /** The standard (fully merged) stage for an entry. */
  46.     public static final int STAGE_0 = 0;

  47.     /** The base tree revision for an entry. */
  48.     public static final int STAGE_1 = 1;

  49.     /** The first tree revision (usually called "ours"). */
  50.     public static final int STAGE_2 = 2;

  51.     /** The second tree revision (usually called "theirs"). */
  52.     public static final int STAGE_3 = 3;

  53.     private static final int P_CTIME = 0;

  54.     // private static final int P_CTIME_NSEC = 4;

  55.     private static final int P_MTIME = 8;

  56.     // private static final int P_MTIME_NSEC = 12;

  57.     // private static final int P_DEV = 16;

  58.     // private static final int P_INO = 20;

  59.     private static final int P_MODE = 24;

  60.     // private static final int P_UID = 28;

  61.     // private static final int P_GID = 32;

  62.     private static final int P_SIZE = 36;

  63.     private static final int P_OBJECTID = 40;

  64.     private static final int P_FLAGS = 60;
  65.     private static final int P_FLAGS2 = 62;

  66.     /** Mask applied to data in {@link #P_FLAGS} to get the name length. */
  67.     private static final int NAME_MASK = 0xfff;

  68.     private static final int INTENT_TO_ADD = 0x20000000;
  69.     private static final int SKIP_WORKTREE = 0x40000000;
  70.     private static final int EXTENDED_FLAGS = (INTENT_TO_ADD | SKIP_WORKTREE);

  71.     private static final int INFO_LEN = 62;
  72.     private static final int INFO_LEN_EXTENDED = 64;

  73.     private static final int EXTENDED = 0x40;
  74.     private static final int ASSUME_VALID = 0x80;

  75.     /** In-core flag signaling that the entry should be considered as modified. */
  76.     private static final int UPDATE_NEEDED = 0x1;

  77.     /** (Possibly shared) header information storage. */
  78.     private final byte[] info;

  79.     /** First location within {@link #info} where our header starts. */
  80.     private final int infoOffset;

  81.     /** Our encoded path name, from the root of the repository. */
  82.     final byte[] path;

  83.     /** Flags which are never stored to disk. */
  84.     private byte inCoreFlags;

  85.     DirCacheEntry(byte[] sharedInfo, MutableInteger infoAt, InputStream in,
  86.             MessageDigest md, Instant smudge, DirCacheVersion version,
  87.             DirCacheEntry previous)
  88.             throws IOException {
  89.         info = sharedInfo;
  90.         infoOffset = infoAt.value;

  91.         IO.readFully(in, info, infoOffset, INFO_LEN);

  92.         int len;
  93.         if (isExtended()) {
  94.             len = INFO_LEN_EXTENDED;
  95.             IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN);

  96.             if ((getExtendedFlags() & ~EXTENDED_FLAGS) != 0)
  97.                 throw new IOException(MessageFormat.format(JGitText.get()
  98.                         .DIRCUnrecognizedExtendedFlags, String.valueOf(getExtendedFlags())));
  99.         } else
  100.             len = INFO_LEN;

  101.         infoAt.value += len;
  102.         md.update(info, infoOffset, len);

  103.         int toRemove = 0;
  104.         if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
  105.             // Read variable int and update digest
  106.             int b = in.read();
  107.             md.update((byte) b);
  108.             toRemove = b & 0x7F;
  109.             while ((b & 0x80) != 0) {
  110.                 toRemove++;
  111.                 b = in.read();
  112.                 md.update((byte) b);
  113.                 toRemove = (toRemove << 7) | (b & 0x7F);
  114.             }
  115.             if (toRemove < 0
  116.                     || (previous != null && toRemove > previous.path.length)) {
  117.                 if (previous == null) {
  118.                     throw new IOException(MessageFormat.format(
  119.                             JGitText.get().DIRCCorruptLengthFirst,
  120.                             Integer.valueOf(toRemove)));
  121.                 }
  122.                 throw new IOException(MessageFormat.format(
  123.                         JGitText.get().DIRCCorruptLength,
  124.                         Integer.valueOf(toRemove), previous.getPathString()));
  125.             }
  126.         }
  127.         int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
  128.         int skipped = 0;
  129.         if (pathLen < NAME_MASK) {
  130.             path = new byte[pathLen];
  131.             if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
  132.                     && previous != null) {
  133.                 System.arraycopy(previous.path, 0, path, 0,
  134.                         previous.path.length - toRemove);
  135.                 IO.readFully(in, path, previous.path.length - toRemove,
  136.                         pathLen - (previous.path.length - toRemove));
  137.                 md.update(path, previous.path.length - toRemove,
  138.                         pathLen - (previous.path.length - toRemove));
  139.                 pathLen = pathLen - (previous.path.length - toRemove);
  140.             } else {
  141.                 IO.readFully(in, path, 0, pathLen);
  142.                 md.update(path, 0, pathLen);
  143.             }
  144.         } else if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
  145.                 || previous == null || toRemove == previous.path.length) {
  146.             ByteArrayOutputStream tmp = new ByteArrayOutputStream();
  147.             byte[] buf = new byte[NAME_MASK];
  148.             IO.readFully(in, buf, 0, NAME_MASK);
  149.             tmp.write(buf);
  150.             readNulTerminatedString(in, tmp);
  151.             path = tmp.toByteArray();
  152.             pathLen = path.length;
  153.             md.update(path, 0, pathLen);
  154.             skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString
  155.             md.update((byte) 0);
  156.         } else {
  157.             ByteArrayOutputStream tmp = new ByteArrayOutputStream();
  158.             tmp.write(previous.path, 0, previous.path.length - toRemove);
  159.             pathLen = readNulTerminatedString(in, tmp);
  160.             path = tmp.toByteArray();
  161.             md.update(path, previous.path.length - toRemove, pathLen);
  162.             skipped = 1; // we already skipped 1 '\0' in readNulTerminatedString
  163.             md.update((byte) 0);
  164.         }

  165.         try {
  166.             checkPath(path);
  167.         } catch (InvalidPathException e) {
  168.             CorruptObjectException p =
  169.                 new CorruptObjectException(e.getMessage());
  170.             if (e.getCause() != null)
  171.                 p.initCause(e.getCause());
  172.             throw p;
  173.         }

  174.         if (version == DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
  175.             if (skipped == 0) {
  176.                 int b = in.read();
  177.                 if (b < 0) {
  178.                     throw new EOFException(JGitText.get().shortReadOfBlock);
  179.                 }
  180.                 md.update((byte) b);
  181.             }
  182.         } else {
  183.             // Index records are padded out to the next 8 byte alignment
  184.             // for historical reasons related to how C Git read the files.
  185.             //
  186.             final int actLen = len + pathLen;
  187.             final int expLen = (actLen + 8) & ~7;
  188.             final int padLen = expLen - actLen - skipped;
  189.             if (padLen > 0) {
  190.                 IO.skipFully(in, padLen);
  191.                 md.update(nullpad, 0, padLen);
  192.             }
  193.         }
  194.         if (mightBeRacilyClean(smudge)) {
  195.             smudgeRacilyClean();
  196.         }
  197.     }

  198.     /**
  199.      * Create an empty entry at stage 0.
  200.      *
  201.      * @param newPath
  202.      *            name of the cache entry.
  203.      * @throws java.lang.IllegalArgumentException
  204.      *             If the path starts or ends with "/", or contains "//" either
  205.      *             "\0". These sequences are not permitted in a git tree object
  206.      *             or DirCache file.
  207.      */
  208.     public DirCacheEntry(String newPath) {
  209.         this(Constants.encode(newPath), STAGE_0);
  210.     }

  211.     /**
  212.      * Create an empty entry at the specified stage.
  213.      *
  214.      * @param newPath
  215.      *            name of the cache entry.
  216.      * @param stage
  217.      *            the stage index of the new entry.
  218.      * @throws java.lang.IllegalArgumentException
  219.      *             If the path starts or ends with "/", or contains "//" either
  220.      *             "\0". These sequences are not permitted in a git tree object
  221.      *             or DirCache file.  Or if {@code stage} is outside of the
  222.      *             range 0..3, inclusive.
  223.      */
  224.     public DirCacheEntry(String newPath, int stage) {
  225.         this(Constants.encode(newPath), stage);
  226.     }

  227.     /**
  228.      * Create an empty entry at stage 0.
  229.      *
  230.      * @param newPath
  231.      *            name of the cache entry, in the standard encoding.
  232.      * @throws java.lang.IllegalArgumentException
  233.      *             If the path starts or ends with "/", or contains "//" either
  234.      *             "\0". These sequences are not permitted in a git tree object
  235.      *             or DirCache file.
  236.      */
  237.     public DirCacheEntry(byte[] newPath) {
  238.         this(newPath, STAGE_0);
  239.     }

  240.     /**
  241.      * Create an empty entry at the specified stage.
  242.      *
  243.      * @param path
  244.      *            name of the cache entry, in the standard encoding.
  245.      * @param stage
  246.      *            the stage index of the new entry.
  247.      * @throws java.lang.IllegalArgumentException
  248.      *             If the path starts or ends with "/", or contains "//" either
  249.      *             "\0". These sequences are not permitted in a git tree object
  250.      *             or DirCache file.  Or if {@code stage} is outside of the
  251.      *             range 0..3, inclusive.
  252.      */
  253.     @SuppressWarnings("boxing")
  254.     public DirCacheEntry(byte[] path, int stage) {
  255.         checkPath(path);
  256.         if (stage < 0 || 3 < stage)
  257.             throw new IllegalArgumentException(MessageFormat.format(
  258.                     JGitText.get().invalidStageForPath,
  259.                     stage, toString(path)));

  260.         info = new byte[INFO_LEN];
  261.         infoOffset = 0;
  262.         this.path = path;

  263.         int flags = ((stage & 0x3) << 12);
  264.         if (path.length < NAME_MASK)
  265.             flags |= path.length;
  266.         else
  267.             flags |= NAME_MASK;
  268.         NB.encodeInt16(info, infoOffset + P_FLAGS, flags);
  269.     }

  270.     /**
  271.      * Duplicate DirCacheEntry with same path and copied info.
  272.      * <p>
  273.      * The same path buffer is reused (avoiding copying), however a new info
  274.      * buffer is created and its contents are copied.
  275.      *
  276.      * @param src
  277.      *            entry to clone.
  278.      * @since 4.2
  279.      */
  280.     public DirCacheEntry(DirCacheEntry src) {
  281.         path = src.path;
  282.         info = new byte[INFO_LEN];
  283.         infoOffset = 0;
  284.         System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN);
  285.     }

  286.     private int readNulTerminatedString(InputStream in, OutputStream out)
  287.             throws IOException {
  288.         int n = 0;
  289.         for (;;) {
  290.             int c = in.read();
  291.             if (c < 0) {
  292.                 throw new EOFException(JGitText.get().shortReadOfBlock);
  293.             }
  294.             if (c == 0) {
  295.                 break;
  296.             }
  297.             out.write(c);
  298.             n++;
  299.         }
  300.         return n;
  301.     }

  302.     void write(OutputStream os, DirCacheVersion version, DirCacheEntry previous)
  303.             throws IOException {
  304.         final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
  305.         if (version != DirCacheVersion.DIRC_VERSION_PATHCOMPRESS) {
  306.             os.write(info, infoOffset, len);
  307.             os.write(path, 0, path.length);
  308.             // Index records are padded out to the next 8 byte alignment
  309.             // for historical reasons related to how C Git read the files.
  310.             //
  311.             int entryLen = len + path.length;
  312.             int expLen = (entryLen + 8) & ~7;
  313.             if (entryLen != expLen)
  314.                 os.write(nullpad, 0, expLen - entryLen);
  315.         } else {
  316.             int pathCommon = 0;
  317.             int toRemove;
  318.             if (previous != null) {
  319.                 // Figure out common prefix
  320.                 int pathLen = Math.min(path.length, previous.path.length);
  321.                 while (pathCommon < pathLen
  322.                         && path[pathCommon] == previous.path[pathCommon]) {
  323.                     pathCommon++;
  324.                 }
  325.                 toRemove = previous.path.length - pathCommon;
  326.             } else {
  327.                 toRemove = 0;
  328.             }
  329.             byte[] tmp = new byte[16];
  330.             int n = tmp.length;
  331.             tmp[--n] = (byte) (toRemove & 0x7F);
  332.             while ((toRemove >>>= 7) != 0) {
  333.                 tmp[--n] = (byte) (0x80 | (--toRemove & 0x7F));
  334.             }
  335.             os.write(info, infoOffset, len);
  336.             os.write(tmp, n, tmp.length - n);
  337.             os.write(path, pathCommon, path.length - pathCommon);
  338.             os.write(0);
  339.         }
  340.     }

  341.     /**
  342.      * Is it possible for this entry to be accidentally assumed clean?
  343.      * <p>
  344.      * The "racy git" problem happens when a work file can be updated faster
  345.      * than the filesystem records file modification timestamps. It is possible
  346.      * for an application to edit a work file, update the index, then edit it
  347.      * again before the filesystem will give the work file a new modification
  348.      * timestamp. This method tests to see if file was written out at the same
  349.      * time as the index.
  350.      *
  351.      * @param smudge_s
  352.      *            seconds component of the index's last modified time.
  353.      * @param smudge_ns
  354.      *            nanoseconds component of the index's last modified time.
  355.      * @return true if extra careful checks should be used.
  356.      * @deprecated use {@link #mightBeRacilyClean(Instant)} instead
  357.      */
  358.     @Deprecated
  359.     public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) {
  360.         return mightBeRacilyClean(Instant.ofEpochSecond(smudge_s, smudge_ns));
  361.     }

  362.     /**
  363.      * Is it possible for this entry to be accidentally assumed clean?
  364.      * <p>
  365.      * The "racy git" problem happens when a work file can be updated faster
  366.      * than the filesystem records file modification timestamps. It is possible
  367.      * for an application to edit a work file, update the index, then edit it
  368.      * again before the filesystem will give the work file a new modification
  369.      * timestamp. This method tests to see if file was written out at the same
  370.      * time as the index.
  371.      *
  372.      * @param smudge
  373.      *            index's last modified time.
  374.      * @return true if extra careful checks should be used.
  375.      * @since 5.1.9
  376.      */
  377.     public final boolean mightBeRacilyClean(Instant smudge) {
  378.         // If the index has a modification time then it came from disk
  379.         // and was not generated from scratch in memory. In such cases
  380.         // the entry is 'racily clean' if the entry's cached modification
  381.         // time is equal to or later than the index modification time. In
  382.         // such cases the work file is too close to the index to tell if
  383.         // it is clean or not based on the modification time alone.
  384.         //
  385.         final int base = infoOffset + P_MTIME;
  386.         final int mtime = NB.decodeInt32(info, base);
  387.         if ((int) smudge.getEpochSecond() == mtime) {
  388.             return smudge.getNano() <= NB.decodeInt32(info, base + 4);
  389.         }
  390.         return false;
  391.     }

  392.     /**
  393.      * Force this entry to no longer match its working tree file.
  394.      * <p>
  395.      * This avoids the "racy git" problem by making this index entry no longer
  396.      * match the file in the working directory. Later git will be forced to
  397.      * compare the file content to ensure the file matches the working tree.
  398.      */
  399.     public final void smudgeRacilyClean() {
  400.         // To mark an entry racily clean we set its length to 0 (like native git
  401.         // does). Entries which are not racily clean and have zero length can be
  402.         // distinguished from racily clean entries by checking P_OBJECTID
  403.         // against the SHA1 of empty content. When length is 0 and P_OBJECTID is
  404.         // different from SHA1 of empty content we know the entry is marked
  405.         // racily clean
  406.         final int base = infoOffset + P_SIZE;
  407.         Arrays.fill(info, base, base + 4, (byte) 0);
  408.     }

  409.     /**
  410.      * Check whether this entry has been smudged or not
  411.      * <p>
  412.      * If a blob has length 0 we know its id, see
  413.      * {@link org.eclipse.jgit.lib.Constants#EMPTY_BLOB_ID}. If an entry has
  414.      * length 0 and an ID different from the one for empty blob we know this
  415.      * entry was smudged.
  416.      *
  417.      * @return <code>true</code> if the entry is smudged, <code>false</code>
  418.      *         otherwise
  419.      */
  420.     public final boolean isSmudged() {
  421.         final int base = infoOffset + P_OBJECTID;
  422.         return (getLength() == 0) && (Constants.EMPTY_BLOB_ID.compareTo(info, base) != 0);
  423.     }

  424.     final byte[] idBuffer() {
  425.         return info;
  426.     }

  427.     final int idOffset() {
  428.         return infoOffset + P_OBJECTID;
  429.     }

  430.     /**
  431.      * Is this entry always thought to be unmodified?
  432.      * <p>
  433.      * Most entries in the index do not have this flag set. Users may however
  434.      * set them on if the file system stat() costs are too high on this working
  435.      * directory, such as on NFS or SMB volumes.
  436.      *
  437.      * @return true if we must assume the entry is unmodified.
  438.      */
  439.     public boolean isAssumeValid() {
  440.         return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0;
  441.     }

  442.     /**
  443.      * Set the assume valid flag for this entry,
  444.      *
  445.      * @param assume
  446.      *            true to ignore apparent modifications; false to look at last
  447.      *            modified to detect file modifications.
  448.      */
  449.     public void setAssumeValid(boolean assume) {
  450.         if (assume)
  451.             info[infoOffset + P_FLAGS] |= (byte) ASSUME_VALID;
  452.         else
  453.             info[infoOffset + P_FLAGS] &= (byte) ~ASSUME_VALID;
  454.     }

  455.     /**
  456.      * Whether this entry should be checked for changes
  457.      *
  458.      * @return {@code true} if this entry should be checked for changes
  459.      */
  460.     public boolean isUpdateNeeded() {
  461.         return (inCoreFlags & UPDATE_NEEDED) != 0;
  462.     }

  463.     /**
  464.      * Set whether this entry must be checked for changes
  465.      *
  466.      * @param updateNeeded
  467.      *            whether this entry must be checked for changes
  468.      */
  469.     public void setUpdateNeeded(boolean updateNeeded) {
  470.         if (updateNeeded)
  471.             inCoreFlags |= (byte) UPDATE_NEEDED;
  472.         else
  473.             inCoreFlags &= (byte) ~UPDATE_NEEDED;
  474.     }

  475.     /**
  476.      * Get the stage of this entry.
  477.      * <p>
  478.      * Entries have one of 4 possible stages: 0-3.
  479.      *
  480.      * @return the stage of this entry.
  481.      */
  482.     public int getStage() {
  483.         return (info[infoOffset + P_FLAGS] >>> 4) & 0x3;
  484.     }

  485.     /**
  486.      * Sets the stage of an entry.
  487.      *
  488.      * @param stage
  489.      *            to set, in the range [0..3]
  490.      * @throws IllegalArgumentException
  491.      *             if the stage is outside the range [0..3]
  492.      * @since 5.10
  493.      */
  494.     public void setStage(int stage) {
  495.         if ((stage & ~0x3) != 0) {
  496.             throw new IllegalArgumentException(
  497.                     "Invalid stage, must be in range [0..3]"); //$NON-NLS-1$
  498.         }
  499.         byte flags = info[infoOffset + P_FLAGS];
  500.         info[infoOffset + P_FLAGS] = (byte) ((flags & 0xCF) | (stage << 4));
  501.     }

  502.     /**
  503.      * Returns whether this entry should be skipped from the working tree.
  504.      *
  505.      * @return true if this entry should be skipepd.
  506.      */
  507.     public boolean isSkipWorkTree() {
  508.         return (getExtendedFlags() & SKIP_WORKTREE) != 0;
  509.     }

  510.     /**
  511.      * Returns whether this entry is intent to be added to the Index.
  512.      *
  513.      * @return true if this entry is intent to add.
  514.      */
  515.     public boolean isIntentToAdd() {
  516.         return (getExtendedFlags() & INTENT_TO_ADD) != 0;
  517.     }

  518.     /**
  519.      * Returns whether this entry is in the fully-merged stage (0).
  520.      *
  521.      * @return true if this entry is merged
  522.      * @since 2.2
  523.      */
  524.     public boolean isMerged() {
  525.         return getStage() == STAGE_0;
  526.     }

  527.     /**
  528.      * Obtain the raw {@link org.eclipse.jgit.lib.FileMode} bits for this entry.
  529.      *
  530.      * @return mode bits for the entry.
  531.      * @see FileMode#fromBits(int)
  532.      */
  533.     public int getRawMode() {
  534.         return NB.decodeInt32(info, infoOffset + P_MODE);
  535.     }

  536.     /**
  537.      * Obtain the {@link org.eclipse.jgit.lib.FileMode} for this entry.
  538.      *
  539.      * @return the file mode singleton for this entry.
  540.      */
  541.     public FileMode getFileMode() {
  542.         return FileMode.fromBits(getRawMode());
  543.     }

  544.     /**
  545.      * Set the file mode for this entry.
  546.      *
  547.      * @param mode
  548.      *            the new mode constant.
  549.      * @throws java.lang.IllegalArgumentException
  550.      *             If {@code mode} is
  551.      *             {@link org.eclipse.jgit.lib.FileMode#MISSING},
  552.      *             {@link org.eclipse.jgit.lib.FileMode#TREE}, or any other type
  553.      *             code not permitted in a tree object.
  554.      */
  555.     public void setFileMode(FileMode mode) {
  556.         switch (mode.getBits() & FileMode.TYPE_MASK) {
  557.         case FileMode.TYPE_MISSING:
  558.         case FileMode.TYPE_TREE:
  559.             throw new IllegalArgumentException(MessageFormat.format(
  560.                     JGitText.get().invalidModeForPath, mode, getPathString()));
  561.         }
  562.         NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
  563.     }

  564.     void setFileMode(int mode) {
  565.         NB.encodeInt32(info, infoOffset + P_MODE, mode);
  566.     }

  567.     /**
  568.      * Get the cached creation time of this file, in milliseconds.
  569.      *
  570.      * @return cached creation time of this file, in milliseconds since the
  571.      *         Java epoch (midnight Jan 1, 1970 UTC).
  572.      */
  573.     public long getCreationTime() {
  574.         return decodeTS(P_CTIME);
  575.     }

  576.     /**
  577.      * Set the cached creation time of this file, using milliseconds.
  578.      *
  579.      * @param when
  580.      *            new cached creation time of the file, in milliseconds.
  581.      */
  582.     public void setCreationTime(long when) {
  583.         encodeTS(P_CTIME, when);
  584.     }

  585.     /**
  586.      * Get the cached last modification date of this file, in milliseconds.
  587.      * <p>
  588.      * One of the indicators that the file has been modified by an application
  589.      * changing the working tree is if the last modification time for the file
  590.      * differs from the time stored in this entry.
  591.      *
  592.      * @return last modification time of this file, in milliseconds since the
  593.      *         Java epoch (midnight Jan 1, 1970 UTC).
  594.      * @deprecated use {@link #getLastModifiedInstant()} instead
  595.      */
  596.     @Deprecated
  597.     public long getLastModified() {
  598.         return decodeTS(P_MTIME);
  599.     }

  600.     /**
  601.      * Get the cached last modification date of this file.
  602.      * <p>
  603.      * One of the indicators that the file has been modified by an application
  604.      * changing the working tree is if the last modification time for the file
  605.      * differs from the time stored in this entry.
  606.      *
  607.      * @return last modification time of this file.
  608.      * @since 5.1.9
  609.      */
  610.     public Instant getLastModifiedInstant() {
  611.         return decodeTSInstant(P_MTIME);
  612.     }

  613.     /**
  614.      * Set the cached last modification date of this file, using milliseconds.
  615.      *
  616.      * @param when
  617.      *            new cached modification date of the file, in milliseconds.
  618.      * @deprecated use {@link #setLastModified(Instant)} instead
  619.      */
  620.     @Deprecated
  621.     public void setLastModified(long when) {
  622.         encodeTS(P_MTIME, when);
  623.     }

  624.     /**
  625.      * Set the cached last modification date of this file.
  626.      *
  627.      * @param when
  628.      *            new cached modification date of the file.
  629.      * @since 5.1.9
  630.      */
  631.     public void setLastModified(Instant when) {
  632.         encodeTS(P_MTIME, when);
  633.     }

  634.     /**
  635.      * Get the cached size (mod 4 GB) (in bytes) of this file.
  636.      * <p>
  637.      * One of the indicators that the file has been modified by an application
  638.      * changing the working tree is if the size of the file (in bytes) differs
  639.      * from the size stored in this entry.
  640.      * <p>
  641.      * Note that this is the length of the file in the working directory, which
  642.      * may differ from the size of the decompressed blob if work tree filters
  643.      * are being used, such as LF&lt;-&gt;CRLF conversion.
  644.      * <p>
  645.      * Note also that for very large files, this is the size of the on-disk file
  646.      * truncated to 32 bits, i.e. modulo 4294967296. If that value is larger
  647.      * than 2GB, it will appear negative.
  648.      *
  649.      * @return cached size of the working directory file, in bytes.
  650.      */
  651.     public int getLength() {
  652.         return NB.decodeInt32(info, infoOffset + P_SIZE);
  653.     }

  654.     /**
  655.      * Set the cached size (in bytes) of this file.
  656.      *
  657.      * @param sz
  658.      *            new cached size of the file, as bytes. If the file is larger
  659.      *            than 2G, cast it to (int) before calling this method.
  660.      */
  661.     public void setLength(int sz) {
  662.         NB.encodeInt32(info, infoOffset + P_SIZE, sz);
  663.     }

  664.     /**
  665.      * Set the cached size (in bytes) of this file.
  666.      *
  667.      * @param sz
  668.      *            new cached size of the file, as bytes.
  669.      */
  670.     public void setLength(long sz) {
  671.         setLength((int) sz);
  672.     }

  673.     /**
  674.      * Obtain the ObjectId for the entry.
  675.      * <p>
  676.      * Using this method to compare ObjectId values between entries is
  677.      * inefficient as it causes memory allocation.
  678.      *
  679.      * @return object identifier for the entry.
  680.      */
  681.     public ObjectId getObjectId() {
  682.         return ObjectId.fromRaw(idBuffer(), idOffset());
  683.     }

  684.     /**
  685.      * Set the ObjectId for the entry.
  686.      *
  687.      * @param id
  688.      *            new object identifier for the entry. May be
  689.      *            {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to remove the
  690.      *            current identifier.
  691.      */
  692.     public void setObjectId(AnyObjectId id) {
  693.         id.copyRawTo(idBuffer(), idOffset());
  694.     }

  695.     /**
  696.      * Set the ObjectId for the entry from the raw binary representation.
  697.      *
  698.      * @param bs
  699.      *            the raw byte buffer to read from. At least 20 bytes after p
  700.      *            must be available within this byte array.
  701.      * @param p
  702.      *            position to read the first byte of data from.
  703.      */
  704.     public void setObjectIdFromRaw(byte[] bs, int p) {
  705.         final int n = Constants.OBJECT_ID_LENGTH;
  706.         System.arraycopy(bs, p, idBuffer(), idOffset(), n);
  707.     }

  708.     /**
  709.      * Get the entry's complete path.
  710.      * <p>
  711.      * This method is not very efficient and is primarily meant for debugging
  712.      * and final output generation. Applications should try to avoid calling it,
  713.      * and if invoked do so only once per interesting entry, where the name is
  714.      * absolutely required for correct function.
  715.      *
  716.      * @return complete path of the entry, from the root of the repository. If
  717.      *         the entry is in a subtree there will be at least one '/' in the
  718.      *         returned string.
  719.      */
  720.     public String getPathString() {
  721.         return toString(path);
  722.     }

  723.     /**
  724.      * Get a copy of the entry's raw path bytes.
  725.      *
  726.      * @return raw path bytes.
  727.      * @since 3.4
  728.      */
  729.     public byte[] getRawPath() {
  730.         return path.clone();
  731.     }

  732.     /**
  733.      * {@inheritDoc}
  734.      * <p>
  735.      * Use for debugging only !
  736.      */
  737.     @SuppressWarnings("nls")
  738.     @Override
  739.     public String toString() {
  740.         return getFileMode() + " " + getLength() + " "
  741.                 + getLastModifiedInstant()
  742.                 + " " + getObjectId() + " " + getStage() + " "
  743.                 + getPathString() + "\n";
  744.     }

  745.     /**
  746.      * Copy the ObjectId and other meta fields from an existing entry.
  747.      * <p>
  748.      * This method copies everything except the path from one entry to another,
  749.      * supporting renaming.
  750.      *
  751.      * @param src
  752.      *            the entry to copy ObjectId and meta fields from.
  753.      */
  754.     public void copyMetaData(DirCacheEntry src) {
  755.         copyMetaData(src, false);
  756.     }

  757.     /**
  758.      * Copy the ObjectId and other meta fields from an existing entry.
  759.      * <p>
  760.      * This method copies everything except the path and possibly stage from one
  761.      * entry to another, supporting renaming.
  762.      *
  763.      * @param src
  764.      *            the entry to copy ObjectId and meta fields from.
  765.      * @param keepStage
  766.      *            if true, the stage attribute will not be copied
  767.      */
  768.     void copyMetaData(DirCacheEntry src, boolean keepStage) {
  769.         int origflags = NB.decodeUInt16(info, infoOffset + P_FLAGS);
  770.         int newflags = NB.decodeUInt16(src.info, src.infoOffset + P_FLAGS);
  771.         System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
  772.         final int pLen = origflags & NAME_MASK;
  773.         final int SHIFTED_STAGE_MASK = 0x3 << 12;
  774.         final int pStageShifted;
  775.         if (keepStage)
  776.             pStageShifted = origflags & SHIFTED_STAGE_MASK;
  777.         else
  778.             pStageShifted = newflags & SHIFTED_STAGE_MASK;
  779.         NB.encodeInt16(info, infoOffset + P_FLAGS, pStageShifted | pLen
  780.                 | (newflags & ~NAME_MASK & ~SHIFTED_STAGE_MASK));
  781.     }

  782.     /**
  783.      * @return true if the entry contains extended flags.
  784.      */
  785.     boolean isExtended() {
  786.         return (info[infoOffset + P_FLAGS] & EXTENDED) != 0;
  787.     }

  788.     private long decodeTS(int pIdx) {
  789.         final int base = infoOffset + pIdx;
  790.         final int sec = NB.decodeInt32(info, base);
  791.         final int ms = NB.decodeInt32(info, base + 4) / 1000000;
  792.         return 1000L * sec + ms;
  793.     }

  794.     private Instant decodeTSInstant(int pIdx) {
  795.         final int base = infoOffset + pIdx;
  796.         final int sec = NB.decodeInt32(info, base);
  797.         final int nano = NB.decodeInt32(info, base + 4);
  798.         return Instant.ofEpochSecond(sec, nano);
  799.     }

  800.     private void encodeTS(int pIdx, long when) {
  801.         final int base = infoOffset + pIdx;
  802.         NB.encodeInt32(info, base, (int) (when / 1000));
  803.         NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
  804.     }

  805.     private void encodeTS(int pIdx, Instant when) {
  806.         final int base = infoOffset + pIdx;
  807.         NB.encodeInt32(info, base, (int) when.getEpochSecond());
  808.         NB.encodeInt32(info, base + 4, when.getNano());
  809.     }

  810.     private int getExtendedFlags() {
  811.         if (isExtended()) {
  812.             return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
  813.         }
  814.         return 0;
  815.     }

  816.     private static void checkPath(byte[] path) {
  817.         try {
  818.             SystemReader.getInstance().checkPath(path);
  819.         } catch (CorruptObjectException e) {
  820.             InvalidPathException p = new InvalidPathException(toString(path));
  821.             p.initCause(e);
  822.             throw p;
  823.         }
  824.     }

  825.     static String toString(byte[] path) {
  826.         return UTF_8.decode(ByteBuffer.wrap(path)).toString();
  827.     }

  828.     static int getMaximumInfoLength(boolean extended) {
  829.         return extended ? INFO_LEN_EXTENDED : INFO_LEN;
  830.     }
  831. }