From 711426a632aee0affb7009cd8e3492c4ff7273e8 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 17 Feb 2020 14:43:22 -0500 Subject: [PATCH] cli: add --no-require-git flag This flag prevents ripgrep from requiring one to search a git repository in order to respect git-related ignore rules (global, .gitignore and local excludes). This actually corresponds to behavior ripgrep had long ago, but #934 changed that. It turns out that users were relying on this buggy behavior. In most cases, fixing it as simple as converting one's rules to .ignore or .rgignore files. Unfortunately, there are other use cases---like Perforce automatically respecting .gitignore files---that make a strong case for ripgrep to at least support this. The UX of a flag like this is absolutely atrocious. It's so obscure that it's really not worth explicitly calling it out anywhere. Moreover, the error cases that occur when this flag isn't used (but its behavior is desirable) will not be intuitive, do not seem easily detectable and will not guide users to this flag. Nevertheless, the motivation for this is just barely strong enough for me to begrudgingly accept this. Fixes #1414, Closes #1416 --- CHANGELOG.md | 2 ++ ci/test_complete.sh | 2 +- complete/_rg | 3 +++ ignore/src/dir.rs | 33 ++++++++++++++++++++++++++++++++- ignore/src/walk.rs | 10 ++++++++++ src/app.rs | 23 +++++++++++++++++++++++ src/args.rs | 1 + tests/feature.rs | 28 ++++++++++++++++++++++++++++ 8 files changed, 100 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95cf28d..e72d48a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Feature enhancements: Add `--include-zero` flag that shows files searched without matches. * [FEATURE #1390](https://github.com/BurntSushi/ripgrep/pull/1390): Add `--no-context-separator` flag that always hides context separators. +* [FEATURE #1414](https://github.com/BurntSushi/ripgrep/pull/1414): + Add `--no-require-git` flag to allow ripgrep to respect gitignores anywhere. * [FEATURE #1420](https://github.com/BurntSushi/ripgrep/pull/1420): Add `--no-ignore-exclude` to disregard rules in `.git/info/exclude` files. diff --git a/ci/test_complete.sh b/ci/test_complete.sh index 985ef11..0268de8 100755 --- a/ci/test_complete.sh +++ b/ci/test_complete.sh @@ -44,7 +44,7 @@ main() { # Occasionally we may have to handle some manually, however help_args=( ${(f)"$( $rg --help | - $rg -i -- '^\s+--?[a-z0-9]|--[imnp]' | + $rg -i -- '^\s+--?[a-z0-9]|--[a-z]' | $rg -ior '$1' -- $'[\t /\"\'`.,](-[a-z0-9]|--[a-z0-9-]+)\\b' | $rg -v -- --print0 | # False positives sort -u diff --git a/complete/_rg b/complete/_rg index b90d180..82dc3cd 100644 --- a/complete/_rg +++ b/complete/_rg @@ -144,6 +144,8 @@ _rg() { + '(ignore-vcs)' # VCS ignore-file options "--no-ignore-vcs[don't respect version control ignore files]" $no'--ignore-vcs[respect version control ignore files]' + "--no-require-git[don't require git repository to respect gitignore rules]" + $no'--require-git[require git repository to respect gitignore rules]' + '(ignore-dot)' # .ignore-file options "--no-ignore-dot[don't respect .ignore files]" @@ -284,6 +286,7 @@ _rg() { '--context-separator=[specify string used to separate non-continuous context lines in output]:separator' $no"--no-context-separator[don't print context separators]" '--debug[show debug messages]' + '--trace[show more verbose debug messages]' '--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size (bytes)' "(1 stats)--files[show each file that would be searched (but don't search)]" '*--ignore-file=[specify additional ignore file]:ignore file:_files' diff --git a/ignore/src/dir.rs b/ignore/src/dir.rs index cf95789..3f4d10b 100644 --- a/ignore/src/dir.rs +++ b/ignore/src/dir.rs @@ -78,6 +78,9 @@ struct IgnoreOptions { git_exclude: bool, /// Whether to ignore files case insensitively ignore_case_insensitive: bool, + /// Whether a git repository must be present in order to apply any + /// git-related ignore rules. + require_git: bool, } /// Ignore is a matcher useful for recursively walking one or more directories. @@ -385,7 +388,9 @@ impl Ignore { Match::None, Match::None, ); - let any_git = self.parents().any(|ig| ig.0.has_git); + let any_git = + !self.0.opts.require_git + || self.parents().any(|ig| ig.0.has_git); let mut saw_git = false; for ig in self.parents().take_while(|ig| !ig.0.is_absolute_parent) { if m_custom_ignore.is_none() { @@ -537,6 +542,7 @@ impl IgnoreBuilder { git_ignore: true, git_exclude: true, ignore_case_insensitive: false, + require_git: true, }, } } @@ -686,6 +692,16 @@ impl IgnoreBuilder { self } + /// Whether a git repository is required to apply git-related ignore + /// rules (global rules, .gitignore and local exclude rules). + /// + /// When disabled, git-related ignore rules are applied even when searching + /// outside a git repository. + pub fn require_git(&mut self, yes: bool) -> &mut IgnoreBuilder { + self.opts.require_git = yes; + self + } + /// Process ignore files case insensitively /// /// This is disabled by default. @@ -882,6 +898,21 @@ mod tests { assert!(ig.matched("baz", false).is_none()); } + #[test] + fn gitignore_allowed_no_git() { + let td = tmpdir(); + wfile(td.path().join(".gitignore"), "foo\n!bar"); + + let (ig, err) = IgnoreBuilder::new() + .require_git(false) + .build() + .add_child(td.path()); + assert!(err.is_none()); + assert!(ig.matched("foo", false).is_ignore()); + assert!(ig.matched("bar", false).is_whitelist()); + assert!(ig.matched("baz", false).is_none()); + } + #[test] fn ignore() { let td = tmpdir(); diff --git a/ignore/src/walk.rs b/ignore/src/walk.rs index bbe6d7d..331268a 100644 --- a/ignore/src/walk.rs +++ b/ignore/src/walk.rs @@ -780,6 +780,16 @@ impl WalkBuilder { self } + /// Whether a git repository is required to apply git-related ignore + /// rules (global rules, .gitignore and local exclude rules). + /// + /// When disabled, git-related ignore rules are applied even when searching + /// outside a git repository. + pub fn require_git(&mut self, yes: bool) -> &mut WalkBuilder { + self.ig_builder.require_git(yes); + self + } + /// Process ignore files case insensitively /// /// This is disabled by default. diff --git a/src/app.rs b/src/app.rs index 94e8a42..320261b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -602,6 +602,7 @@ pub fn all_args_and_flags() -> Vec { flag_no_ignore_vcs(&mut args); flag_no_messages(&mut args); flag_no_pcre2_unicode(&mut args); + flag_no_require_git(&mut args); flag_null(&mut args); flag_null_data(&mut args); flag_one_file_system(&mut args); @@ -1928,6 +1929,28 @@ This flag can be disabled with --pcre2-unicode. args.push(arg); } +fn flag_no_require_git(args: &mut Vec) { + const SHORT: &str = "Do not require a git repository to use gitignores."; + const LONG: &str = long!("\ +By default, ripgrep will only respect global gitignore rules, .gitignore rules +and local exclude rules if ripgrep detects that you are searching inside a +git repository. This flag allows you to relax this restriction such that +ripgrep will respect all git related ignore rules regardless of whether you're +searching in a git repository or not. + +This flag can be disabled with --require-git. +"); + let arg = RGArg::switch("no-require-git") + .help(SHORT).long_help(LONG) + .overrides("require-git"); + args.push(arg); + + let arg = RGArg::switch("require-git") + .hidden() + .overrides("no-require-git"); + args.push(arg); +} + fn flag_null(args: &mut Vec) { const SHORT: &str = "Print a NUL byte after file paths."; const LONG: &str = long!("\ diff --git a/src/args.rs b/src/args.rs index a322c55..d9eeb2e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -882,6 +882,7 @@ impl ArgMatches { .git_global(!self.no_ignore_vcs() && !self.no_ignore_global()) .git_ignore(!self.no_ignore_vcs()) .git_exclude(!self.no_ignore_vcs() && !self.no_ignore_exclude()) + .require_git(!self.is_present("no-require-git")) .ignore_case_insensitive(self.ignore_file_case_insensitive()); if !self.no_ignore() { builder.add_custom_ignore_filename(".rgignore"); diff --git a/tests/feature.rs b/tests/feature.rs index 33ab4e1..3d61f45 100644 --- a/tests/feature.rs +++ b/tests/feature.rs @@ -728,6 +728,34 @@ rgtest!(f1207_ignore_encoding, |dir: Dir, mut cmd: TestCommand| { eqnice!("\u{FFFD}\u{FFFD}\x00b\n", cmd.stdout()); }); +// See: https://github.com/BurntSushi/ripgrep/issues/1414 +rgtest!(f1414_no_require_git, |dir: Dir, mut cmd: TestCommand| { + dir.create(".gitignore", "foo"); + dir.create("foo", ""); + dir.create("bar", ""); + + let stdout = cmd.args(&[ + "--sort", "path", + "--files", + ]).stdout(); + eqnice!("bar\nfoo\n", stdout); + + let stdout = cmd.args(&[ + "--sort", "path", + "--files", + "--no-require-git", + ]).stdout(); + eqnice!("bar\n", stdout); + + let stdout = cmd.args(&[ + "--sort", "path", + "--files", + "--no-require-git", + "--require-git", + ]).stdout(); + eqnice!("bar\nfoo\n", stdout); +}); + // See: https://github.com/BurntSushi/ripgrep/pull/1420 rgtest!(f1420_no_ignore_dot, |dir: Dir, mut cmd: TestCommand| { dir.create_dir(".git/info");