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.
This commit is contained in:
Benjamin Sago 2017-09-28 16:13:47 +01:00
parent b95446d834
commit 827aa8bfc3
7 changed files with 69 additions and 12 deletions

View File

@ -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) 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) => { 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)
} }
} }
} }

View File

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

View File

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

View File

@ -6,7 +6,6 @@ use ansi_term::ANSIStrings;
use term_grid as grid; use term_grid as grid;
use fs::{Dir, File}; use fs::{Dir, File};
use fs::feature::ignore::IgnoreCache;
use fs::feature::git::GitCache; use fs::feature::git::GitCache;
use fs::feature::xattr::FileAttributes; use fs::feature::xattr::FileAttributes;
use fs::filter::FileFilter; 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<()> { // This doesnt take an IgnoreCache even though the details one does
if let Some((grid, width)) = self.find_fitting_grid(git, ignore) { // 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)) write!(w, "{}", grid.fit_into_columns(width))
} }
else { 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 options = self.details.table.as_ref().expect("Details table options not given!");
let drender = self.details(); let drender = self.details();

View File

@ -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
xtests/git_2_ignore_tree Normal file
View File

@ -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

View File

@ -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 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 # Hidden files
COLUMNS=80 $exa $testcases/hiddens 2>&1 | diff -q - $results/hiddens || exit 1 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 COLUMNS=80 $exa $testcases/hiddens -a 2>&1 | diff -q - $results/hiddens_a || exit 1