// 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![ // 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![ // 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![Path::new("b/c/d"),] // ); // assert_eq!( // tree.descendent_entries(true, false, Path::new("b")) // .map(|entry| entry.path.as_ref()) // .collect::>(), // 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::::new() // ); // assert_eq!( // tree.descendent_entries(true, false, Path::new("g")) // .map(|entry| entry.path.as_ref()) // .collect::>(), // 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::::new() // ); // assert_eq!( // tree.descendent_entries(false, true, Path::new("i")) // .map(|entry| entry.path.as_ref()) // .collect::>(), // vec![Path::new("i/j/k")] // ); // assert_eq!( // tree.descendent_entries(true, false, Path::new("i")) // .map(|entry| entry.path.as_ref()) // .collect::>(), // vec![Path::new("i"), Path::new("i/l"),] // ); // }) // } // #[gpui::test(iterations = 10)] // async fn test_circular_symlinks(executor: Arc, 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![ // 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![ // 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![ // (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![ // (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![ // (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![ // (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![ // (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![ // (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::>(), // &[ // (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::>(), // &[ // (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::>(), // snapshot2.entries(true).collect::>() // ); // } // #[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; // 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::>(), // final_snapshot.entries(true).collect::>(), // "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; // 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::>(); // { // 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::>(), // snapshot // .entries(true) // .map(ignore_pending_dir) // .collect::>(), // "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) { // let mut entries = tree.entries(true).cloned().collect::>(); // 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::>(); // assert_eq!(entries, new_entries, "incorrect changes: {:?}", changes); // } // }) // .detach(); // } // fn randomly_mutate_worktree( // worktree: &mut Worktree, // rng: &mut impl Rng, // cx: &mut ModelContext, // ) -> Task> { // 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, // 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::>(); // let subfiles = files // .iter() // .filter(|d| d.starts_with(&ignore_dir_path)) // .cloned() // .collect::>(); // 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::>(); // 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![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, 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)], // ) { // let mut entries = expected_statuses // .iter() // .map(|(path, _)| snapshot.entry_for_path(path).unwrap().clone()) // .collect::>(); // snapshot.propagate_git_statuses(&mut entries); // assert_eq!( // entries // .iter() // .map(|e| (e.path.as_ref(), e.git_status)) // .collect::>(), // expected_statuses // ); // } // } // fn build_client(cx: &mut TestAppContext) -> Arc { // 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>(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 { // repo.statuses(None) // .unwrap() // .iter() // .map(|status| (status.path().unwrap().to_string(), status.status())) // .collect() // }