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