IndexDiff.java

  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>
  7.  * and other copyright owners as documented in the project's IP log.
  8.  *
  9.  * This program and the accompanying materials are made available
  10.  * under the terms of the Eclipse Distribution License v1.0 which
  11.  * accompanies this distribution, is reproduced below, and is
  12.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  13.  *
  14.  * All rights reserved.
  15.  *
  16.  * Redistribution and use in source and binary forms, with or
  17.  * without modification, are permitted provided that the following
  18.  * conditions are met:
  19.  *
  20.  * - Redistributions of source code must retain the above copyright
  21.  *   notice, this list of conditions and the following disclaimer.
  22.  *
  23.  * - Redistributions in binary form must reproduce the above
  24.  *   copyright notice, this list of conditions and the following
  25.  *   disclaimer in the documentation and/or other materials provided
  26.  *   with the distribution.
  27.  *
  28.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  29.  *   names of its contributors may be used to endorse or promote
  30.  *   products derived from this software without specific prior
  31.  *   written permission.
  32.  *
  33.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  34.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  35.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  36.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  37.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  38.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  39.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  40.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  41.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  42.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  43.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  44.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  45.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  46.  */

  47. package org.eclipse.jgit.lib;

  48. import java.io.File;
  49. import java.io.IOException;
  50. import java.nio.file.DirectoryIteratorException;
  51. import java.nio.file.DirectoryStream;
  52. import java.nio.file.Files;
  53. import java.text.MessageFormat;
  54. import java.util.ArrayList;
  55. import java.util.Collection;
  56. import java.util.Collections;
  57. import java.util.HashMap;
  58. import java.util.HashSet;
  59. import java.util.Map;
  60. import java.util.Set;

  61. import org.eclipse.jgit.dircache.DirCache;
  62. import org.eclipse.jgit.dircache.DirCacheEntry;
  63. import org.eclipse.jgit.dircache.DirCacheIterator;
  64. import org.eclipse.jgit.errors.ConfigInvalidException;
  65. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  66. import org.eclipse.jgit.errors.MissingObjectException;
  67. import org.eclipse.jgit.errors.StopWalkException;
  68. import org.eclipse.jgit.internal.JGitText;
  69. import org.eclipse.jgit.revwalk.RevWalk;
  70. import org.eclipse.jgit.submodule.SubmoduleWalk;
  71. import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
  72. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  73. import org.eclipse.jgit.treewalk.EmptyTreeIterator;
  74. import org.eclipse.jgit.treewalk.FileTreeIterator;
  75. import org.eclipse.jgit.treewalk.TreeWalk;
  76. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  77. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  78. import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
  79. import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
  80. import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
  81. import org.eclipse.jgit.treewalk.filter.TreeFilter;

  82. /**
  83.  * Compares the index, a tree, and the working directory Ignored files are not
  84.  * taken into account. The following information is retrieved:
  85.  * <ul>
  86.  * <li>added files</li>
  87.  * <li>changed files</li>
  88.  * <li>removed files</li>
  89.  * <li>missing files</li>
  90.  * <li>modified files</li>
  91.  * <li>conflicting files</li>
  92.  * <li>untracked files</li>
  93.  * <li>files with assume-unchanged flag</li>
  94.  * </ul>
  95.  */
  96. public class IndexDiff {

  97.     /**
  98.      * Represents the state of the index for a certain path regarding the stages
  99.      * - which stages exist for a path and which not (base, ours, theirs).
  100.      * <p>
  101.      * This is used for figuring out what kind of conflict occurred.
  102.      *
  103.      * @see IndexDiff#getConflictingStageStates()
  104.      * @since 3.0
  105.      */
  106.     public static enum StageState {
  107.         /**
  108.          * Exists in base, but neither in ours nor in theirs.
  109.          */
  110.         BOTH_DELETED(1),

  111.         /**
  112.          * Only exists in ours.
  113.          */
  114.         ADDED_BY_US(2),

  115.         /**
  116.          * Exists in base and ours, but no in theirs.
  117.          */
  118.         DELETED_BY_THEM(3),

  119.         /**
  120.          * Only exists in theirs.
  121.          */
  122.         ADDED_BY_THEM(4),

  123.         /**
  124.          * Exists in base and theirs, but not in ours.
  125.          */
  126.         DELETED_BY_US(5),

  127.         /**
  128.          * Exists in ours and theirs, but not in base.
  129.          */
  130.         BOTH_ADDED(6),

  131.         /**
  132.          * Exists in all stages, content conflict.
  133.          */
  134.         BOTH_MODIFIED(7);

  135.         private final int stageMask;

  136.         private StageState(int stageMask) {
  137.             this.stageMask = stageMask;
  138.         }

  139.         int getStageMask() {
  140.             return stageMask;
  141.         }

  142.         /**
  143.          * @return whether there is a "base" stage entry
  144.          */
  145.         public boolean hasBase() {
  146.             return (stageMask & 1) != 0;
  147.         }

  148.         /**
  149.          * @return whether there is an "ours" stage entry
  150.          */
  151.         public boolean hasOurs() {
  152.             return (stageMask & 2) != 0;
  153.         }

  154.         /**
  155.          * @return whether there is a "theirs" stage entry
  156.          */
  157.         public boolean hasTheirs() {
  158.             return (stageMask & 4) != 0;
  159.         }

  160.         static StageState fromMask(int stageMask) {
  161.             // bits represent: theirs, ours, base
  162.             switch (stageMask) {
  163.             case 1: // 0b001
  164.                 return BOTH_DELETED;
  165.             case 2: // 0b010
  166.                 return ADDED_BY_US;
  167.             case 3: // 0b011
  168.                 return DELETED_BY_THEM;
  169.             case 4: // 0b100
  170.                 return ADDED_BY_THEM;
  171.             case 5: // 0b101
  172.                 return DELETED_BY_US;
  173.             case 6: // 0b110
  174.                 return BOTH_ADDED;
  175.             case 7: // 0b111
  176.                 return BOTH_MODIFIED;
  177.             default:
  178.                 return null;
  179.             }
  180.         }
  181.     }

  182.     private static final class ProgressReportingFilter extends TreeFilter {

  183.         private final ProgressMonitor monitor;

  184.         private int count = 0;

  185.         private int stepSize;

  186.         private final int total;

  187.         private ProgressReportingFilter(ProgressMonitor monitor, int total) {
  188.             this.monitor = monitor;
  189.             this.total = total;
  190.             stepSize = total / 100;
  191.             if (stepSize == 0)
  192.                 stepSize = 1000;
  193.         }

  194.         @Override
  195.         public boolean shouldBeRecursive() {
  196.             return false;
  197.         }

  198.         @Override
  199.         public boolean include(TreeWalk walker)
  200.                 throws MissingObjectException,
  201.                 IncorrectObjectTypeException, IOException {
  202.             count++;
  203.             if (count % stepSize == 0) {
  204.                 if (count <= total)
  205.                     monitor.update(stepSize);
  206.                 if (monitor.isCancelled())
  207.                     throw StopWalkException.INSTANCE;
  208.             }
  209.             return true;
  210.         }

  211.         @Override
  212.         public TreeFilter clone() {
  213.             throw new IllegalStateException(
  214.                     "Do not clone this kind of filter: " //$NON-NLS-1$
  215.                             + getClass().getName());
  216.         }
  217.     }

  218.     private final static int TREE = 0;

  219.     private final static int INDEX = 1;

  220.     private final static int WORKDIR = 2;

  221.     private final Repository repository;

  222.     private final AnyObjectId tree;

  223.     private TreeFilter filter = null;

  224.     private final WorkingTreeIterator initialWorkingTreeIterator;

  225.     private Set<String> added = new HashSet<>();

  226.     private Set<String> changed = new HashSet<>();

  227.     private Set<String> removed = new HashSet<>();

  228.     private Set<String> missing = new HashSet<>();

  229.     private Set<String> missingSubmodules = new HashSet<>();

  230.     private Set<String> modified = new HashSet<>();

  231.     private Set<String> untracked = new HashSet<>();

  232.     private Map<String, StageState> conflicts = new HashMap<>();

  233.     private Set<String> ignored;

  234.     private Set<String> assumeUnchanged;

  235.     private DirCache dirCache;

  236.     private IndexDiffFilter indexDiffFilter;

  237.     private Map<String, IndexDiff> submoduleIndexDiffs = new HashMap<>();

  238.     private IgnoreSubmoduleMode ignoreSubmoduleMode = null;

  239.     private Map<FileMode, Set<String>> fileModes = new HashMap<>();

  240.     /**
  241.      * Construct an IndexDiff
  242.      *
  243.      * @param repository
  244.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  245.      * @param revstr
  246.      *            symbolic name e.g. HEAD An EmptyTreeIterator is used if
  247.      *            <code>revstr</code> cannot be resolved.
  248.      * @param workingTreeIterator
  249.      *            iterator for working directory
  250.      * @throws java.io.IOException
  251.      */
  252.     public IndexDiff(Repository repository, String revstr,
  253.             WorkingTreeIterator workingTreeIterator) throws IOException {
  254.         this(repository, repository.resolve(revstr), workingTreeIterator);
  255.     }

  256.     /**
  257.      * Construct an Indexdiff
  258.      *
  259.      * @param repository
  260.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  261.      * @param objectId
  262.      *            tree id. If null, an EmptyTreeIterator is used.
  263.      * @param workingTreeIterator
  264.      *            iterator for working directory
  265.      * @throws java.io.IOException
  266.      */
  267.     public IndexDiff(Repository repository, ObjectId objectId,
  268.             WorkingTreeIterator workingTreeIterator) throws IOException {
  269.         this.repository = repository;
  270.         if (objectId != null) {
  271.             try (RevWalk rw = new RevWalk(repository)) {
  272.                 tree = rw.parseTree(objectId);
  273.             }
  274.         } else {
  275.             tree = null;
  276.         }
  277.         this.initialWorkingTreeIterator = workingTreeIterator;
  278.     }

  279.     /**
  280.      * Defines how modifications in submodules are treated
  281.      *
  282.      * @param mode
  283.      *            defines how modifications in submodules are treated
  284.      * @since 3.6
  285.      */
  286.     public void setIgnoreSubmoduleMode(IgnoreSubmoduleMode mode) {
  287.         this.ignoreSubmoduleMode = mode;
  288.     }

  289.     /**
  290.      * A factory to producing WorkingTreeIterators
  291.      * @since 3.6
  292.      */
  293.     public interface WorkingTreeIteratorFactory {
  294.         /**
  295.          * @param repo
  296.          *            the repository
  297.          * @return working tree iterator
  298.          */
  299.         public WorkingTreeIterator getWorkingTreeIterator(Repository repo);
  300.     }

  301.     private WorkingTreeIteratorFactory wTreeIt = new WorkingTreeIteratorFactory() {
  302.         @Override
  303.         public WorkingTreeIterator getWorkingTreeIterator(Repository repo) {
  304.             return new FileTreeIterator(repo);
  305.         }
  306.     };

  307.     /**
  308.      * Allows higher layers to set the factory for WorkingTreeIterators.
  309.      *
  310.      * @param wTreeIt
  311.      * @since 3.6
  312.      */
  313.     public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
  314.         this.wTreeIt = wTreeIt;
  315.     }

  316.     /**
  317.      * Sets a filter. Can be used e.g. for restricting the tree walk to a set of
  318.      * files.
  319.      *
  320.      * @param filter
  321.      *            a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} object.
  322.      */
  323.     public void setFilter(TreeFilter filter) {
  324.         this.filter = filter;
  325.     }

  326.     /**
  327.      * Run the diff operation. Until this is called, all lists will be empty.
  328.      * Use {@link #diff(ProgressMonitor, int, int, String)} if a progress
  329.      * monitor is required.
  330.      *
  331.      * @return if anything is different between index, tree, and workdir
  332.      * @throws java.io.IOException
  333.      */
  334.     public boolean diff() throws IOException {
  335.         return diff(null, 0, 0, ""); //$NON-NLS-1$
  336.     }

  337.     /**
  338.      * Run the diff operation. Until this is called, all lists will be empty.
  339.      * <p>
  340.      * The operation may be aborted by the progress monitor. In that event it
  341.      * will report what was found before the cancel operation was detected.
  342.      * Callers should ignore the result if monitor.isCancelled() is true. If a
  343.      * progress monitor is not needed, callers should use {@link #diff()}
  344.      * instead. Progress reporting is crude and approximate and only intended
  345.      * for informing the user.
  346.      *
  347.      * @param monitor
  348.      *            for reporting progress, may be null
  349.      * @param estWorkTreeSize
  350.      *            number or estimated files in the working tree
  351.      * @param estIndexSize
  352.      *            number of estimated entries in the cache
  353.      * @param title a {@link java.lang.String} object.
  354.      * @return if anything is different between index, tree, and workdir
  355.      * @throws java.io.IOException
  356.      */
  357.     public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
  358.             int estIndexSize, final String title)
  359.             throws IOException {
  360.         dirCache = repository.readDirCache();

  361.         try (TreeWalk treeWalk = new TreeWalk(repository)) {
  362.             treeWalk.setOperationType(OperationType.CHECKIN_OP);
  363.             treeWalk.setRecursive(true);
  364.             // add the trees (tree, dirchache, workdir)
  365.             if (tree != null)
  366.                 treeWalk.addTree(tree);
  367.             else
  368.                 treeWalk.addTree(new EmptyTreeIterator());
  369.             treeWalk.addTree(new DirCacheIterator(dirCache));
  370.             treeWalk.addTree(initialWorkingTreeIterator);
  371.             initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
  372.             Collection<TreeFilter> filters = new ArrayList<>(4);

  373.             if (monitor != null) {
  374.                 // Get the maximum size of the work tree and index
  375.                 // and add some (quite arbitrary)
  376.                 if (estIndexSize == 0)
  377.                     estIndexSize = dirCache.getEntryCount();
  378.                 int total = Math.max(estIndexSize * 10 / 9,
  379.                         estWorkTreeSize * 10 / 9);
  380.                 monitor.beginTask(title, total);
  381.                 filters.add(new ProgressReportingFilter(monitor, total));
  382.             }

  383.             if (filter != null)
  384.                 filters.add(filter);
  385.             filters.add(new SkipWorkTreeFilter(INDEX));
  386.             indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR);
  387.             filters.add(indexDiffFilter);
  388.             treeWalk.setFilter(AndTreeFilter.create(filters));
  389.             fileModes.clear();
  390.             while (treeWalk.next()) {
  391.                 AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
  392.                         AbstractTreeIterator.class);
  393.                 DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX,
  394.                         DirCacheIterator.class);
  395.                 WorkingTreeIterator workingTreeIterator = treeWalk
  396.                         .getTree(WORKDIR, WorkingTreeIterator.class);

  397.                 if (dirCacheIterator != null) {
  398.                     final DirCacheEntry dirCacheEntry = dirCacheIterator
  399.                             .getDirCacheEntry();
  400.                     if (dirCacheEntry != null) {
  401.                         int stage = dirCacheEntry.getStage();
  402.                         if (stage > 0) {
  403.                             String path = treeWalk.getPathString();
  404.                             addConflict(path, stage);
  405.                             continue;
  406.                         }
  407.                     }
  408.                 }

  409.                 if (treeIterator != null) {
  410.                     if (dirCacheIterator != null) {
  411.                         if (!treeIterator.idEqual(dirCacheIterator)
  412.                                 || treeIterator
  413.                                         .getEntryRawMode() != dirCacheIterator
  414.                                                 .getEntryRawMode()) {
  415.                             // in repo, in index, content diff => changed
  416.                             if (!isEntryGitLink(treeIterator)
  417.                                     || !isEntryGitLink(dirCacheIterator)
  418.                                     || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
  419.                                 changed.add(treeWalk.getPathString());
  420.                         }
  421.                     } else {
  422.                         // in repo, not in index => removed
  423.                         if (!isEntryGitLink(treeIterator)
  424.                                 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
  425.                             removed.add(treeWalk.getPathString());
  426.                         if (workingTreeIterator != null)
  427.                             untracked.add(treeWalk.getPathString());
  428.                     }
  429.                 } else {
  430.                     if (dirCacheIterator != null) {
  431.                         // not in repo, in index => added
  432.                         if (!isEntryGitLink(dirCacheIterator)
  433.                                 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
  434.                             added.add(treeWalk.getPathString());
  435.                     } else {
  436.                         // not in repo, not in index => untracked
  437.                         if (workingTreeIterator != null
  438.                                 && !workingTreeIterator.isEntryIgnored()) {
  439.                             untracked.add(treeWalk.getPathString());
  440.                         }
  441.                     }
  442.                 }

  443.                 if (dirCacheIterator != null) {
  444.                     if (workingTreeIterator == null) {
  445.                         // in index, not in workdir => missing
  446.                         boolean isGitLink = isEntryGitLink(dirCacheIterator);
  447.                         if (!isGitLink
  448.                                 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
  449.                             String path = treeWalk.getPathString();
  450.                             missing.add(path);
  451.                             if (isGitLink) {
  452.                                 missingSubmodules.add(path);
  453.                             }
  454.                         }
  455.                     } else {
  456.                         if (workingTreeIterator.isModified(
  457.                                 dirCacheIterator.getDirCacheEntry(), true,
  458.                                 treeWalk.getObjectReader())) {
  459.                             // in index, in workdir, content differs => modified
  460.                             if (!isEntryGitLink(dirCacheIterator)
  461.                                     || !isEntryGitLink(workingTreeIterator)
  462.                                     || (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL
  463.                                             && ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY))
  464.                                 modified.add(treeWalk.getPathString());
  465.                         }
  466.                     }
  467.                 }

  468.                 String path = treeWalk.getPathString();
  469.                 if (path != null) {
  470.                     for (int i = 0; i < treeWalk.getTreeCount(); i++) {
  471.                         recordFileMode(path, treeWalk.getFileMode(i));
  472.                     }
  473.                 }
  474.             }
  475.         }

  476.         if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
  477.             IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
  478.             SubmoduleWalk smw = SubmoduleWalk.forIndex(repository);
  479.             while (smw.next()) {
  480.                 try {
  481.                     if (localIgnoreSubmoduleMode == null)
  482.                         localIgnoreSubmoduleMode = smw.getModulesIgnore();
  483.                     if (IgnoreSubmoduleMode.ALL
  484.                             .equals(localIgnoreSubmoduleMode))
  485.                         continue;
  486.                 } catch (ConfigInvalidException e) {
  487.                     throw new IOException(MessageFormat.format(
  488.                             JGitText.get().invalidIgnoreParamSubmodule,
  489.                             smw.getPath()), e);
  490.                 }
  491.                 try (Repository subRepo = smw.getRepository()) {
  492.                     String subRepoPath = smw.getPath();
  493.                     if (subRepo != null) {
  494.                         ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
  495.                         if (subHead != null
  496.                                 && !subHead.equals(smw.getObjectId())) {
  497.                             modified.add(subRepoPath);
  498.                             recordFileMode(subRepoPath, FileMode.GITLINK);
  499.                         } else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
  500.                             IndexDiff smid = submoduleIndexDiffs.get(smw
  501.                                     .getPath());
  502.                             if (smid == null) {
  503.                                 smid = new IndexDiff(subRepo,
  504.                                         smw.getObjectId(),
  505.                                         wTreeIt.getWorkingTreeIterator(subRepo));
  506.                                 submoduleIndexDiffs.put(subRepoPath, smid);
  507.                             }
  508.                             if (smid.diff()) {
  509.                                 if (ignoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
  510.                                         && smid.getAdded().isEmpty()
  511.                                         && smid.getChanged().isEmpty()
  512.                                         && smid.getConflicting().isEmpty()
  513.                                         && smid.getMissing().isEmpty()
  514.                                         && smid.getModified().isEmpty()
  515.                                         && smid.getRemoved().isEmpty()) {
  516.                                     continue;
  517.                                 }
  518.                                 modified.add(subRepoPath);
  519.                                 recordFileMode(subRepoPath, FileMode.GITLINK);
  520.                             }
  521.                         }
  522.                     } else if (missingSubmodules.remove(subRepoPath)) {
  523.                         // If the directory is there and empty but the submodule
  524.                         // repository in .git/modules doesn't exist yet it isn't
  525.                         // "missing".
  526.                         File gitDir = new File(
  527.                                 new File(repository.getDirectory(),
  528.                                         Constants.MODULES),
  529.                                 subRepoPath);
  530.                         if (!gitDir.isDirectory()) {
  531.                             File dir = SubmoduleWalk.getSubmoduleDirectory(
  532.                                     repository, subRepoPath);
  533.                             if (dir.isDirectory() && !hasFiles(dir)) {
  534.                                 missing.remove(subRepoPath);
  535.                             }
  536.                         }
  537.                     }
  538.                 }
  539.             }

  540.         }

  541.         // consume the remaining work
  542.         if (monitor != null)
  543.             monitor.endTask();

  544.         ignored = indexDiffFilter.getIgnoredPaths();
  545.         if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
  546.                 && missing.isEmpty() && modified.isEmpty()
  547.                 && untracked.isEmpty())
  548.             return false;
  549.         else
  550.             return true;
  551.     }

  552.     private boolean hasFiles(File directory) {
  553.         try (DirectoryStream<java.nio.file.Path> dir = Files
  554.                 .newDirectoryStream(directory.toPath())) {
  555.             return dir.iterator().hasNext();
  556.         } catch (DirectoryIteratorException | IOException e) {
  557.             return false;
  558.         }
  559.     }

  560.     private void recordFileMode(String path, FileMode mode) {
  561.         Set<String> values = fileModes.get(mode);
  562.         if (path != null) {
  563.             if (values == null) {
  564.                 values = new HashSet<>();
  565.                 fileModes.put(mode, values);
  566.             }
  567.             values.add(path);
  568.         }
  569.     }

  570.     private boolean isEntryGitLink(AbstractTreeIterator ti) {
  571.         return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
  572.                 .getBits()));
  573.     }

  574.     private void addConflict(String path, int stage) {
  575.         StageState existingStageStates = conflicts.get(path);
  576.         byte stageMask = 0;
  577.         if (existingStageStates != null)
  578.             stageMask |= existingStageStates.getStageMask();
  579.         // stage 1 (base) should be shifted 0 times
  580.         int shifts = stage - 1;
  581.         stageMask |= (1 << shifts);
  582.         StageState stageState = StageState.fromMask(stageMask);
  583.         conflicts.put(path, stageState);
  584.     }

  585.     /**
  586.      * Get list of files added to the index, not in the tree
  587.      *
  588.      * @return list of files added to the index, not in the tree
  589.      */
  590.     public Set<String> getAdded() {
  591.         return added;
  592.     }

  593.     /**
  594.      * Get list of files changed from tree to index
  595.      *
  596.      * @return list of files changed from tree to index
  597.      */
  598.     public Set<String> getChanged() {
  599.         return changed;
  600.     }

  601.     /**
  602.      * Get list of files removed from index, but in tree
  603.      *
  604.      * @return list of files removed from index, but in tree
  605.      */
  606.     public Set<String> getRemoved() {
  607.         return removed;
  608.     }

  609.     /**
  610.      * Get list of files in index, but not filesystem
  611.      *
  612.      * @return list of files in index, but not filesystem
  613.      */
  614.     public Set<String> getMissing() {
  615.         return missing;
  616.     }

  617.     /**
  618.      * Get list of files modified on disk relative to the index
  619.      *
  620.      * @return list of files modified on disk relative to the index
  621.      */
  622.     public Set<String> getModified() {
  623.         return modified;
  624.     }

  625.     /**
  626.      * Get list of files that are not ignored, and not in the index.
  627.      *
  628.      * @return list of files that are not ignored, and not in the index.
  629.      */
  630.     public Set<String> getUntracked() {
  631.         return untracked;
  632.     }

  633.     /**
  634.      * Get list of files that are in conflict, corresponds to the keys of
  635.      * {@link #getConflictingStageStates()}
  636.      *
  637.      * @return list of files that are in conflict, corresponds to the keys of
  638.      *         {@link #getConflictingStageStates()}
  639.      */
  640.     public Set<String> getConflicting() {
  641.         return conflicts.keySet();
  642.     }

  643.     /**
  644.      * Get the map from each path of {@link #getConflicting()} to its
  645.      * corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState}
  646.      *
  647.      * @return the map from each path of {@link #getConflicting()} to its
  648.      *         corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState}
  649.      * @since 3.0
  650.      */
  651.     public Map<String, StageState> getConflictingStageStates() {
  652.         return conflicts;
  653.     }

  654.     /**
  655.      * The method returns the list of ignored files and folders. Only the root
  656.      * folder of an ignored folder hierarchy is reported. If a/b/c is listed in
  657.      * the .gitignore then you should not expect a/b/c/d/e/f to be reported
  658.      * here. Only a/b/c will be reported. Furthermore only ignored files /
  659.      * folders are returned that are NOT in the index.
  660.      *
  661.      * @return list of files / folders that are ignored
  662.      */
  663.     public Set<String> getIgnoredNotInIndex() {
  664.         return ignored;
  665.     }

  666.     /**
  667.      * Get list of files with the flag assume-unchanged
  668.      *
  669.      * @return list of files with the flag assume-unchanged
  670.      */
  671.     public Set<String> getAssumeUnchanged() {
  672.         if (assumeUnchanged == null) {
  673.             HashSet<String> unchanged = new HashSet<>();
  674.             for (int i = 0; i < dirCache.getEntryCount(); i++)
  675.                 if (dirCache.getEntry(i).isAssumeValid())
  676.                     unchanged.add(dirCache.getEntry(i).getPathString());
  677.             assumeUnchanged = unchanged;
  678.         }
  679.         return assumeUnchanged;
  680.     }

  681.     /**
  682.      * Get list of folders containing only untracked files/folders
  683.      *
  684.      * @return list of folders containing only untracked files/folders
  685.      */
  686.     public Set<String> getUntrackedFolders() {
  687.         return ((indexDiffFilter == null) ? Collections.<String> emptySet()
  688.                 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
  689.     }

  690.     /**
  691.      * Get the file mode of the given path in the index
  692.      *
  693.      * @param path a {@link java.lang.String} object.
  694.      * @return file mode
  695.      */
  696.     public FileMode getIndexMode(String path) {
  697.         final DirCacheEntry entry = dirCache.getEntry(path);
  698.         return entry != null ? entry.getFileMode() : FileMode.MISSING;
  699.     }

  700.     /**
  701.      * Get the list of paths that IndexDiff has detected to differ and have the
  702.      * given file mode
  703.      *
  704.      * @param mode a {@link org.eclipse.jgit.lib.FileMode} object.
  705.      * @return the list of paths that IndexDiff has detected to differ and have
  706.      *         the given file mode
  707.      * @since 3.6
  708.      */
  709.     public Set<String> getPathsWithIndexMode(FileMode mode) {
  710.         Set<String> paths = fileModes.get(mode);
  711.         if (paths == null)
  712.             paths = new HashSet<>();
  713.         return paths;
  714.     }
  715. }