View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
5    * Copyright (C) 2013, Robin Stocker <robin@nibor.org>
6    * Copyright (C) 2014, Axel Richard <axel.richard@obeo.fr> and others
7    *
8    * This program and the accompanying materials are made available under the
9    * terms of the Eclipse Distribution License v. 1.0 which is available at
10   * https://www.eclipse.org/org/documents/edl-v10.php.
11   *
12   * SPDX-License-Identifier: BSD-3-Clause
13   */
14  
15  package org.eclipse.jgit.lib;
16  
17  import java.io.File;
18  import java.io.IOException;
19  import java.nio.file.DirectoryIteratorException;
20  import java.nio.file.DirectoryStream;
21  import java.nio.file.Files;
22  import java.text.MessageFormat;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.eclipse.jgit.dircache.DirCache;
32  import org.eclipse.jgit.dircache.DirCacheEntry;
33  import org.eclipse.jgit.dircache.DirCacheIterator;
34  import org.eclipse.jgit.errors.ConfigInvalidException;
35  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
36  import org.eclipse.jgit.errors.MissingObjectException;
37  import org.eclipse.jgit.errors.StopWalkException;
38  import org.eclipse.jgit.internal.JGitText;
39  import org.eclipse.jgit.revwalk.RevWalk;
40  import org.eclipse.jgit.submodule.SubmoduleWalk;
41  import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
42  import org.eclipse.jgit.treewalk.AbstractTreeIterator;
43  import org.eclipse.jgit.treewalk.EmptyTreeIterator;
44  import org.eclipse.jgit.treewalk.FileTreeIterator;
45  import org.eclipse.jgit.treewalk.TreeWalk;
46  import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
47  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
48  import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
49  import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
50  import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
51  import org.eclipse.jgit.treewalk.filter.TreeFilter;
52  
53  /**
54   * Compares the index, a tree, and the working directory Ignored files are not
55   * taken into account. The following information is retrieved:
56   * <ul>
57   * <li>added files</li>
58   * <li>changed files</li>
59   * <li>removed files</li>
60   * <li>missing files</li>
61   * <li>modified files</li>
62   * <li>conflicting files</li>
63   * <li>untracked files</li>
64   * <li>files with assume-unchanged flag</li>
65   * </ul>
66   */
67  public class IndexDiff {
68  
69  	/**
70  	 * Represents the state of the index for a certain path regarding the stages
71  	 * - which stages exist for a path and which not (base, ours, theirs).
72  	 * <p>
73  	 * This is used for figuring out what kind of conflict occurred.
74  	 *
75  	 * @see IndexDiff#getConflictingStageStates()
76  	 * @since 3.0
77  	 */
78  	public enum StageState {
79  		/**
80  		 * Exists in base, but neither in ours nor in theirs.
81  		 */
82  		BOTH_DELETED(1),
83  
84  		/**
85  		 * Only exists in ours.
86  		 */
87  		ADDED_BY_US(2),
88  
89  		/**
90  		 * Exists in base and ours, but no in theirs.
91  		 */
92  		DELETED_BY_THEM(3),
93  
94  		/**
95  		 * Only exists in theirs.
96  		 */
97  		ADDED_BY_THEM(4),
98  
99  		/**
100 		 * Exists in base and theirs, but not in ours.
101 		 */
102 		DELETED_BY_US(5),
103 
104 		/**
105 		 * Exists in ours and theirs, but not in base.
106 		 */
107 		BOTH_ADDED(6),
108 
109 		/**
110 		 * Exists in all stages, content conflict.
111 		 */
112 		BOTH_MODIFIED(7);
113 
114 		private final int stageMask;
115 
116 		private StageState(int stageMask) {
117 			this.stageMask = stageMask;
118 		}
119 
120 		int getStageMask() {
121 			return stageMask;
122 		}
123 
124 		/**
125 		 * @return whether there is a "base" stage entry
126 		 */
127 		public boolean hasBase() {
128 			return (stageMask & 1) != 0;
129 		}
130 
131 		/**
132 		 * @return whether there is an "ours" stage entry
133 		 */
134 		public boolean hasOurs() {
135 			return (stageMask & 2) != 0;
136 		}
137 
138 		/**
139 		 * @return whether there is a "theirs" stage entry
140 		 */
141 		public boolean hasTheirs() {
142 			return (stageMask & 4) != 0;
143 		}
144 
145 		static StageState fromMask(int stageMask) {
146 			// bits represent: theirs, ours, base
147 			switch (stageMask) {
148 			case 1: // 0b001
149 				return BOTH_DELETED;
150 			case 2: // 0b010
151 				return ADDED_BY_US;
152 			case 3: // 0b011
153 				return DELETED_BY_THEM;
154 			case 4: // 0b100
155 				return ADDED_BY_THEM;
156 			case 5: // 0b101
157 				return DELETED_BY_US;
158 			case 6: // 0b110
159 				return BOTH_ADDED;
160 			case 7: // 0b111
161 				return BOTH_MODIFIED;
162 			default:
163 				return null;
164 			}
165 		}
166 	}
167 
168 	private static final class ProgressReportingFilter extends TreeFilter {
169 
170 		private final ProgressMonitor monitor;
171 
172 		private int count = 0;
173 
174 		private int stepSize;
175 
176 		private final int total;
177 
178 		private ProgressReportingFilter(ProgressMonitor monitor, int total) {
179 			this.monitor = monitor;
180 			this.total = total;
181 			stepSize = total / 100;
182 			if (stepSize == 0)
183 				stepSize = 1000;
184 		}
185 
186 		@Override
187 		public boolean shouldBeRecursive() {
188 			return false;
189 		}
190 
191 		@Override
192 		public boolean include(TreeWalk walker)
193 				throws MissingObjectException,
194 				IncorrectObjectTypeException, IOException {
195 			count++;
196 			if (count % stepSize == 0) {
197 				if (count <= total)
198 					monitor.update(stepSize);
199 				if (monitor.isCancelled())
200 					throw StopWalkException.INSTANCE;
201 			}
202 			return true;
203 		}
204 
205 		@Override
206 		public TreeFilter clone() {
207 			throw new IllegalStateException(
208 					"Do not clone this kind of filter: " //$NON-NLS-1$
209 							+ getClass().getName());
210 		}
211 	}
212 
213 	private static final int TREE = 0;
214 
215 	private static final int INDEX = 1;
216 
217 	private static final int WORKDIR = 2;
218 
219 	private final Repository repository;
220 
221 	private final AnyObjectId tree;
222 
223 	private TreeFilter filter = null;
224 
225 	private final WorkingTreeIterator initialWorkingTreeIterator;
226 
227 	private Set<String> added = new HashSet<>();
228 
229 	private Set<String> changed = new HashSet<>();
230 
231 	private Set<String> removed = new HashSet<>();
232 
233 	private Set<String> missing = new HashSet<>();
234 
235 	private Set<String> missingSubmodules = new HashSet<>();
236 
237 	private Set<String> modified = new HashSet<>();
238 
239 	private Set<String> untracked = new HashSet<>();
240 
241 	private Map<String, StageState> conflicts = new HashMap<>();
242 
243 	private Set<String> ignored;
244 
245 	private Set<String> assumeUnchanged;
246 
247 	private DirCache dirCache;
248 
249 	private IndexDiffFilter indexDiffFilter;
250 
251 	private Map<String, IndexDiff> submoduleIndexDiffs = new HashMap<>();
252 
253 	private IgnoreSubmoduleMode ignoreSubmoduleMode = null;
254 
255 	private Map<FileMode, Set<String>> fileModes = new HashMap<>();
256 
257 	/**
258 	 * Construct an IndexDiff
259 	 *
260 	 * @param repository
261 	 *            a {@link org.eclipse.jgit.lib.Repository} object.
262 	 * @param revstr
263 	 *            symbolic name e.g. HEAD An EmptyTreeIterator is used if
264 	 *            <code>revstr</code> cannot be resolved.
265 	 * @param workingTreeIterator
266 	 *            iterator for working directory
267 	 * @throws java.io.IOException
268 	 */
269 	public IndexDiff(Repository repository, String revstr,
270 			WorkingTreeIterator workingTreeIterator) throws IOException {
271 		this(repository, repository.resolve(revstr), workingTreeIterator);
272 	}
273 
274 	/**
275 	 * Construct an Indexdiff
276 	 *
277 	 * @param repository
278 	 *            a {@link org.eclipse.jgit.lib.Repository} object.
279 	 * @param objectId
280 	 *            tree id. If null, an EmptyTreeIterator is used.
281 	 * @param workingTreeIterator
282 	 *            iterator for working directory
283 	 * @throws java.io.IOException
284 	 */
285 	public IndexDiff(Repository repository, ObjectId objectId,
286 			WorkingTreeIterator workingTreeIterator) throws IOException {
287 		this.repository = repository;
288 		if (objectId != null) {
289 			try (RevWalk rw = new RevWalk(repository)) {
290 				tree = rw.parseTree(objectId);
291 			}
292 		} else {
293 			tree = null;
294 		}
295 		this.initialWorkingTreeIterator = workingTreeIterator;
296 	}
297 
298 	/**
299 	 * Defines how modifications in submodules are treated
300 	 *
301 	 * @param mode
302 	 *            defines how modifications in submodules are treated
303 	 * @since 3.6
304 	 */
305 	public void setIgnoreSubmoduleMode(IgnoreSubmoduleMode mode) {
306 		this.ignoreSubmoduleMode = mode;
307 	}
308 
309 	/**
310 	 * A factory to producing WorkingTreeIterators
311 	 * @since 3.6
312 	 */
313 	public interface WorkingTreeIteratorFactory {
314 		/**
315 		 * @param repo
316 		 *            the repository
317 		 * @return working tree iterator
318 		 */
319 		public WorkingTreeIterator getWorkingTreeIterator(Repository repo);
320 	}
321 
322 	private WorkingTreeIteratorFactory wTreeIt = FileTreeIterator::new;
323 
324 	/**
325 	 * Allows higher layers to set the factory for WorkingTreeIterators.
326 	 *
327 	 * @param wTreeIt
328 	 * @since 3.6
329 	 */
330 	public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
331 		this.wTreeIt = wTreeIt;
332 	}
333 
334 	/**
335 	 * Sets a filter. Can be used e.g. for restricting the tree walk to a set of
336 	 * files.
337 	 *
338 	 * @param filter
339 	 *            a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} object.
340 	 */
341 	public void setFilter(TreeFilter filter) {
342 		this.filter = filter;
343 	}
344 
345 	/**
346 	 * Run the diff operation. Until this is called, all lists will be empty.
347 	 * Use {@link #diff(ProgressMonitor, int, int, String)} if a progress
348 	 * monitor is required.
349 	 *
350 	 * @return if anything is different between index, tree, and workdir
351 	 * @throws java.io.IOException
352 	 */
353 	public boolean diff() throws IOException {
354 		return diff(null);
355 	}
356 
357 	/**
358 	 * Run the diff operation. Until this is called, all lists will be empty.
359 	 * Use
360 	 * {@link #diff(ProgressMonitor, int, int, String, RepositoryBuilderFactory)}
361 	 * if a progress monitor is required.
362 	 * <p>
363 	 * The operation may create repositories for submodules using builders
364 	 * provided by the given {@code factory}, if any, and will also close these
365 	 * submodule repositories again.
366 	 * </p>
367 	 *
368 	 * @param factory
369 	 *            the {@link RepositoryBuilderFactory} to use to create builders
370 	 *            to create submodule repositories, if needed; if {@code null},
371 	 *            submodule repositories will be built using a plain
372 	 *            {@link RepositoryBuilder}.
373 	 * @return if anything is different between index, tree, and workdir
374 	 * @throws java.io.IOException
375 	 * @since 5.6
376 	 */
377 	public boolean diff(RepositoryBuilderFactory factory)
378 			throws IOException {
379 		return diff(null, 0, 0, "", factory); //$NON-NLS-1$
380 	}
381 
382 	/**
383 	 * Run the diff operation. Until this is called, all lists will be empty.
384 	 * <p>
385 	 * The operation may be aborted by the progress monitor. In that event it
386 	 * will report what was found before the cancel operation was detected.
387 	 * Callers should ignore the result if monitor.isCancelled() is true. If a
388 	 * progress monitor is not needed, callers should use {@link #diff()}
389 	 * instead. Progress reporting is crude and approximate and only intended
390 	 * for informing the user.
391 	 *
392 	 * @param monitor
393 	 *            for reporting progress, may be null
394 	 * @param estWorkTreeSize
395 	 *            number or estimated files in the working tree
396 	 * @param estIndexSize
397 	 *            number of estimated entries in the cache
398 	 * @param title a {@link java.lang.String} object.
399 	 * @return if anything is different between index, tree, and workdir
400 	 * @throws java.io.IOException
401 	 */
402 	public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
403 			int estIndexSize, final String title)
404 			throws IOException {
405 		return diff(monitor, estWorkTreeSize, estIndexSize, title, null);
406 	}
407 
408 	/**
409 	 * Run the diff operation. Until this is called, all lists will be empty.
410 	 * <p>
411 	 * The operation may be aborted by the progress monitor. In that event it
412 	 * will report what was found before the cancel operation was detected.
413 	 * Callers should ignore the result if monitor.isCancelled() is true. If a
414 	 * progress monitor is not needed, callers should use {@link #diff()}
415 	 * instead. Progress reporting is crude and approximate and only intended
416 	 * for informing the user.
417 	 * </p>
418 	 * <p>
419 	 * The operation may create repositories for submodules using builders
420 	 * provided by the given {@code factory}, if any, and will also close these
421 	 * submodule repositories again.
422 	 * </p>
423 	 *
424 	 * @param monitor
425 	 *            for reporting progress, may be null
426 	 * @param estWorkTreeSize
427 	 *            number or estimated files in the working tree
428 	 * @param estIndexSize
429 	 *            number of estimated entries in the cache
430 	 * @param title
431 	 *            a {@link java.lang.String} object.
432 	 * @param factory
433 	 *            the {@link RepositoryBuilderFactory} to use to create builders
434 	 *            to create submodule repositories, if needed; if {@code null},
435 	 *            submodule repositories will be built using a plain
436 	 *            {@link RepositoryBuilder}.
437 	 * @return if anything is different between index, tree, and workdir
438 	 * @throws java.io.IOException
439 	 * @since 5.6
440 	 */
441 	public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
442 			int estIndexSize, String title, RepositoryBuilderFactory factory)
443 			throws IOException {
444 		dirCache = repository.readDirCache();
445 
446 		try (TreeWalk treeWalk = new TreeWalk(repository)) {
447 			treeWalk.setOperationType(OperationType.CHECKIN_OP);
448 			treeWalk.setRecursive(true);
449 			// add the trees (tree, dirchache, workdir)
450 			if (tree != null)
451 				treeWalk.addTree(tree);
452 			else
453 				treeWalk.addTree(new EmptyTreeIterator());
454 			treeWalk.addTree(new DirCacheIterator(dirCache));
455 			treeWalk.addTree(initialWorkingTreeIterator);
456 			initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
457 			Collection<TreeFilter> filters = new ArrayList<>(4);
458 
459 			if (monitor != null) {
460 				// Get the maximum size of the work tree and index
461 				// and add some (quite arbitrary)
462 				if (estIndexSize == 0)
463 					estIndexSize = dirCache.getEntryCount();
464 				int total = Math.max(estIndexSize * 10 / 9,
465 						estWorkTreeSize * 10 / 9);
466 				monitor.beginTask(title, total);
467 				filters.add(new ProgressReportingFilter(monitor, total));
468 			}
469 
470 			if (filter != null)
471 				filters.add(filter);
472 			filters.add(new SkipWorkTreeFilter(INDEX));
473 			indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR);
474 			filters.add(indexDiffFilter);
475 			treeWalk.setFilter(AndTreeFilter.create(filters));
476 			fileModes.clear();
477 			while (treeWalk.next()) {
478 				AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
479 						AbstractTreeIterator.class);
480 				DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX,
481 						DirCacheIterator.class);
482 				WorkingTreeIterator workingTreeIterator = treeWalk
483 						.getTree(WORKDIR, WorkingTreeIterator.class);
484 
485 				if (dirCacheIterator != null) {
486 					final DirCacheEntry dirCacheEntry = dirCacheIterator
487 							.getDirCacheEntry();
488 					if (dirCacheEntry != null) {
489 						int stage = dirCacheEntry.getStage();
490 						if (stage > 0) {
491 							String path = treeWalk.getPathString();
492 							addConflict(path, stage);
493 							continue;
494 						}
495 					}
496 				}
497 
498 				if (treeIterator != null) {
499 					if (dirCacheIterator != null) {
500 						if (!treeIterator.idEqual(dirCacheIterator)
501 								|| treeIterator
502 										.getEntryRawMode() != dirCacheIterator
503 												.getEntryRawMode()) {
504 							// in repo, in index, content diff => changed
505 							if (!isEntryGitLink(treeIterator)
506 									|| !isEntryGitLink(dirCacheIterator)
507 									|| ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
508 								changed.add(treeWalk.getPathString());
509 						}
510 					} else {
511 						// in repo, not in index => removed
512 						if (!isEntryGitLink(treeIterator)
513 								|| ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
514 							removed.add(treeWalk.getPathString());
515 						if (workingTreeIterator != null)
516 							untracked.add(treeWalk.getPathString());
517 					}
518 				} else {
519 					if (dirCacheIterator != null) {
520 						// not in repo, in index => added
521 						if (!isEntryGitLink(dirCacheIterator)
522 								|| ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
523 							added.add(treeWalk.getPathString());
524 					} else {
525 						// not in repo, not in index => untracked
526 						if (workingTreeIterator != null
527 								&& !workingTreeIterator.isEntryIgnored()) {
528 							untracked.add(treeWalk.getPathString());
529 						}
530 					}
531 				}
532 
533 				if (dirCacheIterator != null) {
534 					if (workingTreeIterator == null) {
535 						// in index, not in workdir => missing
536 						boolean isGitLink = isEntryGitLink(dirCacheIterator);
537 						if (!isGitLink
538 								|| ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
539 							String path = treeWalk.getPathString();
540 							missing.add(path);
541 							if (isGitLink) {
542 								missingSubmodules.add(path);
543 							}
544 						}
545 					} else {
546 						if (workingTreeIterator.isModified(
547 								dirCacheIterator.getDirCacheEntry(), true,
548 								treeWalk.getObjectReader())) {
549 							// in index, in workdir, content differs => modified
550 							if (!isEntryGitLink(dirCacheIterator)
551 									|| !isEntryGitLink(workingTreeIterator)
552 									|| (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL
553 											&& ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY))
554 								modified.add(treeWalk.getPathString());
555 						}
556 					}
557 				}
558 
559 				String path = treeWalk.getPathString();
560 				if (path != null) {
561 					for (int i = 0; i < treeWalk.getTreeCount(); i++) {
562 						recordFileMode(path, treeWalk.getFileMode(i));
563 					}
564 				}
565 			}
566 		}
567 
568 		if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
569 			try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
570 				smw.setTree(new DirCacheIterator(dirCache));
571 				smw.setBuilderFactory(factory);
572 				while (smw.next()) {
573 					IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
574 					try {
575 						if (localIgnoreSubmoduleMode == null)
576 							localIgnoreSubmoduleMode = smw.getModulesIgnore();
577 						if (IgnoreSubmoduleMode.ALL
578 								.equals(localIgnoreSubmoduleMode))
579 							continue;
580 					} catch (ConfigInvalidException e) {
581 						throw new IOException(MessageFormat.format(
582 								JGitText.get().invalidIgnoreParamSubmodule,
583 								smw.getPath()), e);
584 					}
585 					try (Repository subRepo = smw.getRepository()) {
586 						String subRepoPath = smw.getPath();
587 						if (subRepo != null) {
588 							ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
589 							if (subHead != null
590 									&& !subHead.equals(smw.getObjectId())) {
591 								modified.add(subRepoPath);
592 								recordFileMode(subRepoPath, FileMode.GITLINK);
593 							} else if (localIgnoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
594 								IndexDiff smid = submoduleIndexDiffs
595 										.get(smw.getPath());
596 								if (smid == null) {
597 									smid = new IndexDiff(subRepo,
598 											smw.getObjectId(),
599 											wTreeIt.getWorkingTreeIterator(
600 													subRepo));
601 									submoduleIndexDiffs.put(subRepoPath, smid);
602 								}
603 								if (smid.diff(factory)) {
604 									if (localIgnoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
605 											&& smid.getAdded().isEmpty()
606 											&& smid.getChanged().isEmpty()
607 											&& smid.getConflicting().isEmpty()
608 											&& smid.getMissing().isEmpty()
609 											&& smid.getModified().isEmpty()
610 											&& smid.getRemoved().isEmpty()) {
611 										continue;
612 									}
613 									modified.add(subRepoPath);
614 									recordFileMode(subRepoPath,
615 											FileMode.GITLINK);
616 								}
617 							}
618 						} else if (missingSubmodules.remove(subRepoPath)) {
619 							// If the directory is there and empty but the
620 							// submodule repository in .git/modules doesn't
621 							// exist yet it isn't "missing".
622 							File gitDir = new File(
623 									new File(repository.getDirectory(),
624 											Constants.MODULES),
625 									subRepoPath);
626 							if (!gitDir.isDirectory()) {
627 								File dir = SubmoduleWalk.getSubmoduleDirectory(
628 										repository, subRepoPath);
629 								if (dir.isDirectory() && !hasFiles(dir)) {
630 									missing.remove(subRepoPath);
631 								}
632 							}
633 						}
634 					}
635 				}
636 			}
637 
638 		}
639 
640 		// consume the remaining work
641 		if (monitor != null) {
642 			monitor.endTask();
643 		}
644 
645 		ignored = indexDiffFilter.getIgnoredPaths();
646 		if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
647 				&& missing.isEmpty() && modified.isEmpty()
648 				&& untracked.isEmpty()) {
649 			return false;
650 		}
651 		return true;
652 	}
653 
654 	private boolean hasFiles(File directory) {
655 		try (DirectoryStream<java.nio.file.Path> dir = Files
656 				.newDirectoryStream(directory.toPath())) {
657 			return dir.iterator().hasNext();
658 		} catch (DirectoryIteratorException | IOException e) {
659 			return false;
660 		}
661 	}
662 
663 	private void recordFileMode(String path, FileMode mode) {
664 		Set<String> values = fileModes.get(mode);
665 		if (path != null) {
666 			if (values == null) {
667 				values = new HashSet<>();
668 				fileModes.put(mode, values);
669 			}
670 			values.add(path);
671 		}
672 	}
673 
674 	private boolean isEntryGitLink(AbstractTreeIterator ti) {
675 		return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
676 				.getBits()));
677 	}
678 
679 	private void addConflict(String path, int stage) {
680 		StageState existingStageStates = conflicts.get(path);
681 		byte stageMask = 0;
682 		if (existingStageStates != null) {
683 			stageMask |= (byte) existingStageStates.getStageMask();
684 		}
685 		// stage 1 (base) should be shifted 0 times
686 		int shifts = stage - 1;
687 		stageMask |= (byte) (1 << shifts);
688 		StageState stageState = StageState.fromMask(stageMask);
689 		conflicts.put(path, stageState);
690 	}
691 
692 	/**
693 	 * Get list of files added to the index, not in the tree
694 	 *
695 	 * @return list of files added to the index, not in the tree
696 	 */
697 	public Set<String> getAdded() {
698 		return added;
699 	}
700 
701 	/**
702 	 * Get list of files changed from tree to index
703 	 *
704 	 * @return list of files changed from tree to index
705 	 */
706 	public Set<String> getChanged() {
707 		return changed;
708 	}
709 
710 	/**
711 	 * Get list of files removed from index, but in tree
712 	 *
713 	 * @return list of files removed from index, but in tree
714 	 */
715 	public Set<String> getRemoved() {
716 		return removed;
717 	}
718 
719 	/**
720 	 * Get list of files in index, but not filesystem
721 	 *
722 	 * @return list of files in index, but not filesystem
723 	 */
724 	public Set<String> getMissing() {
725 		return missing;
726 	}
727 
728 	/**
729 	 * Get list of files modified on disk relative to the index
730 	 *
731 	 * @return list of files modified on disk relative to the index
732 	 */
733 	public Set<String> getModified() {
734 		return modified;
735 	}
736 
737 	/**
738 	 * Get list of files that are not ignored, and not in the index.
739 	 *
740 	 * @return list of files that are not ignored, and not in the index.
741 	 */
742 	public Set<String> getUntracked() {
743 		return untracked;
744 	}
745 
746 	/**
747 	 * Get list of files that are in conflict, corresponds to the keys of
748 	 * {@link #getConflictingStageStates()}
749 	 *
750 	 * @return list of files that are in conflict, corresponds to the keys of
751 	 *         {@link #getConflictingStageStates()}
752 	 */
753 	public Set<String> getConflicting() {
754 		return conflicts.keySet();
755 	}
756 
757 	/**
758 	 * Get the map from each path of {@link #getConflicting()} to its
759 	 * corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState}
760 	 *
761 	 * @return the map from each path of {@link #getConflicting()} to its
762 	 *         corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState}
763 	 * @since 3.0
764 	 */
765 	public Map<String, StageState> getConflictingStageStates() {
766 		return conflicts;
767 	}
768 
769 	/**
770 	 * The method returns the list of ignored files and folders. Only the root
771 	 * folder of an ignored folder hierarchy is reported. If a/b/c is listed in
772 	 * the .gitignore then you should not expect a/b/c/d/e/f to be reported
773 	 * here. Only a/b/c will be reported. Furthermore only ignored files /
774 	 * folders are returned that are NOT in the index.
775 	 *
776 	 * @return list of files / folders that are ignored
777 	 */
778 	public Set<String> getIgnoredNotInIndex() {
779 		return ignored;
780 	}
781 
782 	/**
783 	 * Get list of files with the flag assume-unchanged
784 	 *
785 	 * @return list of files with the flag assume-unchanged
786 	 */
787 	public Set<String> getAssumeUnchanged() {
788 		if (assumeUnchanged == null) {
789 			HashSet<String> unchanged = new HashSet<>();
790 			for (int i = 0; i < dirCache.getEntryCount(); i++)
791 				if (dirCache.getEntry(i).isAssumeValid())
792 					unchanged.add(dirCache.getEntry(i).getPathString());
793 			assumeUnchanged = unchanged;
794 		}
795 		return assumeUnchanged;
796 	}
797 
798 	/**
799 	 * Get list of folders containing only untracked files/folders
800 	 *
801 	 * @return list of folders containing only untracked files/folders
802 	 */
803 	public Set<String> getUntrackedFolders() {
804 		return ((indexDiffFilter == null) ? Collections.<String> emptySet()
805 				: new HashSet<>(indexDiffFilter.getUntrackedFolders()));
806 	}
807 
808 	/**
809 	 * Get the file mode of the given path in the index
810 	 *
811 	 * @param path a {@link java.lang.String} object.
812 	 * @return file mode
813 	 */
814 	public FileMode getIndexMode(String path) {
815 		final DirCacheEntry entry = dirCache.getEntry(path);
816 		return entry != null ? entry.getFileMode() : FileMode.MISSING;
817 	}
818 
819 	/**
820 	 * Get the list of paths that IndexDiff has detected to differ and have the
821 	 * given file mode
822 	 *
823 	 * @param mode a {@link org.eclipse.jgit.lib.FileMode} object.
824 	 * @return the list of paths that IndexDiff has detected to differ and have
825 	 *         the given file mode
826 	 * @since 3.6
827 	 */
828 	public Set<String> getPathsWithIndexMode(FileMode mode) {
829 		Set<String> paths = fileModes.get(mode);
830 		if (paths == null)
831 			paths = new HashSet<>();
832 		return paths;
833 	}
834 }