diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 628c216ccd..35ffad9511 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -1337,7 +1337,10 @@ impl FakeFs { pub fn paths(&self, include_dot_git: bool) -> Vec { let mut result = Vec::new(); let mut queue = collections::VecDeque::new(); - queue.push_back((PathBuf::from("/"), self.state.lock().root.clone())); + queue.push_back(( + PathBuf::from(util::path!("/")), + self.state.lock().root.clone(), + )); while let Some((path, entry)) = queue.pop_front() { if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() { for (name, entry) in entries { @@ -1358,7 +1361,10 @@ impl FakeFs { pub fn directories(&self, include_dot_git: bool) -> Vec { let mut result = Vec::new(); let mut queue = collections::VecDeque::new(); - queue.push_back((PathBuf::from("/"), self.state.lock().root.clone())); + queue.push_back(( + PathBuf::from(util::path!("/")), + self.state.lock().root.clone(), + )); while let Some((path, entry)) = queue.pop_front() { if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() { for (name, entry) in entries { @@ -2020,7 +2026,11 @@ pub async fn copy_recursive<'a>( let Ok(item_relative_path) = item.strip_prefix(source) else { continue; }; - let target_item = target.join(item_relative_path); + let target_item = if item_relative_path == Path::new("") { + target.to_path_buf() + } else { + target.join(item_relative_path) + }; if is_dir { if !options.overwrite && fs.metadata(&target_item).await.is_ok_and(|m| m.is_some()) { if options.ignore_if_exists { @@ -2174,6 +2184,142 @@ mod tests { ); } + #[gpui::test] + async fn test_copy_recursive_with_single_file(executor: BackgroundExecutor) { + let fs = FakeFs::new(executor.clone()); + fs.insert_tree( + path!("/outer"), + json!({ + "a": "A", + "b": "B", + "inner": {} + }), + ) + .await; + + assert_eq!( + fs.files(), + vec![ + PathBuf::from(path!("/outer/a")), + PathBuf::from(path!("/outer/b")), + ] + ); + + let source = Path::new(path!("/outer/a")); + let target = Path::new(path!("/outer/a copy")); + copy_recursive(fs.as_ref(), source, target, Default::default()) + .await + .unwrap(); + + assert_eq!( + fs.files(), + vec![ + PathBuf::from(path!("/outer/a")), + PathBuf::from(path!("/outer/a copy")), + PathBuf::from(path!("/outer/b")), + ] + ); + + let source = Path::new(path!("/outer/a")); + let target = Path::new(path!("/outer/inner/a copy")); + copy_recursive(fs.as_ref(), source, target, Default::default()) + .await + .unwrap(); + + assert_eq!( + fs.files(), + vec![ + PathBuf::from(path!("/outer/a")), + PathBuf::from(path!("/outer/a copy")), + PathBuf::from(path!("/outer/b")), + PathBuf::from(path!("/outer/inner/a copy")), + ] + ); + } + + #[gpui::test] + async fn test_copy_recursive_with_single_dir(executor: BackgroundExecutor) { + let fs = FakeFs::new(executor.clone()); + fs.insert_tree( + path!("/outer"), + json!({ + "a": "A", + "empty": {}, + "non-empty": { + "b": "B", + } + }), + ) + .await; + + assert_eq!( + fs.files(), + vec![ + PathBuf::from(path!("/outer/a")), + PathBuf::from(path!("/outer/non-empty/b")), + ] + ); + assert_eq!( + fs.directories(false), + vec![ + PathBuf::from(path!("/")), + PathBuf::from(path!("/outer")), + PathBuf::from(path!("/outer/empty")), + PathBuf::from(path!("/outer/non-empty")), + ] + ); + + let source = Path::new(path!("/outer/empty")); + let target = Path::new(path!("/outer/empty copy")); + copy_recursive(fs.as_ref(), source, target, Default::default()) + .await + .unwrap(); + + assert_eq!( + fs.files(), + vec![ + PathBuf::from(path!("/outer/a")), + PathBuf::from(path!("/outer/non-empty/b")), + ] + ); + assert_eq!( + fs.directories(false), + vec![ + PathBuf::from(path!("/")), + PathBuf::from(path!("/outer")), + PathBuf::from(path!("/outer/empty")), + PathBuf::from(path!("/outer/empty copy")), + PathBuf::from(path!("/outer/non-empty")), + ] + ); + + let source = Path::new(path!("/outer/non-empty")); + let target = Path::new(path!("/outer/non-empty copy")); + copy_recursive(fs.as_ref(), source, target, Default::default()) + .await + .unwrap(); + + assert_eq!( + fs.files(), + vec![ + PathBuf::from(path!("/outer/a")), + PathBuf::from(path!("/outer/non-empty/b")), + PathBuf::from(path!("/outer/non-empty copy/b")), + ] + ); + assert_eq!( + fs.directories(false), + vec![ + PathBuf::from(path!("/")), + PathBuf::from(path!("/outer")), + PathBuf::from(path!("/outer/empty")), + PathBuf::from(path!("/outer/empty copy")), + PathBuf::from(path!("/outer/non-empty")), + PathBuf::from(path!("/outer/non-empty copy")), + ] + ); + } + #[gpui::test] async fn test_copy_recursive(executor: BackgroundExecutor) { let fs = FakeFs::new(executor.clone()); @@ -2185,7 +2331,8 @@ mod tests { "b": "B", "inner3": { "d": "D", - } + }, + "inner4": {} }, "inner2": { "c": "C", @@ -2203,6 +2350,17 @@ mod tests { PathBuf::from(path!("/outer/inner1/inner3/d")), ] ); + assert_eq!( + fs.directories(false), + vec![ + PathBuf::from(path!("/")), + PathBuf::from(path!("/outer")), + PathBuf::from(path!("/outer/inner1")), + PathBuf::from(path!("/outer/inner2")), + PathBuf::from(path!("/outer/inner1/inner3")), + PathBuf::from(path!("/outer/inner1/inner4")), + ] + ); let source = Path::new(path!("/outer")); let target = Path::new(path!("/outer/inner1/outer")); @@ -2223,6 +2381,22 @@ mod tests { PathBuf::from(path!("/outer/inner1/outer/inner1/inner3/d")), ] ); + assert_eq!( + fs.directories(false), + vec![ + PathBuf::from(path!("/")), + PathBuf::from(path!("/outer")), + PathBuf::from(path!("/outer/inner1")), + PathBuf::from(path!("/outer/inner2")), + PathBuf::from(path!("/outer/inner1/inner3")), + PathBuf::from(path!("/outer/inner1/inner4")), + PathBuf::from(path!("/outer/inner1/outer")), + PathBuf::from(path!("/outer/inner1/outer/inner1")), + PathBuf::from(path!("/outer/inner1/outer/inner2")), + PathBuf::from(path!("/outer/inner1/outer/inner1/inner3")), + PathBuf::from(path!("/outer/inner1/outer/inner1/inner4")), + ] + ); } #[gpui::test] diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 150fc9fa56..c6e51f930a 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4958,14 +4958,14 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) { assert_eq!( fs.paths(true), vec![ - PathBuf::from("/"), - PathBuf::from("/one"), - PathBuf::from("/one/two"), - PathBuf::from("/one/two/c.rs"), - PathBuf::from("/one/two/three"), - PathBuf::from("/one/two/three/a.txt"), - PathBuf::from("/one/two/three/b.."), - PathBuf::from("/one/two/three/four"), + PathBuf::from(path!("/")), + PathBuf::from(path!("/one")), + PathBuf::from(path!("/one/two")), + PathBuf::from(path!("/one/two/c.rs")), + PathBuf::from(path!("/one/two/three")), + PathBuf::from(path!("/one/two/three/a.txt")), + PathBuf::from(path!("/one/two/three/b..")), + PathBuf::from(path!("/one/two/three/four")), ] ); diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 405c1b752b..cd2c2e051d 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -26,7 +26,7 @@ use std::{ sync::Arc, time::Duration, }; -use util::{test::TempTree, ResultExt}; +use util::{path, test::TempTree, ResultExt}; #[gpui::test] async fn test_traversal(cx: &mut TestAppContext) { @@ -1650,7 +1650,7 @@ async fn test_random_worktree_operations_during_initial_scan( .map(|o| o.parse().unwrap()) .unwrap_or(20); - let root_dir = Path::new("/test"); + let root_dir = Path::new(path!("/test")); let fs = FakeFs::new(cx.background_executor.clone()) as Arc; fs.as_fake().insert_tree(root_dir, json!({})).await; for _ in 0..initial_entries { @@ -1741,7 +1741,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) .map(|o| o.parse().unwrap()) .unwrap_or(20); - let root_dir = Path::new("/test"); + let root_dir = Path::new(path!("/test")); let fs = FakeFs::new(cx.background_executor.clone()) as Arc; fs.as_fake().insert_tree(root_dir, json!({})).await; for _ in 0..initial_entries {