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 46KB

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