diff --git a/xtask/src/assets.rs b/xtask/src/assets.rs index a7169ee66..da7371b8e 100644 --- a/xtask/src/assets.rs +++ b/xtask/src/assets.rs @@ -61,16 +61,23 @@ pub fn missing(files: &[String], assets_md: &str) -> Vec { files.iter().filter(|f| is_asset(f) && !patterns.iter().any(|p| glob(p, f))).cloned().collect() } -/// `*` matches any run of characters except `/`; everything else literally. +/// `*` matches any run of characters except `/`; everything else literally. Matched with a +/// table over the pattern, so a pattern with many `*` costs pattern length times name length +/// rather than exploring every way to split the name. fn glob(pat: &str, s: &str) -> bool { - match pat.split_once('*') { - None => pat == s, - Some((head, tail)) => { - let Some(rest) = s.strip_prefix(head) else { return false }; - let ends = rest.char_indices().map(|(i, _)| i).chain(std::iter::once(rest.len())); - ends.take_while(|&i| !rest[..i].contains('/')).any(|i| glob(tail, &rest[i..])) + let t: Vec = s.chars().collect(); + // `row[k]`: the pattern so far matches the first k characters of the name. + let mut row = vec![false; t.len() + 1]; + row[0] = true; + for pc in pat.chars() { + let mut next = vec![false; t.len() + 1]; + next[0] = pc == '*' && row[0]; + for k in 1..=t.len() { + next[k] = if pc == '*' { row[k] || (next[k - 1] && t[k - 1] != '/') } else { row[k - 1] && t[k - 1] == pc }; } + row = next; } + row[t.len()] } /// Splits `git ls-files -z` output into real file names (no display quoting of Unicode or special characters). @@ -120,6 +127,15 @@ mod tests { assert_eq!(missing(&["assets/é.png".into()], "`assets/*.svg`"), vec!["assets/é.png".to_string()]); } + #[test] + fn many_wildcards_on_a_failing_name_finish_at_once() { + let pat = format!("{}b", "a*".repeat(40)); + assert!(!glob(&pat, &"a".repeat(60))); + assert!(glob(&pat, &format!("{}b", "a".repeat(60)))); + assert!(!glob("a*b", "a/b")); + assert!(glob("a*b", "ab")); + } + #[test] fn ls_files_output_is_nul_separated_and_unquoted() { let names = parse_ls_files("assets/zażółć.png\0docs/images/a b\nc.png\0".as_bytes());