Преглед изворни кода

Merge pull request #653 from ariasuni/fix-gitignore-option

Use git2 instead of parsing .gitignore for --git-ignore
Benjamin Sago пре 5 година
родитељ
комит
1fe06a7682
8 измењених фајлова са 26 додато и 236 уклоњено
  1. 3 18
      src/exa.rs
  2. 8 8
      src/fs/dir.rs
  3. 0 198
      src/fs/feature/ignore.rs
  4. 0 1
      src/fs/feature/mod.rs
  5. 1 0
      src/fs/fields.rs
  6. 5 1
      src/options/mod.rs
  7. 8 9
      src/output/details.rs
  8. 1 1
      src/output/grid_details.rs

+ 3 - 18
src/exa.rs

@@ -11,7 +11,6 @@ use ansi_term::{ANSIStrings, Style};
 use log::debug;
 
 use crate::fs::{Dir, File};
-use crate::fs::feature::ignore::IgnoreCache;
 use crate::fs::feature::git::GitCache;
 use crate::options::{Options, Vars};
 pub use crate::options::vars;
@@ -44,10 +43,6 @@ pub struct Exa<'args, 'w, W: Write + 'w> {
     /// This has to last the lifetime of the program, because the user might
     /// want to list several directories in the same repository.
     pub git: Option<GitCache>,
-
-    /// A cache of git-ignored files.
-    /// This lasts the lifetime of the program too, for the same reason.
-    pub ignore: Option<IgnoreCache>,
 }
 
 /// The “real” environment variables type.
@@ -71,15 +66,6 @@ fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
     }
 }
 
-fn ignore_cache(options: &Options) -> Option<IgnoreCache> {
-    use crate::fs::filter::GitIgnore;
-
-    match options.filter.git_ignore {
-        GitIgnore::CheckAndIgnore => Some(IgnoreCache::new()),
-        GitIgnore::Off            => None,
-    }
-}
-
 impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
     pub fn from_args<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
     where I: Iterator<Item=&'args OsString> {
@@ -95,8 +81,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
             }
 
             let git = git_options(&options, &args);
-            let ignore = ignore_cache(&options);
-            Exa { options, writer, args, git, ignore }
+            Exa { options, writer, args, git }
         })
     }
 
@@ -157,7 +142,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
             }
 
             let mut children = Vec::new();
-            for file in dir.files(self.options.filter.dot_filter, self.ignore.as_ref()) {
+            for file in dir.files(self.options.filter.dot_filter, self.git.as_ref()) {
                 match file {
                     Ok(file)       => children.push(file),
                     Err((path, e)) => writeln!(stderr(), "[{}: {}]", path.display(), e)?,
@@ -217,7 +202,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
                     let recurse = self.options.dir_action.recurse_options();
 
                     let r = details::Render { dir, files, colours, style, opts, filter, recurse };
-                    r.render(self.git.as_ref(), self.ignore.as_ref(), self.writer)
+                    r.render(self.git.as_ref(), self.writer)
                 }
 
                 Mode::GridDetails(ref opts) => {

+ 8 - 8
src/fs/dir.rs

@@ -1,3 +1,5 @@
+use crate::fs::feature::git::GitCache;
+use crate::fs::fields::GitStatus;
 use std::io::{self, Result as IOResult};
 use std::fs;
 use std::path::{Path, PathBuf};
@@ -6,7 +8,6 @@ use std::slice::Iter as SliceIter;
 use log::info;
 
 use crate::fs::File;
-use crate::fs::feature::ignore::IgnoreCache;
 
 
 /// A **Dir** provides a cached list of the file paths in a directory that's
@@ -46,15 +47,13 @@ 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); }
-
+    pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, git: Option<&'ig GitCache>) -> Files<'dir, 'ig> {
         Files {
             inner:     self.contents.iter(),
             dir:       self,
             dotfiles:  dots.shows_dotfiles(),
             dots:      dots.dots(),
-            ignore,
+            git,
         }
     }
 
