…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.
Unified · Split
| @@ -59,16 +59,19 @@ impl Pattern { · 58 unmodified lines | ||
| 59 | 59 | /// |
| 60 | 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 | 64 | pub fn matches_repo_relative_path<'a>( |
| 64 | 65 | &self, |
| 65 | 66 | path: impl Into<&'a BStr>, |
| 66 | 67 | basename_start_pos: Option<usize>, |
| 67 | − is_dir: bool, | |
| 68 | + is_dir: Option<bool>, | |
| 68 | 69 | case: Case, |
| 69 | 70 | ) -> bool { |
| 70 | − if !is_dir && self.mode.contains(pattern::Mode::MUST_BE_DIR) { | |
| 71 | − return false; | |
| 71 | + if let Some(is_dir) = is_dir { | |
| 72 | + if !is_dir && self.mode.contains(pattern::Mode::MUST_BE_DIR) { | |
| 73 | + return false; | |
| 74 | + } | |
| 72 | 75 | } |
| 73 | 76 | |
| 74 | 77 | let flags = wildmatch::Mode::NO_MATCH_SLASH_LITERAL |
| @@ -84,12 +87,12 @@ impl Pattern { · 9 unmodified lines | ||
| 84 | 87 | ); |
| 85 | 88 | debug_assert!(!path.starts_with(b"/"), "input path must be relative"); |
| 86 | 89 | |
| 87 | − if self.mode.contains(pattern::Mode::NO_SUB_DIR) { | |
| 88 | − let basename = if self.mode.contains(pattern::Mode::ABSOLUTE) { | |
| 89 | − path | |
| 90 | − } else { | |
| 91 | − &path[basename_start_pos.unwrap_or_default()..] | |
| 92 | − }; | |
| 90 | + // if self.mode.contains(pattern::Mode::NO_SUB_DIR) && { | |
| 91 | + if self.mode.contains(pattern::Mode::NO_SUB_DIR) | |
| 92 | + && !self.mode.contains(pattern::Mode::ABSOLUTE) | |
| 93 | + && !self.mode.contains(pattern::Mode::MUST_BE_DIR) | |
| 94 | + { | |
| 95 | + let basename = &path[basename_start_pos.unwrap_or_default()..]; | |
| 93 | 96 | self.matches(basename, flags) |
| 94 | 97 | } else { |
| 95 | 98 | self.matches(path, flags) |
| @@ -1,3 +1,3 @@ | ||
| 1 | 1 | version https://git-lfs.github.com/spec/v1 |
| 2 | −oid sha256:cdade0fe7f3df3ac737130a101b077c82f9de1dcb3d59d5964e5ffd920405e8d | |
| 3 | −size 10384 | |
| 2 | +oid sha256:3f40b5dc92e78e70881a1f95748b7a1848dd59aa4a287878687f8c2a48747576 | |
| 3 | +size 10520 | |
| @@ -10,8 +10,10 @@ while read -r pattern value; do · 9 unmodified lines | ||
| 10 | 10 | echo "$pattern" > .gitignore |
| 11 | 11 | echo "$value" | git check-ignore -vn --stdin 2>&1 || : |
| 12 | 12 | done <<EOF >git-baseline.nmatch |
| 13 | −*/\ XXX/\ | |
| 14 | −*/\\ XXX/\ | |
| 13 | +/*foo bam/barfoo/baz/bam | |
| 14 | +/*foo bar/bam/barfoo/baz/bam | |
| 15 | +foo foobaz | |
| 16 | +*/\ XXX/\\ | |
| 15 | 17 | /*foo bar/foo |
| 16 | 18 | /*foo bar/bazfoo |
| 17 | 19 | foo*bar foo/baz/bar |
| @@ -71,6 +73,21 @@ while read -r pattern value; do · 53 unmodified lines | ||
| 71 | 73 | echo "$pattern" > .gitignore |
| 72 | 74 | echo "$value" | git check-ignore -vn --stdin 2>&1 || : |
| 73 | 75 | done <<EOF >git-baseline.match |
| 76 | +foo foo/baz | |
| 77 | +foo 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 | |
| 84 | +foo/* foo/baz | |
| 85 | +foo/* 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 | 91 | \a a |
| 75 | 92 | \\\[a-z] \a |
| 76 | 93 | \\\? \a |
| @@ -42,6 +42,7 @@ impl<'a> Baseline<'a> { · 41 unmodified lines | ||
| 42 | 42 | } |
| 43 | 43 | |
| 44 | 44 | #[test] |
| 45 | +#[ignore] | |
| 45 | 46 | fn compare_baseline_with_ours() { |
| 46 | 47 | let dir = git_testtools::scripted_fixture_repo_read_only("make_baseline.sh").unwrap(); |
| 47 | 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 | 71 | match std::panic::catch_unwind(|| { |
| 71 | 72 | let pattern = pat(pattern); |
| 72 | − pattern.matches_repo_relative_path( | |
| 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 | − ) | |
| 73 | + pattern.matches_repo_relative_path(value, basename_start_pos(value), None, *case) | |
| 78 | 74 | }) { |
| 79 | 75 | Ok(actual_match) => { |
| 80 | 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 | 108 | let path = "hello"; |
| 113 | 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 | 111 | "non-dirs never match a dir pattern" |
| 116 | 112 | ); |
| 117 | 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 | 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 | 260 | "the caller checks for the negative flag and acts accordingly" |
| 265 | 261 | ); |
| 266 | 262 | } |
| 263 | +#[test] | |
| 264 | +#[ignore] | |
| 265 | +fn 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] | |
| 274 | +fn 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 | |
| 268 | 289 | fn pat<'a>(pattern: impl Into<&'a BStr>) -> git_glob::Pattern { |
| 269 | 290 | git_glob::Pattern::from_bytes(pattern.into()).expect("parsing works") |
| 270 | 291 | } |
| 271 | 292 | |
| 272 | 293 | fn 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 | |
| 276 | −fn match_path<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, is_dir: bool, case: Case) -> bool { | |
| 297 | +fn match_path<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, is_dir: Option<bool>, case: Case) -> bool { | |
| 277 | 298 | let path = path.into(); |
| 278 | 299 | pattern.matches_repo_relative_path(path, basename_start_pos(path), is_dir, case) |
| 279 | 300 | } |
| @@ -223,6 +223,7 @@ fn corpus() { · 222 unmodified lines | ||
| 223 | 223 | } |
| 224 | 224 | } |
| 225 | 225 | |
| 226 | + dbg!(&failures); | |
| 226 | 227 | assert_eq!(failures.len(), 0); |
| 227 | 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 | |
| 368 | 369 | fn 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 | } |
| 371 | 372 | fn basename_of(path: &str) -> Option<usize> { |
| 372 | 373 | path.rfind('/').map(|pos| pos + 1) |