diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java index e350fea32..b7caecc2c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java @@ -51,6 +51,9 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.TreeSet; import org.eclipse.jgit.api.Git; @@ -105,6 +108,7 @@ public void testAdded() throws IOException { assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getModified().size()); assertEquals(0, diff.getRemoved().size()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } @Test @@ -131,6 +135,7 @@ public void testRemoved() throws IOException { assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getModified().size()); assertEquals(0, diff.getAdded().size()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } @Test @@ -163,6 +168,7 @@ public void testModified() throws IOException, NoFilepatternException { assertEquals(0, diff.getAdded().size()); assertEquals(0, diff.getRemoved().size()); assertEquals(0, diff.getMissing().size()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } @Test @@ -204,6 +210,7 @@ public void testConflicting() throws Exception { assertEquals("[]", diff.getMissing().toString()); assertEquals("[]", diff.getModified().toString()); assertEquals("[a]", diff.getConflicting().toString()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } @Test @@ -242,6 +249,7 @@ public void testConflictingDeletedAndModified() throws Exception { assertEquals("[]", diff.getMissing().toString()); assertEquals("[]", diff.getModified().toString()); assertEquals("[a]", diff.getConflicting().toString()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } @Test @@ -279,6 +287,7 @@ public void testConflictingFromMultipleCreations() throws Exception { assertEquals("[]", diff.getMissing().toString()); assertEquals("[]", diff.getModified().toString()); assertEquals("[b]", diff.getConflicting().toString()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } @Test @@ -311,6 +320,7 @@ public void testUnchangedSimple() throws IOException, assertEquals(0, diff.getRemoved().size()); assertEquals(0, diff.getMissing().size()); assertEquals(0, diff.getModified().size()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } /** @@ -360,6 +370,7 @@ public void testUnchangedComplex() throws IOException, assertEquals(0, diff.getRemoved().size()); assertEquals(0, diff.getMissing().size()); assertEquals(0, diff.getModified().size()); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } private ObjectId insertTree(Tree tree) throws IOException { @@ -392,6 +403,60 @@ public void testRemovedUntracked() throws Exception{ diff.diff(); assertTrue(diff.getRemoved().contains(path)); assertTrue(diff.getUntracked().contains(path)); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); + } + + /** + * + * @throws Exception + */ + @Test + public void testUntrackedFolders() throws Exception { + Git git = new Git(db); + + IndexDiff diff = new IndexDiff(db, Constants.HEAD, + new FileTreeIterator(db)); + diff.diff(); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); + + writeTrashFile("readme", ""); + writeTrashFile("src/com/A.java", ""); + writeTrashFile("src/com/B.java", ""); + writeTrashFile("src/org/A.java", ""); + writeTrashFile("src/org/B.java", ""); + writeTrashFile("target/com/A.java", ""); + writeTrashFile("target/com/B.java", ""); + writeTrashFile("target/org/A.java", ""); + writeTrashFile("target/org/B.java", ""); + + git.add().addFilepattern("src").addFilepattern("readme").call(); + git.commit().setMessage("initial").call(); + + diff = new IndexDiff(db, Constants.HEAD, + new FileTreeIterator(db)); + diff.diff(); + assertEquals(new HashSet(Arrays.asList("target")), + diff.getUntrackedFolders()); + + writeTrashFile("src/tst/A.java", ""); + writeTrashFile("src/tst/B.java", ""); + + diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); + diff.diff(); + assertEquals(new HashSet(Arrays.asList("target", "src/tst")), + diff.getUntrackedFolders()); + + git.rm().addFilepattern("src/com/B.java").addFilepattern("src/org") + .call(); + git.commit().setMessage("second").call(); + writeTrashFile("src/org/C.java", ""); + + diff = new IndexDiff(db, Constants.HEAD, new FileTreeIterator(db)); + diff.diff(); + assertEquals( + new HashSet(Arrays.asList("src/org", "src/tst", + "target")), + diff.getUntrackedFolders()); } @Test @@ -427,6 +492,7 @@ public void testAssumeUnchanged() throws Exception { assertEquals(1, diff.getChanged().size()); assertTrue(diff.getAssumeUnchanged().contains("file2")); assertTrue(diff.getChanged().contains("file")); + assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } private void removeFromIndex(String path) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java index af64f2094..39f732fce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java @@ -48,6 +48,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -163,6 +164,8 @@ public TreeFilter clone() { private DirCache dirCache; + private IndexDiffFilter indexDiffFilter; + /** * Construct an IndexDiff * @@ -278,7 +281,7 @@ public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize, if (filter != null) filters.add(filter); filters.add(new SkipWorkTreeFilter(INDEX)); - IndexDiffFilter indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR); + indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR); filters.add(indexDiffFilter); treeWalk.setFilter(AndTreeFilter.create(filters)); while (treeWalk.next()) { @@ -427,4 +430,12 @@ public Set getAssumeUnchanged() { } return assumeUnchanged; } + + /** + * @return list of folders containing only untracked files/folders + */ + public Set getUntrackedFolders() { + return ((indexDiffFilter == null) ? Collections. emptySet() + : new HashSet(indexDiffFilter.getUntrackedFolders())); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java index 2f8608ee4..34e92b6b1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java @@ -44,11 +44,14 @@ import java.io.IOException; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; @@ -83,6 +86,10 @@ public class IndexDiffFilter extends TreeFilter { private final Set ignoredPaths = new HashSet(); + private final LinkedList untrackedParentFolders = new LinkedList(); + + private final LinkedList untrackedFolders = new LinkedList(); + /** * Creates a new instance of this filter. Do not use an instance of this * filter in multiple treewalks. @@ -122,16 +129,46 @@ public IndexDiffFilter(int dirCacheIndex, int workingTreeIndex, @Override public boolean include(TreeWalk tw) throws MissingObjectException, IncorrectObjectTypeException, IOException { + final int cnt = tw.getTreeCount(); + final int wm = tw.getRawMode(workingTree); + String path = tw.getPathString(); + + if (!tw.isPostOrderTraversal()) { + // detect untracked Folders + // Whenever we enter a folder in the workingtree assume it will + // contain only untracked files and add it to + // untrackedParentFolders. If we later find tracked files we will + // remove it from this list + if (FileMode.TREE.equals(wm)) { + // Clean untrackedParentFolders. This potentially moves entries + // from untrackedParentFolders to untrackedFolders + copyUntrackedFolders(path); + // add the folder we just entered to untrackedParentFolders + untrackedParentFolders.addFirst(path); + } + + // detect untracked Folders + // Whenever we see a tracked file we know that all of its parent + // folders do not belong into untrackedParentFolders anymore. Clean + // it. + for (int i = 0; i < cnt; i++) { + int rmode = tw.getRawMode(i); + if (i != workingTree && rmode != 0 + && FileMode.TREE.equals(rmode)) { + untrackedParentFolders.clear(); + break; + } + } + } + // If the working tree file doesn't exist, it does exist for at least // one other so include this difference. - final int wm = tw.getRawMode(workingTree); if (wm == 0) return true; // If the path does not appear in the DirCache and its ignored // we can avoid returning a result here, but only if its not in any // other tree. - final int cnt = tw.getTreeCount(); final int dm = tw.getRawMode(dirCache); WorkingTreeIterator wi = workingTree(tw); if (dm == 0) { @@ -176,6 +213,29 @@ public boolean include(TreeWalk tw) throws MissingObjectException, return wi.isModified(di.getDirCacheEntry(), true); } + /** + * Copy all entries which are still in untrackedParentFolders and which + * belong to a path this treewalk has left into untrackedFolders. It is sure + * that we will not find any tracked files underneath these paths. Therefore + * these paths definitely belong to untracked folders. + * + * @param currentPath + * the current path of the treewalk + */ + private void copyUntrackedFolders(String currentPath) { + String pathToBeSaved = null; + while (!untrackedParentFolders.isEmpty() + && !currentPath.startsWith(untrackedParentFolders.getFirst() + + "/")) + pathToBeSaved = untrackedParentFolders.removeFirst(); + if (pathToBeSaved != null) { + while (!untrackedFolders.isEmpty() + && untrackedFolders.getLast().startsWith(pathToBeSaved)) + untrackedFolders.removeLast(); + untrackedFolders.addLast(pathToBeSaved); + } + } + private WorkingTreeIterator workingTree(TreeWalk tw) { return tw.getTree(workingTree, WorkingTreeIterator.class); } @@ -209,4 +269,21 @@ public String toString() { public Set getIgnoredPaths() { return ignoredPaths; } + + /** + * @return all paths of folders which contain only untracked files/folders. + * If on the associated treewalk postorder traversal was turned on + * (see {@link TreeWalk#setPostOrderTraversal(boolean)}) then an + * empty list will be returned. + */ + public List getUntrackedFolders() { + LinkedList ret = new LinkedList(untrackedFolders); + if (!untrackedParentFolders.isEmpty()) { + String toBeAdded = untrackedParentFolders.getLast(); + while (!ret.isEmpty() && ret.getLast().startsWith(toBeAdded)) + ret.removeLast(); + ret.addLast(toBeAdded); + } + return ret; + } }