From 1611c04e6f2cf2de7bdf4ab1ab1647b1efd6796b Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Sat, 28 Jul 2018 10:04:16 -0400 Subject: ignore: respect XDG_CONFIG_DIR/git/config This commit updates the logic for finding the value of git's `core.excludesFile` configuration parameter. Namely, we now check `$XDG_CONFIG_DIR/git/config` in addition to `$HOME/.gitconfig` (where the latter overrules the former on a knob-by-knob basis). Fixes #995 --- CHANGELOG.md | 2 ++ ignore/src/gitignore.rs | 64 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 630cfa17..54c65ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,8 @@ Bug fixes: * [BUG #988](https://github.com/BurntSushi/ripgrep/issues/988): Fix a bug in the `ignore` crate that prevented the use of explicit ignore files after disabling all other ignore rules. +* [BUG #995](https://github.com/BurntSushi/ripgrep/issues/995): + Respect `$XDG_CONFIG_DIR/git/config` for detecting `core.excludesFile`. 0.8.1 (2018-02-20) diff --git a/ignore/src/gitignore.rs b/ignore/src/gitignore.rs index bcfe8b53..2a3016b8 100644 --- a/ignore/src/gitignore.rs +++ b/ignore/src/gitignore.rs @@ -515,16 +515,27 @@ impl GitignoreBuilder { /// /// Note that the file path returned may not exist. fn gitconfig_excludes_path() -> Option { - gitconfig_contents() - .and_then(|data| parse_excludes_file(&data)) - .or_else(excludes_file_default) + // git supports $HOME/.gitconfig and $XDG_CONFIG_DIR/git/config. Notably, + // both can be active at the same time, where $HOME/.gitconfig takes + // precedent. So if $HOME/.gitconfig defines a `core.excludesFile`, then + // we're done. + match gitconfig_home_contents().and_then(|x| parse_excludes_file(&x)) { + Some(path) => return Some(path), + None => {} + } + match gitconfig_xdg_contents().and_then(|x| parse_excludes_file(&x)) { + Some(path) => return Some(path), + None => {} + } + excludes_file_default() } -/// Returns the file contents of git's global config file, if one exists. -fn gitconfig_contents() -> Option> { - let home = match env::var_os("HOME") { +/// Returns the file contents of git's global config file, if one exists, in +/// the user's home directory. +fn gitconfig_home_contents() -> Option> { + let home = match home_dir() { None => return None, - Some(home) => PathBuf::from(home), + Some(home) => home, }; let mut file = match File::open(home.join(".gitconfig")) { Err(_) => return None, @@ -534,17 +545,28 @@ fn gitconfig_contents() -> Option> { file.read_to_end(&mut contents).ok().map(|_| contents) } +/// Returns the file contents of git's global config file, if one exists, in +/// the user's XDG_CONFIG_DIR directory. +fn gitconfig_xdg_contents() -> Option> { + let path = env::var_os("XDG_CONFIG_HOME") + .and_then(|x| if x.is_empty() { None } else { Some(PathBuf::from(x)) }) + .or_else(|| home_dir().map(|p| p.join(".config"))) + .map(|x| x.join("git/config")); + let mut file = match path.and_then(|p| File::open(p).ok()) { + None => return None, + Some(file) => io::BufReader::new(file), + }; + let mut contents = vec![]; + file.read_to_end(&mut contents).ok().map(|_| contents) +} + /// Returns the default file path for a global .gitignore file. /// /// Specifically, this respects XDG_CONFIG_HOME. fn excludes_file_default() -> Option { - // We're fine with using env::home_dir for now. Its bugs are, IMO, pretty - // minor corner cases. We should still probably eventually migrate to - // the `dirs` crate to get a proper implementation. - #![allow(deprecated)] env::var_os("XDG_CONFIG_HOME") .and_then(|x| if x.is_empty() { None } else { Some(PathBuf::from(x)) }) - .or_else(|| env::home_dir().map(|p| p.join(".config"))) + .or_else(|| home_dir().map(|p| p.join(".config"))) .map(|x| x.join("git/ignore")) } @@ -556,7 +578,8 @@ fn parse_excludes_file(data: &[u8]) -> Option { // a full INI parser. Yuck. lazy_static! { static ref RE: Regex = Regex::new( - r"(?ium)^\s*excludesfile\s*=\s*(.+)\s*$").unwrap(); + r"(?im)^\s*excludesfile\s*=\s*(.+)\s*$" + ).unwrap(); }; let caps = match RE.captures(data) { None => return None, @@ -567,13 +590,22 @@ fn parse_excludes_file(data: &[u8]) -> Option { /// Expands ~ in file paths to the value of $HOME. fn expand_tilde(path: &str) -> String { - let home = match env::var("HOME") { - Err(_) => return path.to_string(), - Ok(home) => home, + let home = match home_dir() { + None => return path.to_string(), + Some(home) => home.to_string_lossy().into_owned(), }; path.replace("~", &home) } +/// Returns the location of the user's home directory. +fn home_dir() -> Option { + // We're fine with using env::home_dir for now. Its bugs are, IMO, pretty + // minor corner cases. We should still probably eventually migrate to + // the `dirs` crate to get a proper implementation. + #![allow(deprecated)] + env::home_dir() +} + #[cfg(test)] mod tests { use std::path::Path; -- cgit v1.2.3