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 = FileTreeIterator::new;

  302.     /**
  303.      * Allows higher layers to set the factory for WorkingTreeIterators.
  304.      *
  305.      * @param wTreeIt
  306.      * @since 3.6
  307.      */
  308.     public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
  309.         this.wTreeIt = wTreeIt;
  310.     }

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

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

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

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

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

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

  392.                 if (dirCacheIterator != null) {
  393.                     final DirCacheEntry dirCacheEntry = dirCacheIterator
  394.                             .getDirCacheEntry();
  395.                     if (dirCacheEntry != null) {
  396.                         int stage = dirCacheEntry.getStage();
  397.                         if (stage > 0) {
  398.                             String path = treeWalk.getPathString();
  399.                             addConflict(path, stage);
  400.                             continue;
  401.                         }
  402.                     }
  403.                 }

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

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

  463.                 String path = treeWalk.getPathString();
  464.                 if (path != null) {
  465.                     for (int i = 0; i < treeWalk.getTreeCount(); i++) {
  466.                         recordFileMode(path, treeWalk.getFileMode(i));
  467.                     }
  468.                 }
  469.             }
  470.         }

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

  535.         }

  536.         // consume the remaining work
  537.         if (monitor != null)
  538.             monitor.endTask();

  539.         ignored = indexDiffFilter.getIgnoredPaths();
  540.         if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
  541.                 && missing.isEmpty() && modified.isEmpty()
  542.                 && untracked.isEmpty())
  543.             return false;
  544.         else
  545.             return true;
  546.     }

  547.     private boolean hasFiles(File directory) {
  548.         try (DirectoryStream<java.nio.file.Path> dir = Files
  549.                 .newDirectoryStream(directory.toPath())) {
  550.             return dir.iterator().hasNext();
  551.         } catch (DirectoryIteratorException | IOException e) {
  552.             return false;
  553.         }
  554.     }

  555.     private void recordFileMode(String path, FileMode mode) {
  556.         Set<String> values = fileModes.get(mode);
  557.         if (path != null) {
  558.             if (values == null) {
  559.                 values = new HashSet<>();
  560.                 fileModes.put(mode, values);
  561.             }
  562.             values.add(path);
  563.         }
  564.     }

  565.     private boolean isEntryGitLink(AbstractTreeIterator ti) {
  566.         return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
  567.                 .getBits()));
  568.     }

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

  580.     /**
  581.      * Get list of files added to the index, not in the tree
  582.      *
  583.      * @return list of files added to the index, not in the tree
  584.      */
  585.     public Set<String> getAdded() {
  586.         return added;
  587.     }

  588.     /**
  589.      * Get list of files changed from tree to index
  590.      *
  591.      * @return list of files changed from tree to index
  592.      */
  593.     public Set<String> getChanged() {
  594.         return changed;
  595.     }

  596.     /**
  597.      * Get list of files removed from index, but in tree
  598.      *
  599.      * @return list of files removed from index, but in tree
  600.      */
  601.     public Set<String> getRemoved() {
  602.         return removed;
  603.     }

  604.     /**
  605.      * Get list of files in index, but not filesystem
  606.      *
  607.      * @return list of files in index, but not filesystem
  608.      */
  609.     public Set<String> getMissing() {
  610.         return missing;
  611.     }

  612.     /**
  613.      * Get list of files modified on disk relative to the index
  614.      *
  615.      * @return list of files modified on disk relative to the index
  616.      */
  617.     public Set<String> getModified() {
  618.         return modified;
  619.     }

  620.     /**
  621.      * Get list of files that are not ignored, and not in the index.
  622.      *
  623.      * @return list of files that are not ignored, and not in the index.
  624.      */
  625.     public Set<String> getUntracked() {
  626.         return untracked;
  627.     }

  628.     /**
  629.      * Get list of files that are in conflict, corresponds to the keys of
  630.      * {@link #getConflictingStageStates()}
  631.      *
  632.      * @return list of files that are in conflict, corresponds to the keys of
  633.      *         {@link #getConflictingStageStates()}
  634.      */
  635.     public Set<String> getConflicting() {
  636.         return conflicts.keySet();
  637.     }

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

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

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

  676.     /**
  677.      * Get list of folders containing only untracked files/folders
  678.      *
  679.      * @return list of folders containing only untracked files/folders
  680.      */
  681.     public Set<String> getUntrackedFolders() {
  682.         return ((indexDiffFilter == null) ? Collections.<String> emptySet()
  683.                 : new HashSet<>(indexDiffFilter.getUntrackedFolders()));
  684.     }

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

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