diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java index 3b11616fe..0c6ed0c19 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java @@ -141,7 +141,9 @@ private void jgitIgnoredAndUntracked(LinkedHashSet ignored, // Do a tree walk that does descend into ignored directories and return // a list of all ignored files try (TreeWalk walk = new TreeWalk(db)) { - walk.addTree(new FileTreeIterator(db)); + FileTreeIterator iter = new FileTreeIterator(db); + iter.setWalkIgnoredDirectories(true); + walk.addTree(iter); walk.setRecursive(true); while (walk.next()) { if (walk.getTree(WorkingTreeIterator.class).isEntryIgnored()) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java index 80595feff..78d9a82c2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java @@ -732,7 +732,9 @@ public void testToString() throws Exception { private void beginWalk() { walk = new TreeWalk(db); - walk.addTree(new FileTreeIterator(db)); + FileTreeIterator iter = new FileTreeIterator(db); + iter.setWalkIgnoredDirectories(true); + walk.addTree(iter); } private void endWalk() throws IOException { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index 0412242eb..eb8782780 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -78,8 +78,10 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.junit.Assume; @@ -1904,6 +1906,117 @@ public void testLongFilename() throws Exception { assertUpdated(longFileName); } + @Test + public void testIgnoredDirectory() throws Exception { + writeTrashFile(".gitignore", "src/ignored"); + writeTrashFile("src/ignored/sub/foo.txt", "1"); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("adding .gitignore") + .call(); + writeTrashFile("foo.txt", "2"); + writeTrashFile("zzz.txt", "3"); + git.add().addFilepattern("foo.txt").call(); + git.commit().setMessage("add file").call(); + assertEquals("Should not have entered ignored directory", 1, + resetHardAndCount(commit)); + } + } + + @Test + public void testIgnoredDirectoryWithTrackedContent() throws Exception { + writeTrashFile("src/ignored/sub/foo.txt", "1"); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + git.commit().setMessage("adding foo.txt").call(); + writeTrashFile(".gitignore", "src/ignored"); + writeTrashFile("src/ignored/sub/foo.txt", "2"); + writeTrashFile("src/ignored/other/bar.txt", "3"); + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("adding .gitignore") + .call(); + writeTrashFile("foo.txt", "2"); + writeTrashFile("zzz.txt", "3"); + git.add().addFilepattern("foo.txt").call(); + git.commit().setMessage("add file").call(); + File file = writeTrashFile("src/ignored/sub/foo.txt", "3"); + assertEquals("Should have entered ignored directory", 3, + resetHardAndCount(commit)); + checkFile(file, "2"); + } + } + + @Test + public void testResetWithChangeInGitignore() throws Exception { + writeTrashFile(".gitignore", "src/ignored"); + writeTrashFile("src/ignored/sub/foo.txt", "1"); + try (Git git = new Git(db)) { + git.add().addFilepattern(".").call(); + RevCommit initial = git.commit().setMessage("initial").call(); + writeTrashFile("src/newignored/foo.txt", "2"); + writeTrashFile("src/.gitignore", "newignored"); + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("newignored").call(); + assertEquals("Should not have entered src/newignored directory", 1, + resetHardAndCount(initial)); + assertEquals("Should have entered src/newignored directory", 2, + resetHardAndCount(commit)); + deleteTrashFile("src/.gitignore"); + git.rm().addFilepattern("src/.gitignore").call(); + RevCommit top = git.commit().setMessage("Unignore newignore") + .call(); + assertEquals("Should have entered src/newignored directory", 2, + resetHardAndCount(initial)); + assertEquals("Should have entered src/newignored directory", 2, + resetHardAndCount(commit)); + assertEquals("Should not have entered src/newignored directory", 1, + resetHardAndCount(top)); + + } + } + + private static class TestFileTreeIterator extends FileTreeIterator { + + // For assertions only + private final int[] count; + + public TestFileTreeIterator(Repository repo, int[] count) { + super(repo); + this.count = count; + } + + protected TestFileTreeIterator(final WorkingTreeIterator p, + final File root, FS fs, FileModeStrategy fileModeStrategy, + int[] count) { + super(p, root, fs, fileModeStrategy); + this.count = count; + } + + @Override + protected AbstractTreeIterator enterSubtree() { + count[0] += 1; + return new TestFileTreeIterator(this, + ((FileEntry) current()).getFile(), fs, fileModeStrategy, + count); + } + } + + private int resetHardAndCount(RevCommit commit) throws Exception { + int[] callCount = { 0 }; + DirCache cache = db.lockDirCache(); + FileTreeIterator workingTreeIterator = new TestFileTreeIterator(db, + callCount); + try { + DirCacheCheckout checkout = new DirCacheCheckout(db, null, cache, + commit.getTree().getId(), workingTreeIterator); + checkout.setFailOnConflict(false); + checkout.checkout(); + } finally { + cache.unlock(); + } + return callCount[0]; + } + public void assertWorkDir(Map i) throws CorruptObjectException, IOException { 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 2cb8f86fc..580b08b42 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 @@ -470,7 +470,8 @@ public void testUntrackedFolders() throws Exception { } /** - * Test that ignored folders aren't listed as untracked + * Test that ignored folders aren't listed as untracked, but are listed as + * ignored. * * @throws Exception */ @@ -499,6 +500,8 @@ public void testUntrackedNotIgnoredFolders() throws Exception { diff.diff(); assertEquals(new HashSet<>(Arrays.asList("src")), diff.getUntrackedFolders()); + assertEquals(new HashSet<>(Arrays.asList("sr", "target")), + diff.getIgnoredNotInIndex()); git.add().addFilepattern("src").call(); writeTrashFile("sr/com/X1.java", ""); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java index ccd5e7091..24b9ac086 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java @@ -52,6 +52,7 @@ import java.io.IOException; import java.io.InputStream; +import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; @@ -67,6 +68,7 @@ * {@link org.eclipse.jgit.treewalk.TreeWalk}. */ public class FileTreeIterator extends WorkingTreeIterator { + /** * the starting directory of this Iterator. All entries are located directly * in this directory. @@ -207,7 +209,33 @@ protected FileTreeIterator(final WorkingTreeIterator p, final File root, @Override public AbstractTreeIterator createSubtreeIterator(ObjectReader reader) throws IncorrectObjectTypeException, IOException { - return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, fileModeStrategy); + if (!walksIgnoredDirectories() && isEntryIgnored()) { + DirCacheIterator iterator = getDirCacheIterator(); + if (iterator == null) { + return new EmptyTreeIterator(this); + } + // Only enter if we have an associated DirCacheIterator that is + // at the same entry (which indicates there is some already + // tracked file underneath this directory). Otherwise the + // directory is indeed ignored and can be skipped entirely. + } + return enterSubtree(); + } + + + /** + * Create a new iterator for the current entry's subtree. + *

+ * The parent reference of the iterator must be this, otherwise + * the caller would not be able to exit out of the subtree iterator + * correctly and return to continue walking this. + * + * @return a new iterator that walks over the current subtree. + * @since 5.0 + */ + protected AbstractTreeIterator enterSubtree() { + return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, + fileModeStrategy); } private Entry[] entries() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 10e919641..179fd4679 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -256,6 +256,45 @@ public void setDirCacheIterator(TreeWalk walk, int treeId) { state.dirCacheTree = treeId; } + /** + * Retrieves the {@link DirCacheIterator} at the current entry if + * {@link #setDirCacheIterator(TreeWalk, int)} was called. + * + * @return the DirCacheIterator, or {@code null} if not set or not at the + * current entry + * @since 5.0 + */ + protected DirCacheIterator getDirCacheIterator() { + if (state.dirCacheTree >= 0 && state.walk != null) { + return state.walk.getTree(state.dirCacheTree, + DirCacheIterator.class); + } + return null; + } + + /** + * Defines whether this {@link WorkingTreeIterator} walks ignored + * directories. + * + * @param includeIgnored + * {@code false} to skip ignored directories, if possible; + * {@code true} to always include them in the walk + * @since 5.0 + */ + public void setWalkIgnoredDirectories(boolean includeIgnored) { + state.walkIgnored = includeIgnored; + } + + /** + * Tells whether this {@link WorkingTreeIterator} walks ignored directories. + * + * @return {@code true} if it does, {@code false} otherwise + * @since 5.0 + */ + public boolean walksIgnoredDirectories() { + return state.walkIgnored; + } + /** {@inheritDoc} */ @Override public boolean hasId() { @@ -1365,7 +1404,10 @@ private static final class IteratorState { TreeWalk walk; /** Position of the matching {@link DirCacheIterator}. */ - int dirCacheTree; + int dirCacheTree = -1; + + /** Whether the iterator shall walk ignored directories. */ + boolean walkIgnored = false; final Map directoryToIgnored = new HashMap<>();