rmannibucau commented on code in PR #746: URL: https://github.com/apache/creadur-rat/pull/746#discussion_r4381390016
########## apache-rat-core/src/main/java/org/apache/rat/config/exclusion/fileprocessors/GitConfig.java: ########## @@ -0,0 +1,462 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rat.config.exclusion.fileprocessors; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; + +import org.apache.rat.api.EnvVar; + +/** + * Reads the global git configuration files to resolve the file configured + * for {@code core.excludesFile}. + * <p> + * The configuration files are read in git order: {@code $XDG_CONFIG_HOME/git/config} + * first and {@code ~/.gitconfig} second. The last value found for + * {@code core.excludesFile} wins. + * </p> + * <p> + * {@code [include] path} directives are supported and the included files are expanded + * in place, recursively. The relative paths of included files are resolved against the + * directory of the including file, {@code ~/} is expanded to the home directory and a + * depth limit of {@value #MAX_INCLUDE_DEPTH} is enforced like git does. + * </p> + * <p> + * {@code includeIf} directives, the system configuration ({@code /etc/gitconfig}), the + * {@code GIT_CONFIG_SYSTEM} variable and the repository configuration ({@code .git/config}) are + * not supported. The {@code GIT_CONFIG_GLOBAL} variable, when set, replaces the + * {@code $HOME/.gitconfig} and {@code $XDG_CONFIG_HOME/git/config} files. + * </p> + * <p> + * The home directory used for {@code ~} expansion is resolved from {@code HOME}, falling back to + * {@link org.apache.rat.api.EnvVar#USER_PROFILE} (Windows, backed by the {@code USERPROFILE} + * environment variable) and then the {@code user.home} system property. + * </p> + * <p> + * When {@code core.excludesFile} is not configured the default location next to the XDG + * configuration ({@code $XDG_CONFIG_HOME/git/ignore} or {@code $HOME/.config/git/ignore}) + * is used. + * </p> + */ +public class GitConfig { + /** The name of the 'core' section. */ + private static final String CORE_SECTION = "core"; + /** The name of the 'include' section. */ + private static final String INCLUDE_SECTION = "include"; + /** The key of the core section holding the global ignore file. */ + private static final String EXCLUDES_FILE_KEY = "excludesfile"; + /** The key of the include section holding the file to include. */ + private static final String INCLUDE_PATH_KEY = "path"; + /** The maximum depth of nested includes, matching the git limit. */ + private static final int MAX_INCLUDE_DEPTH = 10; + /** The home directory prefix of a path. */ + private static final String HOME_PREFIX = "~/"; + /** The Windows style home directory prefix of a path. */ + private static final String HOME_BACKSLASH_PREFIX = "~\\"; + /** The comment marker for a full line comment. */ + private static final String COMMENT_PREFIX = "#"; + /** The alternate comment marker for a full line comment. */ + private static final String SEMICOLON_COMMENT_PREFIX = ";"; + /** The opening quote of a quoted value. */ + private static final String QUOTE = "\""; + /** The escape character of a quoted value. */ + private static final String ESCAPE = "\\"; + + /** The XDG configuration file. */ + private final File xdgConfig; + /** The user configuration file. */ + private final File userConfig; + /** The home directory or {@code null} when not defined. */ + private final String home; + /** The directory against which relative paths are resolved. */ + private final File cwd; + /** The default global ignore file, next to the XDG configuration or {@code null}. */ + private final File defaultIgnore; + /** The file configured by the {@code GIT_CONFIG_GLOBAL} environment variable or {@code null}. */ + private final File globalConfig; + + /** + * Creates a GitConfig reading the configuration files from the environment. + */ + public GitConfig() { + this(gitConfigGlobalFile(), xdgConfigFile(), userConfigFile(), homeDir(), + new File(System.getProperty("user.dir"))); + } + + /** + * Creates a GitConfig reading the given configuration files. + * @param xdgConfig the XDG configuration file or {@code null} if there is none. + * @param userConfig the user configuration file or {@code null} if there is none. + * @param home the home directory used for {@code ~/} expansion. + */ + GitConfig(final File xdgConfig, final File userConfig, final String home) { + this(null, xdgConfig, userConfig, home, new File(System.getProperty("user.dir"))); + } + + /** + * Creates a GitConfig reading the given configuration files. + * @param xdgConfig the XDG configuration file or {@code null} if there is none. + * @param userConfig the user configuration file or {@code null} if there is none. + * @param home the home directory used for {@code ~/} expansion. + * @param cwd the directory against which relative paths are resolved. + */ + GitConfig(final File xdgConfig, final File userConfig, final String home, final File cwd) { + this(null, xdgConfig, userConfig, home, cwd); + } + + /** + * Creates a GitConfig reading the given configuration files. + * @param globalConfig the effective global config file (from {@code GIT_CONFIG_GLOBAL}) + * or {@code null} to use the XDG and user configuration files. + * @param xdgConfig the XDG configuration file or {@code null} if there is none. + * @param userConfig the user configuration file or {@code null} if there is none. + * @param home the home directory used for {@code ~} expansion. + * @param cwd the directory against which relative paths are resolved. + */ + GitConfig(final File globalConfig, final File xdgConfig, final File userConfig, final String home, final File cwd) { + this.globalConfig = globalConfig; + this.xdgConfig = xdgConfig; + this.userConfig = userConfig; + this.home = home; + this.cwd = cwd; + File parent = xdgConfig == null ? null : xdgConfig.getAbsoluteFile().getParentFile(); + this.defaultIgnore = parent == null ? null : new File(parent, "ignore"); + } + + /** + * Resolves the global git ignore file. + * <p> + * If {@code core.excludesFile} is configured in one of the git configuration files the configured + * file is returned - even if it does not exist, matching git behavior where a configured file + * disables the default location. Otherwise the default location is returned if it exists. + * </p> + * @return the configured or default global git ignore file or an empty Optional if neither exists. + */ + public Optional<File> coreExcludesFile() { + List<String> configured = new ArrayList<>(); + Set<File> includeChain = new HashSet<>(); + if (globalConfig != null) { + // when GIT_CONFIG_GLOBAL is set, neither ~/.gitconfig nor $XDG_CONFIG_HOME/git/config are read + parseFile(globalConfig, configured, includeChain, 0); + } else { + parseFile(xdgConfig, configured, includeChain, 0); + parseFile(userConfig, configured, includeChain, 0); + } + if (!configured.isEmpty()) { + String value = configured.get(configured.size() - 1); + return resolveExcludesFile(value); + } Review Comment: I tested the `GIT_CONFIG_GLOBAL` behavior against real git and it is a single file path, not a newline-separated list. Setting it to a value containing a newline makes git try to open that literal filename and yields no config: ``` $ GIT_CONFIG_GLOBAL="/tmp/dir/a.gitconfig\n/tmp/dir/b.gitconfig" git config --get core.excludesFile # exit 1, empty $ GIT_CONFIG_GLOBAL=/tmp/dir/b.gitconfig git config --get core.excludesFile # exit 0, prints value ``` The "some can contain multiple values separated by newlines" text in `git-var(1)` describes that command's output formatting, not the input variable. In the git source (`config.c::git_global_config_paths`) `GIT_CONFIG_GLOBAL` is read as a single `char *` pathname which replaces `~/.gitconfig` and the XDG file, exactly as this code does. On the path resolution question I also verified with `git check-ignore -v -n`: * a relative `core.excludesFile` value is resolved against the current working directory and its patterns are anchored at the root of the working tree (matches the same file both at the root and in a subdirectory); * a relative `[include] path` value is resolved against the directory of the including config file. Both match this implementation. The system config (`/etc/gitconfig`), `includeIf` and the repository `.git/config` remain intentionally out of scope for RAT's audit-side read. I have updated the code accordingly (`GitConfig` moved into `GitIgnoreBuilder` as a `protected static` nested type, home resolved from `user.home`, and `GIT_CONFIG_GLOBAL` kept as a single path). -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
