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