History

discover an entirely new class of exclude matches… (#301)

…which haven't been covered by tests and which show that the current implementation isn't sufficient. It also makes clear that the match styles of git-attributes and git-ignore are quite different. It's unclear why, even though the way both algorithms work are quite different. So have to take a step back and find a way to figure it out.

Sebastian Thiel committed 4 years ago

Commit f8dd5ce1 parent fe9fb4c5 files · +67 −25Browse files

Unified · Split

5 files
  1. git-glob/src/pattern.rs +13 −10
  2. git-glob/tests/fixtures/generated-archives/make_baseline.tar.xz +2 −2
  3. git-glob/tests/fixtures/make_baseline.sh +19 −2
  4. git-glob/tests/matching/mod.rs +31 −10
  5. git-glob/tests/wildmatch/mod.rs +2 −1
git-glob/src/pattern.rs+13−10
@@ -59,16 +59,19 @@ impl Pattern { · 58 unmodified lines
59 ///59 ///
60 /// Lastly, `case` folding can be configured as well.60 /// Lastly, `case` folding can be configured as well.
61 ///61 ///
62 /// Note that this method uses shortcuts to accelerate simple patterns.62 /// Note that this method uses shortcuts to accelerate simple patterns, and is specific to **exclude** style matching.
63 /// Hence this shouldn't be used for **attribute** style patterns.
63 pub fn matches_repo_relative_path<'a>(64 pub fn matches_repo_relative_path<'a>(
64 &self,65 &self,
65 path: impl Into<&'a BStr>,66 path: impl Into<&'a BStr>,
66 basename_start_pos: Option<usize>,67 basename_start_pos: Option<usize>,
67 is_dir: bool,68 is_dir: Option<bool>,
68 case: Case,69 case: Case,
69 ) -> bool {70 ) -> bool {
70 if !is_dir && self.mode.contains(pattern::Mode::MUST_BE_DIR) {71 if let Some(is_dir) = is_dir {
71 return false;72 if !is_dir && self.mode.contains(pattern::Mode::MUST_BE_DIR) {
73 return false;
74 }
72 }75 }
73 76
74 let flags = wildmatch::Mode::NO_MATCH_SLASH_LITERAL77 let flags = wildmatch::Mode::NO_MATCH_SLASH_LITERAL
@@ -84,12 +87,12 @@ impl Pattern { · 9 unmodified lines
84 );87 );
85 debug_assert!(!path.starts_with(b"/"), "input path must be relative");88 debug_assert!(!path.starts_with(b"/"), "input path must be relative");
86 89
87 if self.mode.contains(pattern::Mode::NO_SUB_DIR) {90 // if self.mode.contains(pattern::Mode::NO_SUB_DIR) && {
88 let basename = if self.mode.contains(pattern::Mode::ABSOLUTE) {91 if self.mode.contains(pattern::Mode::NO_SUB_DIR)
89 path92 && !self.mode.contains(pattern::Mode::ABSOLUTE)
90 } else {93 && !self.mode.contains(pattern::Mode::MUST_BE_DIR)
91 &path[basename_start_pos.unwrap_or_default()..]94 {
92 };95 let basename = &path[basename_start_pos.unwrap_or_default()..];
93 self.matches(basename, flags)96 self.matches(basename, flags)
94 } else {97 } else {
95 self.matches(path, flags)98 self.matches(path, flags)
git-glob/tests/fixtures/generated-archives/make_baseline.tar.xz+2−2
@@ -1,3 +1,3 @@
1version https://git-lfs.github.com/spec/v11version https://git-lfs.github.com/spec/v1
2oid sha256:cdade0fe7f3df3ac737130a101b077c82f9de1dcb3d59d5964e5ffd920405e8d2oid sha256:3f40b5dc92e78e70881a1f95748b7a1848dd59aa4a287878687f8c2a48747576
3size 103843size 10520
git-glob/tests/fixtures/make_baseline.sh+19−2
@@ -10,8 +10,10 @@ while read -r pattern value; do · 9 unmodified lines
10 echo "$pattern" > .gitignore10 echo "$pattern" > .gitignore
11 echo "$value" | git check-ignore -vn --stdin 2>&1 || :11 echo "$value" | git check-ignore -vn --stdin 2>&1 || :
12done <<EOF >git-baseline.nmatch12done <<EOF >git-baseline.nmatch
13*/\ XXX/\13/*foo bam/barfoo/baz/bam
14*/\\ XXX/\14/*foo bar/bam/barfoo/baz/bam
15foo foobaz
16*/\ XXX/\\
15/*foo bar/foo17/*foo bar/foo
16/*foo bar/bazfoo18/*foo bar/bazfoo
17foo*bar foo/baz/bar19foo*bar foo/baz/bar
@@ -71,6 +73,21 @@ while read -r pattern value; do · 53 unmodified lines
71 echo "$pattern" > .gitignore73 echo "$pattern" > .gitignore
72 echo "$value" | git check-ignore -vn --stdin 2>&1 || :74 echo "$value" | git check-ignore -vn --stdin 2>&1 || :
73done <<EOF >git-baseline.match75done <<EOF >git-baseline.match
76foo foo/baz
77foo foo/baz/bam
78*foo barfoo
79/*foo barfoo
80*foo barfoo/baz/bam
81/*foo barfoo/baz/bam
82*foo bam/barfoo/baz/bam
83*foo bar/bam/barfoo/baz/bam
84foo/* foo/baz
85foo/* foo/baz/bam
86*foo/* barfoo/baz
87*foo/* barfoo/baz/bam
88*foo barfoo/baz
89/*foo/ hellofoo/bar
90*foo/ hellofoo/bar
74\a a91\a a
75\\\[a-z] \a92\\\[a-z] \a
76\\\? \a93\\\? \a
git-glob/tests/matching/mod.rs+31−10
@@ -42,6 +42,7 @@ impl<'a> Baseline<'a> { · 41 unmodified lines
42}42}
43 43
44#[test]44#[test]
45#[ignore]
45fn compare_baseline_with_ours() {46fn compare_baseline_with_ours() {
46 let dir = git_testtools::scripted_fixture_repo_read_only("make_baseline.sh").unwrap();47 let dir = git_testtools::scripted_fixture_repo_read_only("make_baseline.sh").unwrap();
47 let (mut total_matches, mut total_correct, mut panics) = (0, 0, 0);48 let (mut total_matches, mut total_correct, mut panics) = (0, 0, 0);
@@ -69,12 +70,7 @@ fn compare_baseline_with_ours() { · 21 unmodified lines
69 );70 );
70 match std::panic::catch_unwind(|| {71 match std::panic::catch_unwind(|| {
71 let pattern = pat(pattern);72 let pattern = pat(pattern);
72 pattern.matches_repo_relative_path(73 pattern.matches_repo_relative_path(value, basename_start_pos(value), None, *case)
73 value,
74 basename_start_pos(value),
75 false, // TODO: does it make sense to pretend it is a dir and see what happens?
76 *case,
77 )
78 }) {74 }) {
79 Ok(actual_match) => {75 Ok(actual_match) => {
80 if actual_match == is_match {76 if actual_match == is_match {
@@ -111,11 +107,11 @@ fn non_dirs_for_must_be_dir_patterns_are_ignored() { · 30 unmodified lines
111 );107 );
112 let path = "hello";108 let path = "hello";
113 assert!(109 assert!(
114 !pattern.matches_repo_relative_path(path, None, false /* is-dir */, Case::Sensitive),110 !pattern.matches_repo_relative_path(path, None, false.into() /* is-dir */, Case::Sensitive),
115 "non-dirs never match a dir pattern"111 "non-dirs never match a dir pattern"
116 );112 );
117 assert!(113 assert!(
118 pattern.matches_repo_relative_path(path, None, true /* is-dir */, Case::Sensitive),114 pattern.matches_repo_relative_path(path, None, true.into() /* is-dir */, Case::Sensitive),
119 "dirs can match a dir pattern with the normal rules"115 "dirs can match a dir pattern with the normal rules"
120 );116 );
121}117}
@@ -264,16 +260,41 @@ fn negated_patterns_are_handled_by_caller() { · 142 unmodified lines
264 "the caller checks for the negative flag and acts accordingly"260 "the caller checks for the negative flag and acts accordingly"
265 );261 );
266}262}
263#[test]
264#[ignore]
265fn names_automatically_match_entire_directories() {
266 let pattern = &pat("foo");
267 assert!(!match_file(pattern, "foobar", Case::Sensitive));
268 assert!(match_file(pattern, "foo/bar", Case::Sensitive));
269 assert!(match_file(pattern, "foo/bar/baz", Case::Sensitive));
270}
271
272#[test]
273#[ignore]
274fn directory_patterns_match_files_within_a_directory_as_well_like_slash_star_star() {
275 let pattern = &pat("dir/");
276 assert!(match_path(pattern, "dir/file", None, Case::Sensitive));
277 assert!(match_path(pattern, "base/dir/file", None, Case::Sensitive));
278 assert!(match_path(pattern, "base/ndir/file", None, Case::Sensitive));
279 assert!(match_path(pattern, "Dir/File", None, Case::Fold));
280 assert!(match_path(pattern, "Base/Dir/File", None, Case::Fold));
281 assert!(!match_path(pattern, "dir2/file", None, Case::Sensitive));
282
283 let pattern = &pat("dir/sub-dir/");
284 assert!(match_path(pattern, "dir/sub-dir/file", None, Case::Sensitive));
285 assert!(match_path(pattern, "dir/Sub-dir/File", None, Case::Fold));
286 assert!(!match_path(pattern, "dir/Sub-dir2/File", None, Case::Fold));
287}
267 288
268fn pat<'a>(pattern: impl Into<&'a BStr>) -> git_glob::Pattern {289fn pat<'a>(pattern: impl Into<&'a BStr>) -> git_glob::Pattern {
269 git_glob::Pattern::from_bytes(pattern.into()).expect("parsing works")290 git_glob::Pattern::from_bytes(pattern.into()).expect("parsing works")
270}291}
271 292
272fn match_file<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, case: Case) -> bool {293fn match_file<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, case: Case) -> bool {
273 match_path(pattern, path, false, case)294 match_path(pattern, path, false.into(), case)
274}295}
275 296
276fn match_path<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, is_dir: bool, case: Case) -> bool {297fn match_path<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, is_dir: Option<bool>, case: Case) -> bool {
277 let path = path.into();298 let path = path.into();
278 pattern.matches_repo_relative_path(path, basename_start_pos(path), is_dir, case)299 pattern.matches_repo_relative_path(path, basename_start_pos(path), is_dir, case)
279}300}
git-glob/tests/wildmatch/mod.rs+2−1
@@ -223,6 +223,7 @@ fn corpus() { · 222 unmodified lines
223 }223 }
224 }224 }
225 225
226 dbg!(&failures);
226 assert_eq!(failures.len(), 0);227 assert_eq!(failures.len(), 0);
227 assert_eq!(at_least_one_panic, 0, "not a single panic in any invocation");228 assert_eq!(at_least_one_panic, 0, "not a single panic in any invocation");
228 229
@@ -366,7 +367,7 @@ impl Display for MatchResult { · 137 unmodified lines
366}367}
367 368
368fn match_file_path(pattern: &git_glob::Pattern, path: &str, case: Case) -> bool {369fn match_file_path(pattern: &git_glob::Pattern, path: &str, case: Case) -> bool {
369 pattern.matches_repo_relative_path(path, basename_of(path), false /* is_dir */, case)370 pattern.matches_repo_relative_path(path, basename_of(path), false.into() /* is_dir */, case)
370}371}
371fn basename_of(path: &str) -> Option<usize> {372fn basename_of(path: &str) -> Option<usize> {
372 path.rfind('/').map(|pos| pos + 1)373 path.rfind('/').map(|pos| pos + 1)

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t