You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DirCacheEntry.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  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. * Returns whether this entry should be skipped from the working tree.
  487. *
  488. * @return true if this entry should be skipepd.
  489. */
  490. public boolean isSkipWorkTree() {
  491. return (getExtendedFlags() & SKIP_WORKTREE) != 0;
  492. }
  493. /**
  494. * Returns whether this entry is intent to be added to the Index.
  495. *
  496. * @return true if this entry is intent to add.
  497. */
  498. public boolean isIntentToAdd() {
  499. return (getExtendedFlags() & INTENT_TO_ADD) != 0;
  500. }
  501. /**
  502. * Returns whether this entry is in the fully-merged stage (0).
  503. *
  504. * @return true if this entry is merged
  505. * @since 2.2
  506. */
  507. public boolean isMerged() {
  508. return getStage() == STAGE_0;
  509. }
  510. /**
  511. * Obtain the raw {@link org.eclipse.jgit.lib.FileMode} bits for this entry.
  512. *
  513. * @return mode bits for the entry.
  514. * @see FileMode#fromBits(int)
  515. */
  516. public int getRawMode() {
  517. return NB.decodeInt32(info, infoOffset + P_MODE);
  518. }
  519. /**
  520. * Obtain the {@link org.eclipse.jgit.lib.FileMode} for this entry.
  521. *
  522. * @return the file mode singleton for this entry.
  523. */
  524. public FileMode getFileMode() {
  525. return FileMode.fromBits(getRawMode());
  526. }
  527. /**
  528. * Set the file mode for this entry.
  529. *
  530. * @param mode
  531. * the new mode constant.
  532. * @throws java.lang.IllegalArgumentException
  533. * If {@code mode} is
  534. * {@link org.eclipse.jgit.lib.FileMode#MISSING},
  535. * {@link org.eclipse.jgit.lib.FileMode#TREE}, or any other type
  536. * code not permitted in a tree object.
  537. */
  538. public void setFileMode(FileMode mode) {
  539. switch (mode.getBits() & FileMode.TYPE_MASK) {
  540. case FileMode.TYPE_MISSING:
  541. case FileMode.TYPE_TREE:
  542. throw new IllegalArgumentException(MessageFormat.format(
  543. JGitText.get().invalidModeForPath, mode, getPathString()));
  544. }
  545. NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
  546. }
  547. void setFileMode(int mode) {
  548. NB.encodeInt32(info, infoOffset + P_MODE, mode);
  549. }
  550. /**
  551. * Get the cached creation time of this file, in milliseconds.
  552. *
  553. * @return cached creation time of this file, in milliseconds since the
  554. * Java epoch (midnight Jan 1, 1970 UTC).
  555. */
  556. public long getCreationTime() {
  557. return decodeTS(P_CTIME);
  558. }
  559. /**
  560. * Set the cached creation time of this file, using milliseconds.
  561. *
  562. * @param when
  563. * new cached creation time of the file, in milliseconds.
  564. */
  565. public void setCreationTime(long when) {
  566. encodeTS(P_CTIME, when);
  567. }
  568. /**
  569. * Get the cached last modification date of this file, in milliseconds.
  570. * <p>
  571. * One of the indicators that the file has been modified by an application
  572. * changing the working tree is if the last modification time for the file
  573. * differs from the time stored in this entry.
  574. *
  575. * @return last modification time of this file, in milliseconds since the
  576. * Java epoch (midnight Jan 1, 1970 UTC).
  577. * @deprecated use {@link #getLastModifiedInstant()} instead
  578. */
  579. @Deprecated
  580. public long getLastModified() {
  581. return decodeTS(P_MTIME);
  582. }
  583. /**
  584. * Get the cached last modification date of this file.
  585. * <p>
  586. * One of the indicators that the file has been modified by an application
  587. * changing the working tree is if the last modification time for the file
  588. * differs from the time stored in this entry.
  589. *
  590. * @return last modification time of this file.
  591. * @since 5.1.9
  592. */
  593. public Instant getLastModifiedInstant() {
  594. return decodeTSInstant(P_MTIME);
  595. }
  596. /**
  597. * Set the cached last modification date of this file, using milliseconds.
  598. *
  599. * @param when
  600. * new cached modification date of the file, in milliseconds.
  601. * @deprecated use {@link #setLastModified(Instant)} instead
  602. */
  603. @Deprecated
  604. public void setLastModified(long when) {
  605. encodeTS(P_MTIME, when);
  606. }
  607. /**
  608. * Set the cached last modification date of this file.
  609. *
  610. * @param when
  611. * new cached modification date of the file.
  612. * @since 5.1.9
  613. */
  614. public void setLastModified(Instant when) {
  615. encodeTS(P_MTIME, when);
  616. }
  617. /**
  618. * Get the cached size (mod 4 GB) (in bytes) of this file.
  619. * <p>
  620. * One of the indicators that the file has been modified by an application
  621. * changing the working tree is if the size of the file (in bytes) differs
  622. * from the size stored in this entry.
  623. * <p>
  624. * Note that this is the length of the file in the working directory, which
  625. * may differ from the size of the decompressed blob if work tree filters
  626. * are being used, such as LF&lt;-&gt;CRLF conversion.
  627. * <p>
  628. * Note also that for very large files, this is the size of the on-disk file
  629. * truncated to 32 bits, i.e. modulo 4294967296. If that value is larger
  630. * than 2GB, it will appear negative.
  631. *
  632. * @return cached size of the working directory file, in bytes.
  633. */
  634. public int getLength() {
  635. return NB.decodeInt32(info, infoOffset + P_SIZE);
  636. }
  637. /**
  638. * Set the cached size (in bytes) of this file.
  639. *
  640. * @param sz
  641. * new cached size of the file, as bytes. If the file is larger
  642. * than 2G, cast it to (int) before calling this method.
  643. */
  644. public void setLength(int sz) {
  645. NB.encodeInt32(info, infoOffset + P_SIZE, sz);
  646. }
  647. /**
  648. * Set the cached size (in bytes) of this file.
  649. *
  650. * @param sz
  651. * new cached size of the file, as bytes.
  652. */
  653. public void setLength(long sz) {
  654. setLength((int) sz);
  655. }
  656. /**
  657. * Obtain the ObjectId for the entry.
  658. * <p>
  659. * Using this method to compare ObjectId values between entries is
  660. * inefficient as it causes memory allocation.
  661. *
  662. * @return object identifier for the entry.
  663. */
  664. public ObjectId getObjectId() {
  665. return ObjectId.fromRaw(idBuffer(), idOffset());
  666. }
  667. /**
  668. * Set the ObjectId for the entry.
  669. *
  670. * @param id
  671. * new object identifier for the entry. May be
  672. * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to remove the
  673. * current identifier.
  674. */
  675. public void setObjectId(AnyObjectId id) {
  676. id.copyRawTo(idBuffer(), idOffset());
  677. }
  678. /**
  679. * Set the ObjectId for the entry from the raw binary representation.
  680. *
  681. * @param bs
  682. * the raw byte buffer to read from. At least 20 bytes after p
  683. * must be available within this byte array.
  684. * @param p
  685. * position to read the first byte of data from.
  686. */
  687. public void setObjectIdFromRaw(byte[] bs, int p) {
  688. final int n = Constants.OBJECT_ID_LENGTH;
  689. System.arraycopy(bs, p, idBuffer(), idOffset(), n);
  690. }
  691. /**
  692. * Get the entry's complete path.
  693. * <p>
  694. * This method is not very efficient and is primarily meant for debugging
  695. * and final output generation. Applications should try to avoid calling it,
  696. * and if invoked do so only once per interesting entry, where the name is
  697. * absolutely required for correct function.
  698. *
  699. * @return complete path of the entry, from the root of the repository. If
  700. * the entry is in a subtree there will be at least one '/' in the
  701. * returned string.
  702. */
  703. public String getPathString() {
  704. return toString(path);
  705. }
  706. /**
  707. * Get a copy of the entry's raw path bytes.
  708. *
  709. * @return raw path bytes.
  710. * @since 3.4
  711. */
  712. public byte[] getRawPath() {
  713. return path.clone();
  714. }
  715. /**
  716. * {@inheritDoc}
  717. * <p>
  718. * Use for debugging only !
  719. */
  720. @SuppressWarnings("nls")
  721. @Override
  722. public String toString() {
  723. return getFileMode() + " " + getLength() + " "
  724. + getLastModifiedInstant()
  725. + " " + getObjectId() + " " + getStage() + " "
  726. + getPathString() + "\n";
  727. }
  728. /**
  729. * Copy the ObjectId and other meta fields from an existing entry.
  730. * <p>
  731. * This method copies everything except the path from one entry to another,
  732. * supporting renaming.
  733. *
  734. * @param src
  735. * the entry to copy ObjectId and meta fields from.
  736. */
  737. public void copyMetaData(DirCacheEntry src) {
  738. copyMetaData(src, false);
  739. }
  740. /**
  741. * Copy the ObjectId and other meta fields from an existing entry.
  742. * <p>
  743. * This method copies everything except the path and possibly stage from one
  744. * entry to another, supporting renaming.
  745. *
  746. * @param src
  747. * the entry to copy ObjectId and meta fields from.
  748. * @param keepStage
  749. * if true, the stage attribute will not be copied
  750. */
  751. void copyMetaData(DirCacheEntry src, boolean keepStage) {
  752. int origflags = NB.decodeUInt16(info, infoOffset + P_FLAGS);
  753. int newflags = NB.decodeUInt16(src.info, src.infoOffset + P_FLAGS);
  754. System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
  755. final int pLen = origflags & NAME_MASK;
  756. final int SHIFTED_STAGE_MASK = 0x3 << 12;
  757. final int pStageShifted;
  758. if (keepStage)
  759. pStageShifted = origflags & SHIFTED_STAGE_MASK;
  760. else
  761. pStageShifted = newflags & SHIFTED_STAGE_MASK;
  762. NB.encodeInt16(info, infoOffset + P_FLAGS, pStageShifted | pLen
  763. | (newflags & ~NAME_MASK & ~SHIFTED_STAGE_MASK));
  764. }
  765. /**
  766. * @return true if the entry contains extended flags.
  767. */
  768. boolean isExtended() {
  769. return (info[infoOffset + P_FLAGS] & EXTENDED) != 0;
  770. }
  771. private long decodeTS(int pIdx) {
  772. final int base = infoOffset + pIdx;
  773. final int sec = NB.decodeInt32(info, base);
  774. final int ms = NB.decodeInt32(info, base + 4) / 1000000;
  775. return 1000L * sec + ms;
  776. }
  777. private Instant decodeTSInstant(int pIdx) {
  778. final int base = infoOffset + pIdx;
  779. final int sec = NB.decodeInt32(info, base);
  780. final int nano = NB.decodeInt32(info, base + 4);
  781. return Instant.ofEpochSecond(sec, nano);
  782. }
  783. private void encodeTS(int pIdx, long when) {
  784. final int base = infoOffset + pIdx;
  785. NB.encodeInt32(info, base, (int) (when / 1000));
  786. NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
  787. }
  788. private void encodeTS(int pIdx, Instant when) {
  789. final int base = infoOffset + pIdx;
  790. NB.encodeInt32(info, base, (int) when.getEpochSecond());
  791. NB.encodeInt32(info, base + 4, when.getNano());
  792. }
  793. private int getExtendedFlags() {
  794. if (isExtended()) {
  795. return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
  796. }
  797. return 0;
  798. }
  799. private static void checkPath(byte[] path) {
  800. try {
  801. SystemReader.getInstance().checkPath(path);
  802. } catch (CorruptObjectException e) {
  803. InvalidPathException p = new InvalidPathException(toString(path));
  804. p.initCause(e);
  805. throw p;
  806. }
  807. }
  808. static String toString(byte[] path) {
  809. return UTF_8.decode(ByteBuffer.wrap(path)).toString();
  810. }
  811. static int getMaximumInfoLength(boolean extended) {
  812. return extended ? INFO_LEN_EXTENDED : INFO_LEN;
  813. }
  814. }