diff --git a/CHANGELOG.md b/CHANGELOG.md index 95cf28d2..e72d48af 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 985ef11b..0268de89 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 b90d1801..82dc3cdb 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 cf957891..3f4d10bb 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 bbe6d7d7..331268a3 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 94e8a421..320261b5 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 a322c55b..d9eeb2e1 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 33ab4e17..3d61f459 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");