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