2141 lines
69 KiB
Rust
2141 lines
69 KiB
Rust
// use crate::{
|
|
// worktree::{Event, Snapshot, WorktreeModelHandle},
|
|
// Entry, EntryKind, PathChange, Worktree,
|
|
// };
|
|
// use anyhow::Result;
|
|
// use client2::Client;
|
|
// use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
|
|
// use git::GITIGNORE;
|
|
// use gpui::{executor::Deterministic, ModelContext, Task, TestAppContext};
|
|
// use parking_lot::Mutex;
|
|
// use postage::stream::Stream;
|
|
// use pretty_assertions::assert_eq;
|
|
// use rand::prelude::*;
|
|
// use serde_json::json;
|
|
// use std::{
|
|
// env,
|
|
// fmt::Write,
|
|
// mem,
|
|
// path::{Path, PathBuf},
|
|
// sync::Arc,
|
|
// };
|
|
// use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
|
|
|
|
// #[gpui::test]
|
|
// async fn test_traversal(cx: &mut TestAppContext) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// 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) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// 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(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// fs.insert_tree(
|
|
// "/root",
|
|
// json!({
|
|
// "lib": {
|
|
// "a": {
|
|
// "a.txt": ""
|
|
// },
|
|
// "b": {
|
|
// "b.txt": ""
|
|
// }
|
|
// }
|
|
// }),
|
|
// )
|
|
// .await;
|
|
// fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
|
|
// fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
|
|
|
|
// 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();
|
|
// 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) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// 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.insert_symlink("/root/dir1/deps/dep-dir2", "../../dir2".into())
|
|
// .await;
|
|
// fs.insert_symlink("/root/dir1/deps/dep-dir3", "../../dir3".into())
|
|
// .await;
|
|
|
|
// 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
|
|
// )
|
|
// ]
|
|
// );
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// 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(
|
|
// 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(true)
|
|
// .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 buffer = tree
|
|
// .update(cx, |tree, cx| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .load_buffer(0, "one/node_modules/b/b1.js".as_ref(), cx)
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// tree.read_with(cx, |tree, cx| {
|
|
// assert_eq!(
|
|
// tree.entries(true)
|
|
// .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!(
|
|
// buffer.read(cx).file().unwrap().path().as_ref(),
|
|
// Path::new("one/node_modules/b/b1.js")
|
|
// );
|
|
|
|
// // Only the newly-expanded directories are scanned.
|
|
// assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 2);
|
|
// });
|
|
|
|
// // Open another file in a different subdirectory of the same
|
|
// // gitignored directory.
|
|
// let prev_read_dir_count = fs.read_dir_call_count();
|
|
// let buffer = tree
|
|
// .update(cx, |tree, cx| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .load_buffer(0, "one/node_modules/a/a2.js".as_ref(), cx)
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// tree.read_with(cx, |tree, cx| {
|
|
// assert_eq!(
|
|
// tree.entries(true)
|
|
// .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/a/a1.js"), true),
|
|
// (Path::new("one/node_modules/a/a2.js"), 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!(
|
|
// buffer.read(cx).file().unwrap().path().as_ref(),
|
|
// Path::new("one/node_modules/a/a2.js")
|
|
// );
|
|
|
|
// // Only the newly-expanded directory is scanned.
|
|
// assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
|
|
// });
|
|
|
|
// // No work happens when files and directories change within an unloaded directory.
|
|
// let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count();
|
|
// fs.create_dir("/root/one/node_modules/c/lib".as_ref())
|
|
// .await
|
|
// .unwrap();
|
|
// cx.foreground().run_until_parked();
|
|
// assert_eq!(
|
|
// fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
|
|
// 0
|
|
// );
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// fs.insert_tree(
|
|
// "/root",
|
|
// json!({
|
|
// ".gitignore": "node_modules\n",
|
|
// "a": {
|
|
// "a.js": "",
|
|
// },
|
|
// "b": {
|
|
// "b.js": "",
|
|
// },
|
|
// "node_modules": {
|
|
// "c": {
|
|
// "c.js": "",
|
|
// },
|
|
// "d": {
|
|
// "d.js": "",
|
|
// "e": {
|
|
// "e1.js": "",
|
|
// "e2.js": "",
|
|
// },
|
|
// "f": {
|
|
// "f1.js": "",
|
|
// "f2.js": "",
|
|
// }
|
|
// },
|
|
// },
|
|
// }),
|
|
// )
|
|
// .await;
|
|
|
|
// 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;
|
|
|
|
// // Open a file within the gitignored directory, forcing some of its
|
|
// // subdirectories to be read, but not all.
|
|
// let read_dir_count_1 = fs.read_dir_call_count();
|
|
// tree.read_with(cx, |tree, _| {
|
|
// tree.as_local()
|
|
// .unwrap()
|
|
// .refresh_entries_for_paths(vec![Path::new("node_modules/d/d.js").into()])
|
|
// })
|
|
// .recv()
|
|
// .await;
|
|
|
|
// // Those subdirectories are now loaded.
|
|
// tree.read_with(cx, |tree, _| {
|
|
// assert_eq!(
|
|
// tree.entries(true)
|
|
// .map(|e| (e.path.as_ref(), e.is_ignored))
|
|
// .collect::<Vec<_>>(),
|
|
// &[
|
|
// (Path::new(""), false),
|
|
// (Path::new(".gitignore"), false),
|
|
// (Path::new("a"), false),
|
|
// (Path::new("a/a.js"), false),
|
|
// (Path::new("b"), false),
|
|
// (Path::new("b/b.js"), false),
|
|
// (Path::new("node_modules"), true),
|
|
// (Path::new("node_modules/c"), true),
|
|
// (Path::new("node_modules/d"), true),
|
|
// (Path::new("node_modules/d/d.js"), true),
|
|
// (Path::new("node_modules/d/e"), true),
|
|
// (Path::new("node_modules/d/f"), true),
|
|
// ]
|
|
// );
|
|
// });
|
|
// let read_dir_count_2 = fs.read_dir_call_count();
|
|
// assert_eq!(read_dir_count_2 - read_dir_count_1, 2);
|
|
|
|
// // Update the gitignore so that node_modules is no longer ignored,
|
|
// // but a subdirectory is ignored
|
|
// fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
|
|
// .await
|
|
// .unwrap();
|
|
// cx.foreground().run_until_parked();
|
|
|
|
// // All of the directories that are no longer ignored are now loaded.
|
|
// tree.read_with(cx, |tree, _| {
|
|
// assert_eq!(
|
|
// tree.entries(true)
|
|
// .map(|e| (e.path.as_ref(), e.is_ignored))
|
|
// .collect::<Vec<_>>(),
|
|
// &[
|
|
// (Path::new(""), false),
|
|
// (Path::new(".gitignore"), false),
|
|
// (Path::new("a"), false),
|
|
// (Path::new("a/a.js"), false),
|
|
// (Path::new("b"), false),
|
|
// (Path::new("b/b.js"), false),
|
|
// // This directory is no longer ignored
|
|
// (Path::new("node_modules"), false),
|
|
// (Path::new("node_modules/c"), false),
|
|
// (Path::new("node_modules/c/c.js"), false),
|
|
// (Path::new("node_modules/d"), false),
|
|
// (Path::new("node_modules/d/d.js"), false),
|
|
// // This subdirectory is now ignored
|
|
// (Path::new("node_modules/d/e"), true),
|
|
// (Path::new("node_modules/d/f"), false),
|
|
// (Path::new("node_modules/d/f/f1.js"), false),
|
|
// (Path::new("node_modules/d/f/f2.js"), false),
|
|
// ]
|
|
// );
|
|
// });
|
|
|
|
// // Each of the newly-loaded directories is scanned only once.
|
|
// let read_dir_count_3 = fs.read_dir_call_count();
|
|
// assert_eq!(read_dir_count_3 - read_dir_count_2, 2);
|
|
// }
|
|
|
|
// #[gpui::test(iterations = 10)]
|
|
// async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// fs.insert_tree(
|
|
// "/root",
|
|
// json!({
|
|
// ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
|
|
// "tree": {
|
|
// ".git": {},
|
|
// ".gitignore": "ignored-dir\n",
|
|
// "tracked-dir": {
|
|
// "tracked-file1": "",
|
|
// "ancestor-ignored-file1": "",
|
|
// },
|
|
// "ignored-dir": {
|
|
// "ignored-file1": ""
|
|
// }
|
|
// }
|
|
// }),
|
|
// )
|
|
// .await;
|
|
|
|
// let tree = Worktree::local(
|
|
// build_client(cx),
|
|
// "/root/tree".as_ref(),
|
|
// 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, _| {
|
|
// tree.as_local()
|
|
// .unwrap()
|
|
// .refresh_entries_for_paths(vec![Path::new("ignored-dir").into()])
|
|
// })
|
|
// .recv()
|
|
// .await;
|
|
|
|
// cx.read(|cx| {
|
|
// let tree = tree.read(cx);
|
|
// assert!(
|
|
// !tree
|
|
// .entry_for_path("tracked-dir/tracked-file1")
|
|
// .unwrap()
|
|
// .is_ignored
|
|
// );
|
|
// assert!(
|
|
// tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
|
|
// .unwrap()
|
|
// .is_ignored
|
|
// );
|
|
// assert!(
|
|
// tree.entry_for_path("ignored-dir/ignored-file1")
|
|
// .unwrap()
|
|
// .is_ignored
|
|
// );
|
|
// });
|
|
|
|
// fs.create_file(
|
|
// "/root/tree/tracked-dir/tracked-file2".as_ref(),
|
|
// Default::default(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// fs.create_file(
|
|
// "/root/tree/tracked-dir/ancestor-ignored-file2".as_ref(),
|
|
// Default::default(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// fs.create_file(
|
|
// "/root/tree/ignored-dir/ignored-file2".as_ref(),
|
|
// Default::default(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// cx.read(|cx| {
|
|
// let tree = tree.read(cx);
|
|
// assert!(
|
|
// !tree
|
|
// .entry_for_path("tracked-dir/tracked-file2")
|
|
// .unwrap()
|
|
// .is_ignored
|
|
// );
|
|
// assert!(
|
|
// tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
|
|
// .unwrap()
|
|
// .is_ignored
|
|
// );
|
|
// assert!(
|
|
// tree.entry_for_path("ignored-dir/ignored-file2")
|
|
// .unwrap()
|
|
// .is_ignored
|
|
// );
|
|
// assert!(tree.entry_for_path(".git").unwrap().is_ignored);
|
|
// });
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_write_file(cx: &mut TestAppContext) {
|
|
// let dir = temp_tree(json!({
|
|
// ".git": {},
|
|
// ".gitignore": "ignored-dir\n",
|
|
// "tracked-dir": {},
|
|
// "ignored-dir": {}
|
|
// }));
|
|
|
|
// let tree = Worktree::local(
|
|
// build_client(cx),
|
|
// dir.path(),
|
|
// true,
|
|
// Arc::new(RealFs),
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
|
// .await;
|
|
// tree.flush_fs_events(cx).await;
|
|
|
|
// tree.update(cx, |tree, cx| {
|
|
// tree.as_local().unwrap().write_file(
|
|
// Path::new("tracked-dir/file.txt"),
|
|
// "hello".into(),
|
|
// Default::default(),
|
|
// cx,
|
|
// )
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
// tree.update(cx, |tree, cx| {
|
|
// tree.as_local().unwrap().write_file(
|
|
// Path::new("ignored-dir/file.txt"),
|
|
// "world".into(),
|
|
// Default::default(),
|
|
// cx,
|
|
// )
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// tree.read_with(cx, |tree, _| {
|
|
// let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
|
|
// let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
|
|
// assert!(!tracked.is_ignored);
|
|
// assert!(ignored.is_ignored);
|
|
// });
|
|
// }
|
|
|
|
// #[gpui::test(iterations = 30)]
|
|
// async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// fs.insert_tree(
|
|
// "/root",
|
|
// json!({
|
|
// "b": {},
|
|
// "c": {},
|
|
// "d": {},
|
|
// }),
|
|
// )
|
|
// .await;
|
|
|
|
// let tree = Worktree::local(
|
|
// build_client(cx),
|
|
// "/root".as_ref(),
|
|
// true,
|
|
// fs,
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// let snapshot1 = tree.update(cx, |tree, cx| {
|
|
// let tree = tree.as_local_mut().unwrap();
|
|
// let snapshot = Arc::new(Mutex::new(tree.snapshot()));
|
|
// let _ = tree.observe_updates(0, cx, {
|
|
// let snapshot = snapshot.clone();
|
|
// move |update| {
|
|
// snapshot.lock().apply_remote_update(update).unwrap();
|
|
// async { true }
|
|
// }
|
|
// });
|
|
// snapshot
|
|
// });
|
|
|
|
// let entry = tree
|
|
// .update(cx, |tree, cx| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .create_entry("a/e".as_ref(), true, cx)
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
// assert!(entry.is_dir());
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// tree.read_with(cx, |tree, _| {
|
|
// assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
|
|
// });
|
|
|
|
// let snapshot2 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
|
// assert_eq!(
|
|
// snapshot1.lock().entries(true).collect::<Vec<_>>(),
|
|
// snapshot2.entries(true).collect::<Vec<_>>()
|
|
// );
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|
// let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
|
|
|
// let fs_fake = FakeFs::new(cx.background());
|
|
// fs_fake
|
|
// .insert_tree(
|
|
// "/root",
|
|
// json!({
|
|
// "a": {},
|
|
// }),
|
|
// )
|
|
// .await;
|
|
|
|
// let tree_fake = Worktree::local(
|
|
// client_fake,
|
|
// "/root".as_ref(),
|
|
// true,
|
|
// fs_fake,
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// let entry = tree_fake
|
|
// .update(cx, |tree, cx| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
// assert!(entry.is_file());
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// tree_fake.read_with(cx, |tree, _| {
|
|
// assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
|
|
// assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
|
|
// assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
|
|
// });
|
|
|
|
// let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
|
|
|
// let fs_real = Arc::new(RealFs);
|
|
// let temp_root = temp_tree(json!({
|
|
// "a": {}
|
|
// }));
|
|
|
|
// let tree_real = Worktree::local(
|
|
// client_real,
|
|
// temp_root.path(),
|
|
// true,
|
|
// fs_real,
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// let entry = tree_real
|
|
// .update(cx, |tree, cx| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
// assert!(entry.is_file());
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// tree_real.read_with(cx, |tree, _| {
|
|
// assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
|
|
// assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
|
|
// assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
|
|
// });
|
|
|
|
// // Test smallest change
|
|
// let entry = tree_real
|
|
// .update(cx, |tree, cx| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .create_entry("a/b/c/e.txt".as_ref(), false, cx)
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
// assert!(entry.is_file());
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// tree_real.read_with(cx, |tree, _| {
|
|
// assert!(tree.entry_for_path("a/b/c/e.txt").unwrap().is_file());
|
|
// });
|
|
|
|
// // Test largest change
|
|
// let entry = tree_real
|
|
// .update(cx, |tree, cx| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .create_entry("d/e/f/g.txt".as_ref(), false, cx)
|
|
// })
|
|
// .await
|
|
// .unwrap();
|
|
// assert!(entry.is_file());
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// tree_real.read_with(cx, |tree, _| {
|
|
// assert!(tree.entry_for_path("d/e/f/g.txt").unwrap().is_file());
|
|
// assert!(tree.entry_for_path("d/e/f").unwrap().is_dir());
|
|
// assert!(tree.entry_for_path("d/e/").unwrap().is_dir());
|
|
// assert!(tree.entry_for_path("d/").unwrap().is_dir());
|
|
// });
|
|
// }
|
|
|
|
// #[gpui::test(iterations = 100)]
|
|
// async fn test_random_worktree_operations_during_initial_scan(
|
|
// cx: &mut TestAppContext,
|
|
// mut rng: StdRng,
|
|
// ) {
|
|
// let operations = env::var("OPERATIONS")
|
|
// .map(|o| o.parse().unwrap())
|
|
// .unwrap_or(5);
|
|
// let initial_entries = env::var("INITIAL_ENTRIES")
|
|
// .map(|o| o.parse().unwrap())
|
|
// .unwrap_or(20);
|
|
|
|
// let root_dir = Path::new("/test");
|
|
// let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
|
|
// fs.as_fake().insert_tree(root_dir, json!({})).await;
|
|
// for _ in 0..initial_entries {
|
|
// randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
|
|
// }
|
|
// log::info!("generated initial tree");
|
|
|
|
// let worktree = Worktree::local(
|
|
// build_client(cx),
|
|
// root_dir,
|
|
// true,
|
|
// fs.clone(),
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// let mut snapshots = vec![worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot())];
|
|
// let updates = Arc::new(Mutex::new(Vec::new()));
|
|
// worktree.update(cx, |tree, cx| {
|
|
// check_worktree_change_events(tree, cx);
|
|
|
|
// let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
|
|
// let updates = updates.clone();
|
|
// move |update| {
|
|
// updates.lock().push(update);
|
|
// async { true }
|
|
// }
|
|
// });
|
|
// });
|
|
|
|
// for _ in 0..operations {
|
|
// worktree
|
|
// .update(cx, |worktree, cx| {
|
|
// randomly_mutate_worktree(worktree, &mut rng, cx)
|
|
// })
|
|
// .await
|
|
// .log_err();
|
|
// worktree.read_with(cx, |tree, _| {
|
|
// tree.as_local().unwrap().snapshot().check_invariants(true)
|
|
// });
|
|
|
|
// if rng.gen_bool(0.6) {
|
|
// snapshots.push(worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot()));
|
|
// }
|
|
// }
|
|
|
|
// worktree
|
|
// .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
|
|
// .await;
|
|
|
|
// cx.foreground().run_until_parked();
|
|
|
|
// let final_snapshot = worktree.read_with(cx, |tree, _| {
|
|
// let tree = tree.as_local().unwrap();
|
|
// let snapshot = tree.snapshot();
|
|
// snapshot.check_invariants(true);
|
|
// snapshot
|
|
// });
|
|
|
|
// for (i, snapshot) in snapshots.into_iter().enumerate().rev() {
|
|
// let mut updated_snapshot = snapshot.clone();
|
|
// for update in updates.lock().iter() {
|
|
// if update.scan_id >= updated_snapshot.scan_id() as u64 {
|
|
// updated_snapshot
|
|
// .apply_remote_update(update.clone())
|
|
// .unwrap();
|
|
// }
|
|
// }
|
|
|
|
// assert_eq!(
|
|
// updated_snapshot.entries(true).collect::<Vec<_>>(),
|
|
// final_snapshot.entries(true).collect::<Vec<_>>(),
|
|
// "wrong updates after snapshot {i}: {snapshot:#?} {updates:#?}",
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
// #[gpui::test(iterations = 100)]
|
|
// async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) {
|
|
// let operations = env::var("OPERATIONS")
|
|
// .map(|o| o.parse().unwrap())
|
|
// .unwrap_or(40);
|
|
// let initial_entries = env::var("INITIAL_ENTRIES")
|
|
// .map(|o| o.parse().unwrap())
|
|
// .unwrap_or(20);
|
|
|
|
// let root_dir = Path::new("/test");
|
|
// let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
|
|
// fs.as_fake().insert_tree(root_dir, json!({})).await;
|
|
// for _ in 0..initial_entries {
|
|
// randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
|
|
// }
|
|
// log::info!("generated initial tree");
|
|
|
|
// let worktree = Worktree::local(
|
|
// build_client(cx),
|
|
// root_dir,
|
|
// true,
|
|
// fs.clone(),
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// let updates = Arc::new(Mutex::new(Vec::new()));
|
|
// worktree.update(cx, |tree, cx| {
|
|
// check_worktree_change_events(tree, cx);
|
|
|
|
// let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
|
|
// let updates = updates.clone();
|
|
// move |update| {
|
|
// updates.lock().push(update);
|
|
// async { true }
|
|
// }
|
|
// });
|
|
// });
|
|
|
|
// worktree
|
|
// .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
|
|
// .await;
|
|
|
|
// fs.as_fake().pause_events();
|
|
// let mut snapshots = Vec::new();
|
|
// let mut mutations_len = operations;
|
|
// while mutations_len > 1 {
|
|
// if rng.gen_bool(0.2) {
|
|
// worktree
|
|
// .update(cx, |worktree, cx| {
|
|
// randomly_mutate_worktree(worktree, &mut rng, cx)
|
|
// })
|
|
// .await
|
|
// .log_err();
|
|
// } else {
|
|
// randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
|
|
// }
|
|
|
|
// let buffered_event_count = fs.as_fake().buffered_event_count();
|
|
// if buffered_event_count > 0 && rng.gen_bool(0.3) {
|
|
// let len = rng.gen_range(0..=buffered_event_count);
|
|
// log::info!("flushing {} events", len);
|
|
// fs.as_fake().flush_events(len);
|
|
// } else {
|
|
// randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await;
|
|
// mutations_len -= 1;
|
|
// }
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// if rng.gen_bool(0.2) {
|
|
// log::info!("storing snapshot {}", snapshots.len());
|
|
// let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
|
// snapshots.push(snapshot);
|
|
// }
|
|
// }
|
|
|
|
// log::info!("quiescing");
|
|
// fs.as_fake().flush_events(usize::MAX);
|
|
// cx.foreground().run_until_parked();
|
|
|
|
// let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
|
// snapshot.check_invariants(true);
|
|
// let expanded_paths = snapshot
|
|
// .expanded_entries()
|
|
// .map(|e| e.path.clone())
|
|
// .collect::<Vec<_>>();
|
|
|
|
// {
|
|
// let new_worktree = Worktree::local(
|
|
// build_client(cx),
|
|
// root_dir,
|
|
// true,
|
|
// fs.clone(),
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// new_worktree
|
|
// .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
|
|
// .await;
|
|
// new_worktree
|
|
// .update(cx, |tree, _| {
|
|
// tree.as_local_mut()
|
|
// .unwrap()
|
|
// .refresh_entries_for_paths(expanded_paths)
|
|
// })
|
|
// .recv()
|
|
// .await;
|
|
// let new_snapshot =
|
|
// new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
|
// assert_eq!(
|
|
// snapshot.entries_without_ids(true),
|
|
// new_snapshot.entries_without_ids(true)
|
|
// );
|
|
// }
|
|
|
|
// for (i, mut prev_snapshot) in snapshots.into_iter().enumerate().rev() {
|
|
// for update in updates.lock().iter() {
|
|
// if update.scan_id >= prev_snapshot.scan_id() as u64 {
|
|
// prev_snapshot.apply_remote_update(update.clone()).unwrap();
|
|
// }
|
|
// }
|
|
|
|
// assert_eq!(
|
|
// prev_snapshot
|
|
// .entries(true)
|
|
// .map(ignore_pending_dir)
|
|
// .collect::<Vec<_>>(),
|
|
// snapshot
|
|
// .entries(true)
|
|
// .map(ignore_pending_dir)
|
|
// .collect::<Vec<_>>(),
|
|
// "wrong updates after snapshot {i}: {updates:#?}",
|
|
// );
|
|
// }
|
|
|
|
// fn ignore_pending_dir(entry: &Entry) -> Entry {
|
|
// let mut entry = entry.clone();
|
|
// if entry.kind.is_dir() {
|
|
// entry.kind = EntryKind::Dir
|
|
// }
|
|
// entry
|
|
// }
|
|
// }
|
|
|
|
// // The worktree's `UpdatedEntries` event can be used to follow along with
|
|
// // all changes to the worktree's snapshot.
|
|
// fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Worktree>) {
|
|
// let mut entries = tree.entries(true).cloned().collect::<Vec<_>>();
|
|
// cx.subscribe(&cx.handle(), move |tree, _, event, _| {
|
|
// if let Event::UpdatedEntries(changes) = event {
|
|
// for (path, _, change_type) in changes.iter() {
|
|
// let entry = tree.entry_for_path(&path).cloned();
|
|
// let ix = match entries.binary_search_by_key(&path, |e| &e.path) {
|
|
// Ok(ix) | Err(ix) => ix,
|
|
// };
|
|
// match change_type {
|
|
// PathChange::Added => entries.insert(ix, entry.unwrap()),
|
|
// PathChange::Removed => drop(entries.remove(ix)),
|
|
// PathChange::Updated => {
|
|
// let entry = entry.unwrap();
|
|
// let existing_entry = entries.get_mut(ix).unwrap();
|
|
// assert_eq!(existing_entry.path, entry.path);
|
|
// *existing_entry = entry;
|
|
// }
|
|
// PathChange::AddedOrUpdated | PathChange::Loaded => {
|
|
// let entry = entry.unwrap();
|
|
// if entries.get(ix).map(|e| &e.path) == Some(&entry.path) {
|
|
// *entries.get_mut(ix).unwrap() = entry;
|
|
// } else {
|
|
// entries.insert(ix, entry);
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// let new_entries = tree.entries(true).cloned().collect::<Vec<_>>();
|
|
// assert_eq!(entries, new_entries, "incorrect changes: {:?}", changes);
|
|
// }
|
|
// })
|
|
// .detach();
|
|
// }
|
|
|
|
// fn randomly_mutate_worktree(
|
|
// worktree: &mut Worktree,
|
|
// rng: &mut impl Rng,
|
|
// cx: &mut ModelContext<Worktree>,
|
|
// ) -> Task<Result<()>> {
|
|
// log::info!("mutating worktree");
|
|
// let worktree = worktree.as_local_mut().unwrap();
|
|
// let snapshot = worktree.snapshot();
|
|
// let entry = snapshot.entries(false).choose(rng).unwrap();
|
|
|
|
// match rng.gen_range(0_u32..100) {
|
|
// 0..=33 if entry.path.as_ref() != Path::new("") => {
|
|
// log::info!("deleting entry {:?} ({})", entry.path, entry.id.0);
|
|
// worktree.delete_entry(entry.id, cx).unwrap()
|
|
// }
|
|
// ..=66 if entry.path.as_ref() != Path::new("") => {
|
|
// let other_entry = snapshot.entries(false).choose(rng).unwrap();
|
|
// let new_parent_path = if other_entry.is_dir() {
|
|
// other_entry.path.clone()
|
|
// } else {
|
|
// other_entry.path.parent().unwrap().into()
|
|
// };
|
|
// let mut new_path = new_parent_path.join(random_filename(rng));
|
|
// if new_path.starts_with(&entry.path) {
|
|
// new_path = random_filename(rng).into();
|
|
// }
|
|
|
|
// log::info!(
|
|
// "renaming entry {:?} ({}) to {:?}",
|
|
// entry.path,
|
|
// entry.id.0,
|
|
// new_path
|
|
// );
|
|
// let task = worktree.rename_entry(entry.id, new_path, cx).unwrap();
|
|
// cx.foreground().spawn(async move {
|
|
// task.await?;
|
|
// Ok(())
|
|
// })
|
|
// }
|
|
// _ => {
|
|
// let task = if entry.is_dir() {
|
|
// let child_path = entry.path.join(random_filename(rng));
|
|
// let is_dir = rng.gen_bool(0.3);
|
|
// log::info!(
|
|
// "creating {} at {:?}",
|
|
// if is_dir { "dir" } else { "file" },
|
|
// child_path,
|
|
// );
|
|
// worktree.create_entry(child_path, is_dir, cx)
|
|
// } else {
|
|
// log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
|
|
// worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx)
|
|
// };
|
|
// cx.foreground().spawn(async move {
|
|
// task.await?;
|
|
// Ok(())
|
|
// })
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// async fn randomly_mutate_fs(
|
|
// fs: &Arc<dyn Fs>,
|
|
// root_path: &Path,
|
|
// insertion_probability: f64,
|
|
// rng: &mut impl Rng,
|
|
// ) {
|
|
// log::info!("mutating fs");
|
|
// let mut files = Vec::new();
|
|
// let mut dirs = Vec::new();
|
|
// for path in fs.as_fake().paths(false) {
|
|
// if path.starts_with(root_path) {
|
|
// if fs.is_file(&path).await {
|
|
// files.push(path);
|
|
// } else {
|
|
// dirs.push(path);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
|
|
// let path = dirs.choose(rng).unwrap();
|
|
// let new_path = path.join(random_filename(rng));
|
|
|
|
// if rng.gen() {
|
|
// log::info!(
|
|
// "creating dir {:?}",
|
|
// new_path.strip_prefix(root_path).unwrap()
|
|
// );
|
|
// fs.create_dir(&new_path).await.unwrap();
|
|
// } else {
|
|
// log::info!(
|
|
// "creating file {:?}",
|
|
// new_path.strip_prefix(root_path).unwrap()
|
|
// );
|
|
// fs.create_file(&new_path, Default::default()).await.unwrap();
|
|
// }
|
|
// } else if rng.gen_bool(0.05) {
|
|
// let ignore_dir_path = dirs.choose(rng).unwrap();
|
|
// let ignore_path = ignore_dir_path.join(&*GITIGNORE);
|
|
|
|
// let subdirs = dirs
|
|
// .iter()
|
|
// .filter(|d| d.starts_with(&ignore_dir_path))
|
|
// .cloned()
|
|
// .collect::<Vec<_>>();
|
|
// let subfiles = files
|
|
// .iter()
|
|
// .filter(|d| d.starts_with(&ignore_dir_path))
|
|
// .cloned()
|
|
// .collect::<Vec<_>>();
|
|
// let files_to_ignore = {
|
|
// let len = rng.gen_range(0..=subfiles.len());
|
|
// subfiles.choose_multiple(rng, len)
|
|
// };
|
|
// let dirs_to_ignore = {
|
|
// let len = rng.gen_range(0..subdirs.len());
|
|
// subdirs.choose_multiple(rng, len)
|
|
// };
|
|
|
|
// let mut ignore_contents = String::new();
|
|
// for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
|
|
// writeln!(
|
|
// ignore_contents,
|
|
// "{}",
|
|
// path_to_ignore
|
|
// .strip_prefix(&ignore_dir_path)
|
|
// .unwrap()
|
|
// .to_str()
|
|
// .unwrap()
|
|
// )
|
|
// .unwrap();
|
|
// }
|
|
// log::info!(
|
|
// "creating gitignore {:?} with contents:\n{}",
|
|
// ignore_path.strip_prefix(&root_path).unwrap(),
|
|
// ignore_contents
|
|
// );
|
|
// fs.save(
|
|
// &ignore_path,
|
|
// &ignore_contents.as_str().into(),
|
|
// Default::default(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// } else {
|
|
// let old_path = {
|
|
// let file_path = files.choose(rng);
|
|
// let dir_path = dirs[1..].choose(rng);
|
|
// file_path.into_iter().chain(dir_path).choose(rng).unwrap()
|
|
// };
|
|
|
|
// let is_rename = rng.gen();
|
|
// if is_rename {
|
|
// let new_path_parent = dirs
|
|
// .iter()
|
|
// .filter(|d| !d.starts_with(old_path))
|
|
// .choose(rng)
|
|
// .unwrap();
|
|
|
|
// let overwrite_existing_dir =
|
|
// !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
|
|
// let new_path = if overwrite_existing_dir {
|
|
// fs.remove_dir(
|
|
// &new_path_parent,
|
|
// RemoveOptions {
|
|
// recursive: true,
|
|
// ignore_if_not_exists: true,
|
|
// },
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// new_path_parent.to_path_buf()
|
|
// } else {
|
|
// new_path_parent.join(random_filename(rng))
|
|
// };
|
|
|
|
// log::info!(
|
|
// "renaming {:?} to {}{:?}",
|
|
// old_path.strip_prefix(&root_path).unwrap(),
|
|
// if overwrite_existing_dir {
|
|
// "overwrite "
|
|
// } else {
|
|
// ""
|
|
// },
|
|
// new_path.strip_prefix(&root_path).unwrap()
|
|
// );
|
|
// fs.rename(
|
|
// &old_path,
|
|
// &new_path,
|
|
// fs::RenameOptions {
|
|
// overwrite: true,
|
|
// ignore_if_exists: true,
|
|
// },
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// } else if fs.is_file(&old_path).await {
|
|
// log::info!(
|
|
// "deleting file {:?}",
|
|
// old_path.strip_prefix(&root_path).unwrap()
|
|
// );
|
|
// fs.remove_file(old_path, Default::default()).await.unwrap();
|
|
// } else {
|
|
// log::info!(
|
|
// "deleting dir {:?}",
|
|
// old_path.strip_prefix(&root_path).unwrap()
|
|
// );
|
|
// fs.remove_dir(
|
|
// &old_path,
|
|
// RemoveOptions {
|
|
// recursive: true,
|
|
// ignore_if_not_exists: true,
|
|
// },
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// fn random_filename(rng: &mut impl Rng) -> String {
|
|
// (0..6)
|
|
// .map(|_| rng.sample(rand::distributions::Alphanumeric))
|
|
// .map(char::from)
|
|
// .collect()
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|
// let root = temp_tree(json!({
|
|
// "projects": {
|
|
// "project1": {
|
|
// "a": "",
|
|
// "b": "",
|
|
// }
|
|
// },
|
|
|
|
// }));
|
|
// let root_path = root.path();
|
|
|
|
// let tree = Worktree::local(
|
|
// build_client(cx),
|
|
// root_path,
|
|
// true,
|
|
// Arc::new(RealFs),
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// let repo = git_init(&root_path.join("projects/project1"));
|
|
// git_add("a", &repo);
|
|
// git_commit("init", &repo);
|
|
// std::fs::write(root_path.join("projects/project1/a"), "aa").ok();
|
|
|
|
// cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
|
// .await;
|
|
|
|
// tree.flush_fs_events(cx).await;
|
|
|
|
// cx.read(|cx| {
|
|
// let tree = tree.read(cx);
|
|
// let (work_dir, _) = tree.repositories().next().unwrap();
|
|
// assert_eq!(work_dir.as_ref(), Path::new("projects/project1"));
|
|
// assert_eq!(
|
|
// tree.status_for_file(Path::new("projects/project1/a")),
|
|
// Some(GitFileStatus::Modified)
|
|
// );
|
|
// assert_eq!(
|
|
// tree.status_for_file(Path::new("projects/project1/b")),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// });
|
|
|
|
// std::fs::rename(
|
|
// root_path.join("projects/project1"),
|
|
// root_path.join("projects/project2"),
|
|
// )
|
|
// .ok();
|
|
// tree.flush_fs_events(cx).await;
|
|
|
|
// cx.read(|cx| {
|
|
// let tree = tree.read(cx);
|
|
// let (work_dir, _) = tree.repositories().next().unwrap();
|
|
// assert_eq!(work_dir.as_ref(), Path::new("projects/project2"));
|
|
// assert_eq!(
|
|
// tree.status_for_file(Path::new("projects/project2/a")),
|
|
// Some(GitFileStatus::Modified)
|
|
// );
|
|
// assert_eq!(
|
|
// tree.status_for_file(Path::new("projects/project2/b")),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// });
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_git_repository_for_path(cx: &mut TestAppContext) {
|
|
// let root = temp_tree(json!({
|
|
// "c.txt": "",
|
|
// "dir1": {
|
|
// ".git": {},
|
|
// "deps": {
|
|
// "dep1": {
|
|
// ".git": {},
|
|
// "src": {
|
|
// "a.txt": ""
|
|
// }
|
|
// }
|
|
// },
|
|
// "src": {
|
|
// "b.txt": ""
|
|
// }
|
|
// },
|
|
// }));
|
|
|
|
// let tree = Worktree::local(
|
|
// build_client(cx),
|
|
// root.path(),
|
|
// true,
|
|
// Arc::new(RealFs),
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
|
// .await;
|
|
// tree.flush_fs_events(cx).await;
|
|
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let tree = tree.as_local().unwrap();
|
|
|
|
// assert!(tree.repository_for_path("c.txt".as_ref()).is_none());
|
|
|
|
// let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap();
|
|
// assert_eq!(
|
|
// entry
|
|
// .work_directory(tree)
|
|
// .map(|directory| directory.as_ref().to_owned()),
|
|
// Some(Path::new("dir1").to_owned())
|
|
// );
|
|
|
|
// let entry = tree
|
|
// .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref())
|
|
// .unwrap();
|
|
// assert_eq!(
|
|
// entry
|
|
// .work_directory(tree)
|
|
// .map(|directory| directory.as_ref().to_owned()),
|
|
// Some(Path::new("dir1/deps/dep1").to_owned())
|
|
// );
|
|
|
|
// let entries = tree.files(false, 0);
|
|
|
|
// let paths_with_repos = tree
|
|
// .entries_with_repositories(entries)
|
|
// .map(|(entry, repo)| {
|
|
// (
|
|
// entry.path.as_ref(),
|
|
// repo.and_then(|repo| {
|
|
// repo.work_directory(&tree)
|
|
// .map(|work_directory| work_directory.0.to_path_buf())
|
|
// }),
|
|
// )
|
|
// })
|
|
// .collect::<Vec<_>>();
|
|
|
|
// assert_eq!(
|
|
// paths_with_repos,
|
|
// &[
|
|
// (Path::new("c.txt"), None),
|
|
// (
|
|
// Path::new("dir1/deps/dep1/src/a.txt"),
|
|
// Some(Path::new("dir1/deps/dep1").into())
|
|
// ),
|
|
// (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())),
|
|
// ]
|
|
// );
|
|
// });
|
|
|
|
// let repo_update_events = Arc::new(Mutex::new(vec![]));
|
|
// tree.update(cx, |_, cx| {
|
|
// let repo_update_events = repo_update_events.clone();
|
|
// cx.subscribe(&tree, move |_, _, event, _| {
|
|
// if let Event::UpdatedGitRepositories(update) = event {
|
|
// repo_update_events.lock().push(update.clone());
|
|
// }
|
|
// })
|
|
// .detach();
|
|
// });
|
|
|
|
// std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
|
|
// tree.flush_fs_events(cx).await;
|
|
|
|
// assert_eq!(
|
|
// repo_update_events.lock()[0]
|
|
// .iter()
|
|
// .map(|e| e.0.clone())
|
|
// .collect::<Vec<Arc<Path>>>(),
|
|
// vec![Path::new("dir1").into()]
|
|
// );
|
|
|
|
// std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
|
|
// tree.flush_fs_events(cx).await;
|
|
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let tree = tree.as_local().unwrap();
|
|
|
|
// assert!(tree
|
|
// .repository_for_path("dir1/src/b.txt".as_ref())
|
|
// .is_none());
|
|
// });
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
|
// const IGNORE_RULE: &'static str = "**/target";
|
|
|
|
// let root = temp_tree(json!({
|
|
// "project": {
|
|
// "a.txt": "a",
|
|
// "b.txt": "bb",
|
|
// "c": {
|
|
// "d": {
|
|
// "e.txt": "eee"
|
|
// }
|
|
// },
|
|
// "f.txt": "ffff",
|
|
// "target": {
|
|
// "build_file": "???"
|
|
// },
|
|
// ".gitignore": IGNORE_RULE
|
|
// },
|
|
|
|
// }));
|
|
|
|
// const A_TXT: &'static str = "a.txt";
|
|
// const B_TXT: &'static str = "b.txt";
|
|
// const E_TXT: &'static str = "c/d/e.txt";
|
|
// const F_TXT: &'static str = "f.txt";
|
|
// const DOTGITIGNORE: &'static str = ".gitignore";
|
|
// const BUILD_FILE: &'static str = "target/build_file";
|
|
// let project_path = Path::new("project");
|
|
|
|
// // Set up git repository before creating the worktree.
|
|
// let work_dir = root.path().join("project");
|
|
// let mut repo = git_init(work_dir.as_path());
|
|
// repo.add_ignore_rule(IGNORE_RULE).unwrap();
|
|
// git_add(A_TXT, &repo);
|
|
// git_add(E_TXT, &repo);
|
|
// git_add(DOTGITIGNORE, &repo);
|
|
// git_commit("Initial commit", &repo);
|
|
|
|
// let tree = Worktree::local(
|
|
// build_client(cx),
|
|
// root.path(),
|
|
// true,
|
|
// Arc::new(RealFs),
|
|
// Default::default(),
|
|
// &mut cx.to_async(),
|
|
// )
|
|
// .await
|
|
// .unwrap();
|
|
|
|
// tree.flush_fs_events(cx).await;
|
|
// cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
|
// .await;
|
|
// deterministic.run_until_parked();
|
|
|
|
// // Check that the right git state is observed on startup
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let snapshot = tree.snapshot();
|
|
// assert_eq!(snapshot.repositories().count(), 1);
|
|
// let (dir, _) = snapshot.repositories().next().unwrap();
|
|
// assert_eq!(dir.as_ref(), Path::new("project"));
|
|
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(project_path.join(B_TXT)),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(project_path.join(F_TXT)),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// });
|
|
|
|
// // Modify a file in the working copy.
|
|
// std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
|
|
// tree.flush_fs_events(cx).await;
|
|
// deterministic.run_until_parked();
|
|
|
|
// // The worktree detects that the file's git status has changed.
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let snapshot = tree.snapshot();
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(project_path.join(A_TXT)),
|
|
// Some(GitFileStatus::Modified)
|
|
// );
|
|
// });
|
|
|
|
// // Create a commit in the git repository.
|
|
// git_add(A_TXT, &repo);
|
|
// git_add(B_TXT, &repo);
|
|
// git_commit("Committing modified and added", &repo);
|
|
// tree.flush_fs_events(cx).await;
|
|
// deterministic.run_until_parked();
|
|
|
|
// // The worktree detects that the files' git status have changed.
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let snapshot = tree.snapshot();
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(project_path.join(F_TXT)),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// assert_eq!(snapshot.status_for_file(project_path.join(B_TXT)), None);
|
|
// assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
|
|
// });
|
|
|
|
// // Modify files in the working copy and perform git operations on other files.
|
|
// git_reset(0, &repo);
|
|
// git_remove_index(Path::new(B_TXT), &repo);
|
|
// git_stash(&mut repo);
|
|
// std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
|
|
// std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
|
|
// tree.flush_fs_events(cx).await;
|
|
// deterministic.run_until_parked();
|
|
|
|
// // Check that more complex repo changes are tracked
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let snapshot = tree.snapshot();
|
|
|
|
// assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(project_path.join(B_TXT)),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(project_path.join(E_TXT)),
|
|
// Some(GitFileStatus::Modified)
|
|
// );
|
|
// });
|
|
|
|
// std::fs::remove_file(work_dir.join(B_TXT)).unwrap();
|
|
// std::fs::remove_dir_all(work_dir.join("c")).unwrap();
|
|
// std::fs::write(
|
|
// work_dir.join(DOTGITIGNORE),
|
|
// [IGNORE_RULE, "f.txt"].join("\n"),
|
|
// )
|
|
// .unwrap();
|
|
|
|
// git_add(Path::new(DOTGITIGNORE), &repo);
|
|
// git_commit("Committing modified git ignore", &repo);
|
|
|
|
// tree.flush_fs_events(cx).await;
|
|
// deterministic.run_until_parked();
|
|
|
|
// let mut renamed_dir_name = "first_directory/second_directory";
|
|
// const RENAMED_FILE: &'static str = "rf.txt";
|
|
|
|
// std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap();
|
|
// std::fs::write(
|
|
// work_dir.join(renamed_dir_name).join(RENAMED_FILE),
|
|
// "new-contents",
|
|
// )
|
|
// .unwrap();
|
|
|
|
// tree.flush_fs_events(cx).await;
|
|
// deterministic.run_until_parked();
|
|
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let snapshot = tree.snapshot();
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(&project_path.join(renamed_dir_name).join(RENAMED_FILE)),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// });
|
|
|
|
// renamed_dir_name = "new_first_directory/second_directory";
|
|
|
|
// std::fs::rename(
|
|
// work_dir.join("first_directory"),
|
|
// work_dir.join("new_first_directory"),
|
|
// )
|
|
// .unwrap();
|
|
|
|
// tree.flush_fs_events(cx).await;
|
|
// deterministic.run_until_parked();
|
|
|
|
// tree.read_with(cx, |tree, _cx| {
|
|
// let snapshot = tree.snapshot();
|
|
|
|
// assert_eq!(
|
|
// snapshot.status_for_file(
|
|
// project_path
|
|
// .join(Path::new(renamed_dir_name))
|
|
// .join(RENAMED_FILE)
|
|
// ),
|
|
// Some(GitFileStatus::Added)
|
|
// );
|
|
// });
|
|
// }
|
|
|
|
// #[gpui::test]
|
|
// async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
|
// let fs = FakeFs::new(cx.background());
|
|
// fs.insert_tree(
|
|
// "/root",
|
|
// json!({
|
|
// ".git": {},
|
|
// "a": {
|
|
// "b": {
|
|
// "c1.txt": "",
|
|
// "c2.txt": "",
|
|
// },
|
|
// "d": {
|
|
// "e1.txt": "",
|
|
// "e2.txt": "",
|
|
// "e3.txt": "",
|
|
// }
|
|
// },
|
|
// "f": {
|
|
// "no-status.txt": ""
|
|
// },
|
|
// "g": {
|
|
// "h1.txt": "",
|
|
// "h2.txt": ""
|
|
// },
|
|
|
|
// }),
|
|
// )
|
|
// .await;
|
|
|
|
// fs.set_status_for_repo_via_git_operation(
|
|
// &Path::new("/root/.git"),
|
|
// &[
|
|
// (Path::new("a/b/c1.txt"), GitFileStatus::Added),
|
|
// (Path::new("a/d/e2.txt"), GitFileStatus::Modified),
|
|
// (Path::new("g/h2.txt"), GitFileStatus::Conflict),
|
|
// ],
|
|
// );
|
|
|
|
// 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;
|
|
|
|
// cx.foreground().run_until_parked();
|
|
// let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
|
|
|
|
// check_propagated_statuses(
|
|
// &snapshot,
|
|
// &[
|
|
// (Path::new(""), Some(GitFileStatus::Conflict)),
|
|
// (Path::new("a"), Some(GitFileStatus::Modified)),
|
|
// (Path::new("a/b"), Some(GitFileStatus::Added)),
|
|
// (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
|
|
// (Path::new("a/b/c2.txt"), None),
|
|
// (Path::new("a/d"), Some(GitFileStatus::Modified)),
|
|
// (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
|
|
// (Path::new("f"), None),
|
|
// (Path::new("f/no-status.txt"), None),
|
|
// (Path::new("g"), Some(GitFileStatus::Conflict)),
|
|
// (Path::new("g/h2.txt"), Some(GitFileStatus::Conflict)),
|
|
// ],
|
|
// );
|
|
|
|
// check_propagated_statuses(
|
|
// &snapshot,
|
|
// &[
|
|
// (Path::new("a/b"), Some(GitFileStatus::Added)),
|
|
// (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
|
|
// (Path::new("a/b/c2.txt"), None),
|
|
// (Path::new("a/d"), Some(GitFileStatus::Modified)),
|
|
// (Path::new("a/d/e1.txt"), None),
|
|
// (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
|
|
// (Path::new("f"), None),
|
|
// (Path::new("f/no-status.txt"), None),
|
|
// (Path::new("g"), Some(GitFileStatus::Conflict)),
|
|
// ],
|
|
// );
|
|
|
|
// check_propagated_statuses(
|
|
// &snapshot,
|
|
// &[
|
|
// (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
|
|
// (Path::new("a/b/c2.txt"), None),
|
|
// (Path::new("a/d/e1.txt"), None),
|
|
// (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
|
|
// (Path::new("f/no-status.txt"), None),
|
|
// ],
|
|
// );
|
|
|
|
// #[track_caller]
|
|
// fn check_propagated_statuses(
|
|
// snapshot: &Snapshot,
|
|
// expected_statuses: &[(&Path, Option<GitFileStatus>)],
|
|
// ) {
|
|
// let mut entries = expected_statuses
|
|
// .iter()
|
|
// .map(|(path, _)| snapshot.entry_for_path(path).unwrap().clone())
|
|
// .collect::<Vec<_>>();
|
|
// snapshot.propagate_git_statuses(&mut entries);
|
|
// assert_eq!(
|
|
// entries
|
|
// .iter()
|
|
// .map(|e| (e.path.as_ref(), e.git_status))
|
|
// .collect::<Vec<_>>(),
|
|
// expected_statuses
|
|
// );
|
|
// }
|
|
// }
|
|
|
|
// fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
|
|
// let http_client = FakeHttpClient::with_404_response();
|
|
// cx.read(|cx| Client::new(http_client, cx))
|
|
// }
|
|
|
|
// #[track_caller]
|
|
// fn git_init(path: &Path) -> git2::Repository {
|
|
// git2::Repository::init(path).expect("Failed to initialize git repository")
|
|
// }
|
|
|
|
// #[track_caller]
|
|
// fn git_add<P: AsRef<Path>>(path: P, repo: &git2::Repository) {
|
|
// let path = path.as_ref();
|
|
// let mut index = repo.index().expect("Failed to get index");
|
|
// index.add_path(path).expect("Failed to add a.txt");
|
|
// index.write().expect("Failed to write index");
|
|
// }
|
|
|
|
// #[track_caller]
|
|
// fn git_remove_index(path: &Path, repo: &git2::Repository) {
|
|
// let mut index = repo.index().expect("Failed to get index");
|
|
// index.remove_path(path).expect("Failed to add a.txt");
|
|
// index.write().expect("Failed to write index");
|
|
// }
|
|
|
|
// #[track_caller]
|
|
// fn git_commit(msg: &'static str, repo: &git2::Repository) {
|
|
// use git2::Signature;
|
|
|
|
// let signature = Signature::now("test", "test@zed.dev").unwrap();
|
|
// let oid = repo.index().unwrap().write_tree().unwrap();
|
|
// let tree = repo.find_tree(oid).unwrap();
|
|
// if let Some(head) = repo.head().ok() {
|
|
// let parent_obj = head.peel(git2::ObjectType::Commit).unwrap();
|
|
|
|
// let parent_commit = parent_obj.as_commit().unwrap();
|
|
|
|
// repo.commit(
|
|
// Some("HEAD"),
|
|
// &signature,
|
|
// &signature,
|
|
// msg,
|
|
// &tree,
|
|
// &[parent_commit],
|
|
// )
|
|
// .expect("Failed to commit with parent");
|
|
// } else {
|
|
// repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[])
|
|
// .expect("Failed to commit");
|
|
// }
|
|
// }
|
|
|
|
// #[track_caller]
|
|
// fn git_stash(repo: &mut git2::Repository) {
|
|
// use git2::Signature;
|
|
|
|
// let signature = Signature::now("test", "test@zed.dev").unwrap();
|
|
// repo.stash_save(&signature, "N/A", None)
|
|
// .expect("Failed to stash");
|
|
// }
|
|
|
|
// #[track_caller]
|
|
// fn git_reset(offset: usize, repo: &git2::Repository) {
|
|
// let head = repo.head().expect("Couldn't get repo head");
|
|
// let object = head.peel(git2::ObjectType::Commit).unwrap();
|
|
// let commit = object.as_commit().unwrap();
|
|
// let new_head = commit
|
|
// .parents()
|
|
// .inspect(|parnet| {
|
|
// parnet.message();
|
|
// })
|
|
// .skip(offset)
|
|
// .next()
|
|
// .expect("Not enough history");
|
|
// repo.reset(&new_head.as_object(), git2::ResetType::Soft, None)
|
|
// .expect("Could not reset");
|
|
// }
|
|
|
|
// #[allow(dead_code)]
|
|
// #[track_caller]
|
|
// fn git_status(repo: &git2::Repository) -> collections::HashMap<String, git2::Status> {
|
|
// repo.statuses(None)
|
|
// .unwrap()
|
|
// .iter()
|
|
// .map(|status| (status.path().unwrap().to_string(), status.status()))
|
|
// .collect()
|
|
// }
|