//! Runs rustfmt on the repository.
use crate::builder::Builder;
-use crate::util::{output, t};
+use crate::util::{output, program_out_of_date, t};
use ignore::WalkBuilder;
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
}
}
+fn get_rustfmt_version(build: &Builder<'_>) -> Option<(String, PathBuf)> {
+ let stamp_file = build.out.join("rustfmt.stamp");
+
+ let mut cmd = Command::new(match build.initial_rustfmt() {
+ Some(p) => p,
+ None => return None,
+ });
+ cmd.arg("--version");
+ let output = match cmd.output() {
+ Ok(status) => status,
+ Err(_) => return None,
+ };
+ if !output.status.success() {
+ return None;
+ }
+ Some((String::from_utf8(output.stdout).unwrap(), stamp_file))
+}
+
+/// Return whether the format cache can be reused.
+fn verify_rustfmt_version(build: &Builder<'_>) -> bool {
+ let Some((version, stamp_file)) = get_rustfmt_version(build) else {return false;};
+ !program_out_of_date(&stamp_file, &version)
+}
+
+/// Updates the last rustfmt version used
+fn update_rustfmt_version(build: &Builder<'_>) {
+ let Some((version, stamp_file)) = get_rustfmt_version(build) else {return;};
+ t!(std::fs::write(stamp_file, version))
+}
+
+/// Returns the files modified between the `merge-base` of HEAD and
+/// rust-lang/master and what is now on the disk.
+///
+/// Returns `None` if all files should be formatted.
+fn get_modified_files(build: &Builder<'_>) -> Option<Vec<String>> {
+ let Ok(remote) = get_rust_lang_rust_remote() else {return None;};
+ if !verify_rustfmt_version(build) {
+ return None;
+ }
+ Some(
+ output(
+ build
+ .config
+ .git()
+ .arg("diff-index")
+ .arg("--name-only")
+ .arg("--merge-base")
+ .arg(&format!("{remote}/master")),
+ )
+ .lines()
+ .map(|s| s.trim().to_owned())
+ .collect(),
+ )
+}
+
/// Finds the remote for rust-lang/rust.
/// For example for these remotes it will return `upstream`.
/// ```text
ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path);
}
if !check && paths.is_empty() {
- let remote = t!(get_rust_lang_rust_remote());
- let base = output(
- build
- .config
- .git()
- .arg("merge-base")
- .arg("HEAD")
- .arg(format!("{remote}/master")),
- );
- let files =
- output(build.config.git().arg("diff").arg("--name-only").arg(base.trim()));
- for file in files.lines() {
- println!("formatting modified file {file}");
- ignore_fmt.add(&format!("/{file}")).expect(file);
+ if let Some(files) = get_modified_files(build) {
+ for file in files {
+ println!("formatting modified file {file}");
+ ignore_fmt.add(&format!("/{file}")).expect(&file);
+ }
}
}
} else {
drop(tx);
thread.join().unwrap();
+ if !check {
+ update_rustfmt_version(build);
+ }
}