diff --git a/.changeset/fix-changed-deleted-file.md b/.changeset/fix-changed-deleted-file.md new file mode 100644 index 000000000000..5b34024bb8c9 --- /dev/null +++ b/.changeset/fix-changed-deleted-file.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fix `--changed` and `--staged` flags throwing "No such file or directory" error when a file has been deleted or renamed in the working directory. The CLI now filters out files that no longer exist before processing. diff --git a/crates/biome_cli/src/changed.rs b/crates/biome_cli/src/changed.rs index b850ebb54de3..f6085cd9ab3a 100644 --- a/crates/biome_cli/src/changed.rs +++ b/crates/biome_cli/src/changed.rs @@ -1,6 +1,7 @@ use crate::CliDiagnostic; use biome_configuration::Configuration; use biome_fs::FileSystem; +use camino::Utf8Path; use std::ffi::OsString; pub(crate) fn get_changed_files( @@ -26,7 +27,12 @@ pub(crate) fn get_changed_files( let changed_files = fs.get_changed_files(base)?; - let filtered_changed_files = changed_files.iter().map(OsString::from).collect::>(); + // Filter out files that no longer exist (e.g., deleted or renamed in the working directory) + let filtered_changed_files = changed_files + .iter() + .filter(|file| fs.path_is_file(Utf8Path::new(file))) + .map(OsString::from) + .collect::>(); Ok(filtered_changed_files) } @@ -34,7 +40,12 @@ pub(crate) fn get_changed_files( pub(crate) fn get_staged_files(fs: &dyn FileSystem) -> Result, CliDiagnostic> { let staged_files = fs.get_staged_files()?; - let filtered_staged_files = staged_files.iter().map(OsString::from).collect::>(); + // Filter out files that no longer exist (e.g., deleted or renamed in the working directory) + let filtered_staged_files = staged_files + .iter() + .filter(|file| fs.path_is_file(Utf8Path::new(file))) + .map(OsString::from) + .collect::>(); Ok(filtered_staged_files) } diff --git a/crates/biome_cli/tests/commands/lint.rs b/crates/biome_cli/tests/commands/lint.rs index a9a734160715..cd7205495063 100644 --- a/crates/biome_cli/tests/commands/lint.rs +++ b/crates/biome_cli/tests/commands/lint.rs @@ -3665,6 +3665,76 @@ fn should_error_if_unchanged_files_only_with_changed_flag() { )); } +#[test] +fn should_skip_nonexistent_changed_files() { + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + + // Git reports "deleted.js" as changed, but it doesn't exist in the working directory + fs.set_on_get_changed_files(Box::new(|| { + vec![String::from("exists.js"), String::from("deleted.js")] + })); + + // Only "exists.js" exists in the file system + fs.insert( + Utf8Path::new("exists.js").into(), + r#"console.log('exists');"#.as_bytes(), + ); + // Note: "deleted.js" is NOT inserted - simulating a deleted file + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--changed", "--since=main"].as_slice()), + ); + + // Should succeed without "No such file or directory" error + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "should_skip_nonexistent_changed_files", + fs, + console, + result, + )); +} + +#[test] +fn should_skip_nonexistent_staged_files() { + let mut console = BufferConsole::default(); + let mut fs = MemoryFileSystem::default(); + + // Git reports "deleted.js" as staged, but it doesn't exist in the working directory + fs.set_on_get_staged_files(Box::new(|| { + vec![String::from("exists.js"), String::from("deleted.js")] + })); + + // Only "exists.js" exists in the file system + fs.insert( + Utf8Path::new("exists.js").into(), + r#"console.log('exists');"#.as_bytes(), + ); + // Note: "deleted.js" is NOT inserted - simulating a deleted file + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--staged"].as_slice()), + ); + + // Should succeed without "No such file or directory" error + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "should_skip_nonexistent_staged_files", + fs, + console, + result, + )); +} + #[test] fn linter_shows_the_default_severity_of_rule_on() { let mut console = BufferConsole::default(); diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/should_skip_nonexistent_changed_files.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/should_skip_nonexistent_changed_files.snap new file mode 100644 index 000000000000..68bcd4926ac9 --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_commands_lint/should_skip_nonexistent_changed_files.snap @@ -0,0 +1,15 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: redactor(content) +--- +## `exists.js` + +```js +console.log('exists'); +``` + +# Emitted Messages + +```block +Checked 1 file in