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> 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. package org.eclipse.jgit.lib;

  15. import java.io.File;
  16. import java.io.IOException;
  17. import java.nio.file.DirectoryIteratorException;
  18. import java.nio.file.DirectoryStream;
  19. import java.nio.file.Files;
  20. import java.text.MessageFormat;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.HashSet;
  26. import java.util.Map;
  27. import java.util.Set;

  28. import org.eclipse.jgit.dircache.DirCache;
  29. import org.eclipse.jgit.dircache.DirCacheEntry;
  30. import org.eclipse.jgit.dircache.DirCacheIterator;
  31. import org.eclipse.jgit.errors.ConfigInvalidException;
  32. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  33. import org.eclipse.jgit.errors.MissingObjectException;
  34. import org.eclipse.jgit.errors.StopWalkException;
  35. import org.eclipse.jgit.internal.JGitText;
  36. import org.eclipse.jgit.revwalk.RevWalk;
  37. import org.eclipse.jgit.submodule.SubmoduleWalk;
  38. import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
  39. import org.eclipse.jgit.treewalk.AbstractTreeIterator;
  40. import org.eclipse.jgit.treewalk.EmptyTreeIterator;
  41. import org.eclipse.jgit.treewalk.FileTreeIterator;
  42. import org.eclipse.jgit.treewalk.TreeWalk;
  43. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  44. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  45. import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
  46. import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
  47. import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
  48. import org.eclipse.jgit.treewalk.filter.TreeFilter;

  49. /**
  50.  * Compares the index, a tree, and the working directory Ignored files are not
  51.  * taken into account. The following information is retrieved:
  52.  * <ul>
  53.  * <li>added files</li>
  54.  * <li>changed files</li>
  55.  * <li>removed files</li>
  56.  * <li>missing files</li>
  57.  * <li>modified files</li>
  58.  * <li>conflicting files</li>
  59.  * <li>untracked files</li>
  60.  * <li>files with assume-unchanged flag</li>
  61.  * </ul>
  62.  */
  63. public class IndexDiff {

  64.     /**
  65.      * Represents the state of the index for a certain path regarding the stages
  66.      * - which stages exist for a path and which not (base, ours, theirs).
  67.      * <p>
  68.      * This is used for figuring out what kind of conflict occurred.
  69.      *
  70.      * @see IndexDiff#getConflictingStageStates()
  71.      * @since 3.0
  72.      */
  73.     public enum StageState {
  74.         /**
  75.          * Exists in base, but neither in ours nor in theirs.
  76.          */
  77.         BOTH_DELETED(1),

  78.         /**
  79.          * Only exists in ours.
  80.          */
  81.         ADDED_BY_US(2),

  82.         /**
  83.          * Exists in base and ours, but no in theirs.
  84.          */
  85.         DELETED_BY_THEM(3),

  86.         /**
  87.          * Only exists in theirs.
  88.          */
  89.         ADDED_BY_THEM(4),

  90.         /**
  91.          * Exists in base and theirs, but not in ours.
  92.          */
  93.         DELETED_BY_US(5),

  94.         /**
  95.          * Exists in ours and theirs, but not in base.
  96.          */
  97.         BOTH_ADDED(6),

  98.         /**
  99.          * Exists in all stages, content conflict.
  100.          */
  101.         BOTH_MODIFIED(7);

  102.         private final int stageMask;

  103.         private StageState(int stageMask) {
  104.             this.stageMask = stageMask;
  105.         }

  106.         int getStageMask() {
  107.             return stageMask;
  108.         }

  109.         /**
  110.          * @return whether there is a "base" stage entry
  111.          */
  112.         public boolean hasBase() {
  113.             return (stageMask & 1) != 0;
  114.         }

  115.         /**
  116.          * @return whether there is an "ours" stage entry
  117.          */
  118.         public boolean hasOurs() {
  119.             return (stageMask & 2) != 0;
  120.         }

  121.         /**
  122.          * @return whether there is a "theirs" stage entry
  123.          */
  124.         public boolean hasTheirs() {
  125.             return (stageMask & 4) != 0;
  126.         }

  127.         static StageState fromMask(int stageMask) {
  128.             // bits represent: theirs, ours, base
  129.             switch (stageMask) {
  130.             case 1: // 0b001
  131.                 return BOTH_DELETED;
  132.             case 2: // 0b010
  133.                 return ADDED_BY_US;
  134.             case 3: // 0b011
  135.                 return DELETED_BY_THEM;
  136.             case 4: // 0b100
  137.                 return ADDED_BY_THEM;
  138.             case 5: // 0b101
  139.                 return DELETED_BY_US;
  140.             case 6: // 0b110
  141.                 return BOTH_ADDED;
  142.             case 7: // 0b111
  143.                 return BOTH_MODIFIED;
  144.             default:
  145.                 return null;
  146.             }
  147.         }
  148.     }

  149.     private static final class ProgressReportingFilter extends TreeFilter {

  150.         private final ProgressMonitor monitor;

  151.         private int count = 0;

  152.         private int stepSize;

  153.         private final int total;

  154.         private ProgressReportingFilter(ProgressMonitor monitor, int total) {
  155.             this.monitor = monitor;
  156.             this.total = total;
  157.             stepSize = total / 100;
  158.             if (stepSize == 0)
  159.                 stepSize = 1000;
  160.         }

  161.         @Override
  162.         public boolean shouldBeRecursive() {
  163.             return false;
  164.         }

  165.         @Override
  166.         public boolean include(TreeWalk walker)
  167.                 throws MissingObjectException,
  168.                 IncorrectObjectTypeException, IOException {
  169.             count++;
  170.             if (count % stepSize == 0) {
  171.                 if (count <= total)
  172.                     monitor.update(stepSize);
  173.                 if (monitor.isCancelled())
  174.                     throw StopWalkException.INSTANCE;
  175.             }
  176.             return true;
  177.         }

  178.         @Override
  179.         public TreeFilter clone() {
  180.             throw new IllegalStateException(
  181.                     "Do not clone this kind of filter: " //$NON-NLS-1$
  182.                             + getClass().getName());
  183.         }
  184.     }

  185.     private static final int TREE = 0;

  186.     private static final int INDEX = 1;

  187.     private static final int WORKDIR = 2;

  188.     private final Repository repository;

  189.     private final AnyObjectId tree;

  190.     private TreeFilter filter = null;

  191.     private final WorkingTreeIterator initialWorkingTreeIterator;

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

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

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

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

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

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

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

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

  200.     private Set<String> ignored;

  201.     private Set<String> assumeUnchanged;

  202.     private DirCache dirCache;

  203.     private IndexDiffFilter indexDiffFilter;

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

  205.     private IgnoreSubmoduleMode ignoreSubmoduleMode = null;

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

  207.     /**
  208.      * Construct an IndexDiff
  209.      *
  210.      * @param repository
  211.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  212.      * @param revstr
  213.      *            symbolic name e.g. HEAD An EmptyTreeIterator is used if
  214.      *            <code>revstr</code> cannot be resolved.
  215.      * @param workingTreeIterator
  216.      *            iterator for working directory
  217.      * @throws java.io.IOException
  218.      */
  219.     public IndexDiff(Repository repository, String revstr,
  220.             WorkingTreeIterator workingTreeIterator) throws IOException {
  221.         this(repository, repository.resolve(revstr), workingTreeIterator);
  222.     }

  223.     /**
  224.      * Construct an Indexdiff
  225.      *
  226.      * @param repository
  227.      *            a {@link org.eclipse.jgit.lib.Repository} object.
  228.      * @param objectId
  229.      *            tree id. If null, an EmptyTreeIterator is used.
  230.      * @param workingTreeIterator
  231.      *            iterator for working directory
  232.      * @throws java.io.IOException
  233.      */
  234.     public IndexDiff(Repository repository, ObjectId objectId,
  235.             WorkingTreeIterator workingTreeIterator) throws IOException {
  236.         this.repository = repository;
  237.         if (objectId != null) {
  238.             try (RevWalk rw = new RevWalk(repository)) {
  239.                 tree = rw.parseTree(objectId);
  240.             }
  241.         } else {
  242.             tree = null;
  243.         }
  244.         this.initialWorkingTreeIterator = workingTreeIterator;
  245.     }

  246.     /**
  247.      * Defines how modifications in submodules are treated
  248.      *
  249.      * @param mode
  250.      *            defines how modifications in submodules are treated
  251.      * @since 3.6
  252.      */
  253.     public void setIgnoreSubmoduleMode(IgnoreSubmoduleMode mode) {
  254.         this.ignoreSubmoduleMode = mode;
  255.     }

  256.     /**
  257.      * A factory to producing WorkingTreeIterators
  258.      * @since 3.6
  259.      */
  260.     public interface WorkingTreeIteratorFactory {
  261.         /**
  262.          * @param repo
  263.          *            the repository
  264.          * @return working tree iterator
  265.          */
  266.         public WorkingTreeIterator getWorkingTreeIterator(Repository repo);
  267.     }

  268.     private WorkingTreeIteratorFactory wTreeIt = FileTreeIterator::new;

  269.     /**
  270.      * Allows higher layers to set the factory for WorkingTreeIterators.
  271.      *
  272.      * @param wTreeIt
  273.      * @since 3.6
  274.      */
  275.     public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
  276.         this.wTreeIt = wTreeIt;
  277.     }

  278.     /**
  279.      * Sets a filter. Can be used e.g. for restricting the tree walk to a set of
  280.      * files.
  281.      *
  282.      * @param filter
  283.      *            a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} object.
  284.      */
  285.     public void setFilter(TreeFilter filter) {
  286.         this.filter = filter;
  287.     }

  288.     /**
  289.      * Run the diff operation. Until this is called, all lists will be empty.
  290.      * Use {@link #diff(ProgressMonitor, int, int, String)} if a progress
  291.      * monitor is required.
  292.      *
  293.      * @return if anything is different between index, tree, and workdir
  294.      * @throws java.io.IOException
  295.      */
  296.     public boolean diff() throws IOException {
  297.         return diff(null);
  298.     }

  299.     /**
  300.      * Run the diff operation. Until this is called, all lists will be empty.
  301.      * Use
  302.      * {@link #diff(ProgressMonitor, int, int, String, RepositoryBuilderFactory)}
  303.      * if a progress monitor is required.
  304.      * <p>
  305.      * The operation may create repositories for submodules using builders
  306.      * provided by the given {@code factory}, if any, and will also close these
  307.      * submodule repositories again.
  308.      * </p>
  309.      *
  310.      * @param factory
  311.      *            the {@link RepositoryBuilderFactory} to use to create builders
  312.      *            to create submodule repositories, if needed; if {@code null},
  313.      *            submodule repositories will be built using a plain
  314.      *            {@link RepositoryBuilder}.
  315.      * @return if anything is different between index, tree, and workdir
  316.      * @throws java.io.IOException
  317.      * @since 5.6
  318.      */
  319.     public boolean diff(RepositoryBuilderFactory factory)
  320.             throws IOException {
  321.         return diff(null, 0, 0, "", factory); //$NON-NLS-1$
  322.     }

  323.     /**
  324.      * Run the diff operation. Until this is called, all lists will be empty.
  325.      * <p>
  326.      * The operation may be aborted by the progress monitor. In that event it
  327.      * will report what was found before the cancel operation was detected.
  328.      * Callers should ignore the result if monitor.isCancelled() is true. If a
  329.      * progress monitor is not needed, callers should use {@link #diff()}
  330.      * instead. Progress reporting is crude and approximate and only intended
  331.      * for informing the user.
  332.      *
  333.      * @param monitor
  334.      *            for reporting progress, may be null
  335.      * @param estWorkTreeSize
  336.      *            number or estimated files in the working tree
  337.      * @param estIndexSize
  338.      *            number of estimated entries in the cache
  339.      * @param title a {@link java.lang.String} object.
  340.      * @return if anything is different between index, tree, and workdir
  341.      * @throws java.io.IOException
  342.      */
  343.     public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
  344.             int estIndexSize, final String title)
  345.             throws IOException {
  346.         return diff(monitor, estWorkTreeSize, estIndexSize, title, null);
  347.     }

  348.     /**
  349.      * Run the diff operation. Until this is called, all lists will be empty.
  350.      * <p>
  351.      * The operation may be aborted by the progress monitor. In that event it
  352.      * will report what was found before the cancel operation was detected.
  353.      * Callers should ignore the result if monitor.isCancelled() is true. If a
  354.      * progress monitor is not needed, callers should use {@link #diff()}
  355.      * instead. Progress reporting is crude and approximate and only intended
  356.      * for informing the user.
  357.      * </p>
  358.      * <p>
  359.      * The operation may create repositories for submodules using builders
  360.      * provided by the given {@code factory}, if any, and will also close these
  361.      * submodule repositories again.
  362.      * </p>
  363.      *
  364.      * @param monitor
  365.      *            for reporting progress, may be null
  366.      * @param estWorkTreeSize
  367.      *            number or estimated files in the working tree
  368.      * @param estIndexSize
  369.      *            number of estimated entries in the cache
  370.      * @param title
  371.      *            a {@link java.lang.String} object.
  372.      * @param factory
  373.      *            the {@link RepositoryBuilderFactory} to use to create builders
  374.      *            to create submodule repositories, if needed; if {@code null},
  375.      *            submodule repositories will be built using a plain
  376.      *            {@link RepositoryBuilder}.
  377.      * @return if anything is different between index, tree, and workdir
  378.      * @throws java.io.IOException
  379.      * @since 5.6
  380.      */
  381.     public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
  382.             int estIndexSize, String title, RepositoryBuilderFactory factory)
  383.             throws IOException {
  384.         dirCache = repository.readDirCache();

  385.         try (TreeWalk treeWalk = new TreeWalk(repository)) {
  386.             treeWalk.setOperationType(OperationType.CHECKIN_OP);
  387.             treeWalk.setRecursive(true);
  388.             // add the trees (tree, dirchache, workdir)
  389.             if (tree != null)
  390.                 treeWalk.addTree(tree);
  391.             else
  392.                 treeWalk.addTree(new EmptyTreeIterator());
  393.             treeWalk.addTree(new DirCacheIterator(dirCache));
  394.             treeWalk.addTree(initialWorkingTreeIterator);
  395.             initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1);
  396.             Collection<TreeFilter> filters = new ArrayList<>(4);

  397.             if (monitor != null) {
  398.                 // Get the maximum size of the work tree and index
  399.                 // and add some (quite arbitrary)
  400.                 if (estIndexSize == 0)
  401.                     estIndexSize = dirCache.getEntryCount();
  402.                 int total = Math.max(estIndexSize * 10 / 9,
  403.                         estWorkTreeSize * 10 / 9);
  404.                 monitor.beginTask(title, total);
  405.                 filters.add(new ProgressReportingFilter(monitor, total));
  406.             }

  407.             if (filter != null)
  408.                 filters.add(filter);
  409.             filters.add(new SkipWorkTreeFilter(INDEX));
  410.             indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR);
  411.             filters.add(indexDiffFilter);
  412.             treeWalk.setFilter(AndTreeFilter.create(filters));
  413.             fileModes.clear();
  414.             while (treeWalk.next()) {
  415.                 AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
  416.                         AbstractTreeIterator.class);
  417.                 DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX,
  418.                         DirCacheIterator.class);
  419.                 WorkingTreeIterator workingTreeIterator = treeWalk
  420.                         .getTree(WORKDIR, WorkingTreeIterator.class);

  421.                 if (dirCacheIterator != null) {
  422.                     final DirCacheEntry dirCacheEntry = dirCacheIterator
  423.                             .getDirCacheEntry();
  424.                     if (dirCacheEntry != null) {
  425.                         int stage = dirCacheEntry.getStage();
  426.                         if (stage > 0) {
  427.                             String path = treeWalk.getPathString();
  428.                             addConflict(path, stage);
  429.                             continue;
  430.                         }
  431.                     }
  432.                 }

  433.                 if (treeIterator != null) {
  434.                     if (dirCacheIterator != null) {
  435.                         if (!treeIterator.idEqual(dirCacheIterator)
  436.                                 || treeIterator
  437.                                         .getEntryRawMode() != dirCacheIterator
  438.                                                 .getEntryRawMode()) {
  439.                             // in repo, in index, content diff => changed
  440.                             if (!isEntryGitLink(treeIterator)
  441.                                     || !isEntryGitLink(dirCacheIterator)
  442.                                     || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
  443.                                 changed.add(treeWalk.getPathString());
  444.                         }
  445.                     } else {
  446.                         // in repo, not in index => removed
  447.                         if (!isEntryGitLink(treeIterator)
  448.                                 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
  449.                             removed.add(treeWalk.getPathString());
  450.                         if (workingTreeIterator != null)
  451.                             untracked.add(treeWalk.getPathString());
  452.                     }
  453.                 } else {
  454.                     if (dirCacheIterator != null) {
  455.                         // not in repo, in index => added
  456.                         if (!isEntryGitLink(dirCacheIterator)
  457.                                 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
  458.                             added.add(treeWalk.getPathString());
  459.                     } else {
  460.                         // not in repo, not in index => untracked
  461.                         if (workingTreeIterator != null
  462.                                 && !workingTreeIterator.isEntryIgnored()) {
  463.                             untracked.add(treeWalk.getPathString());
  464.                         }
  465.                     }
  466.                 }

  467.                 if (dirCacheIterator != null) {
  468.                     if (workingTreeIterator == null) {
  469.                         // in index, not in workdir => missing
  470.                         boolean isGitLink = isEntryGitLink(dirCacheIterator);
  471.                         if (!isGitLink
  472.                                 || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
  473.                             String path = treeWalk.getPathString();
  474.                             missing.add(path);
  475.                             if (isGitLink) {
  476.                                 missingSubmodules.add(path);
  477.                             }
  478.                         }
  479.                     } else {
  480.                         if (workingTreeIterator.isModified(
  481.                                 dirCacheIterator.getDirCacheEntry(), true,
  482.                                 treeWalk.getObjectReader())) {
  483.                             // in index, in workdir, content differs => modified
  484.                             if (!isEntryGitLink(dirCacheIterator)
  485.                                     || !isEntryGitLink(workingTreeIterator)
  486.                                     || (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL
  487.                                             && ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY))
  488.                                 modified.add(treeWalk.getPathString());
  489.                         }
  490.                     }
  491.                 }

  492.                 String path = treeWalk.getPathString();
  493.                 if (path != null) {
  494.                     for (int i = 0; i < treeWalk.getTreeCount(); i++) {
  495.                         recordFileMode(path, treeWalk.getFileMode(i));
  496.                     }
  497.                 }
  498.             }
  499.         }

  500.         if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
  501.             try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
  502.                 smw.setTree(new DirCacheIterator(dirCache));
  503.                 if (filter != null) {
  504.                     smw.setFilter(filter);
  505.                 }
  506.                 smw.setBuilderFactory(factory);
  507.                 while (smw.next()) {
  508.                     IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
  509.                     try {
  510.                         if (localIgnoreSubmoduleMode == null)
  511.                             localIgnoreSubmoduleMode = smw.getModulesIgnore();
  512.                         if (IgnoreSubmoduleMode.ALL
  513.                                 .equals(localIgnoreSubmoduleMode))
  514.                             continue;
  515.                     } catch (ConfigInvalidException e) {
  516.                         throw new IOException(MessageFormat.format(
  517.                                 JGitText.get().invalidIgnoreParamSubmodule,
  518.                                 smw.getPath()), e);
  519.                     }
  520.                     try (Repository subRepo = smw.getRepository()) {
  521.                         String subRepoPath = smw.getPath();
  522.                         if (subRepo != null) {
  523.                             ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
  524.                             if (subHead != null
  525.                                     && !subHead.equals(smw.getObjectId())) {
  526.                                 modified.add(subRepoPath);
  527.                                 recordFileMode(subRepoPath, FileMode.GITLINK);
  528.                             } else if (localIgnoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
  529.                                 IndexDiff smid = submoduleIndexDiffs
  530.                                         .get(smw.getPath());
  531.                                 if (smid == null) {
  532.                                     smid = new IndexDiff(subRepo,
  533.                                             smw.getObjectId(),
  534.                                             wTreeIt.getWorkingTreeIterator(
  535.                                                     subRepo));
  536.                                     submoduleIndexDiffs.put(subRepoPath, smid);
  537.                                 }
  538.                                 if (smid.diff(factory)) {
  539.                                     if (localIgnoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
  540.                                             && smid.getAdded().isEmpty()
  541.                                             && smid.getChanged().isEmpty()
  542.                                             && smid.getConflicting().isEmpty()
  543.                                             && smid.getMissing().isEmpty()
  544.                                             && smid.getModified().isEmpty()
  545.                                             && smid.getRemoved().isEmpty()) {
  546.                                         continue;
  547.                                     }
  548.                                     modified.add(subRepoPath);
  549.                                     recordFileMode(subRepoPath,
  550.                                             FileMode.GITLINK);
  551.                                 }
  552.                             }
  553.                         } else if (missingSubmodules.remove(subRepoPath)) {
  554.                             // If the directory is there and empty but the
  555.                             // submodule repository in .git/modules doesn't
  556.                             // exist yet it isn't "missing".
  557.                             File gitDir = new File(
  558.                                     new File(repository.getDirectory(),
  559.                                             Constants.MODULES),
  560.                                     subRepoPath);
  561.                             if (!gitDir.isDirectory()) {
  562.                                 File dir = SubmoduleWalk.getSubmoduleDirectory(
  563.                                         repository, subRepoPath);
  564.                                 if (dir.isDirectory() && !hasFiles(dir)) {
  565.                                     missing.remove(subRepoPath);
  566.                                 }
  567.                             }
  568.                         }
  569.                     }
  570.                 }
  571.             }

  572.         }

  573.         // consume the remaining work
  574.         if (monitor != null) {
  575.             monitor.endTask();
  576.         }

  577.         ignored = indexDiffFilter.getIgnoredPaths();
  578.         if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
  579.                 && missing.isEmpty() && modified.isEmpty()
  580.                 && untracked.isEmpty()) {
  581.             return false;
  582.         }
  583.         return true;
  584.     }

  585.     private boolean hasFiles(File directory) {
  586.         try (DirectoryStream<java.nio.file.Path> dir = Files
  587.                 .newDirectoryStream(directory.toPath())) {
  588.             return dir.iterator().hasNext();
  589.         } catch (DirectoryIteratorException | IOException e) {
  590.             return false;
  591.         }
  592.     }

  593.     private void recordFileMode(String path, FileMode mode) {
  594.         Set<String> values = fileModes.get(mode);
  595.         if (path != null) {
  596.             if (values == null) {
  597.                 values = new HashSet<>();
  598.                 fileModes.put(mode, values);
  599.             }
  600.             values.add(path);
  601.         }
  602.     }

  603.     private boolean isEntryGitLink(AbstractTreeIterator ti) {
  604.         return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
  605.                 .getBits()));
  606.     }

  607.     private void addConflict(String path, int stage) {
  608.         StageState existingStageStates = conflicts.get(path);
  609.         byte stageMask = 0;
  610.         if (existingStageStates != null) {
  611.             stageMask |= (byte) existingStageStates.getStageMask();
  612.         }
  613.         // stage 1 (base) should be shifted 0 times
  614.         int shifts = stage - 1;
  615.         stageMask |= (byte) (1 << shifts);
  616.         StageState stageState = StageState.fromMask(stageMask);
  617.         conflicts.put(path, stageState);
  618.     }

  619.     /**
  620.      * Get list of files added to the index, not in the tree
  621.      *
  622.      * @return list of files added to the index, not in the tree
  623.      */
  624.     public Set<String> getAdded() {
  625.         return added;
  626.     }

  627.     /**
  628.      * Get list of files changed from tree to index
  629.      *
  630.      * @return list of files changed from tree to index
  631.      */
  632.     public Set<String> getChanged() {
  633.         return changed;
  634.     }

  635.     /**
  636.      * Get list of files removed from index, but in tree
  637.      *
  638.      * @return list of files removed from index, but in tree
  639.      */
  640.     public Set<String> getRemoved() {
  641.         return removed;
  642.     }

  643.     /**
  644.      * Get list of files in index, but not filesystem
  645.      *
  646.      * @return list of files in index, but not filesystem
  647.      */
  648.     public Set<String> getMissing() {
  649.         return missing;
  650.     }

  651.     /**
  652.      * Get list of files modified on disk relative to the index
  653.      *
  654.      * @return list of files modified on disk relative to the index
  655.      */
  656.     public Set<String> getModified() {
  657.         return modified;
  658.     }

  659.     /**
  660.      * Get list of files that are not ignored, and not in the index.
  661.      *
  662.      * @return list of files that are not ignored, and not in the index.
  663.      */
  664.     public Set<String> getUntracked() {
  665.         return untracked;
  666.     }

  667.     /**
  668.      * Get list of files that are in conflict, corresponds to the keys of
  669.      * {@link #getConflictingStageStates()}
  670.      *
  671.      * @return list of files that are in conflict, corresponds to the keys of
  672.      *         {@link #getConflictingStageStates()}
  673.      */
  674.     public Set<String> getConflicting() {
  675.         return conflicts.keySet();
  676.     }

  677.     /**
  678.      * Get the map from each path of {@link #getConflicting()} to its
  679.      * corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState}
  680.      *
  681.      * @return the map from each path of {@link #getConflicting()} to its
  682.      *         corresponding {@link org.eclipse.jgit.lib.IndexDiff.StageState}
  683.      * @since 3.0
  684.      */
  685.     public Map<String, StageState> getConflictingStageStates() {
  686.         return conflicts;
  687.     }

  688.     /**
  689.      * The method returns the list of ignored files and folders. Only the root
  690.      * folder of an ignored folder hierarchy is reported. If a/b/c is listed in
  691.      * the .gitignore then you should not expect a/b/c/d/e/f to be reported
  692.      * here. Only a/b/c will be reported. Furthermore only ignored files /
  693.      * folders are returned that are NOT in the index.
  694.      *
  695.      * @return list of files / folders that are ignored
  696.      */
  697.     public Set<String> getIgnoredNotInIndex() {
  698.         return ignored;
  699.     }

  700.     /**
  701.      * Get list of files with the flag assume-unchanged
  702.      *
  703.      * @return list of files with the flag assume-unchanged
  704.      */
  705.     public Set<String> getAssumeUnchanged() {
  706.         if (assumeUnchanged == null) {
  707.             HashSet<String> unchanged = new HashSet<>();
  708.             for (int i = 0; i < dirCache.getEntryCount(); i++)
  709.                 if (dirCache.getEntry(i).isAssumeValid())
  710.                     unchanged.add(dirCache.getEntry(i).getPathString());
  711.             assumeUnchanged = unchanged;
  712.         }
  713.         return assumeUnchanged;
  714.     }

  715.     /**
  716.      * Get list of folders containing only untracked files/folders
  717.      *
  718.      * @return list of folders containing only untracked files/folders
  719.      */
  720.     public Set<String> getUntrackedFolders() {
  721.         return ((indexDiffFilter == null) ? Collections.<String> emptySet()
  722.                 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
  723.     }

  724.     /**
  725.      * Get the file mode of the given path in the index
  726.      *
  727.      * @param path a {@link java.lang.String} object.
  728.      * @return file mode
  729.      */
  730.     public FileMode getIndexMode(String path) {
  731.         final DirCacheEntry entry = dirCache.getEntry(path);
  732.         return entry != null ? entry.getFileMode() : FileMode.MISSING;
  733.     }

  734.     /**
  735.      * Get the list of paths that IndexDiff has detected to differ and have the
  736.      * given file mode
  737.      *
  738.      * @param mode a {@link org.eclipse.jgit.lib.FileMode} object.
  739.      * @return the list of paths that IndexDiff has detected to differ and have
  740.      *         the given file mode
  741.      * @since 3.6
  742.      */
  743.     public Set<String> getPathsWithIndexMode(FileMode mode) {
  744.         Set<String> paths = fileModes.get(mode);
  745.         if (paths == null)
  746.             paths = new HashSet<>();
  747.         return paths;
  748.     }
  749. }