Skip to content

Commit 455a72e

Browse files
committed
feat: fmt::Display impl for Pattern. (#301)
This way the original pattern can be reproduced on the fly without actually storing it, saving one allocation.
1 parent 2672a25 commit 455a72e

File tree

5 files changed

+363
-326
lines changed

5 files changed

+363
-326
lines changed

git-glob/src/pattern.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use bitflags::bitflags;
22
use bstr::{BStr, ByteSlice};
3+
use std::fmt;
34

45
use crate::{pattern, wildmatch, Pattern};
56

@@ -142,3 +143,19 @@ impl Pattern {
142143
}
143144
}
144145
}
146+
147+
impl fmt::Display for Pattern {
148+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149+
if self.mode.contains(Mode::NEGATIVE) {
150+
"!".fmt(f)?;
151+
}
152+
if self.mode.contains(Mode::ABSOLUTE) {
153+
"/".fmt(f)?;
154+
}
155+
self.text.fmt(f)?;
156+
if self.mode.contains(Mode::MUST_BE_DIR) {
157+
"/".fmt(f)?;
158+
}
159+
Ok(())
160+
}
161+
}

git-glob/tests/glob.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
mod matching;
21
mod parse;
2+
mod pattern;
33
mod wildmatch;

git-glob/tests/matching/mod.rs

Lines changed: 0 additions & 325 deletions
Original file line numberDiff line numberDiff line change
@@ -1,326 +1 @@
1-
use std::collections::BTreeSet;
21