@@ -86,7 +85,7 @@ pub struct Files<'dir, 'ig> {
     /// any files have been listed.
     dots: DotsNext,
 
-    ignore: Option<&'ig IgnoreCache>,
+    git: Option<&'ig GitCache>,
 }
 
 impl<'dir, 'ig> Files<'dir, 'ig> {
@@ -107,8 +106,9 @@ 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 }
+                let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default();
+                if git_status.unstaged == GitStatus::Ignored {
+                     continue;
                 }
 
                 return Some(File::from_args(path.clone(), self.dir, filename)

+ 0 - 198
src/fs/feature/ignore.rs

@@ -1,198 +0,0 @@
-//! Ignoring globs in `.gitignore` files.
-//!
-//! This uses a cache because the file with the globs in might not be the same
-//! directory that we’re listing!
-
-use std::fs::File;
-use std::io::Read;
-use std::path::{Path, PathBuf};
-use std::sync::RwLock;
-
-use log::debug;
-
-use crate::fs::filter::IgnorePatterns;
-
-
-/// An **ignore cache** holds sets of glob patterns paired with the
-/// directories that they should be ignored underneath. Believe it or not,
-/// that’s a valid English sentence.
-#[derive(Default, Debug)]
-pub struct IgnoreCache {
-    entries: RwLock<Vec<(PathBuf, IgnorePatterns)>>
-}
-
-impl IgnoreCache {
-    pub fn new() -> IgnoreCache {
-        IgnoreCache::default()
-    }
-
-    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();
-
-                    match file.read_to_string(&mut contents) {
-                        Ok(_) => {
-                            let patterns = file_lines_to_patterns(contents.lines());
-                            entries.push((p.into(), patterns));
-                        }
-                        Err(e) => debug!("Failed to read a .gitignore: {:?}", e)
-                    }
-                }
-            }
-            else {
-                debug!("Found no .gitignore file at {:?}", ignore_file);
-            }
-
-            path = p.parent();
-        }
-    }
-
-    pub fn is_ignored(&self, suspect: &Path) -> bool {
-        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)
-            }
-            else {
-                false
-            }
-        })
-    }
-}
-
-
-fn file_lines_to_patterns<'a, I>(iter: I) -> IgnorePatterns
-where I: Iterator<Item=&'a str>
-{
-    let iter = iter.filter(|el| !el.is_empty());
-    let iter = iter.filter(|el| !el.starts_with('#'));
-
-    // TODO: Figure out if this should trim whitespace or not
-
-    // Errors are currently being ignored... not a good look
-    IgnorePatterns::parse_from_iter(iter).0
-}
-
-
-#[cfg(test)]
-mod test {
-    use super::*;
-
-    #[test]
-    fn parse_nothing() {
-        use std::iter::empty;
-        let (patterns, _) = IgnorePatterns::parse_from_iter(empty());
-        assert_eq!(patterns, file_lines_to_patterns(empty()));
-    }
-
-    #[test]
-    fn parse_some_globs() {
-        let stuff = vec![ "*.mp3", "README.md" ];
-        let reals = vec![ "*.mp3", "README.md" ];
-        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
-        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
-    }
-
-    #[test]
-    fn parse_some_comments() {
-        let stuff = vec![ "*.mp3", "# I am a comment!", "#", "README.md" ];
-        let reals = vec![ "*.mp3",                           "README.md" ];
-        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
-        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
-    }
-
-    #[test]
-    fn parse_some_blank_lines() {
-        let stuff = vec![ "*.mp3", "", "", "README.md" ];
-        let reals = vec![ "*.mp3",         "README.md" ];
-        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
-        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
-    }
-
-    #[test]
-    fn parse_some_whitespacey_lines() {
-        let stuff = vec![ " *.mp3", "  ", "  a  ", "README.md   " ];
-        let reals = vec![ " *.mp3", "  ", "  a  ", "README.md   " ];
-        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
-        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
-    }
-
-
-    fn test_cache(dir: &'static str, pats: Vec<&str>) -> IgnoreCache {
-        IgnoreCache { entries: RwLock::new(vec![ (dir.into(), IgnorePatterns::parse_from_iter(pats.into_iter()).0) ]) }
-    }
-
-    #[test]
-    fn an_empty_cache_ignores_nothing() {
-        let ignores = IgnoreCache::default();
-        assert_eq!(false, ignores.is_ignored(Path::new("/usr/bin/drinking")));
-        assert_eq!(false, ignores.is_ignored(Path::new("target/debug/exa")));
-    }
-
-    #[test]
-    fn a_nonempty_cache_ignores_some_things() {
-        let ignores = test_cache("/vagrant", vec![ "target" ]);
-        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/src")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/target")));
-    }
-
-    #[test]
-    fn ignore_some_globs() {
-        let ignores = test_cache("/vagrant", vec![ "*.ipr", "*.iws", ".docker" ]);
-        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/exa.ipr")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/exa.iws")));
-        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/exa.iwiwal")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/.docker")));
-        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/exa.docker")));
-
-        assert_eq!(false, ignores.is_ignored(Path::new("/srcode/exa.ipr")));
-        assert_eq!(false, ignores.is_ignored(Path::new("/srcode/exa.iws")));
-    }
-
-    #[test] #[ignore]
-    fn ignore_relatively() {
-        let ignores = test_cache(".", vec![ "target" ]);
-        assert_eq!(true,  ignores.is_ignored(Path::new("./target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));
-
-        assert_eq!(false, ignores.is_ignored(Path::new("./.target")));
-    }
-
-    #[test] #[ignore]
-    fn ignore_relatively_sometimes() {
-        let ignores = test_cache(".", vec![ "project/target" ]);
-        assert_eq!(false, ignores.is_ignored(Path::new("./target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));
-    }
-
-    #[test] #[ignore]
-    fn ignore_relatively_absolutely() {
-        let ignores = test_cache(".", vec![ "/project/target" ]);
-        assert_eq!(false, ignores.is_ignored(Path::new("./target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));
-    }
-
-    #[test] #[ignore]   // not 100% sure if dot works this way...
-    fn ignore_relatively_absolutely_dot() {
-        let ignores = test_cache(".", vec![ "./project/target" ]);
-        assert_eq!(false, ignores.is_ignored(Path::new("./target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
-        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));
-    }
-}

+ 0 - 1
src/fs/feature/mod.rs

@@ -1,5 +1,4 @@
 pub mod xattr;
-pub mod ignore;
 
 #[cfg(feature="git")] pub mod git;
 

+ 1 - 0
src/fs/fields.rs

@@ -177,6 +177,7 @@ pub struct Time {
 /// A file’s status in a Git repository. Whether a file is in a repository or
 /// not is handled by the Git module, rather than having a “null” variant in
 /// this enum.
+#[derive(PartialEq)]
 pub enum GitStatus {
 
     /// This file hasn’t changed since the last commit.

+ 5 - 1
src/options/mod.rs

@@ -72,7 +72,7 @@
 use std::ffi::{OsStr, OsString};
 
 use crate::fs::dir_action::DirAction;
-use crate::fs::filter::FileFilter;
+use crate::fs::filter::{FileFilter,GitIgnore};
 use crate::output::{View, Mode, details, grid_details};
 
 mod style;
@@ -146,6 +146,10 @@ impl Options {
     /// status column. It’s only worth trying to discover a repository if the
     /// results will end up being displayed.
     pub fn should_scan_for_git(&self) -> bool {
+        if self.filter.git_ignore == GitIgnore::CheckAndIgnore {
+            return true;
+        }
+
         match self.view.mode {
             Mode::Details(details::Options { table: Some(ref table), .. }) |
             Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => table.columns.git,

+ 8 - 9
src/output/details.rs

@@ -70,7 +70,6 @@ use ansi_term::{ANSIGenericString, Style};
 use crate::fs::{Dir, File};
 use crate::fs::dir_action::RecurseOptions;
 use crate::fs::filter::FileFilter;
-use crate::fs::feature::ignore::IgnoreCache;
 use crate::fs::feature::git::GitCache;
 use crate::fs::feature::xattr::{Attribute, FileAttributes};
 use crate::style::Colours;
@@ -138,7 +137,7 @@ struct Egg<'a> {
     errors:    Vec<(IOError, Option<PathBuf>)>,
     dir:       Option<Dir>,
     file:      &'a File<'a>,
-    icon:      Option<String>, 
+    icon:      Option<String>,
 }
 
 impl<'a> AsRef<File<'a>> for Egg<'a> {
@@ -149,7 +148,7 @@ impl<'a> AsRef<File<'a>> for Egg<'a> {
 
 
 impl<'a> Render<'a> {
-    pub fn render<W: Write>(self, mut git: Option<&'a GitCache>, ignore: Option<&'a IgnoreCache>, w: &mut W) -> IOResult<()> {
+    pub fn render<W: Write>(self, mut git: Option<&'a GitCache>, w: &mut W) -> IOResult<()> {
         let mut pool = Pool::new(num_cpus::get() as u32);
         let mut rows = Vec::new();
 
@@ -171,14 +170,14 @@ impl<'a> Render<'a> {
             // This is weird, but I can’t find a way around it:
             // https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6
             let mut table = Some(table);
-            self.add_files_to_table(&mut pool, &mut table, &mut rows, &self.files, ignore, TreeDepth::root());
+            self.add_files_to_table(&mut pool, &mut table, &mut rows, &self.files, git, TreeDepth::root());
 
             for row in self.iterate_with_table(table.unwrap(), rows) {
                 writeln!(w, "{}", row.strings())?
             }
         }
         else {
-            self.add_files_to_table(&mut pool, &mut None, &mut rows, &self.files, ignore, TreeDepth::root());
+            self.add_files_to_table(&mut pool, &mut None, &mut rows, &self.files, git, TreeDepth::root());
 
             for row in self.iterate(rows) {
                 writeln!(w, "{}", row.strings())?
@@ -190,7 +189,7 @@ impl<'a> Render<'a> {
 
     /// Adds files to the table, possibly recursively. This is easily
     /// parallelisable, and uses a pool of threads.
-    fn add_files_to_table<'dir, 'ig>(&self, pool: &mut Pool, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &[File<'dir>], ignore: Option<&'ig IgnoreCache>, depth: TreeDepth) {
+    fn add_files_to_table<'dir, 'ig>(&self, pool: &mut Pool, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &[File<'dir>], git: Option<&'ig GitCache>, depth: TreeDepth) {
         use std::sync::{Arc, Mutex};
         use log::error;
         use crate::fs::feature::xattr;
@@ -263,7 +262,7 @@ impl<'a> Render<'a> {
                         }
                     };
 
-                    let icon = if self.opts.icons { 
+                    let icon = if self.opts.icons {
                         Some(painted_icon(&file, &self.style))
                     } else { None };
 
@@ -304,7 +303,7 @@ impl<'a> Render<'a> {
             rows.push(row);
 
             if let Some(ref dir) = egg.dir {
-                for file_to_add in dir.files(self.filter.dot_filter, ignore) {
+                for file_to_add in dir.files(self.filter.dot_filter, git) {
                     match file_to_add {
                         Ok(f)          => files.push(f),
                         Err((path, e)) => errors.push((e, Some(path)))
@@ -322,7 +321,7 @@ impl<'a> Render<'a> {
                         rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path));
                     }
 
-                    self.add_files_to_table(pool, table, rows, &files, ignore, depth.deeper());
+                    self.add_files_to_table(pool, table, rows, &files, git, depth.deeper());
                     continue;
                 }
             }

+ 1 - 1
src/output/grid_details.rs

@@ -120,7 +120,7 @@ impl<'a> Render<'a> {
             write!(w, "{}", grid.fit_into_columns(width))
         }
         else {
-            self.give_up().render(git, None, w)
+            self.give_up().render(git, w)
         }
     }