Repository: ouch-org/ouch (Rust)

Issue:
Flag `-g/--gitignore` doesn't ignore `.git/`
### Version

0.4.2

### Description

`--gitignore` reads the contents of `.gitignore` files and skip the matches when recursing.

However, there are some files "ignored by default" by `git` which are not in the `.gitignore` files.

The main example being the `.git` folder, which is commonly huge, it would be very useful to have `-g` also ignoring them.

### Current Behavior

Doesn't ignore `.git/` when `-g` is set.

### Expected Behavior

Ignore `.git/` when `-g` is set.

### Additional Information

_No response_


Discussion:
I believe I'd also like to see `.gitignore` and `.gitconfig` be ignored by default.
> `.git/`

I agree

> `.gitignore`

I don't agree with this, I would consider them part of the repository. I think a good comparison is github's archived tarballs.

> `.gitconfig`

Not sure if that's a special path? Isn't this file supposed to be in `$HOME`?
> > .gitconfig
> 
> Not sure if that's a special path? Isn't this file supposed to be in $HOME?

Ah, true, this one isn't searched while traversing parent dirs like `.gitignore` is.

> I don't agree with this, I would consider them part of the repository. I think a good comparison is github's archived tarballs.

Yes, makes sense.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba482f4c0..555039dfd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@ Categories Used:
 
 - CI refactor [\#578](https://github.com/ouch-org/ouch/pull/578) ([cyqsimon](https://github.com/cyqsimon))
 - Use a prefix `tmp-ouch-` for temporary decompression path name to avoid conflicts [\#725](https://github.com/ouch-org/ouch/pull/725) ([valoq](https://github.com/valoq)) & [\#788](https://github.com/ouch-org/ouch/pull/788) ([talis-fb](https://github.com/talis-fb))
+- Ignore `.git/` when `-g/--gitignore` is set [\#507](https://github.com/ouch-org/ouch/pull/507) ([talis-fb](https://github.com/talis-fb))
 - Run clippy for tests too [\#738](https://github.com/ouch-org/ouch/pull/738) ([marcospb19](https://github.com/marcospb19))
 
 ### Improvements
diff --git a/src/utils/file_visibility.rs b/src/utils/file_visibility.rs
index d8abd40a2..fc478b23c 100644
--- a/src/utils/file_visibility.rs
+++ b/src/utils/file_visibility.rs
@@ -69,11 +69,18 @@ impl FileVisibilityPolicy {
 
     /// Walks through a directory using [`ignore::Walk`]
     pub fn build_walker(&self, path: impl AsRef<Path>) -> ignore::Walk {
-        ignore::WalkBuilder::new(path)
+        let mut builder = ignore::WalkBuilder::new(path);
+
+        builder
             .git_exclude(self.read_git_exclude)
             .git_ignore(self.read_git_ignore)
             .ignore(self.read_ignore)
-            .hidden(self.read_hidden)
-            .build()
+            .hidden(self.read_hidden);
+
+        if self.read_git_ignore {
+            builder.filter_entry(|p| p.path().file_name().is_some_and(|name| name != ".git"));
+        }
+
+        builder.build()
     }
 }


Test patch:
diff --git a/tests/integration.rs b/tests/integration.rs
index 1a984a56b..52413a71d 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -381,10 +381,9 @@ fn multiple_files_with_disabled_smart_unpack_by_dir(
     let files_path = ["file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"]
         .into_iter()
         .map(|f| src_files_path.join(f))
-        .map(|path| {
-            let mut file = fs::File::create(&path).unwrap();
+        .inspect(|path| {
+            let mut file = fs::File::create(path).unwrap();
             file.write_all("Some content".as_bytes()).unwrap();
-            path
         })
         .collect::<Vec<_>>();
 
@@ -560,3 +559,48 @@ fn symlink_pack_and_unpack(
         assert!(!f.file_type().unwrap().is_symlink())
     }
 }
+
+#[test]
+fn no_git_folder_after_decompression_with_gitignore_flag_active() {
+    use std::process::Command;
+
+    let dir = tempdir().unwrap();
+    let dir_path = dir.path();
+
+    let before = dir_path.join("before");
+
+    let decompressed = dir_path.join("decompressed");
+
+    // Create directory and a dummy file
+    fs::create_dir(&before).unwrap();
+    fs::write(before.join("hello.txt"), b"Hello, world!").unwrap();
+
+    // Run `git init` inside it
+    Command::new("git")
+        .arg("init")
+        .current_dir(&before)
+        .output()
+        .expect("failed to run git init");
+
+    assert!(before.join(".git").exists(), ".git folder should exist after git init");
+
+    // Compress it
+    let archive = dir_path.join("archive.zip");
+    ouch!("c", &before, &archive, "--gitignore");
+
+    // Decompress it
+    ouch!("d", &archive, "-d", &decompressed);
+
+    // Find the subdirectory inside decompressed (e.g., "before")
+    let decompressed_subdir = fs::read_dir(&decompressed)
+        .unwrap()
+        .find_map(Result::ok)
+        .map(|entry| entry.path())
+        .expect("Expected one directory inside decompressed");
+
+    // Assert that the decompressed folder does not include `.git/`
+    assert!(
+        !decompressed_subdir.join(".git").exists(),
+        ".git folder should not exist after decompression"
+    );
+}