Unified · Split
| @@ -61,6 +61,10 @@ pub enum Value { · 60 unmodified lines | ||
| 61 | 61 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] |
| 62 | 62 | pub struct Attributes; |
| 63 | 63 | |
| 64 | +fn macro_mode() -> gix_glob::pattern::Mode { | |
| 65 | + gix_glob::pattern::Mode::all() | |
| 66 | +} | |
| 67 | + | |
| 64 | 68 | impl Pattern for Attributes { |
| 65 | 69 | type Value = Value; |
| 66 | 70 | |
| @@ -72,7 +76,7 @@ impl Pattern for Attributes { · 5 unmodified lines | ||
| 72 | 76 | crate::parse::Kind::Macro(macro_name) => ( |
| 73 | 77 | gix_glob::Pattern { |
| 74 | 78 | text: macro_name.as_str().into(), |
| 75 | − mode: gix_glob::pattern::Mode::all(), | |
| 79 | + mode: macro_mode(), | |
| 76 | 80 | first_wildcard_pos: None, |
| 77 | 81 | }, |
| 78 | 82 | Value::MacroAttributes(into_owned_assignments(assignments).ok()?), |
| @@ -93,7 +97,7 @@ impl Pattern for Attributes { · 14 unmodified lines | ||
| 93 | 97 | } |
| 94 | 98 | |
| 95 | 99 | fn may_use_glob_pattern(pattern: &gix_glob::Pattern) -> bool { |
| 96 | − pattern.mode != gix_glob::pattern::Mode::all() | |
| 100 | + pattern.mode != macro_mode() | |
| 97 | 101 | } |
| 98 | 102 | } |
| 99 | 103 | |
| @@ -0,0 +1,116 @@ | ||
| 1 | +use std::io::Read; | |
| 2 | + | |
| 3 | +use bstr::{BStr, ByteSlice}; | |
| 4 | +use gix_attributes::{Ignore, Match, MatchGroup}; | |
| 5 | +use gix_glob::pattern::Case; | |
| 6 | + | |
| 7 | +struct Expectations<'a> { | |
| 8 | + lines: bstr::Lines<'a>, | |
| 9 | +} | |
| 10 | + | |
| 11 | +impl<'a> Iterator for Expectations<'a> { | |
| 12 | + type Item = (&'a BStr, Option<(&'a BStr, usize, &'a BStr)>); | |
| 13 | + | |
| 14 | + fn next(&mut self) -> Option<Self::Item> { | |
| 15 | + let line = self.lines.next()?; | |
| 16 | + let (left, value) = line.split_at(line.find_byte(b'\t').unwrap()); | |
| 17 | + let value = value[1..].as_bstr(); | |
| 18 | + | |
| 19 | + let source_and_line = if left == b"::" { | |
| 20 | + None | |
| 21 | + } else { | |
| 22 | + let mut tokens = left.split(|b| *b == b':'); | |
| 23 | + let source = tokens.next().unwrap().as_bstr(); | |
| 24 | + let line_number: usize = tokens.next().unwrap().to_str_lossy().parse().ok().unwrap(); | |
| 25 | + let pattern = tokens.next().unwrap().as_bstr(); | |
| 26 | + Some((source, line_number, pattern)) | |
| 27 | + }; | |
| 28 | + Some((value, source_and_line)) | |
| 29 | + } | |
| 30 | +} | |
| 31 | + | |
| 32 | +#[test] | |
| 33 | +fn from_git_dir() -> crate::Result { | |
| 34 | + let dir = gix_testtools::scripted_fixture_read_only("make_global_and_external_and_dir_ignores.sh")?; | |
| 35 | + let repo_dir = dir.join("repo"); | |
| 36 | + let git_dir = repo_dir.join(".git"); | |
| 37 | + let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?; | |
| 38 | + let mut buf = Vec::new(); | |
| 39 | + let mut group = MatchGroup::from_git_dir(git_dir, Some(dir.join("user.exclude")), &mut buf)?; | |
| 40 | + | |
| 41 | + assert!( | |
| 42 | + !group.add_patterns_file("not-a-file", false, None, &mut buf)?, | |
| 43 | + "missing files are no problem and cause a negative response" | |
| 44 | + ); | |
| 45 | + assert!( | |
| 46 | + group.add_patterns_file(repo_dir.join(".gitignore"), true, repo_dir.as_path().into(), &mut buf)?, | |
| 47 | + "existing files return true" | |
| 48 | + ); | |
| 49 | + | |
| 50 | + buf.clear(); | |
| 51 | + let ignore_file = repo_dir.join("dir-with-ignore").join(".gitignore"); | |
| 52 | + std::fs::File::open(&ignore_file)?.read_to_end(&mut buf)?; | |
| 53 | + group.add_patterns_buffer(&buf, ignore_file, repo_dir.as_path().into()); | |
| 54 | + | |
| 55 | + for (path, source_and_line) in (Expectations { | |
| 56 | + lines: baseline.lines(), | |
| 57 | + }) { | |
| 58 | + let actual = group.pattern_matching_relative_path( | |
| 59 | + path, | |
| 60 | + repo_dir | |
| 61 | + .join(path.to_str_lossy().as_ref()) | |
| 62 | + .metadata() | |
| 63 | + .ok() | |
| 64 | + .map(|m| m.is_dir()), | |
| 65 | + Case::Sensitive, | |
| 66 | + ); | |
| 67 | + match (actual, source_and_line) { | |
| 68 | + ( | |
| 69 | + Some(Match { | |
| 70 | + sequence_number, | |
| 71 | + pattern: _, | |
| 72 | + source, | |
| 73 | + value: _, | |
| 74 | + }), | |
| 75 | + Some((expected_source, line, _expected_pattern)), | |
| 76 | + ) => { | |
| 77 | + assert_eq!(sequence_number, line, "our counting should match the one used in git"); | |
| 78 | + assert_eq!( | |
| 79 | + source.map(|p| p.canonicalize().unwrap()), | |
| 80 | + Some(repo_dir.join(expected_source.to_str_lossy().as_ref()).canonicalize()?) | |
| 81 | + ); | |
| 82 | + } | |
| 83 | + (None, None) => {} | |
| 84 | + (actual, expected) => panic!("actual {actual:?} should match {expected:?} with path '{path}'"), | |
| 85 | + } | |
| 86 | + } | |
| 87 | + Ok(()) | |
| 88 | +} | |
| 89 | + | |
| 90 | +#[test] | |
| 91 | +fn from_overrides() { | |
| 92 | + let input = ["simple", "pattern/"]; | |
| 93 | + let group = gix_attributes::MatchGroup::<Ignore>::from_overrides(input); | |
| 94 | + assert_eq!( | |
| 95 | + group.pattern_matching_relative_path("Simple", None, gix_glob::pattern::Case::Fold), | |
| 96 | + Some(pattern_to_match(&gix_glob::parse("simple").unwrap(), 0)) | |
| 97 | + ); | |
| 98 | + assert_eq!( | |
| 99 | + group.pattern_matching_relative_path("pattern", Some(true), gix_glob::pattern::Case::Sensitive), | |
| 100 | + Some(pattern_to_match(&gix_glob::parse("pattern/").unwrap(), 1)) | |
| 101 | + ); | |
| 102 | + assert_eq!(group.patterns.len(), 1); | |
| 103 | + assert_eq!( | |
| 104 | + gix_attributes::PatternList::<Ignore>::from_overrides(input), | |
| 105 | + group.patterns.into_iter().next().unwrap() | |
| 106 | + ); | |
| 107 | +} | |
| 108 | + | |
| 109 | +fn pattern_to_match(pattern: &gix_glob::Pattern, sequence_number: usize) -> Match<'_, ()> { | |
| 110 | + Match { | |
| 111 | + pattern, | |
| 112 | + value: &(), | |
| 113 | + source: None, | |
| 114 | + sequence_number, | |
| 115 | + } | |
| 116 | +} | |
| @@ -1,118 +1 @@ | ||
| 1 | −mod ignore { | |
| 2 | − use std::io::Read; | |
| 3 | − | |
| 4 | − use bstr::{BStr, ByteSlice}; | |
| 5 | − use gix_attributes::{Ignore, Match, MatchGroup}; | |
| 6 | − use gix_glob::pattern::Case; | |
| 7 | − | |
| 8 | − struct Expectations<'a> { | |
| 9 | − lines: bstr::Lines<'a>, | |
| 10 | − } | |
| 11 | − | |
| 12 | − impl<'a> Iterator for Expectations<'a> { | |
| 13 | − type Item = (&'a BStr, Option<(&'a BStr, usize, &'a BStr)>); | |
| 14 | − | |
| 15 | − fn next(&mut self) -> Option<Self::Item> { | |
| 16 | − let line = self.lines.next()?; | |
| 17 | − let (left, value) = line.split_at(line.find_byte(b'\t').unwrap()); | |
| 18 | − let value = value[1..].as_bstr(); | |
| 19 | − | |
| 20 | − let source_and_line = if left == b"::" { | |
| 21 | − None | |
| 22 | − } else { | |
| 23 | − let mut tokens = left.split(|b| *b == b':'); | |
| 24 | − let source = tokens.next().unwrap().as_bstr(); | |
| 25 | − let line_number: usize = tokens.next().unwrap().to_str_lossy().parse().ok().unwrap(); | |
| 26 | − let pattern = tokens.next().unwrap().as_bstr(); | |
| 27 | − Some((source, line_number, pattern)) | |
| 28 | − }; | |
| 29 | − Some((value, source_and_line)) | |
| 30 | − } | |
| 31 | − } | |
| 32 | − | |
| 33 | − #[test] | |
| 34 | − fn from_git_dir() -> crate::Result { | |
| 35 | − let dir = gix_testtools::scripted_fixture_read_only("make_global_and_external_and_dir_ignores.sh")?; | |
| 36 | − let repo_dir = dir.join("repo"); | |
| 37 | − let git_dir = repo_dir.join(".git"); | |
| 38 | − let baseline = std::fs::read(git_dir.parent().unwrap().join("git-check-ignore.baseline"))?; | |
| 39 | − let mut buf = Vec::new(); | |
| 40 | − let mut group = MatchGroup::from_git_dir(git_dir, Some(dir.join("user.exclude")), &mut buf)?; | |
| 41 | − | |
| 42 | − assert!( | |
| 43 | − !group.add_patterns_file("not-a-file", false, None, &mut buf)?, | |
| 44 | − "missing files are no problem and cause a negative response" | |
| 45 | − ); | |
| 46 | − assert!( | |
| 47 | − group.add_patterns_file(repo_dir.join(".gitignore"), true, repo_dir.as_path().into(), &mut buf)?, | |
| 48 | − "existing files return true" | |
| 49 | − ); | |
| 50 | − | |
| 51 | − buf.clear(); | |
| 52 | − let ignore_file = repo_dir.join("dir-with-ignore").join(".gitignore"); | |
| 53 | − std::fs::File::open(&ignore_file)?.read_to_end(&mut buf)?; | |
| 54 | − group.add_patterns_buffer(&buf, ignore_file, repo_dir.as_path().into()); | |
| 55 | − | |
| 56 | − for (path, source_and_line) in (Expectations { | |
| 57 | − lines: baseline.lines(), | |
| 58 | − }) { | |
| 59 | − let actual = group.pattern_matching_relative_path( | |
| 60 | − path, | |
| 61 | − repo_dir | |
| 62 | − .join(path.to_str_lossy().as_ref()) | |
| 63 | − .metadata() | |
| 64 | − .ok() | |
| 65 | − .map(|m| m.is_dir()), | |
| 66 | − Case::Sensitive, | |
| 67 | − ); | |
| 68 | − match (actual, source_and_line) { | |
| 69 | − ( | |
| 70 | − Some(Match { | |
| 71 | − sequence_number, | |
| 72 | − pattern: _, | |
| 73 | − source, | |
| 74 | − value: _, | |
| 75 | − }), | |
| 76 | − Some((expected_source, line, _expected_pattern)), | |
| 77 | − ) => { | |
| 78 | − assert_eq!(sequence_number, line, "our counting should match the one used in git"); | |
| 79 | − assert_eq!( | |
| 80 | − source.map(|p| p.canonicalize().unwrap()), | |
| 81 | − Some(repo_dir.join(expected_source.to_str_lossy().as_ref()).canonicalize()?) | |
| 82 | − ); | |
| 83 | − } | |
| 84 | − (None, None) => {} | |
| 85 | − (actual, expected) => panic!("actual {actual:?} should match {expected:?} with path '{path}'"), | |
| 86 | − } | |
| 87 | − } | |
| 88 | − Ok(()) | |
| 89 | − } | |
| 90 | − | |
| 91 | − #[test] | |
| 92 | − fn from_overrides() { | |
| 93 | − let input = ["simple", "pattern/"]; | |
| 94 | − let group = gix_attributes::MatchGroup::<Ignore>::from_overrides(input); | |
| 95 | − assert_eq!( | |
| 96 | − group.pattern_matching_relative_path("Simple", None, gix_glob::pattern::Case::Fold), | |
| 97 | − Some(pattern_to_match(&gix_glob::parse("simple").unwrap(), 0)) | |
| 98 | − ); | |
| 99 | − assert_eq!( | |
| 100 | − group.pattern_matching_relative_path("pattern", Some(true), gix_glob::pattern::Case::Sensitive), | |
| 101 | − Some(pattern_to_match(&gix_glob::parse("pattern/").unwrap(), 1)) | |
| 102 | − ); | |
| 103 | − assert_eq!(group.patterns.len(), 1); | |
| 104 | − assert_eq!( | |
| 105 | − gix_attributes::PatternList::<Ignore>::from_overrides(input), | |
| 106 | − group.patterns.into_iter().next().unwrap() | |
| 107 | − ); | |
| 108 | − } | |
| 109 | − | |
| 110 | − fn pattern_to_match(pattern: &gix_glob::Pattern, sequence_number: usize) -> Match<'_, ()> { | |
| 111 | − Match { | |
| 112 | − pattern, | |
| 113 | − value: &(), | |
| 114 | − source: None, | |
| 115 | − sequence_number, | |
| 116 | − } | |
| 117 | − } | |
| 118 | −} | |
| 1 | +mod ignore; | |
| @@ -120,18 +120,25 @@ pub mod excludes { · 119 unmodified lines | ||
| 120 | 120 | /// Configure a file-system cache checking if files below the repository are excluded. |
| 121 | 121 | /// |
| 122 | 122 | /// This takes into consideration all the usual repository configuration. |
| 123 | − // TODO: test, provide higher-level interface that is much easier to use and doesn't panic. | |
| 123 | + /// | |
| 124 | + /// `index` may be used to obtain `.gitignore` files directly from the index under certain conditions. | |
| 125 | + // TODO: test, provide higher-level interface that is much easier to use and doesn't panic when accessing entries | |
| 126 | + // by non-relative path. | |
| 127 | + // TODO: `index` might be so special (given the conditions we are talking about) that it's better obtained internally | |
| 128 | + // so the caller won't have to care. | |
| 129 | + // TODO: global files like `~/.gitignore` seem to be missing here, but we need a way to control if these should be loaded. | |
| 130 | + // probably that needs another permission in the repo options or a custom config variable. The latter is easiest to manage. | |
| 124 | 131 | pub fn excludes( |
| 125 | 132 | &self, |
| 126 | 133 | index: &gix_index::State, |
| 127 | 134 | overrides: Option<gix_attributes::MatchGroup<gix_attributes::Ignore>>, |
| 128 | 135 | ) -> Result<gix_worktree::fs::Cache, Error> { |
| 129 | 136 | let repo = self.parent; |
| 130 | − let case = repo | |
| 131 | − .config | |
| 132 | − .ignore_case | |
| 133 | − .then_some(gix_glob::pattern::Case::Fold) | |
| 134 | − .unwrap_or_default(); | |
| 137 | + let case = if repo.config.ignore_case { | |
| 138 | + gix_glob::pattern::Case::Fold | |
| 139 | + } else { | |
| 140 | + gix_glob::pattern::Case::Sensitive | |
| 141 | + }; | |
| 135 | 142 | let mut buf = Vec::with_capacity(512); |
| 136 | 143 | let excludes_file = match repo.config.excludes_file().transpose()? { |
| 137 | 144 | Some(user_path) => Some(user_path), |