diff --git a/Cargo.lock b/Cargo.lock index 470b38d5a91c..427e93e56cd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5658,6 +5658,7 @@ dependencies = [ "build_helper", "cargo_metadata 0.21.0", "fluent-syntax", + "globset", "ignore", "miropt-test-tools", "regex", diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml index 47b59543c59c..cbf27ea87a07 100644 --- a/src/tools/tidy/Cargo.toml +++ b/src/tools/tidy/Cargo.toml @@ -8,6 +8,7 @@ autobins = false build_helper = { path = "../../build_helper" } cargo_metadata = "0.21" regex = "1" +globset = "0.4.18" miropt-test-tools = { path = "../miropt-test-tools" } walkdir = "2" ignore = "0.4.18" diff --git a/src/tools/tidy/src/triagebot.rs b/src/tools/tidy/src/triagebot.rs index 01401c94d730..59cd9d80b07a 100644 --- a/src/tools/tidy/src/triagebot.rs +++ b/src/tools/tidy/src/triagebot.rs @@ -1,5 +1,6 @@ //! Tidy check to ensure paths mentioned in triagebot.toml exist in the project. +use std::collections::HashSet; use std::path::Path; use toml::Value; @@ -22,6 +23,9 @@ pub fn check(path: &Path, tidy_ctx: TidyCtx) { // Check [mentions."*"] sections, i.e. [mentions."compiler/rustc_const_eval/src/"] if let Some(Value::Table(mentions)) = config.get("mentions") { + let mut builder = globset::GlobSetBuilder::new(); + let mut glob_entries = Vec::new(); + for (entry_key, entry_val) in mentions.iter() { // If the type is set to something other than "filename", then this is not a path. if entry_val.get("type").is_some_and(|t| t.as_str().unwrap_or_default() != "filename") { @@ -33,8 +37,37 @@ pub fn check(path: &Path, tidy_ctx: TidyCtx) { let full_path = path.join(clean_path); if !full_path.exists() { + // The full-path doesn't exists, maybe it's a glob, let's add it to the glob set builder + // to be checked against all the file and directories in the repository. + builder.add(globset::Glob::new(&format!("{clean_path}*")).unwrap()); + glob_entries.push(clean_path.to_string()); + } + } + + let gs = builder.build().unwrap(); + + let mut found = HashSet::new(); + let mut matches = Vec::new(); + + // Walk the entire repository and match any entry against the remaining paths + for entry in ignore::WalkBuilder::new(path).build().flatten() { + // Strip the prefix as mentions entries are always relative to the repo + let entry_path = entry.path().strip_prefix(path).unwrap(); + + // Find the matches and add them to the found set + gs.matches_into(entry_path, &mut matches); + found.extend(matches.iter().copied()); + + // Early-exist if all the globs have been matched + if found.len() == glob_entries.len() { + break; + } + } + + for (i, clean_path) in glob_entries.iter().enumerate() { + if !found.contains(&i) { check.error(format!( - "triagebot.toml [mentions.*] contains path '{clean_path}' which doesn't exist" + "triagebot.toml [mentions.*] contains '{clean_path}' which doesn't match any file or directory in the repository" )); } }