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