1
0
Эх сурвалжийг харах

Ignore files matched in .gitignore

This doesn’t *completely* work: it seems to have trouble with ignored paths beginning with slashes, possibly amongst others. Also, .gitignore scanning could be made more efficient.
Benjamin Sago 8 жил өмнө
parent
commit
827aa8bfc3

+ 1 - 1
src/exa.rs

@@ -230,7 +230,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
                     details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.git.as_ref(), self.ignore.as_ref(), self.writer)
                 }
                 Mode::GridDetails(ref opts) => {
-                    grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.git.as_ref(), self.ignore.as_ref(), self.writer)
+                    grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.git.as_ref(), self.writer)
                 }
             }
         }

+ 6 - 0
src/fs/dir.rs

@@ -45,6 +45,8 @@ impl Dir {
     /// Produce an iterator of IO results of trying to read all the files in
     /// this directory.
     pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, ignore: Option<&'ig IgnoreCache>) -> Files<'dir, 'ig> {
+        if let Some(i) = ignore { i.discover_underneath(&self.path); }
+
         Files {
             inner:     self.contents.iter(),
             dir:       self,
@@ -103,6 +105,10 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
                 let filename = File::filename(path);
                 if !self.dotfiles && filename.starts_with(".") { continue }
 
+                if let Some(i) = self.ignore {
+                    if i.is_ignored(path) { continue }
+                }
+
                 return Some(File::new(path.clone(), self.dir, filename)
                                  .map_err(|e| (path.clone(), e)))
             }

+ 15 - 6
src/fs/feature/ignore.rs

@@ -6,15 +6,17 @@
 use std::fs::File;
 use std::io::Read;
 use std::path::{Path, PathBuf};
+use std::sync::RwLock;
 
 use fs::filter::IgnorePatterns;
 
 
 /// An **ignore cache** holds sets of glob patterns paired with the
-/// directories that they should be ignored underneath.
+/// directories that they should be ignored underneath. Believe it or not,
+/// that’s a valid English sentence.
 #[derive(Default, Debug)]
 pub struct IgnoreCache {
-    entries: Vec<(PathBuf, IgnorePatterns)>
+    entries: RwLock<Vec<(PathBuf, IgnorePatterns)>>
 }
 
 impl IgnoreCache {
@@ -23,27 +25,35 @@ impl IgnoreCache {
     }
 
     #[allow(unused_results)]  // don’t do this
-    pub fn discover_underneath(&mut self, path: &Path) {
+    pub fn discover_underneath(&self, path: &Path) {
         let mut path = Some(path);
+        let mut entries = self.entries.write().unwrap();
 
         while let Some(p) = path {
+            if p.components().next().is_none() { break }
+
             let ignore_file = p.join(".gitignore");
             if ignore_file.is_file() {
+                debug!("Found a .gitignore file: {:?}", ignore_file);
                 if let Ok(mut file) = File::open(ignore_file) {
                     let mut contents = String::new();
                     file.read_to_string(&mut contents).expect("Reading gitignore failed");
 
                     let (patterns, mut _errors) = IgnorePatterns::parse_from_iter(contents.lines());
-                    self.entries.push((p.into(), patterns));
+                    entries.push((p.into(), patterns));
                 }
             }
+            else {
+                debug!("Found no .gitignore file at {:?}", ignore_file);
+            }
 
             path = p.parent();
         }
     }
 
     pub fn is_ignored(&self, suspect: &Path) -> bool {
-        self.entries.iter().any(|&(ref base_path, ref patterns)| {
+        let entries = self.entries.read().unwrap();
+        entries.iter().any(|&(ref base_path, ref patterns)| {
             if let Ok(suffix) = suspect.strip_prefix(&base_path) {
                 patterns.is_ignored_path(suffix)
             }
@@ -55,7 +65,6 @@ impl IgnoreCache {
 }
 
 
-
 #[cfg(test)]
 mod test {
     use super::*;

+ 7 - 5
src/output/grid_details.rs

@@ -6,7 +6,6 @@ use ansi_term::ANSIStrings;
 use term_grid as grid;
 
 use fs::{Dir, File};
-use fs::feature::ignore::IgnoreCache;
 use fs::feature::git::GitCache;
 use fs::feature::xattr::FileAttributes;
 use fs::filter::FileFilter;
@@ -112,16 +111,19 @@ impl<'a> Render<'a> {
         }
     }
 
-    pub fn render<W: Write>(self, git: Option<&GitCache>, ignore: Option<&'a IgnoreCache>, w: &mut W) -> IOResult<()> {
-        if let Some((grid, width)) = self.find_fitting_grid(git, ignore) {
+    // This doesn’t take an IgnoreCache even though the details one does
+    // because grid-details has no tree view.
+
+    pub fn render<W: Write>(self, git: Option<&GitCache>, w: &mut W) -> IOResult<()> {
+        if let Some((grid, width)) = self.find_fitting_grid(git) {
             write!(w, "{}", grid.fit_into_columns(width))
         }
         else {
-            self.give_up().render(git, ignore, w)
+            self.give_up().render(git, None, w)
         }
     }
 
-    pub fn find_fitting_grid(&self, git: Option<&GitCache>, ignore: Option<&'a IgnoreCache>) -> Option<(grid::Grid, grid::Width)> {
+    pub fn find_fitting_grid(&self, git: Option<&GitCache>) -> Option<(grid::Grid, grid::Width)> {
         let options = self.details.table.as_ref().expect("Details table options not given!");
 
         let drender = self.details();

+ 23 - 0
xtests/git_2_ignore_recurse

@@ -0,0 +1,23 @@
+drwxrwxr-x - cassowary  1 Jan 12:34 deeply
+drwxrwxr-x - cassowary  1 Jan 12:34 ignoreds
+
+/testcases/git2/deeply:
+drwxrwxr-x - cassowary  1 Jan 12:34 nested
+
+/testcases/git2/deeply/nested:
+drwxrwxr-x - cassowary  1 Jan 12:34 directory
+drwxrwxr-x - cassowary  1 Jan 12:34 repository
+
+/testcases/git2/deeply/nested/directory:
+.rw-rw-r--  0 cassowary  1 Jan 12:34 l8st
+.rw-rw-r-- 18 cassowary  1 Jan 12:34 upd8d
+
+/testcases/git2/deeply/nested/repository:
+.rw-rw-r-- 0 cassowary  1 Jan 12:34 subfile
+
+/testcases/git2/ignoreds:
+.rw-rw-r-- 0 cassowary  1 Jan 12:34 music.m4a
+drwxrwxr-x - cassowary  1 Jan 12:34 nested
+
+/testcases/git2/ignoreds/nested:
+.rw-rw-r-- 0 cassowary  1 Jan 12:34 funky chicken.m4a

+ 12 - 0
xtests/git_2_ignore_tree

@@ -0,0 +1,12 @@
+drwxrwxr-x  - cassowary  1 Jan 12:34 /testcases/git2
+drwxrwxr-x  - cassowary  1 Jan 12:34 ├── deeply
+drwxrwxr-x  - cassowary  1 Jan 12:34 │  └── nested
+drwxrwxr-x  - cassowary  1 Jan 12:34 │     ├── directory
+.rw-rw-r--  0 cassowary  1 Jan 12:34 │     │  ├── l8st
+.rw-rw-r-- 18 cassowary  1 Jan 12:34 │     │  └── upd8d
+drwxrwxr-x  - cassowary  1 Jan 12:34 │     └── repository
+.rw-rw-r--  0 cassowary  1 Jan 12:34 │        └── subfile
+drwxrwxr-x  - cassowary  1 Jan 12:34 └── ignoreds
+.rw-rw-r--  0 cassowary  1 Jan 12:34    ├── music.m4a
+drwxrwxr-x  - cassowary  1 Jan 12:34    └── nested
+.rw-rw-r--  0 cassowary  1 Jan 12:34       └── funky chicken.m4a

+ 5 - 0
xtests/run.sh

@@ -223,6 +223,11 @@ $exa $testcases/git2/deeply/nested/directory $testcases/git/edits \
 COLUMNS=40  $exa $testcases/files -lG --git | diff -q - $results/files_lG_40  || exit 1    # that aren't under git
 
 
+# .gitignore
+$exa $testcases/git2 --recurse --long --git-ignore 2>&1 | diff - $results/git_2_ignore_recurse
+$exa $testcases/git2    --tree --long --git-ignore 2>&1 | diff - $results/git_2_ignore_tree
+
+
 # Hidden files
 COLUMNS=80 $exa $testcases/hiddens     2>&1 | diff -q - $results/hiddens     || exit 1
 COLUMNS=80 $exa $testcases/hiddens -a  2>&1 | diff -q - $results/hiddens_a   || exit 1