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 = new Comparator<Entry>() {
778 		@Override
779 		public int compare(Entry a, Entry b) {
780 			return Paths.compare(
781 					a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(),
782 					b.encodedName, 0, b.encodedNameLen, b.getMode().getBits());
783 		}
784 	};
785 
786 	/**
787 	 * Constructor helper.
788 	 *
789 	 * @param list
790 	 *            files in the subtree of the work tree this iterator operates
791 	 *            on
792 	 */
793 	protected void init(Entry[] list) {
794 		// Filter out nulls, . and .. as these are not valid tree entries,
795 		// also cache the encoded forms of the path names for efficient use
796 		// later on during sorting and iteration.
797 		//
798 		entries = list;
799 		int i, o;
800 
801 		final CharsetEncoder nameEncoder = state.nameEncoder;
802 		for (i = 0, o = 0; i < entries.length; i++) {
803 			final Entry e = entries[i];
804 			if (e == null)
805 				continue;
806 			final String name = e.getName();
807 			if (".".equals(name) || "..".equals(name)) //$NON-NLS-1$ //$NON-NLS-2$
808 				continue;
809 			if (Constants.DOT_GIT.equals(name))
810 				continue;
811 			if (Constants.DOT_GIT_IGNORE.equals(name))
812 				ignoreNode = new PerDirectoryIgnoreNode(e);
813 			if (Constants.DOT_GIT_ATTRIBUTES.equals(name))
814 				attributesNode = new PerDirectoryAttributesNode(e);
815 			if (i != o)
816 				entries[o] = e;
817 			e.encodeName(nameEncoder);
818 			o++;
819 		}
820 		entryCnt = o;
821 		Arrays.sort(entries, 0, entryCnt, ENTRY_CMP);
822 
823 		contentIdFromPtr = -1;
824 		ptr = 0;
825 		if (!eof())
826 			parseEntry();
827 		else if (pathLen == 0) // see bug 445363
828 			pathLen = pathOffset;
829 	}
830 
831 	/**
832 	 * Obtain the current entry from this iterator.
833 	 *
834 	 * @return the currently selected entry.
835 	 */
836 	protected Entry current() {
837 		return entries[ptr];
838 	}
839 
840 	/**
841 	 * The result of a metadata-comparison between the current entry and a
842 	 * {@link DirCacheEntry}
843 	 */
844 	public enum MetadataDiff {
845 		/**
846 		 * The entries are equal by metaData (mode, length,
847 		 * modification-timestamp) or the <code>assumeValid</code> attribute of
848 		 * the index entry is set
849 		 */
850 		EQUAL,
851 
852 		/**
853 		 * The entries are not equal by metaData (mode, length) or the
854 		 * <code>isUpdateNeeded</code> attribute of the index entry is set
855 		 */
856 		DIFFER_BY_METADATA,
857 
858 		/** index entry is smudged - can't use that entry for comparison */
859 		SMUDGED,
860 
861 		/**
862 		 * The entries are equal by metaData (mode, length) but differ by
863 		 * modification-timestamp.
864 		 */
865 		DIFFER_BY_TIMESTAMP
866 	}
867 
868 	/**
869 	 * Is the file mode of the current entry different than the given raw mode?
870 	 *
871 	 * @param rawMode
872 	 *            an int.
873 	 * @return true if different, false otherwise
874 	 */
875 	public boolean isModeDifferent(int rawMode) {
876 		// Determine difference in mode-bits of file and index-entry. In the
877 		// bitwise presentation of modeDiff we'll have a '1' when the two modes
878 		// differ at this position.
879 		int modeDiff = getEntryRawMode() ^ rawMode;
880 
881 		if (modeDiff == 0)
882 			return false;
883 
884 		// Do not rely on filemode differences in case of symbolic links
885 		if (getOptions().getSymLinks() == SymLinks.FALSE)
886 			if (FileMode.SYMLINK.equals(rawMode))
887 				return false;
888 
889 		// Ignore the executable file bits if WorkingTreeOptions tell me to
890 		// do so. Ignoring is done by setting the bits representing a
891 		// EXECUTABLE_FILE to '0' in modeDiff
892 		if (!state.options.isFileMode())
893 			modeDiff &= ~FileMode.EXECUTABLE_FILE.getBits();
894 		return modeDiff != 0;
895 	}
896 
897 	/**
898 	 * Compare the metadata (mode, length, modification-timestamp) of the
899 	 * current entry and a {@link org.eclipse.jgit.dircache.DirCacheEntry}
900 	 *
901 	 * @param entry
902 	 *            the {@link org.eclipse.jgit.dircache.DirCacheEntry} to compare
903 	 *            with
904 	 * @return a
905 	 *         {@link org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff}
906 	 *         which tells whether and how the entries metadata differ
907 	 */
908 	public MetadataDiff compareMetadata(DirCacheEntry entry) {
909 		if (entry.isAssumeValid())
910 			return MetadataDiff.EQUAL;
911 
912 		if (entry.isUpdateNeeded())
913 			return MetadataDiff.DIFFER_BY_METADATA;
914 
915 		if (isModeDifferent(entry.getRawMode()))
916 			return MetadataDiff.DIFFER_BY_METADATA;
917 
918 		// Don't check for length or lastmodified on folders
919 		int type = mode & FileMode.TYPE_MASK;
920 		if (type == FileMode.TYPE_TREE || type == FileMode.TYPE_GITLINK)
921 			return MetadataDiff.EQUAL;
922 
923 		if (!entry.isSmudged() && entry.getLength() != (int) getEntryLength())
924 			return MetadataDiff.DIFFER_BY_METADATA;
925 
926 		// Git under windows only stores seconds so we round the timestamp
927 		// Java gives us if it looks like the timestamp in index is seconds
928 		// only. Otherwise we compare the timestamp at millisecond precision,
929 		// unless core.checkstat is set to "minimal", in which case we only
930 		// compare the whole second part.
931 		long cacheLastModified = entry.getLastModified();
932 		long fileLastModified = getEntryLastModified();
933 		long lastModifiedMillis = fileLastModified % 1000;
934 		long cacheMillis = cacheLastModified % 1000;
935 		if (getOptions().getCheckStat() == CheckStat.MINIMAL) {
936 			fileLastModified = fileLastModified - lastModifiedMillis;
937 			cacheLastModified = cacheLastModified - cacheMillis;
938 		} else if (cacheMillis == 0)
939 			fileLastModified = fileLastModified - lastModifiedMillis;
940 		// Some Java version on Linux return whole seconds only even when
941 		// the file systems supports more precision.
942 		else if (lastModifiedMillis == 0)
943 			cacheLastModified = cacheLastModified - cacheMillis;
944 
945 		if (fileLastModified != cacheLastModified)
946 			return MetadataDiff.DIFFER_BY_TIMESTAMP;
947 		else if (!entry.isSmudged())
948 			// The file is clean when you look at timestamps.
949 			return MetadataDiff.EQUAL;
950 		else
951 			return MetadataDiff.SMUDGED;
952 	}
953 
954 	/**
955 	 * Checks whether this entry differs from a given entry from the
956 	 * {@link org.eclipse.jgit.dircache.DirCache}.
957 	 *
958 	 * File status information is used and if status is same we consider the
959 	 * file identical to the state in the working directory. Native git uses
960 	 * more stat fields than we have accessible in Java.
961 	 *
962 	 * @param entry
963 	 *            the entry from the dircache we want to compare against
964 	 * @param forceContentCheck
965 	 *            True if the actual file content should be checked if
966 	 *            modification time differs.
967 	 * @param reader
968 	 *            access to repository objects if necessary. Should not be null.
969 	 * @return true if content is most likely different.
970 	 * @throws java.io.IOException
971 	 * @since 3.3
972 	 */
973 	public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
974 			ObjectReader reader) throws IOException {
975 		if (entry == null)
976 			return !FileMode.MISSING.equals(getEntryFileMode());
977 		MetadataDiff diff = compareMetadata(entry);
978 		switch (diff) {
979 		case DIFFER_BY_TIMESTAMP:
980 			if (forceContentCheck)
981 				// But we are told to look at content even though timestamps
982 				// tell us about modification
983 				return contentCheck(entry, reader);
984 			else
985 				// We are told to assume a modification if timestamps differs
986 				return true;
987 		case SMUDGED:
988 			// The file is clean by timestamps but the entry was smudged.
989 			// Lets do a content check
990 			return contentCheck(entry, reader);
991 		case EQUAL:
992 			if (mode == FileMode.SYMLINK.getBits()) {
993 				return contentCheck(entry, reader);
994 			}
995 			return false;
996 		case DIFFER_BY_METADATA:
997 			if (mode == FileMode.TREE.getBits()
998 					&& entry.getFileMode().equals(FileMode.GITLINK)) {
999 				byte[] idBuffer = idBuffer();
1000 				int idOffset = idOffset();
1001 				if (entry.getObjectId().compareTo(idBuffer, idOffset) == 0) {
1002 					return true;
1003 				} else if (ObjectId.zeroId().compareTo(idBuffer,
1004 						idOffset) == 0) {
1005 					return new File(repository.getWorkTree(),
1006 							entry.getPathString()).list().length > 0;
1007 				}
1008 				return false;
1009 			} else if (mode == FileMode.SYMLINK.getBits())
1010 				return contentCheck(entry, reader);
1011 			return true;
1012 		default:
1013 			throw new IllegalStateException(MessageFormat.format(
1014 					JGitText.get().unexpectedCompareResult, diff.name()));
1015 		}
1016 	}
1017 
1018 	/**
1019 	 * Get the file mode to use for the current entry when it is to be updated
1020 	 * in the index.
1021 	 *
1022 	 * @param indexIter
1023 	 *            {@link org.eclipse.jgit.dircache.DirCacheIterator} positioned
1024 	 *            at the same entry as this iterator or null if no
1025 	 *            {@link org.eclipse.jgit.dircache.DirCacheIterator} is
1026 	 *            available at this iterator's current entry
1027 	 * @return index file mode
1028 	 */
1029 	public FileMode getIndexFileMode(DirCacheIterator indexIter) {
1030 		final FileMode wtMode = getEntryFileMode();
1031 		if (indexIter == null) {
1032 			return wtMode;
1033 		}
1034 		final FileMode iMode = indexIter.getEntryFileMode();
1035 		if (getOptions().isFileMode() && iMode != FileMode.GITLINK && iMode != FileMode.TREE) {
1036 			return wtMode;
1037 		}
1038 		if (!getOptions().isFileMode()) {
1039 			if (FileMode.REGULAR_FILE == wtMode
1040 					&& FileMode.EXECUTABLE_FILE == iMode) {
1041 				return iMode;
1042 			}
1043 			if (FileMode.EXECUTABLE_FILE == wtMode
1044 					&& FileMode.REGULAR_FILE == iMode) {
1045 				return iMode;
1046 			}
1047 		}
1048 		if (FileMode.GITLINK == iMode
1049 				&& FileMode.TREE == wtMode && !getOptions().isDirNoGitLinks()) {
1050 			return iMode;
1051 		}
1052 		if (FileMode.TREE == iMode
1053 				&& FileMode.GITLINK == wtMode) {
1054 			return iMode;
1055 		}
1056 		return wtMode;
1057 	}
1058 
1059 	/**
1060 	 * Compares the entries content with the content in the filesystem.
1061 	 * Unsmudges the entry when it is detected that it is clean.
1062 	 *
1063 	 * @param entry
1064 	 *            the entry to be checked
1065 	 * @param reader
1066 	 *            acccess to repository data if necessary
1067 	 * @return <code>true</code> if the content doesn't match,
1068 	 *         <code>false</code> if it matches
1069 	 * @throws IOException
1070 	 */
1071 	private boolean contentCheck(DirCacheEntry entry, ObjectReader reader)
1072 			throws IOException {
1073 		if (getEntryObjectId().equals(entry.getObjectId())) {
1074 			// Content has not changed
1075 
1076 			// We know the entry can't be racily clean because it's still clean.
1077 			// Therefore we unsmudge the entry!
1078 			// If by any chance we now unsmudge although we are still in the
1079 			// same time-slot as the last modification to the index file the
1080 			// next index write operation will smudge again.
1081 			// Caution: we are unsmudging just by setting the length of the
1082 			// in-memory entry object. It's the callers task to detect that we
1083 			// have modified the entry and to persist the modified index.
1084 			entry.setLength((int) getEntryLength());
1085 
1086 			return false;
1087 		} else {
1088 			if (mode == FileMode.SYMLINK.getBits()) {
1089 				return !new File(readSymlinkTarget(current())).equals(
1090 						new File(readContentAsNormalizedString(entry, reader)));
1091 			}
1092 			// Content differs: that's a real change, perhaps
1093 			if (reader == null) // deprecated use, do no further checks
1094 				return true;
1095 
1096 			switch (getEolStreamType()) {
1097 			case DIRECT:
1098 				return true;
1099 			default:
1100 				try {
1101 					ObjectLoader loader = reader.open(entry.getObjectId());
1102 					if (loader == null)
1103 						return true;
1104 
1105 					// We need to compute the length, but only if it is not
1106 					// a binary stream.
1107 					long dcInLen;
1108 					try (InputStream dcIn = new AutoLFInputStream(
1109 							loader.openStream(), true,
1110 							true /* abort if binary */)) {
1111 						dcInLen = computeLength(dcIn);
1112 					} catch (AutoLFInputStream.IsBinaryException e) {
1113 						return true;
1114 					}
1115 
1116 					try (InputStream dcIn = new AutoLFInputStream(
1117 							loader.openStream(), true)) {
1118 						byte[] autoCrLfHash = computeHash(dcIn, dcInLen);
1119 						boolean changed = getEntryObjectId()
1120 								.compareTo(autoCrLfHash, 0) != 0;
1121 						return changed;
1122 					}
1123 				} catch (IOException e) {
1124 					return true;
1125 				}
1126 			}
1127 		}
1128 	}
1129 
1130 	private static String readContentAsNormalizedString(DirCacheEntry entry,
1131 			ObjectReader reader) throws MissingObjectException, IOException {
1132 		ObjectLoader open = reader.open(entry.getObjectId());
1133 		byte[] cachedBytes = open.getCachedBytes();
1134 		return FS.detect().normalize(RawParseUtils.decode(cachedBytes));
1135 	}
1136 
1137 	/**
1138 	 * Reads the target of a symlink as a string. This default implementation
1139 	 * fully reads the entry's input stream and converts it to a normalized
1140 	 * string. Subclasses may override to provide more specialized
1141 	 * implementations.
1142 	 *
1143 	 * @param entry
1144 	 *            to read
1145 	 * @return the entry's content as a normalized string
1146 	 * @throws java.io.IOException
1147 	 *             if the entry cannot be read or does not denote a symlink
1148 	 * @since 4.6
1149 	 */
1150 	protected String readSymlinkTarget(Entry entry) throws IOException {
1151 		if (!entry.getMode().equals(FileMode.SYMLINK)) {
1152 			throw new java.nio.file.NotLinkException(entry.getName());
1153 		}
1154 		long length = entry.getLength();
1155 		byte[] content = new byte[(int) length];
1156 		try (InputStream is = entry.openInputStream()) {
1157 			int bytesRead = IO.readFully(is, content, 0);
1158 			return FS.detect()
1159 					.normalize(RawParseUtils.decode(content, 0, bytesRead));
1160 		}
1161 	}
1162 
1163 	private static long computeLength(InputStream in) throws IOException {
1164 		// Since we only care about the length, use skip. The stream
1165 		// may be able to more efficiently wade through its data.
1166 		//
1167 		long length = 0;
1168 		for (;;) {
1169 			long n = in.skip(1 << 20);
1170 			if (n <= 0)
1171 				break;
1172 			length += n;
1173 		}
1174 		return length;
1175 	}
1176 
1177 	private byte[] computeHash(InputStream in, long length) throws IOException {
1178 		SHA1 contentDigest = SHA1.newInstance();
1179 		final byte[] contentReadBuffer = state.contentReadBuffer;
1180 
1181 		contentDigest.update(hblob);
1182 		contentDigest.update((byte) ' ');
1183 
1184 		long sz = length;
1185 		if (sz == 0) {
1186 			contentDigest.update((byte) '0');
1187 		} else {
1188 			final int bufn = contentReadBuffer.length;
1189 			int p = bufn;
1190 			do {
1191 				contentReadBuffer[--p] = digits[(int) (sz % 10)];
1192 				sz /= 10;
1193 			} while (sz > 0);
1194 			contentDigest.update(contentReadBuffer, p, bufn - p);
1195 		}
1196 		contentDigest.update((byte) 0);
1197 
1198 		for (;;) {
1199 			final int r = in.read(contentReadBuffer);
1200 			if (r <= 0)
1201 				break;
1202 			contentDigest.update(contentReadBuffer, 0, r);
1203 			sz += r;
1204 		}
1205 		if (sz != length)
1206 			return zeroid;
1207 		return contentDigest.digest();
1208 	}
1209 
1210 	/**
1211 	 * A single entry within a working directory tree.
1212 	 *
1213 	 * @since 5.0
1214 	 */
1215 	public static abstract class Entry {
1216 		byte[] encodedName;
1217 
1218 		int encodedNameLen;
1219 
1220 		void encodeName(CharsetEncoder enc) {
1221 			final ByteBuffer b;
1222 			try {
1223 				b = enc.encode(CharBuffer.wrap(getName()));
1224 			} catch (CharacterCodingException e) {
1225 				// This should so never happen.
1226 				throw new RuntimeException(MessageFormat.format(
1227 						JGitText.get().unencodeableFile, getName()));
1228 			}
1229 
1230 			encodedNameLen = b.limit();
1231 			if (b.hasArray() && b.arrayOffset() == 0)
1232 				encodedName = b.array();
1233 			else
1234 				b.get(encodedName = new byte[encodedNameLen]);
1235 		}
1236 
1237 		@Override
1238 		public String toString() {
1239 			return getMode().toString() + " " + getName(); //$NON-NLS-1$
1240 		}
1241 
1242 		/**
1243 		 * Get the type of this entry.
1244 		 * <p>
1245 		 * <b>Note: Efficient implementation required.</b>
1246 		 * <p>
1247 		 * The implementation of this method must be efficient. If a subclass
1248 		 * needs to compute the value they should cache the reference within an
1249 		 * instance member instead.
1250 		 *
1251 		 * @return a file mode constant from {@link FileMode}.
1252 		 */
1253 		public abstract FileMode getMode();
1254 
1255 		/**
1256 		 * Get the byte length of this entry.
1257 		 * <p>
1258 		 * <b>Note: Efficient implementation required.</b>
1259 		 * <p>
1260 		 * The implementation of this method must be efficient. If a subclass
1261 		 * needs to compute the value they should cache the reference within an
1262 		 * instance member instead.
1263 		 *
1264 		 * @return size of this file, in bytes.
1265 		 */
1266 		public abstract long getLength();
1267 
1268 		/**
1269 		 * Get the last modified time of this entry.
1270 		 * <p>
1271 		 * <b>Note: Efficient implementation required.</b>
1272 		 * <p>
1273 		 * The implementation of this method must be efficient. If a subclass
1274 		 * needs to compute the value they should cache the reference within an
1275 		 * instance member instead.
1276 		 *
1277 		 * @return time since the epoch (in ms) of the last change.
1278 		 */
1279 		public abstract long getLastModified();
1280 
1281 		/**
1282 		 * Get the name of this entry within its directory.
1283 		 * <p>
1284 		 * Efficient implementations are not required. The caller will obtain
1285 		 * the name only once and cache it once obtained.
1286 		 *
1287 		 * @return name of the entry.
1288 		 */
1289 		public abstract String getName();
1290 
1291 		/**
1292 		 * Obtain an input stream to read the file content.
1293 		 * <p>
1294 		 * Efficient implementations are not required. The caller will usually
1295 		 * obtain the stream only once per entry, if at all.
1296 		 * <p>
1297 		 * The input stream should not use buffering if the implementation can
1298 		 * avoid it. The caller will buffer as necessary to perform efficient
1299 		 * block IO operations.
1300 		 * <p>
1301 		 * The caller will close the stream once complete.
1302 		 *
1303 		 * @return a stream to read from the file.
1304 		 * @throws IOException
1305 		 *             the file could not be opened for reading.
1306 		 */
1307 		public abstract InputStream openInputStream() throws IOException;
1308 	}
1309 
1310 	/** Magic type indicating we know rules exist, but they aren't loaded. */
1311 	private static class PerDirectoryIgnoreNode extends IgnoreNode {
1312 		final Entry entry;
1313 
1314 		PerDirectoryIgnoreNode(Entry entry) {
1315 			super(Collections.<FastIgnoreRule> emptyList());
1316 			this.entry = entry;
1317 		}
1318 
1319 		IgnoreNode load() throws IOException {
1320 			IgnoreNode r = new IgnoreNode();
1321 			try (InputStream in = entry.openInputStream()) {
1322 				r.parse(in);
1323 			}
1324 			return r.getRules().isEmpty() ? null : r;
1325 		}
1326 	}
1327 
1328 	/** Magic type indicating there may be rules for the top level. */
1329 	private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
1330 		final Repository repository;
1331 
1332 		RootIgnoreNode(Entry entry, Repository repository) {
1333 			super(entry);
1334 			this.repository = repository;
1335 		}
1336 
1337 		@Override
1338 		IgnoreNode load() throws IOException {
1339 			IgnoreNode r;
1340 			if (entry != null) {
1341 				r = super.load();
1342 				if (r == null)
1343 					r = new IgnoreNode();
1344 			} else {
1345 				r = new IgnoreNode();
1346 			}
1347 
1348 			FS fs = repository.getFS();
1349 			String path = repository.getConfig().get(CoreConfig.KEY)
1350 					.getExcludesFile();
1351 			if (path != null) {
1352 				File excludesfile;
1353 				if (path.startsWith("~/")) //$NON-NLS-1$
1354 					excludesfile = fs.resolve(fs.userHome(), path.substring(2));
1355 				else
1356 					excludesfile = fs.resolve(null, path);
1357 				loadRulesFromFile(r, excludesfile);
1358 			}
1359 
1360 			File exclude = fs.resolve(repository.getDirectory(),
1361 					Constants.INFO_EXCLUDE);
1362 			loadRulesFromFile(r, exclude);
1363 
1364 			return r.getRules().isEmpty() ? null : r;
1365 		}
1366 
1367 		private static void loadRulesFromFile(IgnoreNode r, File exclude)
1368 				throws FileNotFoundException, IOException {
1369 			if (FS.DETECTED.exists(exclude)) {
1370 				try (FileInputStream in = new FileInputStream(exclude)) {
1371 					r.parse(in);
1372 				}
1373 			}
1374 		}
1375 	}
1376 
1377 	/** Magic type indicating we know rules exist, but they aren't loaded. */
1378 	private static class PerDirectoryAttributesNode extends AttributesNode {
1379 		final Entry entry;
1380 
1381 		PerDirectoryAttributesNode(Entry entry) {
1382 			super(Collections.<AttributesRule> emptyList());
1383 			this.entry = entry;
1384 		}
1385 
1386 		AttributesNode load() throws IOException {
1387 			AttributesNode r = new AttributesNode();
1388 			try (InputStream in = entry.openInputStream()) {
1389 				r.parse(in);
1390 			}
1391 			return r.getRules().isEmpty() ? null : r;
1392 		}
1393 	}
1394 
1395 
1396 	private static final class IteratorState {
1397 		/** Options used to process the working tree. */
1398 		final WorkingTreeOptions options;
1399 
1400 		/** File name character encoder. */
1401 		final CharsetEncoder nameEncoder;
1402 
1403 		/** Buffer used to perform {@link #contentId} computations. */
1404 		byte[] contentReadBuffer;
1405 
1406 		/** TreeWalk with a (supposedly) matching DirCacheIterator. */
1407 		TreeWalk walk;
1408 
1409 		/** Position of the matching {@link DirCacheIterator}. */
1410 		int dirCacheTree = -1;
1411 
1412 		/** Whether the iterator shall walk ignored directories. */
1413 		boolean walkIgnored = false;
1414 
1415 		final Map<String, Boolean> directoryToIgnored = new HashMap<>();
1416 
1417 		IteratorState(WorkingTreeOptions options) {
1418 			this.options = options;
1419 			this.nameEncoder = UTF_8.newEncoder();
1420 		}
1421 
1422 		void initializeReadBuffer() {
1423 			if (contentReadBuffer == null) {
1424 				contentReadBuffer = new byte[BUFFER_SIZE];
1425 			}
1426 		}
1427 	}
1428 
1429 	/**
1430 	 * Get the clean filter command for the current entry.
1431 	 *
1432 	 * @return the clean filter command for the current entry or
1433 	 *         <code>null</code> if no such command is defined
1434 	 * @throws java.io.IOException
1435 	 * @since 4.2
1436 	 */
1437 	public String getCleanFilterCommand() throws IOException {
1438 		if (cleanFilterCommandHolder == null) {
1439 			String cmd = null;
1440 			if (state.walk != null) {
1441 				cmd = state.walk
1442 						.getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN);
1443 			}
1444 			cleanFilterCommandHolder = new Holder<>(cmd);
1445 		}
1446 		return cleanFilterCommandHolder.get();
1447 	}
1448 
1449 	/**
1450 	 * Get the eol stream type for the current entry.
1451 	 *
1452 	 * @return the eol stream type for the current entry or <code>null</code> if
1453 	 *         it cannot be determined. When state or state.walk is null or the
1454 	 *         {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on a
1455 	 *         {@link org.eclipse.jgit.lib.Repository} then null is returned.
1456 	 * @throws java.io.IOException
1457 	 * @since 4.3
1458 	 */
1459 	public EolStreamType getEolStreamType() throws IOException {
1460 		return getEolStreamType(null);
1461 	}
1462 
1463 	/**
1464 	 * @param opType
1465 	 *            The operationtype (checkin/checkout) which should be used
1466 	 * @return the eol stream type for the current entry or <code>null</code> if
1467 	 *         it cannot be determined. When state or state.walk is null or the
1468 	 *         {@link TreeWalk} is not based on a {@link Repository} then null
1469 	 *         is returned.
1470 	 * @throws IOException
1471 	 */
1472 	private EolStreamType getEolStreamType(OperationType opType)
1473 			throws IOException {
1474 		if (eolStreamTypeHolder == null) {
1475 			EolStreamType type = null;
1476 			if (state.walk != null) {
1477 				type = state.walk.getEolStreamType(opType);
1478 				OperationType operationType = opType != null ? opType
1479 						: state.walk.getOperationType();
1480 				if (OperationType.CHECKIN_OP.equals(operationType)
1481 						&& EolStreamType.AUTO_LF.equals(type)
1482 						&& hasCrLfInIndex(getDirCacheIterator())) {
1483 					// If text=auto (or core.autocrlf=true) and the file has
1484 					// already been committed with CR/LF, then don't convert.
1485 					type = EolStreamType.DIRECT;
1486 				}
1487 			} else {
1488 				switch (getOptions().getAutoCRLF()) {
1489 				case FALSE:
1490 					type = EolStreamType.DIRECT;
1491 					break;
1492 				case TRUE:
1493 				case INPUT:
1494 					type = EolStreamType.AUTO_LF;
1495 					break;
1496 				}
1497 			}
1498 			eolStreamTypeHolder = new Holder<>(type);
1499 		}
1500 		return eolStreamTypeHolder.get();
1501 	}
1502 
1503 	/**
1504 	 * Determines whether the file was committed un-normalized. If the iterator
1505 	 * points to a conflict entry, checks the "ours" version.
1506 	 *
1507 	 * @param dirCache
1508 	 *            iterator pointing to the current entry for the file in the
1509 	 *            index
1510 	 * @return {@code true} if the file in the index is not binary and has CR/LF
1511 	 *         line endings, {@code false} otherwise
1512 	 */
1513 	private boolean hasCrLfInIndex(DirCacheIterator dirCache) {
1514 		if (dirCache == null) {
1515 			return false;
1516 		}
1517 		// Read blob from index and check for CR/LF-delimited text.
1518 		DirCacheEntry entry = dirCache.getDirCacheEntry();
1519 		if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) {
1520 			ObjectId blobId = entry.getObjectId();
1521 			if (entry.getStage() > 0
1522 					&& entry.getStage() != DirCacheEntry.STAGE_2) {
1523 				// Merge conflict: check ours (stage 2)
1524 				byte[] name = entry.getRawPath();
1525 				int i = 0;
1526 				while (!dirCache.eof()) {
1527 					dirCache.next(1);
1528 					i++;
1529 					entry = dirCache.getDirCacheEntry();
1530 					if (!Arrays.equals(name, entry.getRawPath())) {
1531 						break;
1532 					}
1533 					if (entry.getStage() == DirCacheEntry.STAGE_2) {
1534 						blobId = entry.getObjectId();
1535 						break;
1536 					}
1537 				}
1538 				dirCache.back(i);
1539 			}
1540 			try (ObjectReader reader = repository.newObjectReader()) {
1541 				ObjectLoader loader = reader.open(blobId, 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 		return false;
1554 	}
1555 
1556 	private boolean isDirectoryIgnored(String pathRel) throws IOException {
1557 		final int pOff = 0 < pathOffset ? pathOffset - 1 : pathOffset;
1558 		final String base = TreeWalk.pathOf(this.path, 0, pOff);
1559 		final String pathAbs = concatPath(base, pathRel);
1560 		return isDirectoryIgnored(pathRel, pathAbs);
1561 	}
1562 
1563 	private boolean isDirectoryIgnored(String pathRel, String pathAbs)
1564 			throws IOException {
1565 		assert pathRel.length() == 0 || (pathRel.charAt(0) != '/'
1566 				&& pathRel.charAt(pathRel.length() - 1) != '/');
1567 		assert pathAbs.length() == 0 || (pathAbs.charAt(0) != '/'
1568 				&& pathAbs.charAt(pathAbs.length() - 1) != '/');
1569 		assert pathAbs.endsWith(pathRel);
1570 
1571 		Boolean ignored = state.directoryToIgnored.get(pathAbs);
1572 		if (ignored != null) {
1573 			return ignored.booleanValue();
1574 		}
1575 
1576 		final String parentRel = getParentPath(pathRel);
1577 		if (parentRel != null && isDirectoryIgnored(parentRel)) {
1578 			state.directoryToIgnored.put(pathAbs, Boolean.TRUE);
1579 			return true;
1580 		}
1581 
1582 		final IgnoreNode node = getIgnoreNode();
1583 		for (String p = pathRel; node != null
1584 				&& !"".equals(p); p = getParentPath(p)) { //$NON-NLS-1$
1585 			ignored = node.checkIgnored(p, true);
1586 			if (ignored != null) {
1587 				state.directoryToIgnored.put(pathAbs, ignored);
1588 				return ignored.booleanValue();
1589 			}
1590 		}
1591 
1592 		if (!(this.parent instanceof WorkingTreeIterator)) {
1593 			state.directoryToIgnored.put(pathAbs, Boolean.FALSE);
1594 			return false;
1595 		}
1596 
1597 		final WorkingTreeIterator/eclipse/jgit/treewalk/WorkingTreeIterator.html#WorkingTreeIterator">WorkingTreeIterator wtParent = (WorkingTreeIterator) this.parent;
1598 		final String parentRelPath = concatPath(
1599 				TreeWalk.pathOf(this.path, wtParent.pathOffset, pathOffset - 1),
1600 				pathRel);
1601 		assert concatPath(TreeWalk.pathOf(wtParent.path, 0,
1602 				Math.max(0, wtParent.pathOffset - 1)), parentRelPath)
1603 						.equals(pathAbs);
1604 		return wtParent.isDirectoryIgnored(parentRelPath, pathAbs);
1605 	}
1606 
1607 	private static String getParentPath(String path) {
1608 		final int slashIndex = path.lastIndexOf('/', path.length() - 2);
1609 		if (slashIndex > 0) {
1610 			return path.substring(path.charAt(0) == '/' ? 1 : 0, slashIndex);
1611 		}
1612 		return path.length() > 0 ? "" : null; //$NON-NLS-1$
1613 	}
1614 
1615 	private static String concatPath(String p1, String p2) {
1616 		return p1 + (p1.length() > 0 && p2.length() > 0 ? "/" : "") + p2; //$NON-NLS-1$ //$NON-NLS-2$
1617 	}
1618 }