This is an automated email from the ASF dual-hosted git repository.

claude pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/creadur-rat.git


The following commit(s) were added to refs/heads/master by this push:
     new 34eee888 RAT-473: take into account global gitignore (#433)
34eee888 is described below

commit 34eee8886adb6b5db494abe56e25dca4a5206aac
Author: Arnout Engelen <[email protected]>
AuthorDate: Sun Jun 1 10:39:44 2025 +0200

    RAT-473: take into account global gitignore (#433)
    
    Take into account exclusions from the global gitignore file if it is 
present in the default location or specified by the XDG_CONFIG_HOME environment 
variable.
    
    see https://git-scm.com/docs/gitignore for details.
---
 .../rat/config/exclusion/StandardCollection.java   |  6 +-
 .../AbstractFileProcessorBuilder.java              |  6 +-
 .../exclusion/fileProcessors/GitIgnoreBuilder.java | 70 ++++++++++++++++++++++
 .../fileProcessors/AbstractIgnoreBuilderTest.java  |  7 ++-
 .../fileProcessors/GitIgnoreBuilderTest.java       | 63 +++++++++++++++----
 .../GitIgnoreBuilderTest/global-gitignore          |  3 +
 .../resources/GitIgnoreBuilderTest/src/.gitignore  |  3 +
 .../src/local-should-precede-global.md             |  5 ++
 .../src/local-should-precede-global.xml            |  5 ++
 src/changes/changes.xml                            |  3 +
 10 files changed, 155 insertions(+), 16 deletions(-)

diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/StandardCollection.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/StandardCollection.java
index 62c17972..e2387eb8 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/StandardCollection.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/StandardCollection.java
@@ -89,9 +89,11 @@ public enum StandardCollection {
                     "**/.project", "**/.settings/**", 
"**/.externalToolBuilders"),
             null, null),
     /**
-     * The files and directories created by GIT source code control to support 
GIT, also processes files listed in '.gitignore'.
+     * The files and directories created by GIT source code control to support 
GIT, also processes files listed in '.gitignore'
+     * and (unless RAT_NO_GIT_GLOBAL_IGNORE is specified) the global gitignore.
      */
-    GIT("The files and directories created by GIT source code control to 
support GIT, also processes files listed in '.gitignore'.",
+    GIT("The files and directories created by GIT source code control to 
support GIT, also processes files listed in '.gitignore' " +
+        "and (unless RAT_NO_GIT_GLOBAL_IGNORE is specified) the global 
gitignore.",
             Arrays.asList("**/.git/**", "**/.gitignore"),
             null,
             new GitIgnoreBuilder()
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/AbstractFileProcessorBuilder.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/AbstractFileProcessorBuilder.java
index 4fc9f421..4f526261 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/AbstractFileProcessorBuilder.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/AbstractFileProcessorBuilder.java
@@ -203,10 +203,14 @@ public abstract class AbstractFileProcessorBuilder {
         return result == null ? new File[0] : result;
     }
 
+    protected LevelBuilder getLevelBuilder(final int level) {
+        return levelBuilders.computeIfAbsent(level, k -> new LevelBuilder());
+    }
+
     /**
      * Manages the merging of {@link MatcherSet}s for the specified level.
      */
-    private static final class LevelBuilder {
+    protected static final class LevelBuilder {
         /**
          * The list of MatcherSets that this builder produced.
          */
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilder.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilder.java
index cf4e9fb9..d43a2ccd 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilder.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilder.java
@@ -19,7 +19,12 @@
 package org.apache.rat.config.exclusion.fileProcessors;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
 
 import org.apache.rat.config.exclusion.ExclusionUtils;
@@ -53,6 +58,41 @@ public class GitIgnoreBuilder extends 
AbstractFileProcessorBuilder {
         super(IGNORE_FILE, COMMENT_PREFIX, true);
     }
 
+    private MatcherSet processGlobalIgnore(final Consumer<MatcherSet> 
matcherSetConsumer, final DocumentName root, final DocumentName 
globalGitIgnore) {
+        final MatcherSet.Builder matcherSetBuilder = new MatcherSet.Builder();
+        final List<String> iterable = new ArrayList<>();
+        ExclusionUtils.asIterator(globalGitIgnore.asFile(), commentFilter)
+                .map(entry -> modifyEntry(matcherSetConsumer, globalGitIgnore, 
entry).orElse(null))
+                .filter(Objects::nonNull)
+                .map(entry -> ExclusionUtils.qualifyPattern(root, entry))
+                .forEachRemaining(iterable::add);
+
+        Set<String> included = new HashSet<>();
+        Set<String> excluded = new HashSet<>();
+        MatcherSet.Builder.segregateList(excluded, included, iterable);
+        DocumentName displayName = DocumentName.builder(root).setName("global 
gitignore").build();
+        matcherSetBuilder.addExcluded(displayName, excluded);
+        matcherSetBuilder.addIncluded(displayName, included);
+        return matcherSetBuilder.build();
+    }
+
+    @Override
+    protected MatcherSet process(final Consumer<MatcherSet> 
matcherSetConsumer, final DocumentName root, final DocumentName documentName) {
+      if (root.equals(documentName.getBaseDocumentName())) {
+          Optional<File> globalGitIgnore = globalGitIgnore();
+          List<MatcherSet> matcherSets = new ArrayList<MatcherSet>();
+          matcherSets.add(super.process(matcherSetConsumer, root, 
documentName));
+          if (globalGitIgnore.isPresent()) {
+              LevelBuilder levelBuilder = getLevelBuilder(Integer.MAX_VALUE);
+              DocumentName ignore = 
DocumentName.builder(globalGitIgnore.get()).build();
+              matcherSets.add(processGlobalIgnore(levelBuilder::add, root, 
ignore));
+          }
+          return MatcherSet.merge(matcherSets);
+      } else {
+          return super.process(matcherSetConsumer, root, documentName);
+      }
+    }
+
     /**
      * Convert the string entry.
      * If the string ends with a slash an {@link DocumentNameMatcher#and} is 
constructed from a directory check and the file
@@ -102,4 +142,34 @@ public class GitIgnoreBuilder extends 
AbstractFileProcessorBuilder {
         }
         return Optional.of(prefix ? NEGATION_PREFIX + pattern : pattern);
     }
+
+    /**
+     * The global gitignore file to process, based on the
+     * RAT_NO_GIT_GLOBAL_IGNORE, XDG_CONFIG_HOME, and HOME environment
+     * variables.
+     */
+    protected Optional<File> globalGitIgnore() {
+        if (System.getenv("RAT_NO_GIT_GLOBAL_IGNORE") != null) {
+            return Optional.empty();
+        }
+
+        String xdgConfigHome = System.getenv("XDG_CONFIG_HOME");
+        String filename;
+        if (xdgConfigHome != null && !xdgConfigHome.isEmpty()) {
+            filename = xdgConfigHome + File.separator + "git" + File.separator 
+ "ignore";
+        } else {
+            String home = System.getenv("HOME");
+            if (home == null) {
+                home = "";
+            }
+            filename = home + File.separator + ".config" + File.separator + 
"git" + File.separator + "ignore";
+        }
+        File file = new File(filename);
+        if (file.exists()) {
+            return Optional.of(file);
+        } else {
+            return Optional.empty();
+        }
+    }
+
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/AbstractIgnoreBuilderTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/AbstractIgnoreBuilderTest.java
index d813414c..1a4de5ec 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/AbstractIgnoreBuilderTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/AbstractIgnoreBuilderTest.java
@@ -95,13 +95,14 @@ public class AbstractIgnoreBuilderTest {
     }
 
     /**
-     * Asserts the correctness of the excluder. An excluder returns false if 
the document name is matched.
+     * Asserts the correctness of the matcher.
      * @param matcherSets the list of matchers to create the 
DocumentNameMatcher from.
      * @param baseDir the base directory for the excluder test.
-     * @param matching the matching strings.
-     * @param notMatching the non-matching strings.
+     * @param matching the matching strings (i.e. that should be ignored)
+     * @param notMatching the non-matching strings (i.e. that should be 
checked)
      */
     protected void assertCorrect(List<MatcherSet> matcherSets, DocumentName 
baseDir, Iterable<String> matching, Iterable<String> notMatching) {
+        // An excluder returns false if the document name is matched.
         DocumentNameMatcher excluder = 
MatcherSet.merge(matcherSets).createMatcher();
         for (String name : matching) {
             DocumentName docName = 
baseDir.resolve(SelectorUtils.extractPattern(name, 
baseDir.getDirectorySeparator()));
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilderTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilderTest.java
index 60f73442..6a3f9b6d 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilderTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/fileProcessors/GitIgnoreBuilderTest.java
@@ -56,7 +56,12 @@ public class GitIgnoreBuilderTest extends 
AbstractIgnoreBuilderTest {
 
             writeFile(".gitignore", Arrays.asList(lines));
 
-            assertCorrect(new GitIgnoreBuilder(), matches, notMatches);
+            assertCorrect(new GitIgnoreBuilder() {
+                @Override
+                protected Optional<File> globalGitIgnore() {
+                    return Optional.empty();
+                }
+            }, matches, notMatches);
         } finally {
             System.getProperties().remove("FSInfo");
         }
@@ -106,7 +111,7 @@ public class GitIgnoreBuilderTest extends 
AbstractIgnoreBuilderTest {
             DocumentName name = documentName.resolve(test);
             assertThat(matcher.matches(name)).as(test).isTrue();
         }
-        for (String test: notMatching) {
+        for (String test : notMatching) {
             DocumentName name = documentName.resolve(test);
             assertThat(matcher.matches(name)).as(test).isFalse();
         }
@@ -114,7 +119,12 @@ public class GitIgnoreBuilderTest extends 
AbstractIgnoreBuilderTest {
 
     @Test
     public void test_RAT_335() {
-        GitIgnoreBuilder underTest = new GitIgnoreBuilder();
+        GitIgnoreBuilder underTest = new GitIgnoreBuilder() {
+            @Override
+            protected Optional<File> globalGitIgnore() {
+                return Optional.empty();
+            }
+        };
         URL url = 
GitIgnoreBuilderTest.class.getClassLoader().getResource("GitIgnoreBuilderTest/src/");
         File file = new File(url.getFile());
 
@@ -122,16 +132,49 @@ public class GitIgnoreBuilderTest extends 
AbstractIgnoreBuilderTest {
         List<MatcherSet> matcherSets = underTest.build(documentName);
         DocumentNameMatcher matcher = 
MatcherSet.merge(matcherSets).createMatcher();
 
-        DocumentName candidate = DocumentName.builder()
-                
.setName("/home/claude/apache/creadur-rat/apache-rat-core/target/test-classes/GitIgnoreBuilderTest/src/dir1/file1.log")
-                
.setBaseName("home/claude/apache/creadur-rat/apache-rat-core/target/test-classes/GitIgnoreBuilderTest/src/").build();
-        System.out.println("Decomposition for "+candidate);
-
         assertThat(matcher.toString()).isEqualTo("matcherSet(or('included 
dir1/.gitignore', 'included .gitignore'), or('excluded dir1/.gitignore', 
**/.gitignore, 'excluded .gitignore'))");
 
-        List<String> notMatching = Arrays.asList("README.txt", "dir1/dir1.md", 
"dir2/dir2.txt", "dir3/file3.log", "dir1/file1.log");
+        // files that should be checked:
+        List<String> notMatching = Arrays.asList("README.txt", "dir1/dir1.md", 
"dir2/dir2.txt", "dir3/file3.log", "dir1/file1.log", 
"local-should-precede-global.xml");
+
+        // files that should be ignored:
+        List<String> matching = Arrays.asList(".gitignore", "root.md", 
"dir1/.gitignore", "dir1/dir1.txt",  "dir2/dir2.md", "dir3/dir3.log", 
"local-should-precede-global.md");
+
+        assertCorrect(matcherSets, documentName.getBaseDocumentName(), 
matching, notMatching);
+    }
+
+    /**
+     * Test that exclusions from a global gitignore are also applied
+     *
+     * https://issues.apache.org/jira/browse/RAT-473
+     */
+    @Test
+    public void test_global_gitignore() {
+        GitIgnoreBuilder underTest = new GitIgnoreBuilder() {
+            @Override
+            protected Optional<File> globalGitIgnore() {
+                URL globalGitIgnoreUrl = 
GitIgnoreBuilderTest.class.getClassLoader().getResource("GitIgnoreBuilderTest/global-gitignore");
+                String globalGitIgnore = globalGitIgnoreUrl.getFile();
+
+                return Optional.of(new File(globalGitIgnore));
+            }
+        };
+        URL url = 
GitIgnoreBuilderTest.class.getClassLoader().getResource("GitIgnoreBuilderTest/src/");
+        File file = new File(url.getFile());
+
+        DocumentName documentName = DocumentName.builder(file).build();
+        List<MatcherSet> matcherSets = underTest.build(documentName);
+        DocumentNameMatcher matcher = 
MatcherSet.merge(matcherSets).createMatcher();
+
+        assertThat(matcher.toString()).isEqualTo("matcherSet(or('included 
dir1/.gitignore', 'included .gitignore', 'included global gitignore'), 
or('excluded dir1/.gitignore', **/.gitignore, 'excluded .gitignore', 'excluded 
global gitignore'))");
+
+        // files that should be checked:
+        // "local-should-precede-global.md" should be 'matching'
+        // here, but that is a future improvement (RAT-476)
+        List<String> notMatching = Arrays.asList("dir1/dir1.md", 
"dir2/dir2.txt", "dir3/file3.log", "dir1/file1.log", 
"local-should-precede-global.md", "local-should-precede-global.xml");
 
-        List<String> matching = Arrays.asList(".gitignore", "root.md", 
"dir1/.gitignore", "dir1/dir1.txt",  "dir2/dir2.md", "dir3/dir3.log");
+        // files that should be ignored:
+        List<String> matching = Arrays.asList(".gitignore", "README.txt", 
"root.md", "dir1/.gitignore", "dir1/dir1.txt", "dir2/dir2.md", "dir3/dir3.log");
 
         assertCorrect(matcherSets, documentName.getBaseDocumentName(), 
matching, notMatching);
     }
diff --git 
a/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/global-gitignore 
b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/global-gitignore
new file mode 100644
index 00000000..145b9982
--- /dev/null
+++ b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/global-gitignore
@@ -0,0 +1,3 @@
+/*.txt
+!/local-should-precede-global.md
+/local-should-precede-global.xml
diff --git 
a/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/.gitignore 
b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/.gitignore
index 8855fa80..fff8c14b 100644
--- a/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/.gitignore
+++ b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/.gitignore
@@ -5,3 +5,6 @@
 
 # This makes it "unignore" dir3/file3.log
 !file*.log
+
+# Don't ignore xml files
+!*.xml
diff --git 
a/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/local-should-precede-global.md
 
b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/local-should-precede-global.md
new file mode 100644
index 00000000..d8de1725
--- /dev/null
+++ 
b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/local-should-precede-global.md
@@ -0,0 +1,5 @@
+The local .gitignore ignores '*.md'
+
+The global .gitignore un-ignores 'local-should-precede-global.md'
+
+The local .gitignore takes precedence over the global, so this file should be 
ignored.
diff --git 
a/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/local-should-precede-global.xml
 
b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/local-should-precede-global.xml
new file mode 100644
index 00000000..8e3d24e6
--- /dev/null
+++ 
b/apache-rat-core/src/test/resources/GitIgnoreBuilderTest/src/local-should-precede-global.xml
@@ -0,0 +1,5 @@
+The local .gitignore un-ignores '*.xml'
+
+The global .gitignore ignores 'local-should-precede-global.xml'
+
+The local .gitignore takes precedence over the global, so this file should not 
be ignored.
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 6b7b68a0..a1f15383 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -72,6 +72,9 @@ The <action> type attribute can be one of:
     </release>
     -->
     <release version="0.17-SNAPSHOT" date="xxxx-yy-zz" description="Current 
SNAPSHOT - release to be done">
+      <action issue="RAT-473" type="add" dev="engelen">
+        Take global gitignore into account when determining which files to 
audit and which to skip.
+      </action>
       <action issue="RAT-398" type="add" dev="claudenw">
         Deprecated certain Ant report functionality in favour of new CLI 
functionality. Deprecation information is printed to indicate how the new 
options can be configured.
       </action>

Reply via email to