3-
use bstr::{BStr, ByteSlice};
4-
use git_glob::{pattern, pattern::Case};
5-
6-
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
7-
pub struct GitMatch<'a> {
8-
pattern: &'a BStr,
9-
value: &'a BStr,
10-
/// True if git could match `value` with `pattern`
11-
is_match: bool,
12-
}
13-
14-
pub struct Baseline<'a> {
15-
inner: bstr::Lines<'a>,
16-
}
17-
18-
impl<'a> Iterator for Baseline<'a> {
19-
type Item = GitMatch<'a>;
20-
21-
fn next(&mut self) -> Option<Self::Item> {
22-
let mut tokens = self.inner.next()?.splitn(2, |b| *b == b' ');
23-
let pattern = tokens.next().expect("pattern").as_bstr();
24-
let value = tokens.next().expect("value").as_bstr().trim_start().as_bstr();
25-
26-
let git_match = self.inner.next()?;
27-
let is_match = !git_match.starts_with(b"::\t");
28-
Some(GitMatch {
29-
pattern,
30-
value,
31-
is_match,
32-
})
33-
}
34-
}
35-
36-
impl<'a> Baseline<'a> {
37-
fn new(input: &'a [u8]) -> Self {
38-
Baseline {
39-
inner: input.as_bstr().lines(),
40-
}
41-
}
42-
}
43-
44-
#[test]
45-
fn compare_baseline_with_ours() {
46-
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 mismatches = Vec::new();
49-
for (input_file, expected_matches, case) in &[
50-
("git-baseline.match", true, pattern::Case::Sensitive),
51-
("git-baseline.nmatch", false, pattern::Case::Sensitive),
52-
("git-baseline.match-icase", true, pattern::Case::Fold),
53-
] {
54-
let input = std::fs::read(dir.join(*input_file)).unwrap();
55-
let mut seen = BTreeSet::default();
56-
57-
for m @ GitMatch {
58-
pattern,
59-
value,
60-
is_match,
61-
} in Baseline::new(&input)
62-
{
63-
total_matches += 1;
64-
assert!(seen.insert(m), "duplicate match entry: {:?}", m);
65-
assert_eq!(
66-
is_match, *expected_matches,
67-
"baseline for matches must be {} - check baseline and git version: {:?}",
68-
expected_matches, m
69-
);
70-
match std::panic::catch_unwind(|| {
71-
let pattern = pat(pattern);
72-
pattern.matches_repo_relative_path(value, basename_start_pos(value), None, *case)
73-
}) {
74-
Ok(actual_match) => {
75-
if actual_match == is_match {
76-
total_correct += 1;
77-
} else {
78-
mismatches.push((pattern.to_owned(), value.to_owned(), is_match, expected_matches));
79-
}
80-
}
81-
Err(_) => {
82-
panics += 1;
83-
continue;
84-
}
85-
};
86-
}
87-
}
88-
89-
dbg!(mismatches);
90-
assert_eq!(
91-
total_correct,
92-
total_matches - panics,
93-
"We perfectly agree with git here"
94-
);
95-
assert_eq!(panics, 0);
96-
}
97-
98-
#[test]
99-
fn non_dirs_for_must_be_dir_patterns_are_ignored() {
100-
let pattern = pat("hello/");
101-
102-
assert!(pattern.mode.contains(pattern::Mode::MUST_BE_DIR));
103-
assert_eq!(
104-
pattern.text, "hello",
105-
"a dir pattern doesn't actually end with the trailing slash"
106-
);
107-
let path = "hello";
108-
assert!(
109-
!pattern.matches_repo_relative_path(path, None, false.into() /* is-dir */, Case::Sensitive),
110-
"non-dirs never match a dir pattern"
111-
);
112-
assert!(
113-
pattern.matches_repo_relative_path(path, None, true.into() /* is-dir */, Case::Sensitive),
114-
"dirs can match a dir pattern with the normal rules"
115-
);
116-
}
117-
118-
#[test]
119-
fn matches_of_absolute_paths_work() {
120-
let pattern = "/hello/git";
121-
assert!(
122-
git_glob::wildmatch(pattern.into(), pattern.into(), git_glob::wildmatch::Mode::empty()),
123-
"patterns always match themselves"
124-
);
125-
assert!(
126-
git_glob::wildmatch(
127-
pattern.into(),
128-
pattern.into(),
129-
git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL
130-
),
131-
"patterns always match themselves, path mode doesn't change that"
132-
);
133-
}
134-
135-
#[test]
136-
fn basename_matches_from_end() {
137-
let pat = &pat("foo");
138-
assert!(match_file(pat, "FoO", Case::Fold));
139-
assert!(!match_file(pat, "FoOo", Case::Fold));
140-
assert!(!match_file(pat, "Foo", Case::Sensitive));
141-
assert!(match_file(pat, "foo", Case::Sensitive));
142-
assert!(!match_file(pat, "Foo", Case::Sensitive));
143-
assert!(!match_file(pat, "barfoo", Case::Sensitive));
144-
}
145-
146-
#[test]
147-
fn absolute_basename_matches_only_from_beginning() {
148-
let pat = &pat("/foo");
149-
assert!(match_file(pat, "FoO", Case::Fold));
150-
assert!(!match_file(pat, "bar/Foo", Case::Fold));
151-
assert!(match_file(pat, "foo", Case::Sensitive));
152-
assert!(!match_file(pat, "Foo", Case::Sensitive));
153-
assert!(!match_file(pat, "bar/foo", Case::Sensitive));
154-
}
155-
156-
#[test]
157-
fn absolute_path_matches_only_from_beginning() {
158-
let pat = &pat("/bar/foo");
159-
assert!(!match_file(pat, "FoO", Case::Fold));
160-
assert!(match_file(pat, "bar/Foo", Case::Fold));
161-
assert!(!match_file(pat, "foo", Case::Sensitive));
162-
assert!(match_file(pat, "bar/foo", Case::Sensitive));
163-
assert!(!match_file(pat, "bar/Foo", Case::Sensitive));
164-
}
165-
166-
#[test]
167-
fn absolute_path_with_recursive_glob_detects_mismatches_quickly() {
168-
let pat = &pat("/bar/foo/**");
169-
assert!(!match_file(pat, "FoO", Case::Fold));
170-
assert!(!match_file(pat, "bar/Fooo", Case::Fold));
171-
assert!(!match_file(pat, "baz/bar/Foo", Case::Fold));
172-
}
173-
174-
#[test]
175-
fn absolute_path_with_recursive_glob_can_do_case_insensitive_prefix_search() {
176-
let pat = &pat("/bar/foo/**");
177-
assert!(!match_file(pat, "bar/Foo/match", Case::Sensitive));
178-
assert!(match_file(pat, "bar/Foo/match", Case::Fold));
179-
}
180-
181-
#[test]
182-
fn relative_path_does_not_match_from_end() {
183-
for pattern in &["bar/foo", "/bar/foo"] {
184-
let pattern = &pat(*pattern);
185-
assert!(!match_file(pattern, "FoO", Case::Fold));
186-
assert!(match_file(pattern, "bar/Foo", Case::Fold));
187-
assert!(!match_file(pattern, "baz/bar/Foo", Case::Fold));
188-
assert!(!match_file(pattern, "foo", Case::Sensitive));
189-
assert!(match_file(pattern, "bar/foo", Case::Sensitive));
190-
assert!(!match_file(pattern, "baz/bar/foo", Case::Sensitive));
191-
assert!(!match_file(pattern, "Baz/bar/Foo", Case::Sensitive));
192-
}
193-
}
194-
195-
#[test]
196-
fn basename_glob_and_literal_is_ends_with() {
197-
let pattern = &pat("*foo");
198-
assert!(match_file(pattern, "FoO", Case::Fold));
199-
assert!(match_file(pattern, "BarFoO", Case::Fold));
200-
assert!(!match_file(pattern, "BarFoOo", Case::Fold));
201-
assert!(!match_file(pattern, "Foo", Case::Sensitive));
202-
assert!(!match_file(pattern, "BarFoo", Case::Sensitive));
203-
assert!(match_file(pattern, "barfoo", Case::Sensitive));
204-
assert!(!match_file(pattern, "barfooo", Case::Sensitive));
205-
206-
assert!(match_file(pattern, "bar/foo", Case::Sensitive));
207-
assert!(match_file(pattern, "bar/bazfoo", Case::Sensitive));
208-
}
209-
210-
#[test]
211-
fn special_cases_from_corpus() {
212-
let pattern = &pat("foo*bar");
213-
assert!(
214-
!match_file(pattern, "foo/baz/bar", Case::Sensitive),
215-
"asterisk does not match path separators"
216-
);
217-
let pattern = &pat("*some/path/to/hello.txt");
218-
assert!(
219-
!match_file(pattern, "a/bigger/some/path/to/hello.txt", Case::Sensitive),
220-
"asterisk doesn't match path separators"
221-
);
222-
223-
let pattern = &pat("/*foo.txt");
224-
assert!(match_file(pattern, "hello-foo.txt", Case::Sensitive));
225-
assert!(
226-
!match_file(pattern, "hello/foo.txt", Case::Sensitive),
227-
"absolute single asterisk doesn't match paths"
228-
);
229-
}
230-
231-
#[test]
232-
fn absolute_basename_glob_and_literal_is_ends_with_in_basenames() {
233-
let pattern = &pat("/*foo");
234-
235-
assert!(match_file(pattern, "FoO", Case::Fold));
236-
assert!(match_file(pattern, "BarFoO", Case::Fold));
237-
assert!(!match_file(pattern, "BarFoOo", Case::Fold));
238-
assert!(!match_file(pattern, "Foo", Case::Sensitive));
239-
assert!(!match_file(pattern, "BarFoo", Case::Sensitive));
240-
assert!(match_file(pattern, "barfoo", Case::Sensitive));
241-
assert!(!match_file(pattern, "barfooo", Case::Sensitive));
242-
}
243-
244-
#[test]
245-
fn absolute_basename_glob_and_literal_is_glob_in_paths() {
246-
let pattern = &pat("/*foo");
247-
248-
assert!(!match_file(pattern, "bar/foo", Case::Sensitive), "* does not match /");
249-
assert!(!match_file(pattern, "bar/bazfoo", Case::Sensitive));
250-
}
251-
252-
#[test]
253-
fn negated_patterns_are_handled_by_caller() {
254-
let pattern = &pat("!foo");
255-
assert!(
256-
match_file(pattern, "foo", Case::Sensitive),
257-
"negative patterns match like any other"
258-
);
259-
assert!(
260-
pattern.is_negative(),
261-
"the caller checks for the negative flag and acts accordingly"
262-
);
263-
}
264-
#[test]
265-
fn names_do_not_automatically_match_entire_directories() {
266-
// this feature is implemented with the directory stack.
267-
let pattern = &pat("foo");
268-
assert!(!match_file(pattern, "foobar", Case::Sensitive));
269-
assert!(!match_file(pattern, "foo/bar", Case::Sensitive));
270-
assert!(!match_file(pattern, "foo/bar/baz", Case::Sensitive));
271-
}
272-
273-
#[test]
274-
fn directory_patterns_do_not_match_files_within_a_directory_as_well_like_slash_star_star() {
275-
// this feature is implemented with the directory stack, which excludes entire directories
276-
let pattern = &pat("dir/");
277-
assert!(!match_path(pattern, "dir/file", None, Case::Sensitive));
278-
assert!(!match_path(pattern, "base/dir/file", None, Case::Sensitive));
279-
assert!(!match_path(pattern, "base/ndir/file", None, Case::Sensitive));
280-
assert!(!match_path(pattern, "Dir/File", None, Case::Fold));
281-
assert!(!match_path(pattern, "Base/Dir/File", None, Case::Fold));
282-
assert!(!match_path(pattern, "dir2/file", None, Case::Sensitive));
283-
284-
let pattern = &pat("dir/sub-dir/");
285-
assert!(!match_path(pattern, "dir/sub-dir/file", None, Case::Sensitive));
286-
assert!(!match_path(pattern, "dir/Sub-dir/File", None, Case::Fold));
287-
assert!(!match_path(pattern, "dir/Sub-dir2/File", None, Case::Fold));
288-
}
289-
290-
#[test]
291-
fn single_paths_match_anywhere() {
292-
let pattern = &pat("target");
293-
assert!(match_file(pattern, "dir/target", Case::Sensitive));
294-
assert!(!match_file(pattern, "dir/atarget", Case::Sensitive));
295-
assert!(!match_file(pattern, "dir/targeta", Case::Sensitive));
296-
assert!(match_path(pattern, "dir/target", Some(true), Case::Sensitive));
297-
298-
let pattern = &pat("target/");
299-
assert!(!match_file(pattern, "dir/target", Case::Sensitive));
300-
assert!(
301-
!match_path(pattern, "dir/target", None, Case::Sensitive),
302-
"it assumes unknown to not be a directory"
303-
);
304-
assert!(match_path(pattern, "dir/target", Some(true), Case::Sensitive));
305-
assert!(
306-
!match_path(pattern, "dir/target/", Some(true), Case::Sensitive),
307-
"we need sanitized paths that don't have trailing slashes"
308-
);
309-
}
310-
311-
fn pat<'a>(pattern: impl Into<&'a BStr>) -> git_glob::Pattern {
312-
git_glob::Pattern::from_bytes(pattern.into()).expect("parsing works")
313-
}
314-
315-
fn match_file<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, case: Case) -> bool {
316-
match_path(pattern, path, false.into(), case)
317-
}
318-
319-
fn match_path<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, is_dir: Option<bool>, case: Case) -> bool {
320-
let path = path.into();
321-
pattern.matches_repo_relative_path(path, basename_start_pos(path), is_dir, case)
322-
}
323-
324-
fn basename_start_pos(value: &BStr) -> Option<usize> {
325-
value.rfind_byte(b'/').map(|pos| pos + 1)
326-
}

0 commit comments

Comments
 (0)