xtask: assets audit no longer panics on Unicode names and reads real paths from git (#424)

This commit is contained in:
MarekSpodBiedry
2026-10-13 17:34:03 +02:00
parent 457406a7a5
commit 34372569e7
+24 -3
View File
@@ -67,18 +67,24 @@ fn glob(pat: &str, s: &str) -> bool {
None => pat == s,
Some((head, tail)) => {
let Some(rest) = s.strip_prefix(head) else { return false };
(0..=rest.len()).take_while(|&i| i == 0 || !rest[..i].contains('/')).any(|i| rest.is_char_boundary(i) && glob(tail, &rest[i..]))
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..]))
}
}
}
/// Splits `git ls-files -z` output into real file names (no display quoting of Unicode or special characters).
fn parse_ls_files(out: &[u8]) -> Vec<String> {
out.split(|&b| b == 0).filter(|n| !n.is_empty()).map(|n| String::from_utf8_lossy(n).into_owned()).collect()
}
pub fn run(root: &Path) -> Result<(), String> {
let out = Command::new("git")
.current_dir(root)
.args(["ls-files", "--cached", "--others", "--exclude-standard"])
.args(["ls-files", "--cached", "--others", "--exclude-standard", "-z"])
.output()
.map_err(|e| format!("git ls-files: {e}"))?;
let files: Vec<String> = String::from_utf8_lossy(&out.stdout).lines().filter(|l| root.join(l).exists()).map(str::to_owned).collect();
let files: Vec<String> = parse_ls_files(&out.stdout).into_iter().filter(|l| root.join(l).exists()).collect();
let md = std::fs::read_to_string(root.join("ATTRIBUTION.md")).map_err(|e| format!("ATTRIBUTION.md: {e}"))?;
let miss = missing(&files, &md);
if miss.is_empty() {
@@ -104,4 +110,19 @@ mod tests {
assert!(missing(&["assets/app-icon/hicolor/16x16/apps/x.png".into()], md).is_empty());
assert_eq!(missing(&["assets/app-icon/hicolor/a/b/apps/x.png".into()], md).len(), 1);
}
#[test]
fn glob_handles_multibyte_names() {
assert!(glob("assets/*/x.png", "assets/żółć/x.png"));
assert!(glob("assets/*.png", "assets/日本語.png"));
assert!(!glob("assets/*/x.png", "assets/ż/ó/x.png"));
assert!(missing(&["assets/żółć/x.png".into()], "`assets/*/x.png`").is_empty());
assert_eq!(missing(&["assets/é.png".into()], "`assets/*.svg`"), vec!["assets/é.png".to_string()]);
}
#[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());
assert_eq!(names, vec!["assets/zażółć.png".to_string(), "docs/images/a b\nc.png".into()]);
}
}