diff --git a/bin/src/config.rs b/bin/src/config.rs index f20be268..7e583fde 100644 --- a/bin/src/config.rs +++ b/bin/src/config.rs @@ -77,7 +77,8 @@ impl Check { } else { let all_ignores = [self.ignore.as_slice(), extra_ignores].concat(); let ignore = dirs::build_ignore_set(&all_ignores, &self.target, self.unrestricted)?; - let files = dirs::walk_nix_files(ignore, &self.target)?; + let files = + dirs::walk_nix_files(ignore, &self.target, &all_ignores, self.unrestricted)?; Ok(vfs(&files.collect::>())) } } @@ -130,7 +131,8 @@ impl Fix { } else { let all_ignores = [self.ignore.as_slice(), extra_ignores].concat(); let ignore = dirs::build_ignore_set(&all_ignores, &self.target, self.unrestricted)?; - let files = dirs::walk_nix_files(ignore, &self.target)?; + let files = + dirs::walk_nix_files(ignore, &self.target, &all_ignores, self.unrestricted)?; Ok(vfs(&files.collect::>())) } } diff --git a/bin/src/dirs.rs b/bin/src/dirs.rs index 13b66d7a..fca0adfe 100644 --- a/bin/src/dirs.rs +++ b/bin/src/dirs.rs @@ -13,13 +13,19 @@ use ignore::{ #[derive(Debug)] pub struct Walker { - dirs: Vec, + dirs: Vec<(PathBuf, Vec, Gitignore)>, files: Vec, - ignore: Gitignore, + extra_ignores: Vec, + unrestricted: bool, } impl Walker { - pub fn new>(target: P, ignore: Gitignore) -> io::Result { + pub fn new>( + target: P, + ignore: Gitignore, + extra_ignores: Vec, + unrestricted: bool, + ) -> io::Result { let target = target.as_ref().to_path_buf(); if !target.exists() { Err(Error::new( @@ -27,47 +33,98 @@ impl Walker { format!("file not found: {}", target.display()), )) } else if target.is_dir() { + let root_gitignore = target.join(".gitignore"); + let gitignore_files = if !unrestricted && root_gitignore.exists() { + vec![root_gitignore] + } else { + vec![] + }; Ok(Self { - dirs: vec![target], + dirs: vec![(target, gitignore_files, ignore)], files: vec![], - ignore, + extra_ignores, + unrestricted, }) } else { Ok(Self { dirs: vec![], files: vec![target], - ignore, + extra_ignores, + unrestricted, }) } } + + fn build_ignore_for( + &self, + base: &Path, + gitignore_files: &[PathBuf], + ) -> Result { + let mut builder = GitignoreBuilder::new(base); + + if !self.unrestricted { + for gitignore in gitignore_files { + builder.add(gitignore); + } + + builder.add_line(None, ".git")?; + } + for ignore in &self.extra_ignores { + builder.add_line(None, ignore.as_str())?; + } + builder.build() + } } impl Iterator for Walker { type Item = PathBuf; fn next(&mut self) -> Option { self.files.pop().or_else(|| { - while let Some(dir) = self.dirs.pop() { - if dir.is_dir() - && let Match::None | Match::Whitelist(_) = self.ignore.matched(&dir, true) - { - let mut found = false; - for entry in fs::read_dir(&dir).ok()? { - let entry = entry.ok()?; - let path = entry.path(); - if path.is_dir() { - self.dirs.push(path); - } else if path.is_file() - && let Match::None | Match::Whitelist(_) = - self.ignore.matched(&path, false) - { - found = true; - self.files.push(path); - } - } - if found { - break; + while let Some((dir, mut gitignore_files, mut ignore)) = self.dirs.pop() { + if !dir.is_dir() { + continue; + } + let nested = dir.join(".gitignore"); + + if !self.unrestricted && nested.exists() && !gitignore_files.contains(&nested) { + gitignore_files.push(nested); + + ignore = match self.build_ignore_for(&dir, &gitignore_files) { + Ok(ignore) => ignore, + Err(_) => continue, + }; + } + + if !matches!( + ignore.matched(&dir, true), + Match::None | Match::Whitelist(_) + ) { + continue; + } + + let mut found = false; + + for entry in fs::read_dir(&dir).ok()? { + let entry = entry.ok()?; + let path = entry.path(); + + if path.is_dir() { + self.dirs + .push((path, gitignore_files.clone(), ignore.clone())); + } else if path.is_file() + && matches!( + ignore.matched(&path, false), + Match::None | Match::Whitelist(_) + ) + { + found = true; + self.files.push(path); } } + + if found { + break; + } } self.files.pop() }) @@ -106,7 +163,10 @@ pub fn build_ignore_set>( pub fn walk_nix_files>( ignore: Gitignore, target: P, + extra_ignores: &[String], + unrestricted: bool, ) -> Result, io::Error> { - let walker = dirs::Walker::new(target, ignore)?; - Ok(walker.filter(|path: &PathBuf| matches!(path.extension(), Some(e) if e == "nix"))) + let walker = dirs::Walker::new(target, ignore, extra_ignores.to_vec(), unrestricted)?; + Ok(walker + .filter(|path: &PathBuf| matches!(path.extension(), Some(extension) if extension == "nix"))) } diff --git a/bin/tests/walk.rs b/bin/tests/walk.rs index 1d608bd2..0efdf8fa 100644 --- a/bin/tests/walk.rs +++ b/bin/tests/walk.rs @@ -155,6 +155,25 @@ mod gitignored_files { assert_eq!(report.paths, ["./linted.nix"]); } + + #[test] + fn multiple_gitignores() { + let report = Fixture::with_files(&[ + ("file.nix", CODE_THAT_TRIGGERS_A_LINT), + ("a/.gitignore", "file.nix\nbuild/\n"), + ("a/file.nix", CODE_THAT_TRIGGERS_A_LINT), + ("a/build/inside.nix", CODE_THAT_TRIGGERS_A_LINT), + ("b/file.nix", CODE_THAT_TRIGGERS_A_LINT), + ("b/build/inside.nix", CODE_THAT_TRIGGERS_A_LINT), + ]) + .run_with_args(&[]) + .unwrap(); + + assert_eq!( + report.paths, + ["./file.nix", "./b/file.nix", "./b/build/inside.nix"] + ); + } } mod unrestricted {