use crate::{ worktree_settings::WorktreeSettings, Entry, EntryKind, Event, PathChange, Snapshot, Worktree, WorktreeModelHandle,};use anyhow::Result;use client::Client;use clock::FakeSystemClock;use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};use git::GITIGNORE;use gpui::{ModelContext, Task, TestAppContext};use parking_lot::Mutex;use postage::stream::Stream;use pretty_assertions::assert_eq;use rand::prelude::*;use serde_json::json;use settings::{Settings, SettingsStore};use std::{ env, fmt::Write, mem, path::{Path, PathBuf}, sync::Arc,};use text::BufferId;use util::{http::FakeHttpClient, test::temp_tree, ResultExt}; #[gpui::test]async fn test_traversal(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ ".gitignore": "a/b\n", "a": { "b": "", "c": "", } }), ) .await; let tree = Worktree::local( build_client(cx), Path::new("/root"), true, fs, Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(false) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new(".gitignore"), Path::new("a"), Path::new("a/c"), ] ); assert_eq!( tree.entries(true) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new(".gitignore"), Path::new("a"), Path::new("a/b"), Path::new("a/c"), ] ); })} #[gpui::test]async fn test_descendent_entries(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ "a": "", "b": { "c": { "d": "" }, "e": {} }, "f": "", "g": { "h": {} }, "i": { "j": { "k": "" }, "l": { } }, ".gitignore": "i/j\n", }), ) .await; let tree = Worktree::local( build_client(cx), Path::new("/root"), true, fs, Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.descendent_entries(false, false, Path::new("b")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("b/c/d"),] ); assert_eq!( tree.descendent_entries(true, false, Path::new("b")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new("b"), Path::new("b/c"), Path::new("b/c/d"), Path::new("b/e"), ] ); assert_eq!( tree.descendent_entries(false, false, Path::new("g")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), Vec::<PathBuf>::new() ); assert_eq!( tree.descendent_entries(true, false, Path::new("g")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("g"), Path::new("g/h"),] ); }); // Expand gitignored directory. tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() .refresh_entries_for_paths(vec![Path::new("i/j").into()]) }) .recv() .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.descendent_entries(false, false, Path::new("i")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), Vec::<PathBuf>::new() ); assert_eq!( tree.descendent_entries(false, true, Path::new("i")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("i/j/k")] ); assert_eq!( tree.descendent_entries(true, false, Path::new("i")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("i"), Path::new("i/l"),] ); })} #[gpui::test(iterations = 10)]async fn test_circular_symlinks(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ "lib": { "a": { "a.txt": "" }, "b": { "b.txt": "" } } }), ) .await; fs.create_symlink("/root/lib/a/lib".as_ref(), "..".into()) .await .unwrap(); fs.create_symlink("/root/lib/b/lib".as_ref(), "..".into()) .await .unwrap(); let tree = Worktree::local( build_client(cx), Path::new("/root"), true, fs.clone(), Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(false) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new("lib"), Path::new("lib/a"), Path::new("lib/a/a.txt"), Path::new("lib/a/lib"), Path::new("lib/b"), Path::new("lib/b/b.txt"), Path::new("lib/b/lib"), ] ); }); fs.rename( Path::new("/root/lib/a/lib"), Path::new("/root/lib/a/lib-2"), Default::default(), ) .await .unwrap(); cx.executor().run_until_parked(); tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(false) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new("lib"), Path::new("lib/a"), Path::new("lib/a/a.txt"), Path::new("lib/a/lib-2"), Path::new("lib/b"), Path::new("lib/b/b.txt"), Path::new("lib/b/lib"), ] ); });} #[gpui::test]async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ "dir1": { "deps": { // symlinks here }, "src": { "a.rs": "", "b.rs": "", }, }, "dir2": { "src": { "c.rs": "", "d.rs": "", } }, "dir3": { "deps": {}, "src": { "e.rs": "", "f.rs": "", }, } }), ) .await; // These symlinks point to directories outside of the worktree's root, dir1. fs.create_symlink("/root/dir1/deps/dep-dir2".as_ref(), "../../dir2".into()) .await .unwrap(); fs.create_symlink("/root/dir1/deps/dep-dir3".as_ref(), "../../dir3".into()) .await .unwrap(); let tree = Worktree::local( build_client(cx), Path::new("/root/dir1"), true, fs.clone(), Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; let tree_updates = Arc::new(Mutex::new(Vec::new())); tree.update(cx, |_, cx| { let tree_updates = tree_updates.clone(); cx.subscribe(&tree, move |_, _, event, _| { if let Event::UpdatedEntries(update) = event { tree_updates.lock().extend( update .iter() .map(|(path, _, change)| (path.clone(), *change)), ); } }) .detach(); }); // The symlinked directories are not scanned by default. tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::<Vec<_>>(), vec![ (Path::new(""), false), (Path::new("deps"), false), (Path::new("deps/dep-dir2"), true), (Path::new("deps/dep-dir3"), true), (Path::new("src"), false), (Path::new("src/a.rs"), false), (Path::new("src/b.rs"), false), ] ); assert_eq!( tree.entry_for_path("deps/dep-dir2").unwrap().kind, EntryKind::UnloadedDir ); }); // Expand one of the symlinked directories. tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()]) }) .recv() .await; // The expanded directory's contents are loaded. Subdirectories are // not scanned yet. tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::<Vec<_>>(), vec![ (Path::new(""), false), (Path::new("deps"), false), (Path::new("deps/dep-dir2"), true), (Path::new("deps/dep-dir3"), true), (Path::new("deps/dep-dir3/deps"), true), (Path::new("deps/dep-dir3/src"), true), (Path::new("src"), false), (Path::new("src/a.rs"), false), (Path::new("src/b.rs"), false), ] ); }); assert_eq!( mem::take(&mut *tree_updates.lock()), &[ (Path::new("deps/dep-dir3").into(), PathChange::Loaded), (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded), (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded) ] ); // Expand a subdirectory of one of the symlinked directories. tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()]) }) .recv() .await; // The expanded subdirectory's contents are loaded. tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::<Vec<_>>(), vec![ (Path::new(""), false), (Path::new("deps"), false), (Path::new("deps/dep-dir2"), true), (Path::new("deps/dep-dir3"), true), (Path::new("deps/dep-dir3/deps"), true), (Path::new("deps/dep-dir3/src"), true), (Path::new("deps/dep-dir3/src/e.rs"), true), (Path::new("deps/dep-dir3/src/f.rs"), true), (Path::new("src"), false), (Path::new("src/a.rs"), false), (Path::new("src/b.rs"), false), ] ); }); assert_eq!( mem::take(&mut *tree_updates.lock()), &[ (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded), ( Path::new("deps/dep-dir3/src/e.rs").into(), PathChange::Loaded ), ( Path::new("deps/dep-dir3/src/f.rs").into(), PathChange::Loaded ) ] );} #[cfg(target_os = "macos")]#[gpui::test]async fn test_renaming_case_only(cx: &mut TestAppContext) { cx.executor().allow_parking(); init_test(cx); const OLD_NAME: &str = "aaa.rs"; const NEW_NAME: &str = "AAA.rs"; let fs = Arc::new(RealFs); let temp_root = temp_tree(json!({ OLD_NAME: "", })); let tree = Worktree::local( build_client(cx), temp_root.path(), true, fs.clone(), Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new(""), Path::new(OLD_NAME)] ); }); fs.rename( &temp_root.path().join(OLD_NAME), &temp_root.path().join(NEW_NAME), fs::RenameOptions { overwrite: true, ignore_if_exists: true, }, ) .await .unwrap(); tree.flush_fs_events(cx).await;Lines 1–500 of 2582. Load more
use crate::{ worktree_settings::WorktreeSettings, Entry, EntryKind, Event, PathChange, Snapshot, Worktree, WorktreeModelHandle, }; use anyhow::Result; use client::Client; use clock::FakeSystemClock; use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions}; use git::GITIGNORE; use gpui::{ModelContext, Task, TestAppContext}; use parking_lot::Mutex; use postage::stream::Stream; use pretty_assertions::assert_eq; use rand::prelude::*; use serde_json::json; use settings::{Settings, SettingsStore}; use std::{ env, fmt::Write, mem, path::{Path, PathBuf}, sync::Arc, }; use text::BufferId; use util::{http::FakeHttpClient, test::temp_tree, ResultExt}; #[gpui::test] async fn test_traversal(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ ".gitignore": "a/b\n", "a": { "b": "", "c": "", } }), ) .await; let tree = Worktree::local( build_client(cx), Path::new("/root"), true, fs, Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(false) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new(".gitignore"), Path::new("a"), Path::new("a/c"), ] ); assert_eq!( tree.entries(true) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new(".gitignore"), Path::new("a"), Path::new("a/b"), Path::new("a/c"), ] ); }) } #[gpui::test] async fn test_descendent_entries(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ "a": "", "b": { "c": { "d": "" }, "e": {} }, "f": "", "g": { "h": {} }, "i": { "j": { "k": "" }, "l": { } }, ".gitignore": "i/j\n", }), ) .await; let tree = Worktree::local( build_client(cx), Path::new("/root"), true, fs, Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.descendent_entries(false, false, Path::new("b")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("b/c/d"),] ); assert_eq!( tree.descendent_entries(true, false, Path::new("b")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new("b"), Path::new("b/c"), Path::new("b/c/d"), Path::new("b/e"), ] ); assert_eq!( tree.descendent_entries(false, false, Path::new("g")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), Vec::<PathBuf>::new() ); assert_eq!( tree.descendent_entries(true, false, Path::new("g")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("g"), Path::new("g/h"),] ); }); // Expand gitignored directory. tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() .refresh_entries_for_paths(vec![Path::new("i/j").into()]) }) .recv() .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.descendent_entries(false, false, Path::new("i")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), Vec::<PathBuf>::new() ); assert_eq!( tree.descendent_entries(false, true, Path::new("i")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("i/j/k")] ); assert_eq!( tree.descendent_entries(true, false, Path::new("i")) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new("i"), Path::new("i/l"),] ); }) } #[gpui::test(iterations = 10)] async fn test_circular_symlinks(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ "lib": { "a": { "a.txt": "" }, "b": { "b.txt": "" } } }), ) .await; fs.create_symlink("/root/lib/a/lib".as_ref(), "..".into()) .await .unwrap(); fs.create_symlink("/root/lib/b/lib".as_ref(), "..".into()) .await .unwrap(); let tree = Worktree::local( build_client(cx), Path::new("/root"), true, fs.clone(), Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(false) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new("lib"), Path::new("lib/a"), Path::new("lib/a/a.txt"), Path::new("lib/a/lib"), Path::new("lib/b"), Path::new("lib/b/b.txt"), Path::new("lib/b/lib"), ] ); }); fs.rename( Path::new("/root/lib/a/lib"), Path::new("/root/lib/a/lib-2"), Default::default(), ) .await .unwrap(); cx.executor().run_until_parked(); tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(false) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![ Path::new(""), Path::new("lib"), Path::new("lib/a"), Path::new("lib/a/a.txt"), Path::new("lib/a/lib-2"), Path::new("lib/b"), Path::new("lib/b/b.txt"), Path::new("lib/b/lib"), ] ); }); } #[gpui::test] async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/root", json!({ "dir1": { "deps": { // symlinks here }, "src": { "a.rs": "", "b.rs": "", }, }, "dir2": { "src": { "c.rs": "", "d.rs": "", } }, "dir3": { "deps": {}, "src": { "e.rs": "", "f.rs": "", }, } }), ) .await; // These symlinks point to directories outside of the worktree's root, dir1. fs.create_symlink("/root/dir1/deps/dep-dir2".as_ref(), "../../dir2".into()) .await .unwrap(); fs.create_symlink("/root/dir1/deps/dep-dir3".as_ref(), "../../dir3".into()) .await .unwrap(); let tree = Worktree::local( build_client(cx), Path::new("/root/dir1"), true, fs.clone(), Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; let tree_updates = Arc::new(Mutex::new(Vec::new())); tree.update(cx, |_, cx| { let tree_updates = tree_updates.clone(); cx.subscribe(&tree, move |_, _, event, _| { if let Event::UpdatedEntries(update) = event { tree_updates.lock().extend( update .iter() .map(|(path, _, change)| (path.clone(), *change)), ); } }) .detach(); }); // The symlinked directories are not scanned by default. tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::<Vec<_>>(), vec![ (Path::new(""), false), (Path::new("deps"), false), (Path::new("deps/dep-dir2"), true), (Path::new("deps/dep-dir3"), true), (Path::new("src"), false), (Path::new("src/a.rs"), false), (Path::new("src/b.rs"), false), ] ); assert_eq!( tree.entry_for_path("deps/dep-dir2").unwrap().kind, EntryKind::UnloadedDir ); }); // Expand one of the symlinked directories. tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()]) }) .recv() .await; // The expanded directory's contents are loaded. Subdirectories are // not scanned yet. tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::<Vec<_>>(), vec![ (Path::new(""), false), (Path::new("deps"), false), (Path::new("deps/dep-dir2"), true), (Path::new("deps/dep-dir3"), true), (Path::new("deps/dep-dir3/deps"), true), (Path::new("deps/dep-dir3/src"), true), (Path::new("src"), false), (Path::new("src/a.rs"), false), (Path::new("src/b.rs"), false), ] ); }); assert_eq!( mem::take(&mut *tree_updates.lock()), &[ (Path::new("deps/dep-dir3").into(), PathChange::Loaded), (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded), (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded) ] ); // Expand a subdirectory of one of the symlinked directories. tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()]) }) .recv() .await; // The expanded subdirectory's contents are loaded. tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::<Vec<_>>(), vec![ (Path::new(""), false), (Path::new("deps"), false), (Path::new("deps/dep-dir2"), true), (Path::new("deps/dep-dir3"), true), (Path::new("deps/dep-dir3/deps"), true), (Path::new("deps/dep-dir3/src"), true), (Path::new("deps/dep-dir3/src/e.rs"), true), (Path::new("deps/dep-dir3/src/f.rs"), true), (Path::new("src"), false), (Path::new("src/a.rs"), false), (Path::new("src/b.rs"), false), ] ); }); assert_eq!( mem::take(&mut *tree_updates.lock()), &[ (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded), ( Path::new("deps/dep-dir3/src/e.rs").into(), PathChange::Loaded ), ( Path::new("deps/dep-dir3/src/f.rs").into(), PathChange::Loaded ) ] ); } #[cfg(target_os = "macos")] #[gpui::test] async fn test_renaming_case_only(cx: &mut TestAppContext) { cx.executor().allow_parking(); init_test(cx); const OLD_NAME: &str = "aaa.rs"; const NEW_NAME: &str = "AAA.rs"; let fs = Arc::new(RealFs); let temp_root = temp_tree(json!({ OLD_NAME: "", })); let tree = Worktree::local( build_client(cx), temp_root.path(), true, fs.clone(), Default::default(), &mut cx.to_async(), ) .await .unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; tree.read_with(cx, |tree, _| { assert_eq!( tree.entries(true) .map(|entry| entry.path.as_ref()) .collect::<Vec<_>>(), vec![Path::new(""), Path::new(OLD_NAME)] ); }); fs.rename( &temp_root.path().join(OLD_NAME), &temp_root.path().join(NEW_NAME), fs::RenameOptions { overwrite: true, ignore_if_exists: true, }, ) .await .unwrap(); tree.flush_fs_events(cx).await;