Repository: sharkdp/fd
Pull request #1216 (2022-12-29 01:40:23)

Add flag --no-require-git to always respect gitignore files
Add flag --no-require-git to always respect gitignore files

Summary: Currently, `--ignore-vcs` only serves to unset `--no-ignore-vcs`.
There is currently no way to tell fd to respect gitignore files when not in a
git repository.  This commit adds the flag `--no-require-git` to make fd always
respect all gitignore files.

This behaves the same as the `--no-require-git` option in [ripgrep](https://github.com/BurntSushi/ripgrep/blob/3bb71b0cb8727ac43237af78ba5c707298de0128/crates/core/app.rs#L2214-L2226)

This commit also contains an unrelated wording fix to CONTRIBUTING.md

Test Plan: `tests/tests.rs`

Background: I am using [Sapling](https://sapling-scm.com/docs/introduction/)
for working with git repositories (including this commit ☺️).  Since Sapling
uses `.sl` instead of `.git`, tools using the `ignore` crate (rg and fd) would show gitignored files.
I made a patch (https://github.com/vegerot/ripgrep/commit/ebf17eef22a04450ca86e9dc790db7cb00e11e52)
to `ignore` to respect gitignores with _either_ `.git` or `.sl`.  However,
@BurntSushi said he did not want to merge that patch and instead suggested I
use `--no-require-git` (https://github.com/BurntSushi/ripgrep/issues/2374).
This works fine, but I couldn't use this workaround for my other favorite tool!
That's what this patch is for 😁

(a follow-up patch will add a similar `FD_CONFIG_PATH` environment variable
like `RG_CONFIG_PATH`)

---
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/sharkdp/fd/pull/1216).
* #1218
* __->__ #1216


tmccombs: I think I'd rather have a separate option for this. In particular, because changing the semantics of --ignore-vcs would mean there is no longer a way to exactly negate --no-ignore-vcs 
vegerot: > I think I'd rather have a separate option for this. In particular, because changing the semantics of --ignore-vcs would mean there is no longer a way to exactly negate --no-ignore-vcs

Good point.  @tmccombs what do you think about using the same name as ripgrep's option: `--{no-,}require-git`?
tmccombs: That seems ok to me
vegerot: @tmccombs done ✅!
vegerot: I should add `--{no-,}require-git` to the `test_opposing` test

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be1efc2..c3bef47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 ## Features
 
+- Add flag --no-require-git to always respect gitignore files, see #1216 (@vegerot)
+
 ## Bugfixes
 
 - Fix logic for when to use global ignore file. There was a bug where the only case where the
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8a2313e..b3a7480 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,11 +13,11 @@ give us the chance to discuss any potential changes first.
 ## Add an entry to the changelog
 
 If your contribution changes the behavior of `fd` (as opposed to a typo-fix
-in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file
+in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md#upcoming-release) file
 and describe your changes. This makes the release process much easier and
 therefore helps to get your changes into a new `fd` release faster.
 
-The top of the `CHANGELOG` contains a *"unreleased"* section with a few
+The top of the `CHANGELOG` contains an *"Upcoming release"* section with a few
 subsections (Features, Bugfixes, …). Please add your entry to the subsection
 that best describes your change.
 
diff --git a/src/cli.rs b/src/cli.rs
index 8e97f1c..55fbca8 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -74,6 +74,28 @@ pub struct Opts {
     #[arg(long, overrides_with = "no_ignore_vcs", hide = true, action = ArgAction::SetTrue)]
     ignore_vcs: (),
 
+    /// Do not require a git repository to respect gitignores.
+    /// By default, fd will only respect global gitignore rules, .gitignore rules,
+    /// and local exclude rules if fd detects that you are searching inside a
+    /// git repository. This flag allows you to relax this restriction such that
+    /// fd 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.
+    #[arg(
+        long,
+        overrides_with = "require_git",
+        hide_short_help = true,
+        // same description as ripgrep's flag: ripgrep/crates/core/app.rs
+        long_help
+    )]
+    pub no_require_git: bool,
+
+    /// Overrides --no-require-git
+    #[arg(long, overrides_with = "no_require_git", hide = true, action = ArgAction::SetTrue)]
+    require_git: (),
+
     /// Show search results from files and directories that would otherwise be
     /// ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories.
     #[arg(
diff --git a/src/config.rs b/src/config.rs
index 7003a01..75b4c2b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -30,6 +30,9 @@ pub struct Config {
     /// Whether to respect VCS ignore files (`.gitignore`, ..) or not.
     pub read_vcsignore: bool,
 
+    /// Whether to require a `.git` directory to respect gitignore files.
+    pub require_git_to_read_vcsignore: bool,
+
     /// Whether to respect the global ignore file or not.
     pub read_global_ignore: bool,
 
diff --git a/src/main.rs b/src/main.rs
index 001ba5a..567058c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -239,6 +239,7 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result<Config
         ignore_hidden: !(opts.hidden || opts.rg_alias_ignore()),
         read_fdignore: !(opts.no_ignore || opts.rg_alias_ignore()),
         read_vcsignore: !(opts.no_ignore || opts.rg_alias_ignore() || opts.no_ignore_vcs),
+        require_git_to_read_vcsignore: !opts.no_require_git,
         read_parent_ignore: !opts.no_ignore_parent,
         read_global_ignore: !(opts.no_ignore
             || opts.rg_alias_ignore()
diff --git a/src/walk.rs b/src/walk.rs
index a1afda9..2b03fa9 100644
--- a/src/walk.rs
+++ b/src/walk.rs
@@ -77,6 +77,7 @@ pub fn scan(paths: &[PathBuf], patterns: Arc<Vec<Regex>>, config: Arc<Config>) -
         .git_ignore(config.read_vcsignore)
         .git_global(config.read_vcsignore)
         .git_exclude(config.read_vcsignore)
+        .require_git(config.require_git_to_read_vcsignore)
         .overrides(overrides)
         .follow_links(config.follow_links)
         // No need to check for supported platforms, option is unavailable on unsupported ones


Test patch:
diff --git a/tests/tests.rs b/tests/tests.rs
index 5036afd..a2bc4d9 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -808,6 +808,62 @@ fn test_custom_ignore_precedence() {
     te.assert_output(&["--no-ignore", "foo"], "inner/foo");
 }
 
+/// Don't require git to respect gitignore (--no-require-git)
+#[test]
+fn test_respect_ignore_files() {
+    let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
+
+    // Not in a git repo anymore
+    fs::remove_dir(te.test_root().join(".git")).unwrap();
+
+    // don't respect gitignore because we're not in a git repo
+    te.assert_output(
+        &["foo"],
+        "a.foo
+        gitignored.foo
+        one/b.foo
+        one/two/c.foo
+        one/two/C.Foo2
+        one/two/three/d.foo
+        one/two/three/directory_foo/",
+    );
+
+    // respect gitignore because we set `--no-require-git`
+    te.assert_output(
+        &["--no-require-git", "foo"],
+        "a.foo
+        one/b.foo
+        one/two/c.foo
+        one/two/C.Foo2
+        one/two/three/d.foo
+        one/two/three/directory_foo/",
+    );
+
+    // make sure overriding works
+    te.assert_output(
+        &["--no-require-git", "--require-git", "foo"],
+        "a.foo
+        gitignored.foo
+        one/b.foo
+        one/two/c.foo
+        one/two/C.Foo2
+        one/two/three/d.foo
+        one/two/three/directory_foo/",
+    );
+
+    te.assert_output(
+        &["--no-require-git", "--no-ignore", "foo"],
+        "a.foo
+        gitignored.foo
+        fdignored.foo
+        one/b.foo
+        one/two/c.foo
+        one/two/C.Foo2
+        one/two/three/d.foo
+        one/two/three/directory_foo/",
+    );
+}
+
 /// VCS ignored files (--no-ignore-vcs)
 #[test]
 fn test_no_ignore_vcs() {
@@ -2365,6 +2421,7 @@ fn test_number_parsing_errors() {
 #[test_case("--hidden", &["--no-hidden"] ; "hidden")]
 #[test_case("--no-ignore", &["--ignore"] ; "no-ignore")]
 #[test_case("--no-ignore-vcs", &["--ignore-vcs"] ; "no-ignore-vcs")]
+#[test_case("--no-require-git", &["--require-git"] ; "no-require-git")]
 #[test_case("--follow", &["--no-follow"] ; "follow")]
 #[test_case("--absolute-path", &["--relative-path"] ; "absolute-path")]
 #[test_case("-u", &["--ignore", "--no-hidden"] ; "u")]