mod ignore { use std::io::Read; use bstr::{BStr, ByteSlice}; use git_attributes::{Ignore, Match, MatchGroup}; use git_glob::pattern::Case; struct Expectations<'a> { lines: bstr::Lines<'a>, } impl<'a> Iterator for Expectations<'a> { type Item = (&'a BStr, Option<(&'a BStr, usize, &'a BStr)>); fn next(&mut self) -> Option { let line = self.lines.next()?; let (left, value) = line.split_at(line.find_byte(b'\t').unwrap()); let value = value[1..].as_bstr(); let source_and_line = if left == b"::" { None } else { let mut tokens = left.split(|b| *b == b':'); let source = tokens.next().unwrap().as_bstr(); let line_number: usize = tokens.next().unwrap().to_str_lossy().parse().ok().unwrap(); let pattern = tokens.next().unwrap().as_bstr(); Some((source, line_number, pattern)) }; Some((value, source_and_line)) } } #[test] fn from_git_dir() -> crate::Result { let dir = git_testtools::scripted_fixture_repo_read_only("make_global_and_external_and_dir_ignores.sh")?; let repo_dir = dir.join("repo"); let git_dir = repo_dir.join(".git"); let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?; let mut buf = Vec::new(); let mut group = MatchGroup::from_git_dir(git_dir, Some(dir.join("user.exclude")), &mut buf)?; assert!( !group.add_patterns_file("not-a-file", false, None, &mut buf)?, "missing files are no problem and cause a negative response" ); assert!( group.add_patterns_file(repo_dir.join(".gitignore"), true, repo_dir.as_path().into(), &mut buf)?, "existing files return true" ); buf.clear(); let ignore_file = repo_dir.join("dir-with-ignore").join(".gitignore"); std::fs::File::open(&ignore_file)?.read_to_end(&mut buf)?; group.add_patterns_buffer(&buf, ignore_file, repo_dir.as_path().into()); for (path, source_and_line) in (Expectations { lines: baseline.lines(), }) { let actual = group.pattern_matching_relative_path( path, repo_dir .join(path.to_str_lossy().as_ref()) .metadata() .ok() .map(|m| m.is_dir()), Case::Sensitive, ); match (actual, source_and_line) { ( Some(Match { sequence_number, pattern: _, source, value: _, }), Some((expected_source, line, _expected_pattern)), ) => { assert_eq!(sequence_number, line, "our counting should match the one used in git"); assert_eq!( source.map(|p| p.canonicalize().unwrap()), Some(repo_dir.join(expected_source.to_str_lossy().as_ref()).canonicalize()?) ); } (None, None) => {} (actual, expected) => panic!("actual {:?} should match {:?} with path '{}'", actual, expected, path), } } Ok(()) } #[test] fn from_overrides() { let input = ["simple", "pattern/"]; let group = git_attributes::MatchGroup::::from_overrides(input); assert_eq!( group.pattern_matching_relative_path("Simple", None, git_glob::pattern::Case::Fold), Some(pattern_to_match(&git_glob::parse("simple").unwrap(), 0)) ); assert_eq!( group.pattern_matching_relative_path("pattern", Some(true), git_glob::pattern::Case::Sensitive), Some(pattern_to_match(&git_glob::parse("pattern/").unwrap(), 1)) ); assert_eq!(group.patterns.len(), 1); assert_eq!( git_attributes::PatternList::::from_overrides(input), group.patterns.into_iter().next().unwrap() ); } fn pattern_to_match(pattern: &git_glob::Pattern, sequence_number: usize) -> Match<'_, ()> { Match { pattern, value: &(), source: None, sequence_number, } } }