From 5f71b491ba3632ae413dde5cd130f1a353e7ee78 Mon Sep 17 00:00:00 2001 From: Armando Perez Date: Thu, 23 Jan 2025 17:03:44 -0600 Subject: [PATCH 1/2] Warn against overwriting directories if the destination path doesn't end with a directory separator --- helix-view/src/document.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 41c9ee1ef..2aaf18c2c 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -966,9 +966,14 @@ impl Document { } } - // Protect against overwriting changes made externally if !force { if let Ok(metadata) = fs::metadata(&path).await { + // Protect against overwriting entire directories + // if `path` doesn't end with a directory separator. + if !metadata.is_file() { + bail!("path does not point to a regular file, use :w! to overwrite that directory and its contents"); + } + // Protect against overwriting changes made externally if let Ok(mtime) = metadata.modified() { if last_saved_time < mtime { bail!("file modified by an external process, use :w! to overwrite"); From b0c2c567c6df2c8bedfd8b1bdd01d6fa80be99d6 Mon Sep 17 00:00:00 2001 From: Armando Perez Date: Sat, 25 Jan 2025 18:25:06 -0600 Subject: [PATCH 2/2] Add integration test about warn against overwriting directories --- helix-term/tests/test/commands/write.rs | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/helix-term/tests/test/commands/write.rs b/helix-term/tests/test/commands/write.rs index 4b78e14c4..2f2437637 100644 --- a/helix-term/tests/test/commands/write.rs +++ b/helix-term/tests/test/commands/write.rs @@ -130,7 +130,7 @@ async fn test_write() -> anyhow::Result<()> { } #[tokio::test(flavor = "multi_thread")] -async fn test_overwrite_protection() -> anyhow::Result<()> { +async fn test_file_overwrite_protection() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?; let mut app = helpers::AppBuilder::new() .with_file(file.path(), None) @@ -155,6 +155,42 @@ async fn test_overwrite_protection() -> anyhow::Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn test_directory_overwrite_protection() -> anyhow::Result<()> { + let directory = tempfile::TempDir::new()?; + assert!(!directory + .path() + .to_string_lossy() + .ends_with(std::path::MAIN_SEPARATOR_STR)); + + let mut file = tempfile::NamedTempFile::new_in(directory.path())?; + file.as_file_mut() + .write_all("extremely important content".as_bytes())?; + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut app = helpers::AppBuilder::new() + .with_input_text("some text#[|]#") + .build()?; + + helpers::run_event_loop_until_idle(&mut app).await; + + test_key_sequence( + &mut app, + Some(&format!(":w {}", directory.path().to_string_lossy())), + None, + false, + ) + .await?; + + // reload_file(&mut file).unwrap(); + // let mut file_content = String::new(); + let file_content = std::fs::read_to_string(file.path())?; + assert_eq!("extremely important content", file_content); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn test_write_quit() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?;