diff options
author | Benjamin Sago <ogham@bsago.me> | 2017-09-28 16:13:47 +0100 |
---|---|---|
committer | Benjamin Sago <ogham@bsago.me> | 2017-09-30 09:17:29 +0200 |
commit | 827aa8bfc32eadf353f72b0ce41c88dc4ad411c1 (patch) | |
tree | 819427117b9e0fc630a31e0ce8191e4bb2996910 | |
parent | b95446d834f82fc57480f16d65db09e0fd469519 (diff) |
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.
-rw-r--r-- | src/exa.rs | 2 | ||||
-rw-r--r-- | src/fs/dir.rs | 6 | ||||
-rw-r--r-- | src/fs/feature/ignore.rs | 21 | ||||
-rw-r--r-- | src/output/grid_details.rs | 12 | ||||
-rw-r--r-- | xtests/git_2_ignore_recurse | 23 | ||||
-rw-r--r-- | xtests/git_2_ignore_tree | 12 | ||||
-rwxr-xr-x | xtests/run.sh | 5 |
7 files changed, 69 insertions, 12 deletions
@@ -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) } } } diff --git a/src/fs/dir.rs b/src/fs/dir.rs index 72cd2ae..a9c0ccc 100644 --- a/src/fs/dir.rs +++ b/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))) } diff --git a/src/fs/feature/ignore.rs b/src/fs/feature/ignore.rs index 66a8caf..10c0d94 100644 --- a/src/fs/feature/ignore.rs +++ b/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::*; diff --git a/src/output/grid_details.rs b/src/output/grid_details.rs index f4a7748..2327c43 100644 --- a/src/output/grid_details.rs +++ b/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(); diff --git a/xtests/git_2_ignore_recurse b/xtests/git_2_ignore_recurse new file mode 100644 index 0000000..5f98671 --- /dev/null +++ b/xtests/git_2_ignore_recurse @@ -0,0 +1,23 @@ +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34mdeeply[0m +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34mignoreds[0m + +/testcases/git2/deeply: +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34mnested[0m + +/testcases/git2/deeply/nested: +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34mdirectory[0m +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34mrepository[0m + +/testcases/git2/deeply/nested/directory: +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m l8st +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m18[0m cassowary [34m 1 Jan 12:34[0m upd8d + +/testcases/git2/deeply/nested/repository: +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m subfile + +/testcases/git2/ignoreds: +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m [38;5;92mmusic.m4a[0m +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [1;34mnested[0m + +/testcases/git2/ignoreds/nested: +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m [38;5;92mfunky chicken.m4a[0m diff --git a/xtests/git_2_ignore_tree b/xtests/git_2_ignore_tree new file mode 100644 index 0000000..3513fea --- /dev/null +++ b/xtests/git_2_ignore_tree @@ -0,0 +1,12 @@ +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [36m/testcases/[1;34mgit2[0m +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m├──[0m [1;34mdeeply[0m +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m│ └──[0m [1;34mnested[0m +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m│ ├──[0m [1;34mdirectory[0m +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m│ │ ├──[0m l8st +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m18[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m│ │ └──[0m upd8d +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m│ └──[0m [1;34mrepository[0m +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m│ └──[0m subfile +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m└──[0m [1;34mignoreds[0m +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m ├──[0m [38;5;92mmusic.m4a[0m +[1;34md[33mr[31mw[32mx[0m[33mr[31mw[32mx[33mr[38;5;244m-[32mx[0m [38;5;244m-[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m └──[0m [1;34mnested[0m +.[1;33mr[31mw[0m[38;5;244m-[33mr[31mw[38;5;244m-[33mr[38;5;244m--[0m [1;32m0[0m cassowary [34m 1 Jan 12:34[0m [38;5;244m └──[0m [38;5;92mfunky chicken.m4a[0m diff --git a/xtests/run.sh b/xtests/run.sh index b88ae20..d6828ae 100755 --- a/xtests/run.sh +++ b/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 |