Skip to content

Commit

Permalink
Rollup merge of #105702 - albertlarsan68:x-fmt-opt, r=jyn514
Browse files Browse the repository at this point in the history
Format only modified files

As discussed on #105688, this makes x fmt only format modified files.

Fixes #105688
  • Loading branch information
matthiaskrgr committed Dec 28, 2022
2 parents c52d58f + 0b942a8 commit 0630677
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/bootstrap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Several unsupported `./configure` options have been removed: `optimize`, `parallel-compiler`. These can still be enabled with `--set`, although it isn't recommended.
- `remote-test-server`'s `verbose` argument has been removed in favor of the `--verbose` flag
- `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.
- `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)

### Non-breaking changes

Expand Down
1 change: 1 addition & 0 deletions src/bootstrap/clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ fn clean_default(build: &Build, all: bool) {
rm_rf(&build.out.join("tmp"));
rm_rf(&build.out.join("dist"));
rm_rf(&build.out.join("bootstrap"));
rm_rf(&build.out.join("rustfmt.stamp"));

for host in &build.hosts {
let entries = match build.out.join(host.triple).read_dir() {
Expand Down
97 changes: 96 additions & 1 deletion src/bootstrap/format.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! 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};
Expand Down Expand Up @@ -44,6 +44,90 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F
}
}

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
/// origin https://github.com/Nilstrieb/rust.git (fetch)
/// origin https://github.com/Nilstrieb/rust.git (push)
/// upstream https://github.com/rust-lang/rust (fetch)
/// upstream https://github.com/rust-lang/rust (push)
/// ```
fn get_rust_lang_rust_remote() -> Result<String, String> {
let mut git = Command::new("git");
git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]);

let output = git.output().map_err(|err| format!("{err:?}"))?;
if !output.status.success() {
return Err("failed to execute git config command".to_owned());
}

let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;

let rust_lang_remote = stdout
.lines()
.find(|remote| remote.contains("rust-lang"))
.ok_or_else(|| "rust-lang/rust remote not found".to_owned())?;

let remote_name =
rust_lang_remote.split('.').nth(1).ok_or_else(|| "remote name not found".to_owned())?;
Ok(remote_name.into())
}

#[derive(serde::Deserialize)]
struct RustfmtConfig {
ignore: Vec<String>,
Expand Down Expand Up @@ -110,6 +194,14 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
// preventing the latter from being formatted.
ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path);
}
if !check && paths.is_empty() {
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 {
println!("Not in git tree. Skipping git-aware format checks");
}
Expand Down Expand Up @@ -187,4 +279,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
drop(tx);

thread.join().unwrap();
if !check {
update_rustfmt_version(build);
}
}

0 comments on commit 0630677

Please sign in to comment.