Skip to content

Commit b95446d

Browse files
committed
Thread an ignore cache through the program
!
1 parent 07443e8 commit b95446d

File tree

8 files changed

+125
-21
lines changed

8 files changed

+125
-21
lines changed

src/exa.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use std::path::{Component, PathBuf};
3030
use ansi_term::{ANSIStrings, Style};
3131

3232
use fs::{Dir, File};
33+
use fs::feature::ignore::IgnoreCache;
3334
use fs::feature::git::GitCache;
3435
use options::{Options, Vars};
3536
pub use options::Misfire;
@@ -61,6 +62,10 @@ pub struct Exa<'args, 'w, W: Write + 'w> {
6162
/// This has to last the lifetime of the program, because the user might
6263
/// want to list several directories in the same repository.
6364
pub git: Option<GitCache>,
65+
66+
/// A cache of git-ignored files.
67+
/// This lasts the lifetime of the program too, for the same reason.
68+
pub ignore: Option<IgnoreCache>,
6469
}
6570

6671
/// The “real” environment variables type.
@@ -84,6 +89,15 @@ fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
8489
}
8590
}
8691

92+
fn ignore_cache(options: &Options) -> Option<IgnoreCache> {
93+
use fs::filter::GitIgnore;
94+
95+
match options.filter.git_ignore {
96+
GitIgnore::CheckAndIgnore => Some(IgnoreCache::new()),
97+
GitIgnore::Off => None,
98+
}
99+
}
100+
87101
impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
88102
pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
89103
where I: Iterator<Item=&'args OsString> {
@@ -99,7 +113,8 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
99113
}
100114

101115
let git = git_options(&options, &args);
102-
Exa { options, writer, args, git }
116+
let ignore = ignore_cache(&options);
117+
Exa { options, writer, args, git, ignore }
103118
})
104119
}
105120

@@ -160,7 +175,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
160175
}
161176

162177
let mut children = Vec::new();
163-
for file in dir.files(self.options.filter.dot_filter) {
178+
for file in dir.files(self.options.filter.dot_filter, self.ignore.as_ref()) {
164179
match file {
165180
Ok(file) => children.push(file),
166181
Err((path, e)) => writeln!(stderr(), "[{}: {}]", path.display(), e)?,
@@ -212,10 +227,10 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
212227
grid::Render { files, colours, style, opts }.render(self.writer)
213228
}
214229
Mode::Details(ref opts) => {
215-
details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.git.as_ref(), self.writer)
230+
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)
216231
}
217232
Mode::GridDetails(ref opts) => {
218-
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)
233+
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)
219234
}
220235
}
221236
}

src/fs/dir.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
44
use std::slice::Iter as SliceIter;
55

66
use fs::File;
7+
use fs::feature::ignore::IgnoreCache;
78

89

