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