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