910
/// A **Dir** provides a cached list of the file paths in a directory that's
@@ -43,12 +44,13 @@ impl Dir {
4344

4445
/// Produce an iterator of IO results of trying to read all the files in
4546
/// this directory.
46-
pub fn files(&self, dots: DotFilter) -> Files {
47+
pub fn files<'dir, 'ig>(&'dir self, dots: DotFilter, ignore: Option<&'ig IgnoreCache>) -> Files<'dir, 'ig> {
4748
Files {
4849
inner: self.contents.iter(),
4950
dir: self,
5051
dotfiles: dots.shows_dotfiles(),
5152
dots: dots.dots(),
53+
ignore: ignore,
5254
}
5355
}
5456

@@ -65,7 +67,7 @@ impl Dir {
6567

6668

6769
/// Iterator over reading the contents of a directory as `File` objects.
68-
pub struct Files<'dir> {
70+
pub struct Files<'dir, 'ig> {
6971

7072
/// The internal iterator over the paths that have been read already.
7173
inner: SliceIter<'dir, PathBuf>,
@@ -79,9 +81,11 @@ pub struct Files<'dir> {
7981
/// Whether the `.` or `..` directories should be produced first, before
8082
/// any files have been listed.
8183
dots: Dots,
84+
85+
ignore: Option<&'ig IgnoreCache>,
8286
}
8387

84-
impl<'dir> Files<'dir> {
88+
impl<'dir, 'ig> Files<'dir, 'ig> {
8589
fn parent(&self) -> PathBuf {
8690
// We can’t use `Path#parent` here because all it does is remove the
8791
// last path component, which is no good for us if the path is
@@ -124,7 +128,7 @@ enum Dots {
124128
}
125129

126130

127-
impl<'dir> Iterator for Files<'dir> {
131+
impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
128132
type Item = Result<File<'dir>, (PathBuf, io::Error)>;
129133

130134
fn next(&mut self) -> Option<Self::Item> {

src/fs/feature/ignore.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//! Ignoring globs in `.gitignore` files.
2+
//!
3+
//! This uses a cache because the file with the globs in might not be the same
4+
//! directory that we’re listing!
5+
6+
use std::fs::File;
7+
use std::io::Read;
8+
use std::path::{Path, PathBuf};
9+
10+
use fs::filter::IgnorePatterns;
11+
12+
13+
/// An **ignore cache** holds sets of glob patterns paired with the
14+
/// directories that they should be ignored underneath.
15+
#[derive(Default, Debug)]
16+
pub struct IgnoreCache {
17+
entries: Vec<(PathBuf, IgnorePatterns)>
18+
}
19+
20+
impl IgnoreCache {
21+
pub fn new() -> IgnoreCache {
22+
IgnoreCache::default()
23+
}
24+
25+
#[allow(unused_results)] // don’t do this
26+
pub fn discover_underneath(&mut self, path: &Path) {
27+
let mut path = Some(path);
28+
29+
while let Some(p) = path {
30+
let ignore_file = p.join(".gitignore");
31+
if ignore_file.is_file() {
32+
if let Ok(mut file) = File::open(ignore_file) {
33+
let mut contents = String::new();
34+
file.read_to_string(&mut contents).expect("Reading gitignore failed");
35+
36+
let (patterns, mut _errors) = IgnorePatterns::parse_from_iter(contents.lines());
37+
self.entries.push((p.into(), patterns));
38+
}
39+
}
40+
41+
path = p.parent();
42+
}
43+
}
44+
45+
pub fn is_ignored(&self, suspect: &Path) -> bool {
46+
self.entries.iter().any(|&(ref base_path, ref patterns)| {
47+
if let Ok(suffix) = suspect.strip_prefix(&base_path) {
48+
patterns.is_ignored_path(suffix)
49+
}
50+
else {
51+
false
52+
}
53+
})
54+
}
55+
}
56+
57+
58+
59+
#[cfg(test)]
60+
mod test {
61+
use super::*;
62+
63+
#[test]
64+
fn empty() {
65+
let ignores = IgnoreCache::default();
66+
assert_eq!(false, ignores.is_ignored(Path::new("/usr/bin/drinking")));
67+
assert_eq!(false, ignores.is_ignored(Path::new("target/debug/exa")));
68+
}
69+
}

src/fs/feature/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
// Extended attribute support
21
pub mod xattr;
3-
4-
// Git support
2+
pub mod ignore;
53

64
#[cfg(feature="git")] pub mod git;
75

src/fs/filter.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::cmp::Ordering;
44
use std::iter::FromIterator;
55
use std::os::unix::fs::MetadataExt;
6+
use std::path::Path;
67

78
use glob;
89
use natord;
@@ -79,6 +80,12 @@ pub struct FileFilter {
7980
/// Glob patterns to ignore. Any file name that matches *any* of these
8081
/// patterns won’t be displayed in the list.
8182
pub ignore_patterns: IgnorePatterns,
83+
84+
/// Whether to ignore Git-ignored patterns.
85+
/// This is implemented completely separately from the actual Git
86+
/// repository scanning — a `.gitignore` file will still be scanned even
87+
/// if there’s no `.git` folder present.
88+
pub git_ignore: GitIgnore,
8289
}
8390

8491

@@ -302,6 +309,14 @@ impl IgnorePatterns {
302309
fn is_ignored(&self, file: &str) -> bool {
303310
self.patterns.iter().any(|p| p.matches(file))
304311
}
312+
313+
/// Test whether the given file should be hidden from the results.
314+
pub fn is_ignored_path(&self, file: &Path) -> bool {
315+
self.patterns.iter().any(|p| p.matches_path(file))
316+
}
317+
318+
// TODO(ogham): The fact that `is_ignored_path` is pub while `is_ignored`
319+
// isn’t probably means it’s in the wrong place
305320
}
306321

307322

src/options/filter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ impl FileFilter {
1717
sort_field: SortField::deduce(matches)?,
1818
dot_filter: DotFilter::deduce(matches)?,
1919
ignore_patterns: IgnorePatterns::deduce(matches)?,
20+
git_ignore: GitIgnore::deduce(matches)?,
2021
})
2122
}
2223
}

src/output/details.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ use ansi_term::Style;
6969
use fs::{Dir, File};
7070
use fs::dir_action::RecurseOptions;
7171
use fs::filter::FileFilter;
72+
use fs::feature::ignore::IgnoreCache;
7273
use fs::feature::git::GitCache;
7374
use fs::feature::xattr::{Attribute, FileAttributes};
7475
use style::Colours;
@@ -140,7 +141,7 @@ impl<'a> AsRef<File<'a>> for Egg<'a> {
140141

141142

142143
impl<'a> Render<'a> {
143-
pub fn render<W: Write>(self, mut git: Option<&'a GitCache>, w: &mut W) -> IOResult<()> {
144+
pub fn render<W: Write>(self, mut git: Option<&'a GitCache>, ignore: Option<&'a IgnoreCache>, w: &mut W) -> IOResult<()> {
144145
let mut rows = Vec::new();
145146

146147
if let Some(ref table) = self.opts.table {
@@ -161,14 +162,14 @@ impl<'a> Render<'a> {
161162
// This is weird, but I can’t find a way around it:
162163
// https://internals.rust-lang.org/t/should-option-mut-t-implement-copy/3715/6
163164
let mut table = Some(table);
164-
self.add_files_to_table(&mut table, &mut rows, &self.files, TreeDepth::root());
165+
self.add_files_to_table(&mut table, &mut rows, &self.files, ignore, TreeDepth::root());
165166

166167
for row in self.iterate_with_table(table.unwrap(), rows) {
167168
writeln!(w, "{}", row.strings())?
168169
}
169170
}
170171
else {
171-
self.add_files_to_table(&mut None, &mut rows, &self.files, TreeDepth::root());
172+
self.add_files_to_table(&mut None, &mut rows, &self.files, ignore, TreeDepth::root());
172173

173174
for row in self.iterate(rows) {
174175
writeln!(w, "{}", row.strings())?
@@ -180,7 +181,7 @@ impl<'a> Render<'a> {
180181

181182
/// Adds files to the table, possibly recursively. This is easily
182183
/// parallelisable, and uses a pool of threads.
183-
fn add_files_to_table<'dir>(&self, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &Vec<File<'dir>>, depth: TreeDepth) {
184+
fn add_files_to_table<'dir, 'ig>(&self, table: &mut Option<Table<'a>>, rows: &mut Vec<Row>, src: &Vec<File<'dir>>, ignore: Option<&'ig IgnoreCache>, depth: TreeDepth) {
184185
use num_cpus;
185186
use scoped_threadpool::Pool;
186187
use std::sync::{Arc, Mutex};
@@ -282,7 +283,7 @@ impl<'a> Render<'a> {
282283
rows.push(row);
283284

284285
if let Some(ref dir) = egg.dir {
285-
for file_to_add in dir.files(self.filter.dot_filter) {
286+
for file_to_add in dir.files(self.filter.dot_filter, ignore) {
286287
match file_to_add {
287288
Ok(f) => files.push(f),
288289
Err((path, e)) => errors.push((e, Some(path)))
@@ -300,7 +301,7 @@ impl<'a> Render<'a> {
300301
rows.push(self.render_error(&error, TreeParams::new(depth.deeper(), false), path));
301302
}
302303

303-
self.add_files_to_table(table, rows, &files, depth.deeper());
304+
self.add_files_to_table(table, rows, &files, ignore, depth.deeper());
304305
continue;
305306
}
306307
}

src/output/grid_details.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use ansi_term::ANSIStrings;
66
use term_grid as grid;
77

88
use fs::{Dir, File};
9+
use fs::feature::ignore::IgnoreCache;
910
use fs::feature::git::GitCache;
1011
use fs::feature::xattr::FileAttributes;
1112
use fs::filter::FileFilter;
@@ -111,16 +112,16 @@ impl<'a> Render<'a> {
111112
}
112113
}
113114

114-
pub fn render<W: Write>(self, git: Option<&GitCache>, w: &mut W) -> IOResult<()> {
115-
if let Some((grid, width)) = self.find_fitting_grid(git) {
115+
pub fn render<W: Write>(self, git: Option<&GitCache>, ignore: Option<&'a IgnoreCache>, w: &mut W) -> IOResult<()> {
116+
if let Some((grid, width)) = self.find_fitting_grid(git, ignore) {
116117
write!(w, "{}", grid.fit_into_columns(width))
117118
}
118119
else {
119-
self.give_up().render(git, w)
120+
self.give_up().render(git, ignore, w)
120121
}
121122
}
122123

123-
pub fn find_fitting_grid(&self, git: Option<&GitCache>) -> Option<(grid::Grid, grid::Width)> {
124+
pub fn find_fitting_grid(&self, git: Option<&GitCache>, ignore: Option<&'a IgnoreCache>) -> Option<(grid::Grid, grid::Width)> {
124125
let options = self.details.table.as_ref().expect("Details table options not given!");
125126

126127
let drender = self.details();

0 commit comments

Comments
 (0)