Skip to content

Commit c5196f3

Browse files
committed
feat: filter out files from each repo's rustfmt.toml ignore list
Implement skipping logic just like rustfmt does. rustfmt would ignore these files anyway so we can save some time and effort if we avoid spawning a subprocess for files that we know rustfmt is going to ignore anyway. This saves a significant amount of time, especially for repositories like r-l/rust which ignore a lot of files.
1 parent d19650a commit c5196f3

4 files changed

Lines changed: 250 additions & 19 deletions

File tree

check_diff/Cargo.lock

Lines changed: 167 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

check_diff/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ tempfile = "3"
1313
walkdir = "2.5.0"
1414
diffy = "0.4.0"
1515
crossbeam-channel = "0.5.15"
16+
ignore = "0.4.25"
17+
toml = "0.9.11"

check_diff/src/lib.rs

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -670,18 +670,79 @@ pub fn compile_rustfmt<T: AsRef<str>>(
670670
})
671671
}
672672

673-
/// Searches for rust files in the particular path and returns an iterator to them.
674-
pub fn search_for_rs_files(repo: &Path) -> impl Iterator<Item = PathBuf> {
675-
WalkDir::new(repo).into_iter().filter_map(|e| match e.ok() {
676-
Some(entry) => {
677-
let path = entry.path();
678-
if path.is_file() && path.extension().is_some_and(|ext| ext == "rs") {
679-
return Some(entry.into_path());
673+
fn read_rustfmt_ignore_list(rustfmt_toml_path: &Path) -> Vec<String> {
674+
let Ok(file_content) = std::fs::read_to_string(rustfmt_toml_path) else {
675+
return Vec::new();
676+
};
677+
678+
let Ok(mut data) = file_content.parse::<toml::Table>() else {
679+
return Vec::new();
680+
};
681+
682+
let Some(toml::Value::Array(ignore_list)) = data.remove("ignore") else {
683+
return Vec::new();
684+
};
685+
686+
ignore_list
687+
.into_iter()
688+
.map(toml::Value::try_into)
689+
.collect::<Result<_, _>>()
690+
.unwrap_or_default()
691+
}
692+
693+
// Iterator over all rust files in a directory.
694+
//
695+
// Ignores files list in the root `.rustfmt.toml` or `rustfmt.toml` configuration files.
696+
pub struct RustFmtFileFinder<'a, P> {
697+
ignore_set: ignore::gitignore::Gitignore,
698+
repo: &'a Repository<P>,
699+
}
700+
701+
impl<'a, P> RustFmtFileFinder<'a, P>
702+
where
703+
P: AsRef<Path>,
704+
{
705+
pub fn from_repository(repo: &'a Repository<P>) -> Self {
706+
let root = repo.path();
707+
let mut ignore_builder = ignore::gitignore::GitignoreBuilder::new(root);
708+
709+
let repo_name = repo.name();
710+
for rustfmt_config_file in [".rustfmt.toml", "rustfmt.toml"] {
711+
let rustfmt_toml_path = root.join(rustfmt_config_file);
712+
for ignore_path in read_rustfmt_ignore_list(&rustfmt_toml_path) {
713+
debug!("Adding {ignore_path} to the set of ignored files for '{repo_name}'");
714+
let _ = ignore_builder.add_line(None, &ignore_path);
680715
}
681-
None
682716
}
683-
None => None,
684-
})
717+
718+
Self {
719+
repo,
720+
ignore_set: ignore_builder
721+
.build()
722+
.unwrap_or(ignore::gitignore::Gitignore::empty()),
723+
}
724+
}
725+
726+
pub fn iter(&self) -> impl Iterator<Item = PathBuf> + use<'_, P> {
727+
WalkDir::new(self.repo.path())
728+
.into_iter()
729+
.filter_map(|e| match e.ok() {
730+
Some(entry) => {
731+
let path = entry.path();
732+
if path.is_file()
733+
&& path.extension().is_some_and(|ext| ext == "rs")
734+
&& !self
735+
.ignore_set
736+
.matched_path_or_any_parents(path, false)
737+
.is_ignore()
738+
{
739+
return Some(entry.into_path());
740+
}
741+
None
742+
}
743+
None => None,
744+
})
745+
}
685746
}
686747

687748
/// Encapsulate the logic used to clone repositories for the diff check
@@ -817,7 +878,8 @@ where
817878
for repo in repositories.iter() {
818879
let tx = tx.clone();
819880
s.spawn(move || {
820-
for file in search_for_rs_files(repo.path()) {
881+
let file_finder = RustFmtFileFinder::from_repository(repo);
882+
for file in file_finder.iter() {
821883
let _ = tx.send((file, repo));
822884
}
823885
});

check_diff/tests/check_diff.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use check_diff::{
2-
CheckDiffError, CheckDiffRunners, CodeFormatter, FormatCodeError, Repository, check_diff,
3-
search_for_rs_files,
2+
CheckDiffError, CheckDiffRunners, CodeFormatter, FormatCodeError, Repository,
3+
RustFmtFileFinder, check_diff,
44
};
55
use std::fs::File;
66
use tempfile::Builder;
@@ -29,10 +29,11 @@ fn search_for_files_correctly_non_nested() -> Result<(), Box<dyn std::error::Err
2929
let file_path = dir.path().join("test.rs");
3030
let _tmp_file = File::create(file_path)?;
3131

32-
let iter = search_for_rs_files(dir.path());
32+
let repo = Repository::new("", dir.path());
33+
let file_finder = RustFmtFileFinder::from_repository(&repo);
3334

3435
let mut count = 0;
35-
for _ in iter {
36+
for _ in file_finder.iter() {
3637
count += 1;
3738
}
3839

@@ -51,10 +52,11 @@ fn search_for_files_correctly_nested() -> Result<(), Box<dyn std::error::Error>>
5152
let nested_file_path = nested_dir.path().join("nested.rs");
5253
let _ = File::create(nested_file_path)?;
5354

54-
let iter = search_for_rs_files(dir.path());
55+
let repo = Repository::new("", dir.path());
56+
let file_finder = RustFmtFileFinder::from_repository(&repo);
5557

5658
let mut count = 0;
57-
for _ in iter {
59+
for _ in file_finder.iter() {
5860
count += 1;
5961
}
6062

0 commit comments

Comments
 (0)