Fix the handling of .git/info/exclude and core.excludesFile
The RootIgnoreNode in a WorkingTreeIterator must _not_ add the rules
from .git/info/exclude or from the file designated by git config
core.excludesFile to the list of rules read from the root .gitignore.
These really must be separate nodes in a hierarchy, otherwise the
precedence rules from [1] are violated and the outcome is not the
same as in C git.
[1] https://git-scm.com/docs/gitignore
Bug: 580381
Change-Id: I57802ba7bbbe4f183504c882b6c77a78cc3a9b99
Signed-off-by: Thomas Wolf <twolf@apache.org>
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 ae3f051..083e6cd 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
@@ -13,6 +13,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
@@ -20,6 +21,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.nio.file.Files;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -354,4 +356,84 @@ public void testNegationAllExceptJavaInSrcAndExceptChildDirInSrc()
writeTrashFile("src/.gitignore", "*\n!*.java\n!*/");
assertSameAsCGit();
}
+
+ @Test
+ public void testMultipleEntriesIgnored() throws Exception {
+ createFiles("dir/a");
+ writeTrashFile(".gitignore", "!dir/a\ndir/a");
+ assertSameAsCGit();
+ }
+
+ @Test
+ public void testMultipleEntriesNotIgnored() throws Exception {
+ createFiles("dir/a");
+ writeTrashFile(".gitignore", "dir/a\n!dir/a");
+ assertSameAsCGit("dir/a");
+ }
+
+ @Test
+ public void testInfoExcludes() throws Exception {
+ createFiles("dir/a", "dir/b");
+ File gitDir = db.getDirectory();
+ File info = new File(gitDir, "info");
+ assertTrue(info.mkdirs());
+ File infoExclude = new File(info, "exclude");
+ Files.writeString(infoExclude.toPath(), "dir/a");
+ assertSameAsCGit("dir/b");
+ }
+
+ @Test
+ public void testInfoExcludesPrecedence() throws Exception {
+ createFiles("dir/a", "dir/b");
+ writeTrashFile(".gitignore", "!dir/a");
+ File gitDir = db.getDirectory();
+ File info = new File(gitDir, "info");
+ assertTrue(info.mkdirs());
+ File infoExclude = new File(info, "exclude");
+ Files.writeString(infoExclude.toPath(), "dir/a");
+ assertSameAsCGit("dir/a", "dir/b");
+ }
+
+ @Test
+ public void testCoreExcludes() throws Exception {
+ createFiles("dir/a", "dir/b");
+ writeTrashFile(".fake_git_ignore", "dir/a");
+ assertSameAsCGit("dir/b");
+ }
+
+ @Test
+ public void testInfoCoreExcludes() throws Exception {
+ createFiles("dir/a", "dir/b");
+ File gitDir = db.getDirectory();
+ File info = new File(gitDir, "info");
+ assertTrue(info.mkdirs());
+ File infoExclude = new File(info, "exclude");
+ Files.writeString(infoExclude.toPath(), "!a");
+ writeTrashFile(".fake_git_ignore", "dir/a");
+ assertSameAsCGit("dir/b");
+ }
+
+ @Test
+ public void testInfoCoreExcludesPrecedence() throws Exception {
+ createFiles("dir/a", "dir/b");
+ File gitDir = db.getDirectory();
+ File info = new File(gitDir, "info");
+ assertTrue(info.mkdirs());
+ File infoExclude = new File(info, "exclude");
+ Files.writeString(infoExclude.toPath(), "!dir/a");
+ writeTrashFile(".fake_git_ignore", "dir/a");
+ assertSameAsCGit("dir/a", "dir/b");
+ }
+
+ @Test
+ public void testInfoCoreExcludesPrecedence2() throws Exception {
+ createFiles("dir/a", "dir/b");
+ File gitDir = db.getDirectory();
+ File info = new File(gitDir, "info");
+ assertTrue(info.mkdirs());
+ File infoExclude = new File(info, "exclude");
+ Files.writeString(infoExclude.toPath(), "dir/a");
+ writeTrashFile(".fake_git_ignore", "!dir/a");
+ assertSameAsCGit("dir/b");
+ }
}
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 427eac5..b108b0a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -1274,11 +1274,15 @@ private static class PerDirectoryIgnoreNode extends IgnoreNode {
}
IgnoreNode load() throws IOException {
- IgnoreNode r = new IgnoreNode();
+ return load(null);
+ }
+
+ IgnoreNode load(IgnoreNode parent) throws IOException {
+ IgnoreNodeWithParent r = new IgnoreNodeWithParent(parent);
try (InputStream in = entry.openInputStream()) {
r.parse(name, in);
}
- return r.getRules().isEmpty() ? null : r;
+ return r.getRules().isEmpty() && parent == null ? null : r;
}
}
@@ -1292,29 +1296,41 @@ private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
}
@Override
- IgnoreNode load() throws IOException {
- IgnoreNode r;
- if (entry != null) {
- r = super.load();
- if (r == null)
- r = new IgnoreNode();
- } else {
- r = new IgnoreNode();
- }
-
+ IgnoreNode load(IgnoreNode parent) throws IOException {
+ IgnoreNode coreExclude = new IgnoreNodeWithParent(parent);
FS fs = repository.getFS();
Path path = repository.getConfig().getPath(
ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null);
if (path != null) {
- loadRulesFromFile(r, path.toFile());
+ loadRulesFromFile(coreExclude, path.toFile());
+ }
+ if (coreExclude.getRules().isEmpty()) {
+ coreExclude = parent;
}
+ IgnoreNode infoExclude = new IgnoreNodeWithParent(
+ coreExclude);
File exclude = fs.resolve(repository.getDirectory(),
Constants.INFO_EXCLUDE);
- loadRulesFromFile(r, exclude);
+ loadRulesFromFile(infoExclude, exclude);
+ if (infoExclude.getRules().isEmpty()) {
+ infoExclude = null;
+ }
- return r.getRules().isEmpty() ? null : r;
+ IgnoreNode parentNode = infoExclude != null ? infoExclude
+ : coreExclude;
+
+ IgnoreNode r;
+ if (entry != null) {
+ r = super.load(parentNode);
+ if (r == null) {
+ return null;
+ }
+ } else {
+ return parentNode;
+ }
+ return r.getRules().isEmpty() ? parentNode : r;
}
private static void loadRulesFromFile(IgnoreNode r, File exclude)
@@ -1327,6 +1343,24 @@ private static void loadRulesFromFile(IgnoreNode r, File exclude)
}
}
+ private static class IgnoreNodeWithParent extends IgnoreNode {
+
+ private final IgnoreNode parent;
+
+ IgnoreNodeWithParent(IgnoreNode parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public Boolean checkIgnored(String path, boolean isDirectory) {
+ Boolean result = super.checkIgnored(path, isDirectory);
+ if (result == null && parent != null) {
+ return parent.checkIgnored(path, isDirectory);
+ }
+ return result;
+ }
+ }
+
/** Magic type indicating we know rules exist, but they aren't loaded. */
private static class PerDirectoryAttributesNode extends AttributesNode {
final Entry entry;