| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- //! 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 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")));
- }
- }
|