// 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()
// }
