This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_9x in repository https://gitbox.apache.org/repos/asf/solr.git
commit 7a4c09090da169f74989e8d2c826862d5a5e016e Author: Jan Høydahl <[email protected]> AuthorDate: Wed Jun 10 08:41:47 2026 +0200 Run RAT check only on files tracked by git (#4499) (cherry picked from commit 6a7d7664968b9461183eb7d726795f612a79e59b) (cherry picked from commit 8300816fa0ff62dbe5d9f8d9ddade3bd2dca68f2) --- gradle/validation/rat-sources.gradle | 116 ++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 22 deletions(-) diff --git a/gradle/validation/rat-sources.gradle b/gradle/validation/rat-sources.gradle index abd0db489cd..157d6a958b3 100644 --- a/gradle/validation/rat-sources.gradle +++ b/gradle/validation/rat-sources.gradle @@ -15,7 +15,18 @@ * limitations under the License. */ -import groovy.xml.NamespaceBuilder +import groovy.xml.XmlSlurper +import org.eclipse.jgit.storage.file.FileRepositoryBuilder + +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath "org.eclipse.jgit:org.eclipse.jgit:${scriptDepVersions['jgit']}" + } +} // Configure rat dependencies for use in the custom task. configure(rootProject) { @@ -28,38 +39,102 @@ configure(rootProject) { } } +/** + * Reads all file paths from the git index (DirCache) once and caches the result. + * Returns null if this is not a git repository or the index cannot be read. + * Paths use forward slashes and are relative to the repository root. + */ +Set<String> loadGitTrackedFiles() { + if (rootProject.ext.has('ratGitIndexLoaded')) return rootProject.ext.ratGitTrackedFiles + rootProject.ext.ratGitIndexLoaded = true + rootProject.ext.ratGitTrackedFiles = null + + def dotGit = new File(rootProject.projectDir, ".git") + if (dotGit.isFile()) { + // git worktrees use a .git file — jgit does not fully support them + logger.warn("WARNING: git worktrees are not supported by jgit — RAT git-tracking filter disabled.") + return null + } + if (!dotGit.isDirectory()) { + return null // not a git repository + } + + def repository = null + try { + repository = new FileRepositoryBuilder() + .setWorkTree(rootProject.projectDir) + .setMustExist(true) + .build() + def tracked = new HashSet<String>() + def dirCache = repository.readDirCache() + for (int i = 0; i < dirCache.getEntryCount(); i++) { + tracked.add(dirCache.getEntry(i).getPathString()) + } + rootProject.ext.ratGitTrackedFiles = tracked + return tracked + } catch (Exception e) { + logger.warn("WARNING: Could not read git index for RAT check: ${e.message}") + return null + } finally { + if (repository != null) repository.close() + } +} + +/** + * Returns the set of git-tracked file paths relative to the given project directory, + * or null if git tracking is unavailable. + */ +Set<String> gitTrackedFiles(File dir) { + def allFiles = loadGitTrackedFiles() + if (allFiles == null) return null + + def prefix = rootProject.projectDir.toPath().relativize(dir.toPath()) + .toString().replace('\\', '/') + if (prefix.isEmpty()) return allFiles + + prefix += "/" + def result = new HashSet<String>() + for (String path : allFiles) { + if (path.startsWith(prefix)) { + result.add(path.substring(prefix.length())) + } + } + return result +} + // Configure the rat validation task and all scanned directories. allprojects { - task("rat", type: RatTask) { + tasks.register("rat", RatTask) { group = 'Verification' description = 'Runs Apache Rat checks.' def defaultScanFileTree = project.fileTree(projectDir, { - // Don't check under the project's build folder. - exclude project.buildDir.name - - // Exclude any generated stuff. - exclude "src/generated" + // Only check files tracked by git — skip untracked/gitignored files + // (IDE artifacts, AI assistant configs, etc.) + def trackedFiles = gitTrackedFiles(projectDir) + if (trackedFiles != null) { + exclude { element -> + // Allow directories through (they are just containers), exclude untracked files + !element.isDirectory() && !trackedFiles.contains(element.relativePath.pathString) + } + } - // Don't recurse into local Lucene dev repo. - exclude "/lucene" + // Exclude the build directory — even though it's not git-tracked, Gradle + // validates input/output overlaps at configuration time before the git + // filter closure runs, so this must be excluded explicitly. + exclude project.layout.buildDirectory.get().asFile.name // Don't check any of the subprojects - they have their own rat tasks. exclude subprojects.collect { it.projectDir.name } - // At the module scope we only check selected file patterns as folks have various .gitignore-d resources - // generated by IDEs, etc. + // The git index filter above excludes untracked files. These include + // patterns select the file types that should carry license headers. include "**/*.xml" include "**/*.md" include "**/*.py" include "**/*.sh" include "**/*.bat" - // include build.gradle but exclude .gradle directories include "**/*.gradle" - exclude ".gradle/**" - - // Exclude Eclipse - exclude ".metadata" // Include selected patterns from any source folders. We could make this // relative to source sets but it seems to be of little value - all our source sets @@ -84,9 +159,6 @@ allprojects { case ":": include "gradlew" include "gradlew.bat" - exclude ".idea" - exclude ".muse" - exclude ".git" // Exclude github stuff (templates, workflows). exclude ".github" @@ -278,11 +350,11 @@ class RatTask extends DefaultTask { } def printUnknownFiles(File reportFile) { - def ratXml = new XmlParser().parse(reportFile) + def ratXml = new XmlSlurper().parse(reportFile) def errors = [] ratXml.resource.each { resource -> - if (resource.'license-approval'.@name[0] == "false") { - errors << "Unknown license: ${resource.@name}" + if (resource.'license-approval'[email protected]() == "false") { + errors << "Unknown license: ${[email protected]()}" } } def checkProp = "validation.rat.failOnError"
