3003 lines · 90.2 KB · Rust
RawDownload
use crate::{    worktree_settings::WorktreeSettings, Entry, EntryKind, Event, PathChange, StatusEntry,    WorkDirectory, Worktree, WorktreeModelHandle,};use anyhow::Result;use fs::{FakeFs, Fs, RealFs, RemoveOptions};use git::{    repository::RepoPath,    status::{FileStatus, StatusCode, TrackedStatus},    GITIGNORE,};use git2::RepositoryInitOptions;use gpui::{AppContext as _, BorrowAppContext, Context, 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 util::{path, test::TempTree, 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(        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, 0)                .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, 0)                .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(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(        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, 0)                .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, 0)                .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(        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, 0)                .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, 0)                .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, 0)                .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::new(None, cx.executor()));    let temp_root = TempTree::new(json!({        OLD_NAME: "",    }));     let tree = Worktree::local(        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, 0)                .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;     tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(true, 0)                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![Path::new(""), Path::new(NEW_NAME)]        );    });} #[gpui::test]async fn test_open_gitignored_files(cx: &mut TestAppContext) {    init_test(cx);    let fs = FakeFs::new(cx.background_executor.clone());    fs.insert_tree(        "/root",        json!({            ".gitignore": "node_modules\n",            "one": {                "node_modules": {                    "a": {                        "a1.js": "a1",                        "a2.js": "a2",                    },                    "b": {                        "b1.js": "b1",                        "b2.js": "b2",                    },                    "c": {                        "c1.js": "c1",                        "c2.js": "c2",                    }                },            },            "two": {                "x.js": "",                "y.js": "",            },        }),    )    .await;     let tree = Worktree::local(        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(true, 0)                .map(|entry| (entry.path.as_ref(), entry.is_ignored))                .collect::<Vec<_>>(),            vec![                (Path::new(""), false),                (Path::new(".gitignore"), false),                (Path::new("one"), false),                (Path::new("one/node_modules"), true),                (Path::new("two"), false),                (Path::new("two/x.js"), false),                (Path::new("two/y.js"), false),            ]        );    });     // Open a file that is nested inside of a gitignored directory that    // has not yet been expanded.    let prev_read_dir_count = fs.read_dir_call_count();    let loaded = tree        .update(cx, |tree, cx| {            tree.load_file("one/node_modules/b/b1.js".as_ref(), cx)        })        .await        .unwrap();     tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(true, 0)                .map(|entry| (entry.path.as_ref(), entry.is_ignored))                .collect::<Vec<_>>(),            vec![                (Path::new(""), false),                (Path::new(".gitignore"), false),                (Path::new("one"), false),                (Path::new("one/node_modules"), true),                (Path::new("one/node_modules/a"), true),                (Path::new("one/node_modules/b"), true),                (Path::new("one/node_modules/b/b1.js"), true),                (Path::new("one/node_modules/b/b2.js"), true),                (Path::new("one/node_modules/c"), true),                (Path::new("two"), false),                (Path::new("two/x.js"), false),                (Path::new("two/y.js"), false),            ]        );         assert_eq!(            loaded.file.path.as_ref(),            Path::new("one/node_modules/b/b1.js")        ); 

Lines 1–500 of 3003.