Skip to content

Commit b7d857e

Browse files
committed
tidy: increase performance of auto extra checks feature
1 parent b96f238 commit b7d857e

File tree

2 files changed

+56
-30
lines changed

2 files changed

+56
-30
lines changed

src/tools/tidy/src/extra_checks/mod.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ fn check_impl(
8686
std::env::var("TIDY_PRINT_DIFF").is_ok_and(|v| v.eq_ignore_ascii_case("true") || v == "1");
8787

8888
// Split comma-separated args up
89-
let lint_args = match extra_checks {
89+
let mut lint_args = match extra_checks {
9090
Some(s) => s
9191
.strip_prefix("--extra-checks=")
9292
.unwrap()
@@ -99,11 +99,7 @@ fn check_impl(
9999
})
100100
.filter_map(|(res, src)| match res {
101101
Ok(arg) => {
102-
if arg.is_inactive_auto(ci_info) {
103-
None
104-
} else {
105-
Some(arg)
106-
}
102+
Some(arg)
107103
}
108104
Err(err) => {
109105
// only warn because before bad extra checks would be silently ignored.
@@ -114,6 +110,11 @@ fn check_impl(
114110
.collect(),
115111
None => vec![],
116112
};
113+
if lint_args.iter().any(|ck| ck.auto) {
114+
crate::files_modified_batch_filter(ci_info, &mut lint_args, |ck, path| {
115+
ck.is_non_auto_or_matches(path)
116+
});
117+
}
117118

118119
macro_rules! extra_check {
119120
($lang:ident, $kind:ident) => {
@@ -721,23 +722,26 @@ impl ExtraCheckArg {
721722
self.lang == lang && self.kind.map(|k| k == kind).unwrap_or(true)
722723
}
723724

724-
/// Returns `true` if this is an auto arg and the relevant files are not modified.
725-
fn is_inactive_auto(&self, ci_info: &CiInfo) -> bool {
725+
/// Returns `false` if this is an auto arg and the passed filename does not trigger the auto rule
726+
fn is_non_auto_or_matches(&self, filepath: &str) -> bool {
726727
if !self.auto {
727-
return false;
728+
return true;
728729
}
729730
let ext = match self.lang {
730731
ExtraCheckLang::Py => ".py",
731732
ExtraCheckLang::Cpp => ".cpp",
732733
ExtraCheckLang::Shell => ".sh",
733734
ExtraCheckLang::Js => ".js",
734735
ExtraCheckLang::Spellcheck => {
735-
return !crate::files_modified(ci_info, |s| {
736-
SPELLCHECK_DIRS.iter().any(|dir| Path::new(s).starts_with(dir))
737-
});
736+
for dir in SPELLCHECK_DIRS {
737+
if Path::new(filepath).starts_with(dir) {
738+
return true;
739+
}
740+
}
741+
return false;
738742
}
739743
};
740-
!crate::files_modified(ci_info, |s| s.ends_with(ext))
744+
filepath.ends_with(ext)
741745
}
742746

743747
fn has_supported_kind(&self) -> bool {

src/tools/tidy/src/lib.rs

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -125,40 +125,62 @@ pub fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<Stri
125125
Some(String::from_utf8_lossy(&output.stdout).into())
126126
}
127127

128-
/// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files.
129-
pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
128+
/// Similar to `files_modified`, but only involves a single call to `git`.
129+
///
130+
/// removes all elements from `items` that do not cause any match when `pred` is called with the list of modifed files.
131+
///
132+
/// if in CI, no elements will be removed.
133+
pub fn files_modified_batch_filter<T>(
134+
ci_info: &CiInfo,
135+
items: &mut Vec<T>,
136+
pred: impl Fn(&T, &str) -> bool,
137+
) {
130138
if CiEnv::is_ci() {
131139
// assume everything is modified on CI because we really don't want false positives there.
132-
return true;
140+
return;
133141
}
134142
let Some(base_commit) = &ci_info.base_commit else {
135143
eprintln!("No base commit, assuming all files are modified");
136-
return true;
144+
return;
137145
};
138146
match crate::git_diff(base_commit, "--name-status") {
139147
Some(output) => {
140-
let modified_files = output.lines().filter_map(|ln| {
141-
let (status, name) = ln
142-
.trim_end()
143-
.split_once('\t')
144-
.expect("bad format from `git diff --name-status`");
145-
if status == "M" { Some(name) } else { None }
146-
});
147-
for modified_file in modified_files {
148-
if pred(modified_file) {
149-
return true;
148+
let modified_files: Vec<_> = output
149+
.lines()
150+
.filter_map(|ln| {
151+
let (status, name) = ln
152+
.trim_end()
153+
.split_once('\t')
154+
.expect("bad format from `git diff --name-status`");
155+
if status == "M" { Some(name) } else { None }
156+
})
157+
.collect();
158+
items.retain(|item| {
159+
for modified_file in &modified_files {
160+
if pred(item, modified_file) {
161+
// at least one predicate matches, keep this item.
162+
return true;
163+
}
150164
}
151-
}
152-
false
165+
// no predicates matched, remove this item.
166+
false
167+
});
153168
}
154169
None => {
155170
eprintln!("warning: failed to run `git diff` to check for changes");
156171
eprintln!("warning: assuming all files are modified");
157-
true
172+
return;
158173
}
159174
}
160175
}
161176

177+
/// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files.
178+
pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
179+
let mut v = vec![()];
180+
files_modified_batch_filter(ci_info, &mut v, |_, p| pred(p));
181+
v.is_empty()
182+
}
183+
162184
pub mod alphabetical;
163185
pub mod bins;
164186
pub mod debug_artifacts;

0 commit comments

Comments
 (0)