Skip to content

Commit 0630677

Browse files
authored
Rollup merge of #105702 - albertlarsan68:x-fmt-opt, r=jyn514
Format only modified files As discussed on #105688, this makes x fmt only format modified files. Fixes #105688
2 parents c52d58f + 0b942a8 commit 0630677

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

src/bootstrap/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1515
- Several unsupported `./configure` options have been removed: `optimize`, `parallel-compiler`. These can still be enabled with `--set`, although it isn't recommended.
1616
- `remote-test-server`'s `verbose` argument has been removed in favor of the `--verbose` flag
1717
- `remote-test-server`'s `remote` argument has been removed in favor of the `--bind` flag. Use `--bind 0.0.0.0:12345` to replicate the behavior of the `remote` argument.
18+
- `x.py fmt` now formats only files modified between the merge-base of HEAD and the last commit in the master branch of the rust-lang repository and the current working directory. To restore old behaviour, use `x.py fmt .`. The check mode is not affected by this change. [#105702](https://github.com./rust-lang/rust/pull/105702)
1819

1920
### Non-breaking changes
2021

src/bootstrap/clean.rs

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ fn clean_default(build: &Build, all: bool) {
9494
rm_rf(&build.out.join("tmp"));
9595
rm_rf(&build.out.join("dist"));
9696
rm_rf(&build.out.join("bootstrap"));
97+
rm_rf(&build.out.join("rustfmt.stamp"));
9798

9899
for host in &build.hosts {
99100
let entries = match build.out.join(host.triple).read_dir() {

src/bootstrap/format.rs

+96-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Runs rustfmt on the repository.
22
33
use crate::builder::Builder;
4-
use crate::util::{output, t};
4+
use crate::util::{output, program_out_of_date, t};
55
use ignore::WalkBuilder;
66
use std::collections::VecDeque;
77
use std::path::{Path, PathBuf};
@@ -44,6 +44,90 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F
4444
}
4545
}
4646

47+
fn get_rustfmt_version(build: &Builder<'_>) -> Option<(String, PathBuf)> {
48+
let stamp_file = build.out.join("rustfmt.stamp");
49+
50+
let mut cmd = Command::new(match build.initial_rustfmt() {
51+
Some(p) => p,
52+
None => return None,
53+
});
54+
cmd.arg("--version");
55+
let output = match cmd.output() {
56+
Ok(status) => status,
57+
Err(_) => return None,
58+
};
59+
if !output.status.success() {
60+
return None;
61+
}
62+
Some((String::from_utf8(output.stdout).unwrap(), stamp_file))
63+
}
64+
65+
/// Return whether the format cache can be reused.
66+
fn verify_rustfmt_version(build: &Builder<'_>) -> bool {
67+
let Some((version, stamp_file)) = get_rustfmt_version(build) else {return false;};
68+
!program_out_of_date(&stamp_file, &version)
69+
}
70+
71+
/// Updates the last rustfmt version used
72+
fn update_rustfmt_version(build: &Builder<'_>) {
73+
let Some((version, stamp_file)) = get_rustfmt_version(build) else {return;};
74+
t!(std::fs::write(stamp_file, version))
75+
}
76+
77+
/// Returns the files modified between the `merge-base` of HEAD and
78+
/// rust-lang/master and what is now on the disk.
79+
///
80+
/// Returns `None` if all files should be formatted.
81+
fn get_modified_files(build: &Builder<'_>) -> Option<Vec<String>> {
82+
let Ok(remote) = get_rust_lang_rust_remote() else {return None;};
83+
if !verify_rustfmt_version(build) {
84+
return None;
85+
}
86+
Some(
87+
output(
88+
build
89+
.config
90+
.git()
91+
.arg("diff-index")
92+
.arg("--name-only")
93+
.arg("--merge-base")
94+
.arg(&format!("{remote}/master")),
95+
)
96+
.lines()
97+
.map(|s| s.trim().to_owned())
98+
.collect(),
99+
)
100+
}
101+
102+
/// Finds the remote for rust-lang/rust.
103+
/// For example for these remotes it will return `upstream`.
104+
/// ```text
105+
/// origin https://github.com./Nilstrieb/rust.git (fetch)
106+
/// origin https://github.com./Nilstrieb/rust.git (push)
107+
/// upstream https://github.com./rust-lang/rust (fetch)
108+
/// upstream https://github.com./rust-lang/rust (push)
109+
/// ```
110+
fn get_rust_lang_rust_remote() -> Result<String, String> {
111+
let mut git = Command::new("git");
112+
git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]);
113+
114+
let output = git.output().map_err(|err| format!("{err:?}"))?;
115+
if !output.status.success() {
116+
return Err("failed to execute git config command".to_owned());
117+
}
118+
119+
let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;
120+
121+
let rust_lang_remote = stdout
122+
.lines()
123+
.find(|remote| remote.contains("rust-lang"))
124+
.ok_or_else(|| "rust-lang/rust remote not found".to_owned())?;
125+
126+
let remote_name =
127+
rust_lang_remote.split('.').nth(1).ok_or_else(|| "remote name not found".to_owned())?;
128+
Ok(remote_name.into())
129+
}
130+
47131
#[derive(serde::Deserialize)]
48132
struct RustfmtConfig {
49133
ignore: Vec<String>,
@@ -110,6 +194,14 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
110194
// preventing the latter from being formatted.
111195
ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path);
112196
}
197+
if !check && paths.is_empty() {
198+
if let Some(files) = get_modified_files(build) {
199+
for file in files {
200+
println!("formatting modified file {file}");
201+
ignore_fmt.add(&format!("/{file}")).expect(&file);
202+
}
203+
}
204+
}
113205
} else {
114206
println!("Not in git tree. Skipping git-aware format checks");
115207
}
@@ -187,4 +279,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
187279
drop(tx);
188280

189281
thread.join().unwrap();
282+
if !check {
283+
update_rustfmt_version(build);
284+
}
190285
}

0 commit comments

Comments
 (0)