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.

WorkingTreeIterator.java 50KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  3. * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  4. * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
  5. * Copyright (C) 2012-2013, Robin Rosenberg
  6. * and other copyright owners as documented in the project's IP log.
  7. *
  8. * This program and the accompanying materials are made available
  9. * under the terms of the Eclipse Distribution License v1.0 which
  10. * accompanies this distribution, is reproduced below, and is
  11. * available at http://www.eclipse.org/org/documents/edl-v10.php
  12. *
  13. * All rights reserved.
  14. *
  15. * Redistribution and use in source and binary forms, with or
  16. * without modification, are permitted provided that the following
  17. * conditions are met:
  18. *
  19. * - Redistributions of source code must retain the above copyright
  20. * notice, this list of conditions and the following disclaimer.
  21. *
  22. * - Redistributions in binary form must reproduce the above
  23. * copyright notice, this list of conditions and the following
  24. * disclaimer in the documentation and/or other materials provided
  25. * with the distribution.
  26. *
  27. * - Neither the name of the Eclipse Foundation, Inc. nor the
  28. * names of its contributors may be used to endorse or promote
  29. * products derived from this software without specific prior
  30. * written permission.
  31. *
  32. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  33. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  34. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  36. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  37. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  39. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  40. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  41. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  42. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  43. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  44. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45. */
  46. package org.eclipse.jgit.treewalk;
  47. import static java.nio.charset.StandardCharsets.UTF_8;
  48. import java.io.ByteArrayInputStream;
  49. import java.io.File;
  50. import java.io.FileInputStream;
  51. import java.io.FileNotFoundException;
  52. import java.io.IOException;
  53. import java.io.InputStream;
  54. import java.nio.ByteBuffer;
  55. import java.nio.CharBuffer;
  56. import java.nio.charset.CharacterCodingException;
  57. import java.nio.charset.CharsetEncoder;
  58. import java.text.MessageFormat;
  59. import java.time.Instant;
  60. import java.util.Arrays;
  61. import java.util.Collections;
  62. import java.util.Comparator;
  63. import java.util.HashMap;
  64. import java.util.Map;
  65. import org.eclipse.jgit.api.errors.FilterFailedException;
  66. import org.eclipse.jgit.attributes.AttributesNode;
  67. import org.eclipse.jgit.attributes.AttributesRule;
  68. import org.eclipse.jgit.attributes.FilterCommand;
  69. import org.eclipse.jgit.attributes.FilterCommandRegistry;
  70. import org.eclipse.jgit.diff.RawText;
  71. import org.eclipse.jgit.dircache.DirCacheEntry;
  72. import org.eclipse.jgit.dircache.DirCacheIterator;
  73. import org.eclipse.jgit.errors.CorruptObjectException;
  74. import org.eclipse.jgit.errors.LargeObjectException;
  75. import org.eclipse.jgit.errors.MissingObjectException;
  76. import org.eclipse.jgit.errors.NoWorkTreeException;
  77. import org.eclipse.jgit.ignore.FastIgnoreRule;
  78. import org.eclipse.jgit.ignore.IgnoreNode;
  79. import org.eclipse.jgit.internal.JGitText;
  80. import org.eclipse.jgit.lib.Constants;
  81. import org.eclipse.jgit.lib.CoreConfig;
  82. import org.eclipse.jgit.lib.CoreConfig.CheckStat;
  83. import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
  84. import org.eclipse.jgit.lib.CoreConfig.SymLinks;
  85. import org.eclipse.jgit.lib.FileMode;
  86. import org.eclipse.jgit.lib.ObjectId;
  87. import org.eclipse.jgit.lib.ObjectLoader;
  88. import org.eclipse.jgit.lib.ObjectReader;
  89. import org.eclipse.jgit.lib.Repository;
  90. import org.eclipse.jgit.submodule.SubmoduleWalk;
  91. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  92. import org.eclipse.jgit.util.FS;
  93. import org.eclipse.jgit.util.FS.ExecutionResult;
  94. import org.eclipse.jgit.util.Holder;
  95. import org.eclipse.jgit.util.IO;
  96. import org.eclipse.jgit.util.Paths;
  97. import org.eclipse.jgit.util.RawParseUtils;
  98. import org.eclipse.jgit.util.TemporaryBuffer;
  99. import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
  100. import org.eclipse.jgit.util.io.AutoLFInputStream;
  101. import org.eclipse.jgit.util.io.EolStreamTypeUtil;
  102. import org.eclipse.jgit.util.sha1.SHA1;
  103. /**
  104. * Walks a working directory tree as part of a
  105. * {@link org.eclipse.jgit.treewalk.TreeWalk}.
  106. * <p>
  107. * Most applications will want to use the standard implementation of this
  108. * iterator, {@link org.eclipse.jgit.treewalk.FileTreeIterator}, as that does
  109. * all IO through the standard <code>java.io</code> package. Plugins for a Java
  110. * based IDE may however wish to create their own implementations of this class
  111. * to allow traversal of the IDE's project space, as well as benefit from any
  112. * caching the IDE may have.
  113. *
  114. * @see FileTreeIterator
  115. */
  116. public abstract class WorkingTreeIterator extends AbstractTreeIterator {
  117. private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
  118. /** An empty entry array, suitable for {@link #init(Entry[])}. */
  119. protected static final Entry[] EOF = {};
  120. /** Size we perform file IO in if we have to read and hash a file. */
  121. static final int BUFFER_SIZE = 2048;
  122. /**
  123. * Maximum size of files which may be read fully into memory for performance
  124. * reasons.
  125. */
  126. private static final long MAXIMUM_FILE_SIZE_TO_READ_FULLY = 65536;
  127. /** Inherited state of this iterator, describing working tree, etc. */
  128. private final IteratorState state;
  129. /** The {@link #idBuffer()} for the current entry. */
  130. private byte[] contentId;
  131. /** Index within {@link #entries} that {@link #contentId} came from. */
  132. private int contentIdFromPtr;
  133. /** List of entries obtained from the subclass. */
  134. private Entry[] entries;
  135. /** Total number of entries in {@link #entries} that are valid. */
  136. private int entryCnt;
  137. /** Current position within {@link #entries}. */
  138. private int ptr;
  139. /** If there is a .gitignore file present, the parsed rules from it. */
  140. private IgnoreNode ignoreNode;
  141. /**
  142. * cached clean filter command. Use a Ref in order to distinguish between
  143. * the ref not cached yet and the value null
  144. */
  145. private Holder<String> cleanFilterCommandHolder;
  146. /**
  147. * cached eol stream type. Use a Ref in order to distinguish between the ref
  148. * not cached yet and the value null
  149. */
  150. private Holder<EolStreamType> eolStreamTypeHolder;
  151. /** Repository that is the root level being iterated over */
  152. protected Repository repository;
  153. /** Cached canonical length, initialized from {@link #idBuffer()} */
  154. private long canonLen = -1;
  155. /** The offset of the content id in {@link #idBuffer()} */
  156. private int contentIdOffset;
  157. /** A comparator for {@link Instant}s. */
  158. private final InstantComparator timestampComparator = new InstantComparator();
  159. /**
  160. * Create a new iterator with no parent.
  161. *
  162. * @param options
  163. * working tree options to be used
  164. */
  165. protected WorkingTreeIterator(WorkingTreeOptions options) {
  166. super();
  167. state = new IteratorState(options);
  168. }
  169. /**
  170. * Create a new iterator with no parent and a prefix.
  171. * <p>
  172. * The prefix path supplied is inserted in front of all paths generated by
  173. * this iterator. It is intended to be used when an iterator is being
  174. * created for a subsection of an overall repository and needs to be
  175. * combined with other iterators that are created to run over the entire
  176. * repository namespace.
  177. *
  178. * @param prefix
  179. * position of this iterator in the repository tree. The value
  180. * may be null or the empty string to indicate the prefix is the
  181. * root of the repository. A trailing slash ('/') is
  182. * automatically appended if the prefix does not end in '/'.
  183. * @param options
  184. * working tree options to be used
  185. */
  186. protected WorkingTreeIterator(final String prefix,
  187. WorkingTreeOptions options) {
  188. super(prefix);
  189. state = new IteratorState(options);
  190. }
  191. /**
  192. * Create an iterator for a subtree of an existing iterator.
  193. *
  194. * @param p
  195. * parent tree iterator.
  196. */
  197. protected WorkingTreeIterator(WorkingTreeIterator p) {
  198. super(p);
  199. state = p.state;
  200. repository = p.repository;
  201. }
  202. /**
  203. * Initialize this iterator for the root level of a repository.
  204. * <p>
  205. * This method should only be invoked after calling {@link #init(Entry[])},
  206. * and only for the root iterator.
  207. *
  208. * @param repo
  209. * the repository.
  210. */
  211. protected void initRootIterator(Repository repo) {
  212. repository = repo;
  213. Entry entry;
  214. if (ignoreNode instanceof PerDirectoryIgnoreNode)
  215. entry = ((PerDirectoryIgnoreNode) ignoreNode).entry;
  216. else
  217. entry = null;
  218. ignoreNode = new RootIgnoreNode(entry, repo);
  219. }
  220. /**
  221. * Define the matching {@link org.eclipse.jgit.dircache.DirCacheIterator},
  222. * to optimize ObjectIds.
  223. *
  224. * Once the DirCacheIterator has been set this iterator must only be
  225. * advanced by the TreeWalk that is supplied, as it assumes that itself and
  226. * the corresponding DirCacheIterator are positioned on the same file path
  227. * whenever {@link #idBuffer()} is invoked.
  228. *
  229. * @param walk
  230. * the walk that will be advancing this iterator.
  231. * @param treeId
  232. * index of the matching
  233. * {@link org.eclipse.jgit.dircache.DirCacheIterator}.
  234. */
  235. public void setDirCacheIterator(TreeWalk walk, int treeId) {
  236. state.walk = walk;
  237. state.dirCacheTree = treeId;
  238. }
  239. /**
  240. * Retrieves the {@link DirCacheIterator} at the current entry if
  241. * {@link #setDirCacheIterator(TreeWalk, int)} was called.
  242. *
  243. * @return the DirCacheIterator, or {@code null} if not set or not at the
  244. * current entry
  245. * @since 5.0
  246. */
  247. protected DirCacheIterator getDirCacheIterator() {
  248. if (state.dirCacheTree >= 0 && state.walk != null) {
  249. return state.walk.getTree(state.dirCacheTree,
  250. DirCacheIterator.class);
  251. }
  252. return null;
  253. }
  254. /**
  255. * Defines whether this {@link WorkingTreeIterator} walks ignored
  256. * directories.
  257. *
  258. * @param includeIgnored
  259. * {@code false} to skip ignored directories, if possible;
  260. * {@code true} to always include them in the walk
  261. * @since 5.0
  262. */
  263. public void setWalkIgnoredDirectories(boolean includeIgnored) {
  264. state.walkIgnored = includeIgnored;
  265. }
  266. /**
  267. * Tells whether this {@link WorkingTreeIterator} walks ignored directories.
  268. *
  269. * @return {@code true} if it does, {@code false} otherwise
  270. * @since 5.0
  271. */
  272. public boolean walksIgnoredDirectories() {
  273. return state.walkIgnored;
  274. }
  275. /** {@inheritDoc} */
  276. @Override
  277. public boolean hasId() {
  278. if (contentIdFromPtr == ptr)
  279. return true;
  280. return (mode & FileMode.TYPE_MASK) == FileMode.TYPE_FILE;
  281. }
  282. /** {@inheritDoc} */
  283. @Override
  284. public byte[] idBuffer() {
  285. if (contentIdFromPtr == ptr)
  286. return contentId;
  287. if (state.walk != null) {
  288. // If there is a matching DirCacheIterator, we can reuse
  289. // its idBuffer, but only if we appear to be clean against
  290. // the cached index information for the path.
  291. DirCacheIterator i = state.walk.getTree(state.dirCacheTree,
  292. DirCacheIterator.class);
  293. if (i != null) {
  294. DirCacheEntry ent = i.getDirCacheEntry();
  295. if (ent != null && compareMetadata(ent) == MetadataDiff.EQUAL
  296. && ((ent.getFileMode().getBits()
  297. & FileMode.TYPE_MASK) != FileMode.TYPE_GITLINK)) {
  298. contentIdOffset = i.idOffset();
  299. contentIdFromPtr = ptr;
  300. return contentId = i.idBuffer();
  301. }
  302. contentIdOffset = 0;
  303. } else {
  304. contentIdOffset = 0;
  305. }
  306. }
  307. switch (mode & FileMode.TYPE_MASK) {
  308. case FileMode.TYPE_SYMLINK:
  309. case FileMode.TYPE_FILE:
  310. contentIdFromPtr = ptr;
  311. return contentId = idBufferBlob(entries[ptr]);
  312. case FileMode.TYPE_GITLINK:
  313. contentIdFromPtr = ptr;
  314. return contentId = idSubmodule(entries[ptr]);
  315. }
  316. return zeroid;
  317. }
  318. /** {@inheritDoc} */
  319. @Override
  320. public boolean isWorkTree() {
  321. return true;
  322. }
  323. /**
  324. * Get submodule id for given entry.
  325. *
  326. * @param e
  327. * a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry}
  328. * object.
  329. * @return non-null submodule id
  330. */
  331. protected byte[] idSubmodule(Entry e) {
  332. if (repository == null)
  333. return zeroid;
  334. File directory;
  335. try {
  336. directory = repository.getWorkTree();
  337. } catch (NoWorkTreeException nwte) {
  338. return zeroid;
  339. }
  340. return idSubmodule(directory, e);
  341. }
  342. /**
  343. * Get submodule id using the repository at the location of the entry
  344. * relative to the directory.
  345. *
  346. * @param directory
  347. * a {@link java.io.File} object.
  348. * @param e
  349. * a {@link org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry}
  350. * object.
  351. * @return non-null submodule id
  352. */
  353. protected byte[] idSubmodule(File directory, Entry e) {
  354. try (Repository submoduleRepo = SubmoduleWalk.getSubmoduleRepository(
  355. directory, e.getName(),
  356. repository != null ? repository.getFS() : FS.DETECTED)) {
  357. if (submoduleRepo == null) {
  358. return zeroid;
  359. }
  360. ObjectId head = submoduleRepo.resolve(Constants.HEAD);
  361. if (head == null) {
  362. return zeroid;
  363. }
  364. byte[] id = new byte[Constants.OBJECT_ID_LENGTH];
  365. head.copyRawTo(id, 0);
  366. return id;
  367. } catch (IOException exception) {
  368. return zeroid;
  369. }
  370. }
  371. private static final byte[] digits = { '0', '1', '2', '3', '4', '5', '6',
  372. '7', '8', '9' };
  373. private static final byte[] hblob = Constants
  374. .encodedTypeString(Constants.OBJ_BLOB);
  375. private byte[] idBufferBlob(Entry e) {
  376. try {
  377. final InputStream is = e.openInputStream();
  378. if (is == null)
  379. return zeroid;
  380. try {
  381. state.initializeReadBuffer();
  382. final long len = e.getLength();
  383. InputStream filteredIs = possiblyFilteredInputStream(e, is, len,
  384. OperationType.CHECKIN_OP);
  385. return computeHash(filteredIs, canonLen);
  386. } finally {
  387. safeClose(is);
  388. }
  389. } catch (IOException err) {
  390. // Can't read the file? Don't report the failure either.
  391. return zeroid;
  392. }
  393. }
  394. private InputStream possiblyFilteredInputStream(final Entry e,
  395. final InputStream is, final long len) throws IOException {
  396. return possiblyFilteredInputStream(e, is, len, null);
  397. }
  398. private InputStream possiblyFilteredInputStream(final Entry e,
  399. final InputStream is, final long len, OperationType opType)
  400. throws IOException {
  401. if (getCleanFilterCommand() == null
  402. && getEolStreamType(opType) == EolStreamType.DIRECT) {
  403. canonLen = len;
  404. return is;
  405. }
  406. if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
  407. ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
  408. rawbuf = filterClean(rawbuf.array(), rawbuf.limit(), opType);
  409. canonLen = rawbuf.limit();
  410. return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen);
  411. }
  412. if (getCleanFilterCommand() == null && isBinary(e)) {
  413. canonLen = len;
  414. return is;
  415. }
  416. final InputStream lenIs = filterClean(e.openInputStream(),
  417. opType);
  418. try {
  419. canonLen = computeLength(lenIs);
  420. } finally {
  421. safeClose(lenIs);
  422. }
  423. return filterClean(is, opType);
  424. }
  425. private static void safeClose(InputStream in) {
  426. try {
  427. in.close();
  428. } catch (IOException err2) {
  429. // Suppress any error related to closing an input
  430. // stream. We don't care, we should not have any
  431. // outstanding data to flush or anything like that.
  432. }
  433. }
  434. private static boolean isBinary(Entry entry) throws IOException {
  435. InputStream in = entry.openInputStream();
  436. try {
  437. return RawText.isBinary(in);
  438. } finally {
  439. safeClose(in);
  440. }
  441. }
  442. private ByteBuffer filterClean(byte[] src, int n, OperationType opType)
  443. throws IOException {
  444. InputStream in = new ByteArrayInputStream(src);
  445. try {
  446. return IO.readWholeStream(filterClean(in, opType), n);
  447. } finally {
  448. safeClose(in);
  449. }
  450. }
  451. private InputStream filterClean(InputStream in) throws IOException {
  452. return filterClean(in, null);
  453. }
  454. private InputStream filterClean(InputStream in, OperationType opType)
  455. throws IOException {
  456. in = handleAutoCRLF(in, opType);
  457. String filterCommand = getCleanFilterCommand();
  458. if (filterCommand != null) {
  459. if (FilterCommandRegistry.isRegistered(filterCommand)) {
  460. LocalFile buffer = new TemporaryBuffer.LocalFile(null);
  461. FilterCommand command = FilterCommandRegistry
  462. .createFilterCommand(filterCommand, repository, in,
  463. buffer);
  464. while (command.run() != -1) {
  465. // loop as long as command.run() tells there is work to do
  466. }
  467. return buffer.openInputStreamWithAutoDestroy();
  468. }
  469. FS fs = repository.getFS();
  470. ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
  471. new String[0]);
  472. filterProcessBuilder.directory(repository.getWorkTree());
  473. filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
  474. repository.getDirectory().getAbsolutePath());
  475. ExecutionResult result;
  476. try {
  477. result = fs.execute(filterProcessBuilder, in);
  478. } catch (IOException | InterruptedException e) {
  479. throw new IOException(new FilterFailedException(e,
  480. filterCommand, getEntryPathString()));
  481. }
  482. int rc = result.getRc();
  483. if (rc != 0) {
  484. throw new IOException(new FilterFailedException(rc,
  485. filterCommand, getEntryPathString(),
  486. result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE),
  487. RawParseUtils.decode(result.getStderr()
  488. .toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
  489. }
  490. return result.getStdout().openInputStreamWithAutoDestroy();
  491. }
  492. return in;
  493. }
  494. private InputStream handleAutoCRLF(InputStream in, OperationType opType)
  495. throws IOException {
  496. return EolStreamTypeUtil.wrapInputStream(in, getEolStreamType(opType));
  497. }
  498. /**
  499. * Returns the working tree options used by this iterator.
  500. *
  501. * @return working tree options
  502. */
  503. public WorkingTreeOptions getOptions() {
  504. return state.options;
  505. }
  506. /** {@inheritDoc} */
  507. @Override
  508. public int idOffset() {
  509. return contentIdOffset;
  510. }
  511. /** {@inheritDoc} */
  512. @Override
  513. public void reset() {
  514. if (!first()) {
  515. ptr = 0;
  516. if (!eof())
  517. parseEntry();
  518. }
  519. }
  520. /** {@inheritDoc} */
  521. @Override
  522. public boolean first() {
  523. return ptr == 0;
  524. }
  525. /** {@inheritDoc} */
  526. @Override
  527. public boolean eof() {
  528. return ptr == entryCnt;
  529. }
  530. /** {@inheritDoc} */
  531. @Override
  532. public void next(int delta) throws CorruptObjectException {
  533. ptr += delta;
  534. if (!eof()) {
  535. parseEntry();
  536. }
  537. }
  538. /** {@inheritDoc} */
  539. @Override
  540. public void back(int delta) throws CorruptObjectException {
  541. ptr -= delta;
  542. parseEntry();
  543. }
  544. private void parseEntry() {
  545. final Entry e = entries[ptr];
  546. mode = e.getMode().getBits();
  547. final int nameLen = e.encodedNameLen;
  548. ensurePathCapacity(pathOffset + nameLen, pathOffset);
  549. System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
  550. pathLen = pathOffset + nameLen;
  551. canonLen = -1;
  552. cleanFilterCommandHolder = null;
  553. eolStreamTypeHolder = null;
  554. }
  555. /**
  556. * Get the raw byte length of this entry.
  557. *
  558. * @return size of this file, in bytes.
  559. */
  560. public long getEntryLength() {
  561. return current().getLength();
  562. }
  563. /**
  564. * Get the filtered input length of this entry
  565. *
  566. * @return size of the content, in bytes
  567. * @throws java.io.IOException
  568. */
  569. public long getEntryContentLength() throws IOException {
  570. if (canonLen == -1) {
  571. long rawLen = getEntryLength();
  572. if (rawLen == 0)
  573. canonLen = 0;
  574. InputStream is = current().openInputStream();
  575. try {
  576. // canonLen gets updated here
  577. possiblyFilteredInputStream(current(), is, current()
  578. .getLength());
  579. } finally {
  580. safeClose(is);
  581. }
  582. }
  583. return canonLen;
  584. }
  585. /**
  586. * Get the last modified time of this entry.
  587. *
  588. * @return last modified time of this file, in milliseconds since the epoch
  589. * (Jan 1, 1970 UTC).
  590. * @deprecated use {@link #getEntryLastModifiedInstant()} instead
  591. */
  592. @Deprecated
  593. public long getEntryLastModified() {
  594. return current().getLastModified();
  595. }
  596. /**
  597. * Get the last modified time of this entry.
  598. *
  599. * @return last modified time of this file
  600. * @since 5.1.9
  601. */
  602. public Instant getEntryLastModifiedInstant() {
  603. return current().getLastModifiedInstant();
  604. }
  605. /**
  606. * Obtain an input stream to read the file content.
  607. * <p>
  608. * Efficient implementations are not required. The caller will usually
  609. * obtain the stream only once per entry, if at all.
  610. * <p>
  611. * The input stream should not use buffering if the implementation can avoid
  612. * it. The caller will buffer as necessary to perform efficient block IO
  613. * operations.
  614. * <p>
  615. * The caller will close the stream once complete.
  616. *
  617. * @return a stream to read from the file.
  618. * @throws java.io.IOException
  619. * the file could not be opened for reading.
  620. */
  621. public InputStream openEntryStream() throws IOException {
  622. InputStream rawis = current().openInputStream();
  623. if (getCleanFilterCommand() == null
  624. && getEolStreamType() == EolStreamType.DIRECT)
  625. return rawis;
  626. else
  627. return filterClean(rawis);
  628. }
  629. /**
  630. * Determine if the current entry path is ignored by an ignore rule.
  631. *
  632. * @return true if the entry was ignored by an ignore rule file.
  633. * @throws java.io.IOException
  634. * a relevant ignore rule file exists but cannot be read.
  635. */
  636. public boolean isEntryIgnored() throws IOException {
  637. return isEntryIgnored(pathLen);
  638. }
  639. /**
  640. * Determine if the entry path is ignored by an ignore rule.
  641. *
  642. * @param pLen
  643. * the length of the path in the path buffer.
  644. * @return true if the entry is ignored by an ignore rule.
  645. * @throws java.io.IOException
  646. * a relevant ignore rule file exists but cannot be read.
  647. */
  648. protected boolean isEntryIgnored(int pLen) throws IOException {
  649. return isEntryIgnored(pLen, mode);
  650. }
  651. /**
  652. * Determine if the entry path is ignored by an ignore rule.
  653. *
  654. * @param pLen
  655. * the length of the path in the path buffer.
  656. * @param fileMode
  657. * the original iterator file mode
  658. * @return true if the entry is ignored by an ignore rule.
  659. * @throws IOException
  660. * a relevant ignore rule file exists but cannot be read.
  661. */
  662. private boolean isEntryIgnored(int pLen, int fileMode)
  663. throws IOException {
  664. // The ignore code wants path to start with a '/' if possible.
  665. // If we have the '/' in our path buffer because we are inside
  666. // a sub-directory include it in the range we convert to string.
  667. //
  668. final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
  669. String pathRel = TreeWalk.pathOf(this.path, pOff, pLen);
  670. String parentRel = getParentPath(pathRel);
  671. // CGit is processing .gitignore files by starting at the root of the
  672. // repository and then recursing into subdirectories. With this
  673. // approach, top-level ignored directories will be processed first which
  674. // allows to skip entire subtrees and further .gitignore-file processing
  675. // within these subtrees.
  676. //
  677. // We will follow the same approach by marking directories as "ignored"
  678. // here. This allows to have a simplified FastIgnore.checkIgnore()
  679. // implementation (both in terms of code and computational complexity):
  680. //
  681. // Without the "ignored" flag, we would have to apply the ignore-check
  682. // to a path and all of its parents always(!), to determine whether a
  683. // path is ignored directly or by one of its parent directories; with
  684. // the "ignored" flag, we know at this point that the parent directory
  685. // is definitely not ignored, thus the path can only become ignored if
  686. // there is a rule matching the path itself.
  687. if (isDirectoryIgnored(parentRel)) {
  688. return true;
  689. }
  690. IgnoreNode rules = getIgnoreNode();
  691. final Boolean ignored = rules != null
  692. ? rules.checkIgnored(pathRel, FileMode.TREE.equals(fileMode))
  693. : null;
  694. if (ignored != null) {
  695. return ignored.booleanValue();
  696. }
  697. return parent instanceof WorkingTreeIterator
  698. && ((WorkingTreeIterator) parent).isEntryIgnored(pLen,
  699. fileMode);
  700. }
  701. private IgnoreNode getIgnoreNode() throws IOException {
  702. if (ignoreNode instanceof PerDirectoryIgnoreNode)
  703. ignoreNode = ((PerDirectoryIgnoreNode) ignoreNode).load();
  704. return ignoreNode;
  705. }
  706. /**
  707. * Retrieves the {@link org.eclipse.jgit.attributes.AttributesNode} for the
  708. * current entry.
  709. *
  710. * @return the {@link org.eclipse.jgit.attributes.AttributesNode} for the
  711. * current entry.
  712. * @throws IOException
  713. */
  714. public AttributesNode getEntryAttributesNode() throws IOException {
  715. if (attributesNode instanceof PerDirectoryAttributesNode)
  716. attributesNode = ((PerDirectoryAttributesNode) attributesNode)
  717. .load();
  718. return attributesNode;
  719. }
  720. private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
  721. @Override
  722. public int compare(Entry a, Entry b) {
  723. return Paths.compare(
  724. a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(),
  725. b.encodedName, 0, b.encodedNameLen, b.getMode().getBits());
  726. }
  727. };
  728. /**
  729. * Constructor helper.
  730. *
  731. * @param list
  732. * files in the subtree of the work tree this iterator operates
  733. * on
  734. */
  735. protected void init(Entry[] list) {
  736. // Filter out nulls, . and .. as these are not valid tree entries,
  737. // also cache the encoded forms of the path names for efficient use
  738. // later on during sorting and iteration.
  739. //
  740. entries = list;
  741. int i, o;
  742. final CharsetEncoder nameEncoder = state.nameEncoder;
  743. for (i = 0, o = 0; i < entries.length; i++) {
  744. final Entry e = entries[i];
  745. if (e == null)
  746. continue;
  747. final String name = e.getName();
  748. if (".".equals(name) || "..".equals(name)) //$NON-NLS-1$ //$NON-NLS-2$
  749. continue;
  750. if (Constants.DOT_GIT.equals(name))
  751. continue;
  752. if (Constants.DOT_GIT_IGNORE.equals(name))
  753. ignoreNode = new PerDirectoryIgnoreNode(e);
  754. if (Constants.DOT_GIT_ATTRIBUTES.equals(name))
  755. attributesNode = new PerDirectoryAttributesNode(e);
  756. if (i != o)
  757. entries[o] = e;
  758. e.encodeName(nameEncoder);
  759. o++;
  760. }
  761. entryCnt = o;
  762. Arrays.sort(entries, 0, entryCnt, ENTRY_CMP);
  763. contentIdFromPtr = -1;
  764. ptr = 0;
  765. if (!eof())
  766. parseEntry();
  767. else if (pathLen == 0) // see bug 445363
  768. pathLen = pathOffset;
  769. }
  770. /**
  771. * Obtain the current entry from this iterator.
  772. *
  773. * @return the currently selected entry.
  774. */
  775. protected Entry current() {
  776. return entries[ptr];
  777. }
  778. /**
  779. * The result of a metadata-comparison between the current entry and a
  780. * {@link DirCacheEntry}
  781. */
  782. public enum MetadataDiff {
  783. /**
  784. * The entries are equal by metaData (mode, length,
  785. * modification-timestamp) or the <code>assumeValid</code> attribute of
  786. * the index entry is set
  787. */
  788. EQUAL,
  789. /**
  790. * The entries are not equal by metaData (mode, length) or the
  791. * <code>isUpdateNeeded</code> attribute of the index entry is set
  792. */
  793. DIFFER_BY_METADATA,
  794. /** index entry is smudged - can't use that entry for comparison */
  795. SMUDGED,
  796. /**
  797. * The entries are equal by metaData (mode, length) but differ by
  798. * modification-timestamp.
  799. */
  800. DIFFER_BY_TIMESTAMP
  801. }
  802. /**
  803. * Is the file mode of the current entry different than the given raw mode?
  804. *
  805. * @param rawMode
  806. * an int.
  807. * @return true if different, false otherwise
  808. */
  809. public boolean isModeDifferent(int rawMode) {
  810. // Determine difference in mode-bits of file and index-entry. In the
  811. // bitwise presentation of modeDiff we'll have a '1' when the two modes
  812. // differ at this position.
  813. int modeDiff = getEntryRawMode() ^ rawMode;
  814. if (modeDiff == 0)
  815. return false;
  816. // Do not rely on filemode differences in case of symbolic links
  817. if (getOptions().getSymLinks() == SymLinks.FALSE)
  818. if (FileMode.SYMLINK.equals(rawMode))
  819. return false;
  820. // Ignore the executable file bits if WorkingTreeOptions tell me to
  821. // do so. Ignoring is done by setting the bits representing a
  822. // EXECUTABLE_FILE to '0' in modeDiff
  823. if (!state.options.isFileMode())
  824. modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
  825. return modeDiff != 0;
  826. }
  827. /**
  828. * Compare the metadata (mode, length, modification-timestamp) of the
  829. * current entry and a {@link org.eclipse.jgit.dircache.DirCacheEntry}
  830. *
  831. * @param entry
  832. * the {@link org.eclipse.jgit.dircache.DirCacheEntry} to compare
  833. * with
  834. * @return a
  835. * {@link org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff}
  836. * which tells whether and how the entries metadata differ
  837. */
  838. public MetadataDiff compareMetadata(DirCacheEntry entry) {
  839. if (entry.isAssumeValid())
  840. return MetadataDiff.EQUAL;
  841. if (entry.isUpdateNeeded())
  842. return MetadataDiff.DIFFER_BY_METADATA;
  843. if (isModeDifferent(entry.getRawMode()))
  844. return MetadataDiff.DIFFER_BY_METADATA;
  845. // Don't check for length or lastmodified on folders
  846. int type = mode & FileMode.TYPE_MASK;
  847. if (type == FileMode.TYPE_TREE || type == FileMode.TYPE_GITLINK)
  848. return MetadataDiff.EQUAL;
  849. if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
  850. return MetadataDiff.DIFFER_BY_METADATA;
  851. // Cache and file timestamps may differ in resolution. Therefore don't
  852. // compare instants directly but use a comparator that compares only
  853. // up to the lower apparent resolution of either timestamp.
  854. //
  855. // If core.checkstat is set to "minimal", compare only the seconds part.
  856. Instant cacheLastModified = entry.getLastModifiedInstant();
  857. Instant fileLastModified = getEntryLastModifiedInstant();
  858. if (timestampComparator.compare(cacheLastModified, fileLastModified,
  859. getOptions().getCheckStat() == CheckStat.MINIMAL) != 0) {
  860. return MetadataDiff.DIFFER_BY_TIMESTAMP;
  861. }
  862. if (entry.isSmudged()) {
  863. return MetadataDiff.SMUDGED;
  864. }
  865. // The file is clean when when comparing timestamps
  866. return MetadataDiff.EQUAL;
  867. }
  868. /**
  869. * Checks whether this entry differs from a given entry from the
  870. * {@link org.eclipse.jgit.dircache.DirCache}.
  871. *
  872. * File status information is used and if status is same we consider the
  873. * file identical to the state in the working directory. Native git uses
  874. * more stat fields than we have accessible in Java.
  875. *
  876. * @param entry
  877. * the entry from the dircache we want to compare against
  878. * @param forceContentCheck
  879. * True if the actual file content should be checked if
  880. * modification time differs.
  881. * @param reader
  882. * access to repository objects if necessary. Should not be null.
  883. * @return true if content is most likely different.
  884. * @throws java.io.IOException
  885. * @since 3.3
  886. */
  887. public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
  888. ObjectReader reader) throws IOException {
  889. if (entry == null)
  890. return !FileMode.MISSING.equals(getEntryFileMode());
  891. MetadataDiff diff = compareMetadata(entry);
  892. switch (diff) {
  893. case DIFFER_BY_TIMESTAMP:
  894. if (forceContentCheck)
  895. // But we are told to look at content even though timestamps
  896. // tell us about modification
  897. return contentCheck(entry, reader);
  898. else
  899. // We are told to assume a modification if timestamps differs
  900. return true;
  901. case SMUDGED:
  902. // The file is clean by timestamps but the entry was smudged.
  903. // Lets do a content check
  904. return contentCheck(entry, reader);
  905. case EQUAL:
  906. if (mode == FileMode.SYMLINK.getBits()) {
  907. return contentCheck(entry, reader);
  908. }
  909. return false;
  910. case DIFFER_BY_METADATA:
  911. if (mode == FileMode.TREE.getBits()
  912. && entry.getFileMode().equals(FileMode.GITLINK)) {
  913. byte[] idBuffer = idBuffer();
  914. int idOffset = idOffset();
  915. if (entry.getObjectId().compareTo(idBuffer, idOffset) == 0) {
  916. return true;
  917. } else if (ObjectId.zeroId().compareTo(idBuffer,
  918. idOffset) == 0) {
  919. return new File(repository.getWorkTree(),
  920. entry.getPathString()).list().length > 0;
  921. }
  922. return false;
  923. } else if (mode == FileMode.SYMLINK.getBits())
  924. return contentCheck(entry, reader);
  925. return true;
  926. default:
  927. throw new IllegalStateException(MessageFormat.format(
  928. JGitText.get().unexpectedCompareResult, diff.name()));
  929. }
  930. }
  931. /**
  932. * Get the file mode to use for the current entry when it is to be updated
  933. * in the index.
  934. *
  935. * @param indexIter
  936. * {@link org.eclipse.jgit.dircache.DirCacheIterator} positioned
  937. * at the same entry as this iterator or null if no
  938. * {@link org.eclipse.jgit.dircache.DirCacheIterator} is
  939. * available at this iterator's current entry
  940. * @return index file mode
  941. */
  942. public FileMode getIndexFileMode(DirCacheIterator indexIter) {
  943. final FileMode wtMode = getEntryFileMode();
  944. if (indexIter == null) {
  945. return wtMode;
  946. }
  947. final FileMode iMode = indexIter.getEntryFileMode();
  948. if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
  949. return wtMode;
  950. }
  951. if (!getOptions().isFileMode()) {
  952. if (FileMode.REGULAR_FILE == wtMode
  953. && FileMode.EXECUTABLE_FILE == iMode) {
  954. return iMode;
  955. }
  956. if (FileMode.EXECUTABLE_FILE == wtMode
  957. && FileMode.REGULAR_FILE == iMode) {
  958. return iMode;
  959. }
  960. }
  961. if (FileMode.GITLINK == iMode
  962. && FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
  963. return iMode;
  964. }
  965. if (FileMode.TREE == iMode
  966. && FileMode.GITLINK == wtMode) {
  967. return iMode;
  968. }
  969. return wtMode;
  970. }
  971. /**
  972. * Compares the entries content with the content in the filesystem.
  973. * Unsmudges the entry when it is detected that it is clean.
  974. *
  975. * @param entry
  976. * the entry to be checked
  977. * @param reader
  978. * acccess to repository data if necessary
  979. * @return <code>true</code> if the content doesn't match,
  980. * <code>false</code> if it matches
  981. * @throws IOException
  982. */
  983. private boolean contentCheck(DirCacheEntry entry, ObjectReader reader)
  984. throws IOException {
  985. if (getEntryObjectId().equals(entry.getObjectId())) {
  986. // Content has not changed
  987. // We know the entry can't be racily clean because it's still clean.
  988. // Therefore we unsmudge the entry!
  989. // If by any chance we now unsmudge although we are still in the
  990. // same time-slot as the last modification to the index file the
  991. // next index write operation will smudge again.
  992. // Caution: we are unsmudging just by setting the length of the
  993. // in-memory entry object. It's the callers task to detect that we
  994. // have modified the entry and to persist the modified index.
  995. entry.setLength((int) getEntryLength());
  996. return false;
  997. } else {
  998. if (mode == FileMode.SYMLINK.getBits()) {
  999. return !new File(readSymlinkTarget(current())).equals(
  1000. new File(readContentAsNormalizedString(entry, reader)));
  1001. }
  1002. // Content differs: that's a real change, perhaps
  1003. if (reader == null) // deprecated use, do no further checks
  1004. return true;
  1005. switch (getEolStreamType()) {
  1006. case DIRECT:
  1007. return true;
  1008. default:
  1009. try {
  1010. ObjectLoader loader = reader.open(entry.getObjectId());
  1011. if (loader == null)
  1012. return true;
  1013. // We need to compute the length, but only if it is not
  1014. // a binary stream.
  1015. long dcInLen;
  1016. try (InputStream dcIn = new AutoLFInputStream(
  1017. loader.openStream(), true,
  1018. true /* abort if binary */)) {
  1019. dcInLen = computeLength(dcIn);
  1020. } catch (AutoLFInputStream.IsBinaryException e) {
  1021. return true;
  1022. }
  1023. try (InputStream dcIn = new AutoLFInputStream(
  1024. loader.openStream(), true)) {
  1025. byte[] autoCrLfHash = computeHash(dcIn, dcInLen);
  1026. boolean changed = getEntryObjectId()
  1027. .compareTo(autoCrLfHash, 0) != 0;
  1028. return changed;
  1029. }
  1030. } catch (IOException e) {
  1031. return true;
  1032. }
  1033. }
  1034. }
  1035. }
  1036. private static String readContentAsNormalizedString(DirCacheEntry entry,
  1037. ObjectReader reader) throws MissingObjectException, IOException {
  1038. ObjectLoader open = reader.open(entry.getObjectId());
  1039. byte[] cachedBytes = open.getCachedBytes();
  1040. return FS.detect().normalize(RawParseUtils.decode(cachedBytes));
  1041. }
  1042. /**
  1043. * Reads the target of a symlink as a string. This default implementation
  1044. * fully reads the entry's input stream and converts it to a normalized
  1045. * string. Subclasses may override to provide more specialized
  1046. * implementations.
  1047. *
  1048. * @param entry
  1049. * to read
  1050. * @return the entry's content as a normalized string
  1051. * @throws java.io.IOException
  1052. * if the entry cannot be read or does not denote a symlink
  1053. * @since 4.6
  1054. */
  1055. protected String readSymlinkTarget(Entry entry) throws IOException {
  1056. if (!entry.getMode().equals(FileMode.SYMLINK)) {
  1057. throw new java.nio.file.NotLinkException(entry.getName());
  1058. }
  1059. long length = entry.getLength();
  1060. byte[] content = new byte[(int) length];
  1061. try (InputStream is = entry.openInputStream()) {
  1062. int bytesRead = IO.readFully(is, content, 0);
  1063. return FS.detect()
  1064. .normalize(RawParseUtils.decode(content, 0, bytesRead));
  1065. }
  1066. }
  1067. private static long computeLength(InputStream in) throws IOException {
  1068. // Since we only care about the length, use skip. The stream
  1069. // may be able to more efficiently wade through its data.
  1070. //
  1071. long length = 0;
  1072. for (;;) {
  1073. long n = in.skip(1 << 20);
  1074. if (n <= 0)
  1075. break;
  1076. length += n;
  1077. }
  1078. return length;
  1079. }
  1080. private byte[] computeHash(InputStream in, long length) throws IOException {
  1081. SHA1 contentDigest = SHA1.newInstance();
  1082. final byte[] contentReadBuffer = state.contentReadBuffer;
  1083. contentDigest.update(hblob);
  1084. contentDigest.update((byte) ' ');
  1085. long sz = length;
  1086. if (sz == 0) {
  1087. contentDigest.update((byte) '0');
  1088. } else {
  1089. final int bufn = contentReadBuffer.length;
  1090. int p = bufn;
  1091. do {
  1092. contentReadBuffer[--p] = digits[(int) (sz % 10)];
  1093. sz /= 10;
  1094. } while (sz > 0);
  1095. contentDigest.update(contentReadBuffer, p, bufn - p);
  1096. }
  1097. contentDigest.update((byte) 0);
  1098. for (;;) {
  1099. final int r = in.read(contentReadBuffer);
  1100. if (r <= 0)
  1101. break;
  1102. contentDigest.update(contentReadBuffer, 0, r);
  1103. sz += r;
  1104. }
  1105. if (sz != length)
  1106. return zeroid;
  1107. return contentDigest.digest();
  1108. }
  1109. /**
  1110. * A single entry within a working directory tree.
  1111. *
  1112. * @since 5.0
  1113. */
  1114. public static abstract class Entry {
  1115. byte[] encodedName;
  1116. int encodedNameLen;
  1117. void encodeName(CharsetEncoder enc) {
  1118. final ByteBuffer b;
  1119. try {
  1120. b = enc.encode(CharBuffer.wrap(getName()));
  1121. } catch (CharacterCodingException e) {
  1122. // This should so never happen.
  1123. throw new RuntimeException(MessageFormat.format(
  1124. JGitText.get().unencodeableFile, getName()));
  1125. }
  1126. encodedNameLen = b.limit();
  1127. if (b.hasArray() && b.arrayOffset() == 0)
  1128. encodedName = b.array();
  1129. else
  1130. b.get(encodedName = new byte[encodedNameLen]);
  1131. }
  1132. @Override
  1133. public String toString() {
  1134. return getMode().toString() + " " + getName(); //$NON-NLS-1$
  1135. }
  1136. /**
  1137. * Get the type of this entry.
  1138. * <p>
  1139. * <b>Note: Efficient implementation required.</b>
  1140. * <p>
  1141. * The implementation of this method must be efficient. If a subclass
  1142. * needs to compute the value they should cache the reference within an
  1143. * instance member instead.
  1144. *
  1145. * @return a file mode constant from {@link FileMode}.
  1146. */
  1147. public abstract FileMode getMode();
  1148. /**
  1149. * Get the byte length of this entry.
  1150. * <p>
  1151. * <b>Note: Efficient implementation required.</b>
  1152. * <p>
  1153. * The implementation of this method must be efficient. If a subclass
  1154. * needs to compute the value they should cache the reference within an
  1155. * instance member instead.
  1156. *
  1157. * @return size of this file, in bytes.
  1158. */
  1159. public abstract long getLength();
  1160. /**
  1161. * Get the last modified time of this entry.
  1162. * <p>
  1163. * <b>Note: Efficient implementation required.</b>
  1164. * <p>
  1165. * The implementation of this method must be efficient. If a subclass
  1166. * needs to compute the value they should cache the reference within an
  1167. * instance member instead.
  1168. *
  1169. * @return time since the epoch (in ms) of the last change.
  1170. * @deprecated use {@link #getLastModifiedInstant()} instead
  1171. */
  1172. @Deprecated
  1173. public abstract long getLastModified();
  1174. /**
  1175. * Get the last modified time of this entry.
  1176. * <p>
  1177. * <b>Note: Efficient implementation required.</b>
  1178. * <p>
  1179. * The implementation of this method must be efficient. If a subclass
  1180. * needs to compute the value they should cache the reference within an
  1181. * instance member instead.
  1182. *
  1183. * @return time of the last change.
  1184. * @since 5.1.9
  1185. */
  1186. public abstract Instant getLastModifiedInstant();
  1187. /**
  1188. * Get the name of this entry within its directory.
  1189. * <p>
  1190. * Efficient implementations are not required. The caller will obtain
  1191. * the name only once and cache it once obtained.
  1192. *
  1193. * @return name of the entry.
  1194. */
  1195. public abstract String getName();
  1196. /**
  1197. * Obtain an input stream to read the file content.
  1198. * <p>
  1199. * Efficient implementations are not required. The caller will usually
  1200. * obtain the stream only once per entry, if at all.
  1201. * <p>
  1202. * The input stream should not use buffering if the implementation can
  1203. * avoid it. The caller will buffer as necessary to perform efficient
  1204. * block IO operations.
  1205. * <p>
  1206. * The caller will close the stream once complete.
  1207. *
  1208. * @return a stream to read from the file.
  1209. * @throws IOException
  1210. * the file could not be opened for reading.
  1211. */
  1212. public abstract InputStream openInputStream() throws IOException;
  1213. }
  1214. /** Magic type indicating we know rules exist, but they aren't loaded. */
  1215. private static class PerDirectoryIgnoreNode extends IgnoreNode {
  1216. final Entry entry;
  1217. PerDirectoryIgnoreNode(Entry entry) {
  1218. super(Collections.<FastIgnoreRule> emptyList());
  1219. this.entry = entry;
  1220. }
  1221. IgnoreNode load() throws IOException {
  1222. IgnoreNode r = new IgnoreNode();
  1223. try (InputStream in = entry.openInputStream()) {
  1224. r.parse(in);
  1225. }
  1226. return r.getRules().isEmpty() ? null : r;
  1227. }
  1228. }
  1229. /** Magic type indicating there may be rules for the top level. */
  1230. private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
  1231. final Repository repository;
  1232. RootIgnoreNode(Entry entry, Repository repository) {
  1233. super(entry);
  1234. this.repository = repository;
  1235. }
  1236. @Override
  1237. IgnoreNode load() throws IOException {
  1238. IgnoreNode r;
  1239. if (entry != null) {
  1240. r = super.load();
  1241. if (r == null)
  1242. r = new IgnoreNode();
  1243. } else {
  1244. r = new IgnoreNode();
  1245. }
  1246. FS fs = repository.getFS();
  1247. String path = repository.getConfig().get(CoreConfig.KEY)
  1248. .getExcludesFile();
  1249. if (path != null) {
  1250. File excludesfile;
  1251. if (path.startsWith("~/")) //$NON-NLS-1$
  1252. excludesfile = fs.resolve(fs.userHome(), path.substring(2));
  1253. else
  1254. excludesfile = fs.resolve(null, path);
  1255. loadRulesFromFile(r, excludesfile);
  1256. }
  1257. File exclude = fs.resolve(repository.getDirectory(),
  1258. Constants.INFO_EXCLUDE);
  1259. loadRulesFromFile(r, exclude);
  1260. return r.getRules().isEmpty() ? null : r;
  1261. }
  1262. private static void loadRulesFromFile(IgnoreNode r, File exclude)
  1263. throws FileNotFoundException, IOException {
  1264. if (FS.DETECTED.exists(exclude)) {
  1265. try (FileInputStream in = new FileInputStream(exclude)) {
  1266. r.parse(in);
  1267. }
  1268. }
  1269. }
  1270. }
  1271. /** Magic type indicating we know rules exist, but they aren't loaded. */
  1272. private static class PerDirectoryAttributesNode extends AttributesNode {
  1273. final Entry entry;
  1274. PerDirectoryAttributesNode(Entry entry) {
  1275. super(Collections.<AttributesRule> emptyList());
  1276. this.entry = entry;
  1277. }
  1278. AttributesNode load() throws IOException {
  1279. AttributesNode r = new AttributesNode();
  1280. try (InputStream in = entry.openInputStream()) {
  1281. r.parse(in);
  1282. }
  1283. return r.getRules().isEmpty() ? null : r;
  1284. }
  1285. }
  1286. private static final class IteratorState {
  1287. /** Options used to process the working tree. */
  1288. final WorkingTreeOptions options;
  1289. /** File name character encoder. */
  1290. final CharsetEncoder nameEncoder;
  1291. /** Buffer used to perform {@link #contentId} computations. */
  1292. byte[] contentReadBuffer;
  1293. /** TreeWalk with a (supposedly) matching DirCacheIterator. */
  1294. TreeWalk walk;
  1295. /** Position of the matching {@link DirCacheIterator}. */
  1296. int dirCacheTree = -1;
  1297. /** Whether the iterator shall walk ignored directories. */
  1298. boolean walkIgnored = false;
  1299. final Map<String, Boolean> directoryToIgnored = new HashMap<>();
  1300. IteratorState(WorkingTreeOptions options) {
  1301. this.options = options;
  1302. this.nameEncoder = UTF_8.newEncoder();
  1303. }
  1304. void initializeReadBuffer() {
  1305. if (contentReadBuffer == null) {
  1306. contentReadBuffer = new byte[BUFFER_SIZE];
  1307. }
  1308. }
  1309. }
  1310. /**
  1311. * Get the clean filter command for the current entry.
  1312. *
  1313. * @return the clean filter command for the current entry or
  1314. * <code>null</code> if no such command is defined
  1315. * @throws java.io.IOException
  1316. * @since 4.2
  1317. */
  1318. public String getCleanFilterCommand() throws IOException {
  1319. if (cleanFilterCommandHolder == null) {
  1320. String cmd = null;
  1321. if (state.walk != null) {
  1322. cmd = state.walk
  1323. .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
  1324. }
  1325. cleanFilterCommandHolder = new Holder<>(cmd);
  1326. }
  1327. return cleanFilterCommandHolder.get();
  1328. }
  1329. /**
  1330. * Get the eol stream type for the current entry.
  1331. *
  1332. * @return the eol stream type for the current entry or <code>null</code> if
  1333. * it cannot be determined. When state or state.walk is null or the
  1334. * {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on a
  1335. * {@link org.eclipse.jgit.lib.Repository} then null is returned.
  1336. * @throws java.io.IOException
  1337. * @since 4.3
  1338. */
  1339. public EolStreamType getEolStreamType() throws IOException {
  1340. return getEolStreamType(null);
  1341. }
  1342. /**
  1343. * @param opType
  1344. * The operationtype (checkin/checkout) which should be used
  1345. * @return the eol stream type for the current entry or <code>null</code> if
  1346. * it cannot be determined. When state or state.walk is null or the
  1347. * {@link TreeWalk} is not based on a {@link Repository} then null
  1348. * is returned.
  1349. * @throws IOException
  1350. */
  1351. private EolStreamType getEolStreamType(OperationType opType)
  1352. throws IOException {
  1353. if (eolStreamTypeHolder == null) {
  1354. EolStreamType type = null;
  1355. if (state.walk != null) {
  1356. type = state.walk.getEolStreamType(opType);
  1357. OperationType operationType = opType != null ? opType
  1358. : state.walk.getOperationType();
  1359. if (OperationType.CHECKIN_OP.equals(operationType)
  1360. && EolStreamType.AUTO_LF.equals(type)
  1361. && hasCrLfInIndex(getDirCacheIterator())) {
  1362. // If text=auto (or core.autocrlf=true) and the file has
  1363. // already been committed with CR/LF, then don't convert.
  1364. type = EolStreamType.DIRECT;
  1365. }
  1366. } else {
  1367. switch (getOptions().getAutoCRLF()) {
  1368. case FALSE:
  1369. type = EolStreamType.DIRECT;
  1370. break;
  1371. case TRUE:
  1372. case INPUT:
  1373. type = EolStreamType.AUTO_LF;
  1374. break;
  1375. }
  1376. }
  1377. eolStreamTypeHolder = new Holder<>(type);
  1378. }
  1379. return eolStreamTypeHolder.get();
  1380. }
  1381. /**
  1382. * Determines whether the file was committed un-normalized. If the iterator
  1383. * points to a conflict entry, checks the "ours" version.
  1384. *
  1385. * @param dirCache
  1386. * iterator pointing to the current entry for the file in the
  1387. * index
  1388. * @return {@code true} if the file in the index is not binary and has CR/LF
  1389. * line endings, {@code false} otherwise
  1390. */
  1391. private boolean hasCrLfInIndex(DirCacheIterator dirCache) {
  1392. if (dirCache == null) {
  1393. return false;
  1394. }
  1395. // Read blob from index and check for CR/LF-delimited text.
  1396. DirCacheEntry entry = dirCache.getDirCacheEntry();
  1397. if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) {
  1398. ObjectId blobId = entry.getObjectId();
  1399. if (entry.getStage() > 0
  1400. && entry.getStage() != DirCacheEntry.STAGE_2) {
  1401. // Merge conflict: check ours (stage 2)
  1402. byte[] name = entry.getRawPath();
  1403. int i = 0;
  1404. while (!dirCache.eof()) {
  1405. dirCache.next(1);
  1406. i++;
  1407. entry = dirCache.getDirCacheEntry();
  1408. if (!Arrays.equals(name, entry.getRawPath())) {
  1409. break;
  1410. }
  1411. if (entry.getStage() == DirCacheEntry.STAGE_2) {
  1412. blobId = entry.getObjectId();
  1413. break;
  1414. }
  1415. }
  1416. dirCache.back(i);
  1417. }
  1418. try (ObjectReader reader = repository.newObjectReader()) {
  1419. ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB);
  1420. try {
  1421. return RawText.isCrLfText(loader.getCachedBytes());
  1422. } catch (LargeObjectException e) {
  1423. try (InputStream in = loader.openStream()) {
  1424. return RawText.isCrLfText(in);
  1425. }
  1426. }
  1427. } catch (IOException e) {
  1428. // Ignore and return false below
  1429. }
  1430. }
  1431. return false;
  1432. }
  1433. private boolean isDirectoryIgnored(String pathRel) throws IOException {
  1434. final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
  1435. final String base = TreeWalk.pathOf(this.path, 0, pOff);
  1436. final String pathAbs = concatPath(base, pathRel);
  1437. return isDirectoryIgnored(pathRel, pathAbs);
  1438. }
  1439. private boolean isDirectoryIgnored(String pathRel, String pathAbs)
  1440. throws IOException {
  1441. assert pathRel.length() == 0 || (pathRel.charAt(0) != '/'
  1442. && pathRel.charAt(pathRel.length() - 1) != '/');
  1443. assert pathAbs.length() == 0 || (pathAbs.charAt(0) != '/'
  1444. && pathAbs.charAt(pathAbs.length() - 1) != '/');
  1445. assert pathAbs.endsWith(pathRel);
  1446. Boolean ignored = state.directoryToIgnored.get(pathAbs);
  1447. if (ignored != null) {
  1448. return ignored.booleanValue();
  1449. }
  1450. final String parentRel = getParentPath(pathRel);
  1451. if (parentRel != null && isDirectoryIgnored(parentRel)) {
  1452. state.directoryToIgnored.put(pathAbs, Boolean.TRUE);
  1453. return true;
  1454. }
  1455. final IgnoreNode node = getIgnoreNode();
  1456. for (String p = pathRel; node != null
  1457. && !"".equals(p); p = getParentPath(p)) { //$NON-NLS-1$
  1458. ignored = node.checkIgnored(p, true);
  1459. if (ignored != null) {
  1460. state.directoryToIgnored.put(pathAbs, ignored);
  1461. return ignored.booleanValue();
  1462. }
  1463. }
  1464. if (!(this.parent instanceof WorkingTreeIterator)) {
  1465. state.directoryToIgnored.put(pathAbs, Boolean.FALSE);
  1466. return false;
  1467. }
  1468. final WorkingTreeIterator wtParent = (WorkingTreeIterator) this.parent;
  1469. final String parentRelPath = concatPath(
  1470. TreeWalk.pathOf(this.path, wtParent.pathOffset, pathOffset - 1),
  1471. pathRel);
  1472. assert concatPath(TreeWalk.pathOf(wtParent.path, 0,
  1473. Math.max(0, wtParent.pathOffset - 1)), parentRelPath)
  1474. .equals(pathAbs);
  1475. return wtParent.isDirectoryIgnored(parentRelPath, pathAbs);
  1476. }
  1477. private static String getParentPath(String path) {
  1478. final int slashIndex = path.lastIndexOf('/', path.length() - 2);
  1479. if (slashIndex > 0) {
  1480. return path.substring(path.charAt(0) == '/' ? 1 : 0, slashIndex);
  1481. }
  1482. return path.length() > 0 ? "" : null; //$NON-NLS-1$
  1483. }
  1484. private static String concatPath(String p1, String p2) {
  1485. return p1 + (p1.length() > 0 && p2.length() > 0 ? "/" : "") + p2; //$NON-NLS-1$ //$NON-NLS-2$
  1486. }
  1487. }