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