worktree_tests.rs

   1// use crate::{
   2//     worktree::{Event, Snapshot, WorktreeModelHandle},
   3//     Entry, EntryKind, PathChange, Worktree,
   4// };
   5// use anyhow::Result;
   6// use client2::Client;
   7// use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
   8// use git::GITIGNORE;
   9// use gpui::{executor::Deterministic, ModelContext, Task, TestAppContext};
  10// use parking_lot::Mutex;
  11// use postage::stream::Stream;
  12// use pretty_assertions::assert_eq;
  13// use rand::prelude::*;
  14// use serde_json::json;
  15// use std::{
  16//     env,
  17//     fmt::Write,
  18//     mem,
  19//     path::{Path, PathBuf},
  20//     sync::Arc,
  21// };
  22// use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
  23
  24// #[gpui::test]
  25// async fn test_traversal(cx: &mut TestAppContext) {
  26//     let fs = FakeFs::new(cx.background());
  27//     fs.insert_tree(
  28//         "/root",
  29//         json!({
  30//            ".gitignore": "a/b\n",
  31//            "a": {
  32//                "b": "",
  33//                "c": "",
  34//            }
  35//         }),
  36//     )
  37//     .await;
  38
  39//     let tree = Worktree::local(
  40//         build_client(cx),
  41//         Path::new("/root"),
  42//         true,
  43//         fs,
  44//         Default::default(),
  45//         &mut cx.to_async(),
  46//     )
  47//     .await
  48//     .unwrap();
  49//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
  50//         .await;
  51
  52//     tree.read_with(cx, |tree, _| {
  53//         assert_eq!(
  54//             tree.entries(false)
  55//                 .map(|entry| entry.path.as_ref())
  56//                 .collect::<Vec<_>>(),
  57//             vec![
  58//                 Path::new(""),
  59//                 Path::new(".gitignore"),
  60//                 Path::new("a"),
  61//                 Path::new("a/c"),
  62//             ]
  63//         );
  64//         assert_eq!(
  65//             tree.entries(true)
  66//                 .map(|entry| entry.path.as_ref())
  67//                 .collect::<Vec<_>>(),
  68//             vec![
  69//                 Path::new(""),
  70//                 Path::new(".gitignore"),
  71//                 Path::new("a"),
  72//                 Path::new("a/b"),
  73//                 Path::new("a/c"),
  74//             ]
  75//         );
  76//     })
  77// }
  78
  79// #[gpui::test]
  80// async fn test_descendent_entries(cx: &mut TestAppContext) {
  81//     let fs = FakeFs::new(cx.background());
  82//     fs.insert_tree(
  83//         "/root",
  84//         json!({
  85//             "a": "",
  86//             "b": {
  87//                "c": {
  88//                    "d": ""
  89//                },
  90//                "e": {}
  91//             },
  92//             "f": "",
  93//             "g": {
  94//                 "h": {}
  95//             },
  96//             "i": {
  97//                 "j": {
  98//                     "k": ""
  99//                 },
 100//                 "l": {
 101
 102//                 }
 103//             },
 104//             ".gitignore": "i/j\n",
 105//         }),
 106//     )
 107//     .await;
 108
 109//     let tree = Worktree::local(
 110//         build_client(cx),
 111//         Path::new("/root"),
 112//         true,
 113//         fs,
 114//         Default::default(),
 115//         &mut cx.to_async(),
 116//     )
 117//     .await
 118//     .unwrap();
 119//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
 120//         .await;
 121
 122//     tree.read_with(cx, |tree, _| {
 123//         assert_eq!(
 124//             tree.descendent_entries(false, false, Path::new("b"))
 125//                 .map(|entry| entry.path.as_ref())
 126//                 .collect::<Vec<_>>(),
 127//             vec![Path::new("b/c/d"),]
 128//         );
 129//         assert_eq!(
 130//             tree.descendent_entries(true, false, Path::new("b"))
 131//                 .map(|entry| entry.path.as_ref())
 132//                 .collect::<Vec<_>>(),
 133//             vec![
 134//                 Path::new("b"),
 135//                 Path::new("b/c"),
 136//                 Path::new("b/c/d"),
 137//                 Path::new("b/e"),
 138//             ]
 139//         );
 140
 141//         assert_eq!(
 142//             tree.descendent_entries(false, false, Path::new("g"))
 143//                 .map(|entry| entry.path.as_ref())
 144//                 .collect::<Vec<_>>(),
 145//             Vec::<PathBuf>::new()
 146//         );
 147//         assert_eq!(
 148//             tree.descendent_entries(true, false, Path::new("g"))
 149//                 .map(|entry| entry.path.as_ref())
 150//                 .collect::<Vec<_>>(),
 151//             vec![Path::new("g"), Path::new("g/h"),]
 152//         );
 153//     });
 154
 155//     // Expand gitignored directory.
 156//     tree.read_with(cx, |tree, _| {
 157//         tree.as_local()
 158//             .unwrap()
 159//             .refresh_entries_for_paths(vec![Path::new("i/j").into()])
 160//     })
 161//     .recv()
 162//     .await;
 163
 164//     tree.read_with(cx, |tree, _| {
 165//         assert_eq!(
 166//             tree.descendent_entries(false, false, Path::new("i"))
 167//                 .map(|entry| entry.path.as_ref())
 168//                 .collect::<Vec<_>>(),
 169//             Vec::<PathBuf>::new()
 170//         );
 171//         assert_eq!(
 172//             tree.descendent_entries(false, true, Path::new("i"))
 173//                 .map(|entry| entry.path.as_ref())
 174//                 .collect::<Vec<_>>(),
 175//             vec![Path::new("i/j/k")]
 176//         );
 177//         assert_eq!(
 178//             tree.descendent_entries(true, false, Path::new("i"))
 179//                 .map(|entry| entry.path.as_ref())
 180//                 .collect::<Vec<_>>(),
 181//             vec![Path::new("i"), Path::new("i/l"),]
 182//         );
 183//     })
 184// }
 185
 186// #[gpui::test(iterations = 10)]
 187// async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
 188//     let fs = FakeFs::new(cx.background());
 189//     fs.insert_tree(
 190//         "/root",
 191//         json!({
 192//             "lib": {
 193//                 "a": {
 194//                     "a.txt": ""
 195//                 },
 196//                 "b": {
 197//                     "b.txt": ""
 198//                 }
 199//             }
 200//         }),
 201//     )
 202//     .await;
 203//     fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
 204//     fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
 205
 206//     let tree = Worktree::local(
 207//         build_client(cx),
 208//         Path::new("/root"),
 209//         true,
 210//         fs.clone(),
 211//         Default::default(),
 212//         &mut cx.to_async(),
 213//     )
 214//     .await
 215//     .unwrap();
 216
 217//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
 218//         .await;
 219
 220//     tree.read_with(cx, |tree, _| {
 221//         assert_eq!(
 222//             tree.entries(false)
 223//                 .map(|entry| entry.path.as_ref())
 224//                 .collect::<Vec<_>>(),
 225//             vec![
 226//                 Path::new(""),
 227//                 Path::new("lib"),
 228//                 Path::new("lib/a"),
 229//                 Path::new("lib/a/a.txt"),
 230//                 Path::new("lib/a/lib"),
 231//                 Path::new("lib/b"),
 232//                 Path::new("lib/b/b.txt"),
 233//                 Path::new("lib/b/lib"),
 234//             ]
 235//         );
 236//     });
 237
 238//     fs.rename(
 239//         Path::new("/root/lib/a/lib"),
 240//         Path::new("/root/lib/a/lib-2"),
 241//         Default::default(),
 242//     )
 243//     .await
 244//     .unwrap();
 245//     executor.run_until_parked();
 246//     tree.read_with(cx, |tree, _| {
 247//         assert_eq!(
 248//             tree.entries(false)
 249//                 .map(|entry| entry.path.as_ref())
 250//                 .collect::<Vec<_>>(),
 251//             vec![
 252//                 Path::new(""),
 253//                 Path::new("lib"),
 254//                 Path::new("lib/a"),
 255//                 Path::new("lib/a/a.txt"),
 256//                 Path::new("lib/a/lib-2"),
 257//                 Path::new("lib/b"),
 258//                 Path::new("lib/b/b.txt"),
 259//                 Path::new("lib/b/lib"),
 260//             ]
 261//         );
 262//     });
 263// }
 264
 265// #[gpui::test]
 266// async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
 267//     let fs = FakeFs::new(cx.background());
 268//     fs.insert_tree(
 269//         "/root",
 270//         json!({
 271//             "dir1": {
 272//                 "deps": {
 273//                     // symlinks here
 274//                 },
 275//                 "src": {
 276//                     "a.rs": "",
 277//                     "b.rs": "",
 278//                 },
 279//             },
 280//             "dir2": {
 281//                 "src": {
 282//                     "c.rs": "",
 283//                     "d.rs": "",
 284//                 }
 285//             },
 286//             "dir3": {
 287//                 "deps": {},
 288//                 "src": {
 289//                     "e.rs": "",
 290//                     "f.rs": "",
 291//                 },
 292//             }
 293//         }),
 294//     )
 295//     .await;
 296
 297//     // These symlinks point to directories outside of the worktree's root, dir1.
 298//     fs.insert_symlink("/root/dir1/deps/dep-dir2", "../../dir2".into())
 299//         .await;
 300//     fs.insert_symlink("/root/dir1/deps/dep-dir3", "../../dir3".into())
 301//         .await;
 302
 303//     let tree = Worktree::local(
 304//         build_client(cx),
 305//         Path::new("/root/dir1"),
 306//         true,
 307//         fs.clone(),
 308//         Default::default(),
 309//         &mut cx.to_async(),
 310//     )
 311//     .await
 312//     .unwrap();
 313
 314//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
 315//         .await;
 316
 317//     let tree_updates = Arc::new(Mutex::new(Vec::new()));
 318//     tree.update(cx, |_, cx| {
 319//         let tree_updates = tree_updates.clone();
 320//         cx.subscribe(&tree, move |_, _, event, _| {
 321//             if let Event::UpdatedEntries(update) = event {
 322//                 tree_updates.lock().extend(
 323//                     update
 324//                         .iter()
 325//                         .map(|(path, _, change)| (path.clone(), *change)),
 326//                 );
 327//             }
 328//         })
 329//         .detach();
 330//     });
 331
 332//     // The symlinked directories are not scanned by default.
 333//     tree.read_with(cx, |tree, _| {
 334//         assert_eq!(
 335//             tree.entries(true)
 336//                 .map(|entry| (entry.path.as_ref(), entry.is_external))
 337//                 .collect::<Vec<_>>(),
 338//             vec![
 339//                 (Path::new(""), false),
 340//                 (Path::new("deps"), false),
 341//                 (Path::new("deps/dep-dir2"), true),
 342//                 (Path::new("deps/dep-dir3"), true),
 343//                 (Path::new("src"), false),
 344//                 (Path::new("src/a.rs"), false),
 345//                 (Path::new("src/b.rs"), false),
 346//             ]
 347//         );
 348
 349//         assert_eq!(
 350//             tree.entry_for_path("deps/dep-dir2").unwrap().kind,
 351//             EntryKind::UnloadedDir
 352//         );
 353//     });
 354
 355//     // Expand one of the symlinked directories.
 356//     tree.read_with(cx, |tree, _| {
 357//         tree.as_local()
 358//             .unwrap()
 359//             .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()])
 360//     })
 361//     .recv()
 362//     .await;
 363
 364//     // The expanded directory's contents are loaded. Subdirectories are
 365//     // not scanned yet.
 366//     tree.read_with(cx, |tree, _| {
 367//         assert_eq!(
 368//             tree.entries(true)
 369//                 .map(|entry| (entry.path.as_ref(), entry.is_external))
 370//                 .collect::<Vec<_>>(),
 371//             vec![
 372//                 (Path::new(""), false),
 373//                 (Path::new("deps"), false),
 374//                 (Path::new("deps/dep-dir2"), true),
 375//                 (Path::new("deps/dep-dir3"), true),
 376//                 (Path::new("deps/dep-dir3/deps"), true),
 377//                 (Path::new("deps/dep-dir3/src"), true),
 378//                 (Path::new("src"), false),
 379//                 (Path::new("src/a.rs"), false),
 380//                 (Path::new("src/b.rs"), false),
 381//             ]
 382//         );
 383//     });
 384//     assert_eq!(
 385//         mem::take(&mut *tree_updates.lock()),
 386//         &[
 387//             (Path::new("deps/dep-dir3").into(), PathChange::Loaded),
 388//             (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded),
 389//             (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded)
 390//         ]
 391//     );
 392
 393//     // Expand a subdirectory of one of the symlinked directories.
 394//     tree.read_with(cx, |tree, _| {
 395//         tree.as_local()
 396//             .unwrap()
 397//             .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()])
 398//     })
 399//     .recv()
 400//     .await;
 401
 402//     // The expanded subdirectory's contents are loaded.
 403//     tree.read_with(cx, |tree, _| {
 404//         assert_eq!(
 405//             tree.entries(true)
 406//                 .map(|entry| (entry.path.as_ref(), entry.is_external))
 407//                 .collect::<Vec<_>>(),
 408//             vec![
 409//                 (Path::new(""), false),
 410//                 (Path::new("deps"), false),
 411//                 (Path::new("deps/dep-dir2"), true),
 412//                 (Path::new("deps/dep-dir3"), true),
 413//                 (Path::new("deps/dep-dir3/deps"), true),
 414//                 (Path::new("deps/dep-dir3/src"), true),
 415//                 (Path::new("deps/dep-dir3/src/e.rs"), true),
 416//                 (Path::new("deps/dep-dir3/src/f.rs"), true),
 417//                 (Path::new("src"), false),
 418//                 (Path::new("src/a.rs"), false),
 419//                 (Path::new("src/b.rs"), false),
 420//             ]
 421//         );
 422//     });
 423
 424//     assert_eq!(
 425//         mem::take(&mut *tree_updates.lock()),
 426//         &[
 427//             (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded),
 428//             (
 429//                 Path::new("deps/dep-dir3/src/e.rs").into(),
 430//                 PathChange::Loaded
 431//             ),
 432//             (
 433//                 Path::new("deps/dep-dir3/src/f.rs").into(),
 434//                 PathChange::Loaded
 435//             )
 436//         ]
 437//     );
 438// }
 439
 440// #[gpui::test]
 441// async fn test_open_gitignored_files(cx: &mut TestAppContext) {
 442//     let fs = FakeFs::new(cx.background());
 443//     fs.insert_tree(
 444//         "/root",
 445//         json!({
 446//             ".gitignore": "node_modules\n",
 447//             "one": {
 448//                 "node_modules": {
 449//                     "a": {
 450//                         "a1.js": "a1",
 451//                         "a2.js": "a2",
 452//                     },
 453//                     "b": {
 454//                         "b1.js": "b1",
 455//                         "b2.js": "b2",
 456//                     },
 457//                     "c": {
 458//                         "c1.js": "c1",
 459//                         "c2.js": "c2",
 460//                     }
 461//                 },
 462//             },
 463//             "two": {
 464//                 "x.js": "",
 465//                 "y.js": "",
 466//             },
 467//         }),
 468//     )
 469//     .await;
 470
 471//     let tree = Worktree::local(
 472//         build_client(cx),
 473//         Path::new("/root"),
 474//         true,
 475//         fs.clone(),
 476//         Default::default(),
 477//         &mut cx.to_async(),
 478//     )
 479//     .await
 480//     .unwrap();
 481
 482//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
 483//         .await;
 484
 485//     tree.read_with(cx, |tree, _| {
 486//         assert_eq!(
 487//             tree.entries(true)
 488//                 .map(|entry| (entry.path.as_ref(), entry.is_ignored))
 489//                 .collect::<Vec<_>>(),
 490//             vec![
 491//                 (Path::new(""), false),
 492//                 (Path::new(".gitignore"), false),
 493//                 (Path::new("one"), false),
 494//                 (Path::new("one/node_modules"), true),
 495//                 (Path::new("two"), false),
 496//                 (Path::new("two/x.js"), false),
 497//                 (Path::new("two/y.js"), false),
 498//             ]
 499//         );
 500//     });
 501
 502//     // Open a file that is nested inside of a gitignored directory that
 503//     // has not yet been expanded.
 504//     let prev_read_dir_count = fs.read_dir_call_count();
 505//     let buffer = tree
 506//         .update(cx, |tree, cx| {
 507//             tree.as_local_mut()
 508//                 .unwrap()
 509//                 .load_buffer(0, "one/node_modules/b/b1.js".as_ref(), cx)
 510//         })
 511//         .await
 512//         .unwrap();
 513
 514//     tree.read_with(cx, |tree, cx| {
 515//         assert_eq!(
 516//             tree.entries(true)
 517//                 .map(|entry| (entry.path.as_ref(), entry.is_ignored))
 518//                 .collect::<Vec<_>>(),
 519//             vec![
 520//                 (Path::new(""), false),
 521//                 (Path::new(".gitignore"), false),
 522//                 (Path::new("one"), false),
 523//                 (Path::new("one/node_modules"), true),
 524//                 (Path::new("one/node_modules/a"), true),
 525//                 (Path::new("one/node_modules/b"), true),
 526//                 (Path::new("one/node_modules/b/b1.js"), true),
 527//                 (Path::new("one/node_modules/b/b2.js"), true),
 528//                 (Path::new("one/node_modules/c"), true),
 529//                 (Path::new("two"), false),
 530//                 (Path::new("two/x.js"), false),
 531//                 (Path::new("two/y.js"), false),
 532//             ]
 533//         );
 534
 535//         assert_eq!(
 536//             buffer.read(cx).file().unwrap().path().as_ref(),
 537//             Path::new("one/node_modules/b/b1.js")
 538//         );
 539
 540//         // Only the newly-expanded directories are scanned.
 541//         assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 2);
 542//     });
 543
 544//     // Open another file in a different subdirectory of the same
 545//     // gitignored directory.
 546//     let prev_read_dir_count = fs.read_dir_call_count();
 547//     let buffer = tree
 548//         .update(cx, |tree, cx| {
 549//             tree.as_local_mut()
 550//                 .unwrap()
 551//                 .load_buffer(0, "one/node_modules/a/a2.js".as_ref(), cx)
 552//         })
 553//         .await
 554//         .unwrap();
 555
 556//     tree.read_with(cx, |tree, cx| {
 557//         assert_eq!(
 558//             tree.entries(true)
 559//                 .map(|entry| (entry.path.as_ref(), entry.is_ignored))
 560//                 .collect::<Vec<_>>(),
 561//             vec![
 562//                 (Path::new(""), false),
 563//                 (Path::new(".gitignore"), false),
 564//                 (Path::new("one"), false),
 565//                 (Path::new("one/node_modules"), true),
 566//                 (Path::new("one/node_modules/a"), true),
 567//                 (Path::new("one/node_modules/a/a1.js"), true),
 568//                 (Path::new("one/node_modules/a/a2.js"), true),
 569//                 (Path::new("one/node_modules/b"), true),
 570//                 (Path::new("one/node_modules/b/b1.js"), true),
 571//                 (Path::new("one/node_modules/b/b2.js"), true),
 572//                 (Path::new("one/node_modules/c"), true),
 573//                 (Path::new("two"), false),
 574//                 (Path::new("two/x.js"), false),
 575//                 (Path::new("two/y.js"), false),
 576//             ]
 577//         );
 578
 579//         assert_eq!(
 580//             buffer.read(cx).file().unwrap().path().as_ref(),
 581//             Path::new("one/node_modules/a/a2.js")
 582//         );
 583
 584//         // Only the newly-expanded directory is scanned.
 585//         assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
 586//     });
 587
 588//     // No work happens when files and directories change within an unloaded directory.
 589//     let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count();
 590//     fs.create_dir("/root/one/node_modules/c/lib".as_ref())
 591//         .await
 592//         .unwrap();
 593//     cx.foreground().run_until_parked();
 594//     assert_eq!(
 595//         fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count,
 596//         0
 597//     );
 598// }
 599
 600// #[gpui::test]
 601// async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
 602//     let fs = FakeFs::new(cx.background());
 603//     fs.insert_tree(
 604//         "/root",
 605//         json!({
 606//             ".gitignore": "node_modules\n",
 607//             "a": {
 608//                 "a.js": "",
 609//             },
 610//             "b": {
 611//                 "b.js": "",
 612//             },
 613//             "node_modules": {
 614//                 "c": {
 615//                     "c.js": "",
 616//                 },
 617//                 "d": {
 618//                     "d.js": "",
 619//                     "e": {
 620//                         "e1.js": "",
 621//                         "e2.js": "",
 622//                     },
 623//                     "f": {
 624//                         "f1.js": "",
 625//                         "f2.js": "",
 626//                     }
 627//                 },
 628//             },
 629//         }),
 630//     )
 631//     .await;
 632
 633//     let tree = Worktree::local(
 634//         build_client(cx),
 635//         Path::new("/root"),
 636//         true,
 637//         fs.clone(),
 638//         Default::default(),
 639//         &mut cx.to_async(),
 640//     )
 641//     .await
 642//     .unwrap();
 643
 644//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
 645//         .await;
 646
 647//     // Open a file within the gitignored directory, forcing some of its
 648//     // subdirectories to be read, but not all.
 649//     let read_dir_count_1 = fs.read_dir_call_count();
 650//     tree.read_with(cx, |tree, _| {
 651//         tree.as_local()
 652//             .unwrap()
 653//             .refresh_entries_for_paths(vec![Path::new("node_modules/d/d.js").into()])
 654//     })
 655//     .recv()
 656//     .await;
 657
 658//     // Those subdirectories are now loaded.
 659//     tree.read_with(cx, |tree, _| {
 660//         assert_eq!(
 661//             tree.entries(true)
 662//                 .map(|e| (e.path.as_ref(), e.is_ignored))
 663//                 .collect::<Vec<_>>(),
 664//             &[
 665//                 (Path::new(""), false),
 666//                 (Path::new(".gitignore"), false),
 667//                 (Path::new("a"), false),
 668//                 (Path::new("a/a.js"), false),
 669//                 (Path::new("b"), false),
 670//                 (Path::new("b/b.js"), false),
 671//                 (Path::new("node_modules"), true),
 672//                 (Path::new("node_modules/c"), true),
 673//                 (Path::new("node_modules/d"), true),
 674//                 (Path::new("node_modules/d/d.js"), true),
 675//                 (Path::new("node_modules/d/e"), true),
 676//                 (Path::new("node_modules/d/f"), true),
 677//             ]
 678//         );
 679//     });
 680//     let read_dir_count_2 = fs.read_dir_call_count();
 681//     assert_eq!(read_dir_count_2 - read_dir_count_1, 2);
 682
 683//     // Update the gitignore so that node_modules is no longer ignored,
 684//     // but a subdirectory is ignored
 685//     fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
 686//         .await
 687//         .unwrap();
 688//     cx.foreground().run_until_parked();
 689
 690//     // All of the directories that are no longer ignored are now loaded.
 691//     tree.read_with(cx, |tree, _| {
 692//         assert_eq!(
 693//             tree.entries(true)
 694//                 .map(|e| (e.path.as_ref(), e.is_ignored))
 695//                 .collect::<Vec<_>>(),
 696//             &[
 697//                 (Path::new(""), false),
 698//                 (Path::new(".gitignore"), false),
 699//                 (Path::new("a"), false),
 700//                 (Path::new("a/a.js"), false),
 701//                 (Path::new("b"), false),
 702//                 (Path::new("b/b.js"), false),
 703//                 // This directory is no longer ignored
 704//                 (Path::new("node_modules"), false),
 705//                 (Path::new("node_modules/c"), false),
 706//                 (Path::new("node_modules/c/c.js"), false),
 707//                 (Path::new("node_modules/d"), false),
 708//                 (Path::new("node_modules/d/d.js"), false),
 709//                 // This subdirectory is now ignored
 710//                 (Path::new("node_modules/d/e"), true),
 711//                 (Path::new("node_modules/d/f"), false),
 712//                 (Path::new("node_modules/d/f/f1.js"), false),
 713//                 (Path::new("node_modules/d/f/f2.js"), false),
 714//             ]
 715//         );
 716//     });
 717
 718//     // Each of the newly-loaded directories is scanned only once.
 719//     let read_dir_count_3 = fs.read_dir_call_count();
 720//     assert_eq!(read_dir_count_3 - read_dir_count_2, 2);
 721// }
 722
 723// #[gpui::test(iterations = 10)]
 724// async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
 725//     let fs = FakeFs::new(cx.background());
 726//     fs.insert_tree(
 727//         "/root",
 728//         json!({
 729//             ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
 730//             "tree": {
 731//                 ".git": {},
 732//                 ".gitignore": "ignored-dir\n",
 733//                 "tracked-dir": {
 734//                     "tracked-file1": "",
 735//                     "ancestor-ignored-file1": "",
 736//                 },
 737//                 "ignored-dir": {
 738//                     "ignored-file1": ""
 739//                 }
 740//             }
 741//         }),
 742//     )
 743//     .await;
 744
 745//     let tree = Worktree::local(
 746//         build_client(cx),
 747//         "/root/tree".as_ref(),
 748//         true,
 749//         fs.clone(),
 750//         Default::default(),
 751//         &mut cx.to_async(),
 752//     )
 753//     .await
 754//     .unwrap();
 755//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
 756//         .await;
 757
 758//     tree.read_with(cx, |tree, _| {
 759//         tree.as_local()
 760//             .unwrap()
 761//             .refresh_entries_for_paths(vec![Path::new("ignored-dir").into()])
 762//     })
 763//     .recv()
 764//     .await;
 765
 766//     cx.read(|cx| {
 767//         let tree = tree.read(cx);
 768//         assert!(
 769//             !tree
 770//                 .entry_for_path("tracked-dir/tracked-file1")
 771//                 .unwrap()
 772//                 .is_ignored
 773//         );
 774//         assert!(
 775//             tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
 776//                 .unwrap()
 777//                 .is_ignored
 778//         );
 779//         assert!(
 780//             tree.entry_for_path("ignored-dir/ignored-file1")
 781//                 .unwrap()
 782//                 .is_ignored
 783//         );
 784//     });
 785
 786//     fs.create_file(
 787//         "/root/tree/tracked-dir/tracked-file2".as_ref(),
 788//         Default::default(),
 789//     )
 790//     .await
 791//     .unwrap();
 792//     fs.create_file(
 793//         "/root/tree/tracked-dir/ancestor-ignored-file2".as_ref(),
 794//         Default::default(),
 795//     )
 796//     .await
 797//     .unwrap();
 798//     fs.create_file(
 799//         "/root/tree/ignored-dir/ignored-file2".as_ref(),
 800//         Default::default(),
 801//     )
 802//     .await
 803//     .unwrap();
 804
 805//     cx.foreground().run_until_parked();
 806//     cx.read(|cx| {
 807//         let tree = tree.read(cx);
 808//         assert!(
 809//             !tree
 810//                 .entry_for_path("tracked-dir/tracked-file2")
 811//                 .unwrap()
 812//                 .is_ignored
 813//         );
 814//         assert!(
 815//             tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
 816//                 .unwrap()
 817//                 .is_ignored
 818//         );
 819//         assert!(
 820//             tree.entry_for_path("ignored-dir/ignored-file2")
 821//                 .unwrap()
 822//                 .is_ignored
 823//         );
 824//         assert!(tree.entry_for_path(".git").unwrap().is_ignored);
 825//     });
 826// }
 827
 828// #[gpui::test]
 829// async fn test_write_file(cx: &mut TestAppContext) {
 830//     let dir = temp_tree(json!({
 831//         ".git": {},
 832//         ".gitignore": "ignored-dir\n",
 833//         "tracked-dir": {},
 834//         "ignored-dir": {}
 835//     }));
 836
 837//     let tree = Worktree::local(
 838//         build_client(cx),
 839//         dir.path(),
 840//         true,
 841//         Arc::new(RealFs),
 842//         Default::default(),
 843//         &mut cx.to_async(),
 844//     )
 845//     .await
 846//     .unwrap();
 847//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
 848//         .await;
 849//     tree.flush_fs_events(cx).await;
 850
 851//     tree.update(cx, |tree, cx| {
 852//         tree.as_local().unwrap().write_file(
 853//             Path::new("tracked-dir/file.txt"),
 854//             "hello".into(),
 855//             Default::default(),
 856//             cx,
 857//         )
 858//     })
 859//     .await
 860//     .unwrap();
 861//     tree.update(cx, |tree, cx| {
 862//         tree.as_local().unwrap().write_file(
 863//             Path::new("ignored-dir/file.txt"),
 864//             "world".into(),
 865//             Default::default(),
 866//             cx,
 867//         )
 868//     })
 869//     .await
 870//     .unwrap();
 871
 872//     tree.read_with(cx, |tree, _| {
 873//         let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
 874//         let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
 875//         assert!(!tracked.is_ignored);
 876//         assert!(ignored.is_ignored);
 877//     });
 878// }
 879
 880// #[gpui::test(iterations = 30)]
 881// async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
 882//     let fs = FakeFs::new(cx.background());
 883//     fs.insert_tree(
 884//         "/root",
 885//         json!({
 886//             "b": {},
 887//             "c": {},
 888//             "d": {},
 889//         }),
 890//     )
 891//     .await;
 892
 893//     let tree = Worktree::local(
 894//         build_client(cx),
 895//         "/root".as_ref(),
 896//         true,
 897//         fs,
 898//         Default::default(),
 899//         &mut cx.to_async(),
 900//     )
 901//     .await
 902//     .unwrap();
 903
 904//     let snapshot1 = tree.update(cx, |tree, cx| {
 905//         let tree = tree.as_local_mut().unwrap();
 906//         let snapshot = Arc::new(Mutex::new(tree.snapshot()));
 907//         let _ = tree.observe_updates(0, cx, {
 908//             let snapshot = snapshot.clone();
 909//             move |update| {
 910//                 snapshot.lock().apply_remote_update(update).unwrap();
 911//                 async { true }
 912//             }
 913//         });
 914//         snapshot
 915//     });
 916
 917//     let entry = tree
 918//         .update(cx, |tree, cx| {
 919//             tree.as_local_mut()
 920//                 .unwrap()
 921//                 .create_entry("a/e".as_ref(), true, cx)
 922//         })
 923//         .await
 924//         .unwrap();
 925//     assert!(entry.is_dir());
 926
 927//     cx.foreground().run_until_parked();
 928//     tree.read_with(cx, |tree, _| {
 929//         assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
 930//     });
 931
 932//     let snapshot2 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot());
 933//     assert_eq!(
 934//         snapshot1.lock().entries(true).collect::<Vec<_>>(),
 935//         snapshot2.entries(true).collect::<Vec<_>>()
 936//     );
 937// }
 938
 939// #[gpui::test]
 940// async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
 941//     let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 942
 943//     let fs_fake = FakeFs::new(cx.background());
 944//     fs_fake
 945//         .insert_tree(
 946//             "/root",
 947//             json!({
 948//                 "a": {},
 949//             }),
 950//         )
 951//         .await;
 952
 953//     let tree_fake = Worktree::local(
 954//         client_fake,
 955//         "/root".as_ref(),
 956//         true,
 957//         fs_fake,
 958//         Default::default(),
 959//         &mut cx.to_async(),
 960//     )
 961//     .await
 962//     .unwrap();
 963
 964//     let entry = tree_fake
 965//         .update(cx, |tree, cx| {
 966//             tree.as_local_mut()
 967//                 .unwrap()
 968//                 .create_entry("a/b/c/d.txt".as_ref(), false, cx)
 969//         })
 970//         .await
 971//         .unwrap();
 972//     assert!(entry.is_file());
 973
 974//     cx.foreground().run_until_parked();
 975//     tree_fake.read_with(cx, |tree, _| {
 976//         assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
 977//         assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
 978//         assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
 979//     });
 980
 981//     let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
 982
 983//     let fs_real = Arc::new(RealFs);
 984//     let temp_root = temp_tree(json!({
 985//         "a": {}
 986//     }));
 987
 988//     let tree_real = Worktree::local(
 989//         client_real,
 990//         temp_root.path(),
 991//         true,
 992//         fs_real,
 993//         Default::default(),
 994//         &mut cx.to_async(),
 995//     )
 996//     .await
 997//     .unwrap();
 998
 999//     let entry = tree_real
1000//         .update(cx, |tree, cx| {
1001//             tree.as_local_mut()
1002//                 .unwrap()
1003//                 .create_entry("a/b/c/d.txt".as_ref(), false, cx)
1004//         })
1005//         .await
1006//         .unwrap();
1007//     assert!(entry.is_file());
1008
1009//     cx.foreground().run_until_parked();
1010//     tree_real.read_with(cx, |tree, _| {
1011//         assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
1012//         assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
1013//         assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
1014//     });
1015
1016//     // Test smallest change
1017//     let entry = tree_real
1018//         .update(cx, |tree, cx| {
1019//             tree.as_local_mut()
1020//                 .unwrap()
1021//                 .create_entry("a/b/c/e.txt".as_ref(), false, cx)
1022//         })
1023//         .await
1024//         .unwrap();
1025//     assert!(entry.is_file());
1026
1027//     cx.foreground().run_until_parked();
1028//     tree_real.read_with(cx, |tree, _| {
1029//         assert!(tree.entry_for_path("a/b/c/e.txt").unwrap().is_file());
1030//     });
1031
1032//     // Test largest change
1033//     let entry = tree_real
1034//         .update(cx, |tree, cx| {
1035//             tree.as_local_mut()
1036//                 .unwrap()
1037//                 .create_entry("d/e/f/g.txt".as_ref(), false, cx)
1038//         })
1039//         .await
1040//         .unwrap();
1041//     assert!(entry.is_file());
1042
1043//     cx.foreground().run_until_parked();
1044//     tree_real.read_with(cx, |tree, _| {
1045//         assert!(tree.entry_for_path("d/e/f/g.txt").unwrap().is_file());
1046//         assert!(tree.entry_for_path("d/e/f").unwrap().is_dir());
1047//         assert!(tree.entry_for_path("d/e/").unwrap().is_dir());
1048//         assert!(tree.entry_for_path("d/").unwrap().is_dir());
1049//     });
1050// }
1051
1052// #[gpui::test(iterations = 100)]
1053// async fn test_random_worktree_operations_during_initial_scan(
1054//     cx: &mut TestAppContext,
1055//     mut rng: StdRng,
1056// ) {
1057//     let operations = env::var("OPERATIONS")
1058//         .map(|o| o.parse().unwrap())
1059//         .unwrap_or(5);
1060//     let initial_entries = env::var("INITIAL_ENTRIES")
1061//         .map(|o| o.parse().unwrap())
1062//         .unwrap_or(20);
1063
1064//     let root_dir = Path::new("/test");
1065//     let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
1066//     fs.as_fake().insert_tree(root_dir, json!({})).await;
1067//     for _ in 0..initial_entries {
1068//         randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
1069//     }
1070//     log::info!("generated initial tree");
1071
1072//     let worktree = Worktree::local(
1073//         build_client(cx),
1074//         root_dir,
1075//         true,
1076//         fs.clone(),
1077//         Default::default(),
1078//         &mut cx.to_async(),
1079//     )
1080//     .await
1081//     .unwrap();
1082
1083//     let mut snapshots = vec![worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot())];
1084//     let updates = Arc::new(Mutex::new(Vec::new()));
1085//     worktree.update(cx, |tree, cx| {
1086//         check_worktree_change_events(tree, cx);
1087
1088//         let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
1089//             let updates = updates.clone();
1090//             move |update| {
1091//                 updates.lock().push(update);
1092//                 async { true }
1093//             }
1094//         });
1095//     });
1096
1097//     for _ in 0..operations {
1098//         worktree
1099//             .update(cx, |worktree, cx| {
1100//                 randomly_mutate_worktree(worktree, &mut rng, cx)
1101//             })
1102//             .await
1103//             .log_err();
1104//         worktree.read_with(cx, |tree, _| {
1105//             tree.as_local().unwrap().snapshot().check_invariants(true)
1106//         });
1107
1108//         if rng.gen_bool(0.6) {
1109//             snapshots.push(worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot()));
1110//         }
1111//     }
1112
1113//     worktree
1114//         .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
1115//         .await;
1116
1117//     cx.foreground().run_until_parked();
1118
1119//     let final_snapshot = worktree.read_with(cx, |tree, _| {
1120//         let tree = tree.as_local().unwrap();
1121//         let snapshot = tree.snapshot();
1122//         snapshot.check_invariants(true);
1123//         snapshot
1124//     });
1125
1126//     for (i, snapshot) in snapshots.into_iter().enumerate().rev() {
1127//         let mut updated_snapshot = snapshot.clone();
1128//         for update in updates.lock().iter() {
1129//             if update.scan_id >= updated_snapshot.scan_id() as u64 {
1130//                 updated_snapshot
1131//                     .apply_remote_update(update.clone())
1132//                     .unwrap();
1133//             }
1134//         }
1135
1136//         assert_eq!(
1137//             updated_snapshot.entries(true).collect::<Vec<_>>(),
1138//             final_snapshot.entries(true).collect::<Vec<_>>(),
1139//             "wrong updates after snapshot {i}: {snapshot:#?} {updates:#?}",
1140//         );
1141//     }
1142// }
1143
1144// #[gpui::test(iterations = 100)]
1145// async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) {
1146//     let operations = env::var("OPERATIONS")
1147//         .map(|o| o.parse().unwrap())
1148//         .unwrap_or(40);
1149//     let initial_entries = env::var("INITIAL_ENTRIES")
1150//         .map(|o| o.parse().unwrap())
1151//         .unwrap_or(20);
1152
1153//     let root_dir = Path::new("/test");
1154//     let fs = FakeFs::new(cx.background()) as Arc<dyn Fs>;
1155//     fs.as_fake().insert_tree(root_dir, json!({})).await;
1156//     for _ in 0..initial_entries {
1157//         randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
1158//     }
1159//     log::info!("generated initial tree");
1160
1161//     let worktree = Worktree::local(
1162//         build_client(cx),
1163//         root_dir,
1164//         true,
1165//         fs.clone(),
1166//         Default::default(),
1167//         &mut cx.to_async(),
1168//     )
1169//     .await
1170//     .unwrap();
1171
1172//     let updates = Arc::new(Mutex::new(Vec::new()));
1173//     worktree.update(cx, |tree, cx| {
1174//         check_worktree_change_events(tree, cx);
1175
1176//         let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
1177//             let updates = updates.clone();
1178//             move |update| {
1179//                 updates.lock().push(update);
1180//                 async { true }
1181//             }
1182//         });
1183//     });
1184
1185//     worktree
1186//         .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
1187//         .await;
1188
1189//     fs.as_fake().pause_events();
1190//     let mut snapshots = Vec::new();
1191//     let mut mutations_len = operations;
1192//     while mutations_len > 1 {
1193//         if rng.gen_bool(0.2) {
1194//             worktree
1195//                 .update(cx, |worktree, cx| {
1196//                     randomly_mutate_worktree(worktree, &mut rng, cx)
1197//                 })
1198//                 .await
1199//                 .log_err();
1200//         } else {
1201//             randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
1202//         }
1203
1204//         let buffered_event_count = fs.as_fake().buffered_event_count();
1205//         if buffered_event_count > 0 && rng.gen_bool(0.3) {
1206//             let len = rng.gen_range(0..=buffered_event_count);
1207//             log::info!("flushing {} events", len);
1208//             fs.as_fake().flush_events(len);
1209//         } else {
1210//             randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await;
1211//             mutations_len -= 1;
1212//         }
1213
1214//         cx.foreground().run_until_parked();
1215//         if rng.gen_bool(0.2) {
1216//             log::info!("storing snapshot {}", snapshots.len());
1217//             let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
1218//             snapshots.push(snapshot);
1219//         }
1220//     }
1221
1222//     log::info!("quiescing");
1223//     fs.as_fake().flush_events(usize::MAX);
1224//     cx.foreground().run_until_parked();
1225
1226//     let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
1227//     snapshot.check_invariants(true);
1228//     let expanded_paths = snapshot
1229//         .expanded_entries()
1230//         .map(|e| e.path.clone())
1231//         .collect::<Vec<_>>();
1232
1233//     {
1234//         let new_worktree = Worktree::local(
1235//             build_client(cx),
1236//             root_dir,
1237//             true,
1238//             fs.clone(),
1239//             Default::default(),
1240//             &mut cx.to_async(),
1241//         )
1242//         .await
1243//         .unwrap();
1244//         new_worktree
1245//             .update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
1246//             .await;
1247//         new_worktree
1248//             .update(cx, |tree, _| {
1249//                 tree.as_local_mut()
1250//                     .unwrap()
1251//                     .refresh_entries_for_paths(expanded_paths)
1252//             })
1253//             .recv()
1254//             .await;
1255//         let new_snapshot =
1256//             new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
1257//         assert_eq!(
1258//             snapshot.entries_without_ids(true),
1259//             new_snapshot.entries_without_ids(true)
1260//         );
1261//     }
1262
1263//     for (i, mut prev_snapshot) in snapshots.into_iter().enumerate().rev() {
1264//         for update in updates.lock().iter() {
1265//             if update.scan_id >= prev_snapshot.scan_id() as u64 {
1266//                 prev_snapshot.apply_remote_update(update.clone()).unwrap();
1267//             }
1268//         }
1269
1270//         assert_eq!(
1271//             prev_snapshot
1272//                 .entries(true)
1273//                 .map(ignore_pending_dir)
1274//                 .collect::<Vec<_>>(),
1275//             snapshot
1276//                 .entries(true)
1277//                 .map(ignore_pending_dir)
1278//                 .collect::<Vec<_>>(),
1279//             "wrong updates after snapshot {i}: {updates:#?}",
1280//         );
1281//     }
1282
1283//     fn ignore_pending_dir(entry: &Entry) -> Entry {
1284//         let mut entry = entry.clone();
1285//         if entry.kind.is_dir() {
1286//             entry.kind = EntryKind::Dir
1287//         }
1288//         entry
1289//     }
1290// }
1291
1292// // The worktree's `UpdatedEntries` event can be used to follow along with
1293// // all changes to the worktree's snapshot.
1294// fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Worktree>) {
1295//     let mut entries = tree.entries(true).cloned().collect::<Vec<_>>();
1296//     cx.subscribe(&cx.handle(), move |tree, _, event, _| {
1297//         if let Event::UpdatedEntries(changes) = event {
1298//             for (path, _, change_type) in changes.iter() {
1299//                 let entry = tree.entry_for_path(&path).cloned();
1300//                 let ix = match entries.binary_search_by_key(&path, |e| &e.path) {
1301//                     Ok(ix) | Err(ix) => ix,
1302//                 };
1303//                 match change_type {
1304//                     PathChange::Added => entries.insert(ix, entry.unwrap()),
1305//                     PathChange::Removed => drop(entries.remove(ix)),
1306//                     PathChange::Updated => {
1307//                         let entry = entry.unwrap();
1308//                         let existing_entry = entries.get_mut(ix).unwrap();
1309//                         assert_eq!(existing_entry.path, entry.path);
1310//                         *existing_entry = entry;
1311//                     }
1312//                     PathChange::AddedOrUpdated | PathChange::Loaded => {
1313//                         let entry = entry.unwrap();
1314//                         if entries.get(ix).map(|e| &e.path) == Some(&entry.path) {
1315//                             *entries.get_mut(ix).unwrap() = entry;
1316//                         } else {
1317//                             entries.insert(ix, entry);
1318//                         }
1319//                     }
1320//                 }
1321//             }
1322
1323//             let new_entries = tree.entries(true).cloned().collect::<Vec<_>>();
1324//             assert_eq!(entries, new_entries, "incorrect changes: {:?}", changes);
1325//         }
1326//     })
1327//     .detach();
1328// }
1329
1330// fn randomly_mutate_worktree(
1331//     worktree: &mut Worktree,
1332//     rng: &mut impl Rng,
1333//     cx: &mut ModelContext<Worktree>,
1334// ) -> Task<Result<()>> {
1335//     log::info!("mutating worktree");
1336//     let worktree = worktree.as_local_mut().unwrap();
1337//     let snapshot = worktree.snapshot();
1338//     let entry = snapshot.entries(false).choose(rng).unwrap();
1339
1340//     match rng.gen_range(0_u32..100) {
1341//         0..=33 if entry.path.as_ref() != Path::new("") => {
1342//             log::info!("deleting entry {:?} ({})", entry.path, entry.id.0);
1343//             worktree.delete_entry(entry.id, cx).unwrap()
1344//         }
1345//         ..=66 if entry.path.as_ref() != Path::new("") => {
1346//             let other_entry = snapshot.entries(false).choose(rng).unwrap();
1347//             let new_parent_path = if other_entry.is_dir() {
1348//                 other_entry.path.clone()
1349//             } else {
1350//                 other_entry.path.parent().unwrap().into()
1351//             };
1352//             let mut new_path = new_parent_path.join(random_filename(rng));
1353//             if new_path.starts_with(&entry.path) {
1354//                 new_path = random_filename(rng).into();
1355//             }
1356
1357//             log::info!(
1358//                 "renaming entry {:?} ({}) to {:?}",
1359//                 entry.path,
1360//                 entry.id.0,
1361//                 new_path
1362//             );
1363//             let task = worktree.rename_entry(entry.id, new_path, cx).unwrap();
1364//             cx.foreground().spawn(async move {
1365//                 task.await?;
1366//                 Ok(())
1367//             })
1368//         }
1369//         _ => {
1370//             let task = if entry.is_dir() {
1371//                 let child_path = entry.path.join(random_filename(rng));
1372//                 let is_dir = rng.gen_bool(0.3);
1373//                 log::info!(
1374//                     "creating {} at {:?}",
1375//                     if is_dir { "dir" } else { "file" },
1376//                     child_path,
1377//                 );
1378//                 worktree.create_entry(child_path, is_dir, cx)
1379//             } else {
1380//                 log::info!("overwriting file {:?} ({})", entry.path, entry.id.0);
1381//                 worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx)
1382//             };
1383//             cx.foreground().spawn(async move {
1384//                 task.await?;
1385//                 Ok(())
1386//             })
1387//         }
1388//     }
1389// }
1390
1391// async fn randomly_mutate_fs(
1392//     fs: &Arc<dyn Fs>,
1393//     root_path: &Path,
1394//     insertion_probability: f64,
1395//     rng: &mut impl Rng,
1396// ) {
1397//     log::info!("mutating fs");
1398//     let mut files = Vec::new();
1399//     let mut dirs = Vec::new();
1400//     for path in fs.as_fake().paths(false) {
1401//         if path.starts_with(root_path) {
1402//             if fs.is_file(&path).await {
1403//                 files.push(path);
1404//             } else {
1405//                 dirs.push(path);
1406//             }
1407//         }
1408//     }
1409
1410//     if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
1411//         let path = dirs.choose(rng).unwrap();
1412//         let new_path = path.join(random_filename(rng));
1413
1414//         if rng.gen() {
1415//             log::info!(
1416//                 "creating dir {:?}",
1417//                 new_path.strip_prefix(root_path).unwrap()
1418//             );
1419//             fs.create_dir(&new_path).await.unwrap();
1420//         } else {
1421//             log::info!(
1422//                 "creating file {:?}",
1423//                 new_path.strip_prefix(root_path).unwrap()
1424//             );
1425//             fs.create_file(&new_path, Default::default()).await.unwrap();
1426//         }
1427//     } else if rng.gen_bool(0.05) {
1428//         let ignore_dir_path = dirs.choose(rng).unwrap();
1429//         let ignore_path = ignore_dir_path.join(&*GITIGNORE);
1430
1431//         let subdirs = dirs
1432//             .iter()
1433//             .filter(|d| d.starts_with(&ignore_dir_path))
1434//             .cloned()
1435//             .collect::<Vec<_>>();
1436//         let subfiles = files
1437//             .iter()
1438//             .filter(|d| d.starts_with(&ignore_dir_path))
1439//             .cloned()
1440//             .collect::<Vec<_>>();
1441//         let files_to_ignore = {
1442//             let len = rng.gen_range(0..=subfiles.len());
1443//             subfiles.choose_multiple(rng, len)
1444//         };
1445//         let dirs_to_ignore = {
1446//             let len = rng.gen_range(0..subdirs.len());
1447//             subdirs.choose_multiple(rng, len)
1448//         };
1449
1450//         let mut ignore_contents = String::new();
1451//         for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
1452//             writeln!(
1453//                 ignore_contents,
1454//                 "{}",
1455//                 path_to_ignore
1456//                     .strip_prefix(&ignore_dir_path)
1457//                     .unwrap()
1458//                     .to_str()
1459//                     .unwrap()
1460//             )
1461//             .unwrap();
1462//         }
1463//         log::info!(
1464//             "creating gitignore {:?} with contents:\n{}",
1465//             ignore_path.strip_prefix(&root_path).unwrap(),
1466//             ignore_contents
1467//         );
1468//         fs.save(
1469//             &ignore_path,
1470//             &ignore_contents.as_str().into(),
1471//             Default::default(),
1472//         )
1473//         .await
1474//         .unwrap();
1475//     } else {
1476//         let old_path = {
1477//             let file_path = files.choose(rng);
1478//             let dir_path = dirs[1..].choose(rng);
1479//             file_path.into_iter().chain(dir_path).choose(rng).unwrap()
1480//         };
1481
1482//         let is_rename = rng.gen();
1483//         if is_rename {
1484//             let new_path_parent = dirs
1485//                 .iter()
1486//                 .filter(|d| !d.starts_with(old_path))
1487//                 .choose(rng)
1488//                 .unwrap();
1489
1490//             let overwrite_existing_dir =
1491//                 !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
1492//             let new_path = if overwrite_existing_dir {
1493//                 fs.remove_dir(
1494//                     &new_path_parent,
1495//                     RemoveOptions {
1496//                         recursive: true,
1497//                         ignore_if_not_exists: true,
1498//                     },
1499//                 )
1500//                 .await
1501//                 .unwrap();
1502//                 new_path_parent.to_path_buf()
1503//             } else {
1504//                 new_path_parent.join(random_filename(rng))
1505//             };
1506
1507//             log::info!(
1508//                 "renaming {:?} to {}{:?}",
1509//                 old_path.strip_prefix(&root_path).unwrap(),
1510//                 if overwrite_existing_dir {
1511//                     "overwrite "
1512//                 } else {
1513//                     ""
1514//                 },
1515//                 new_path.strip_prefix(&root_path).unwrap()
1516//             );
1517//             fs.rename(
1518//                 &old_path,
1519//                 &new_path,
1520//                 fs::RenameOptions {
1521//                     overwrite: true,
1522//                     ignore_if_exists: true,
1523//                 },
1524//             )
1525//             .await
1526//             .unwrap();
1527//         } else if fs.is_file(&old_path).await {
1528//             log::info!(
1529//                 "deleting file {:?}",
1530//                 old_path.strip_prefix(&root_path).unwrap()
1531//             );
1532//             fs.remove_file(old_path, Default::default()).await.unwrap();
1533//         } else {
1534//             log::info!(
1535//                 "deleting dir {:?}",
1536//                 old_path.strip_prefix(&root_path).unwrap()
1537//             );
1538//             fs.remove_dir(
1539//                 &old_path,
1540//                 RemoveOptions {
1541//                     recursive: true,
1542//                     ignore_if_not_exists: true,
1543//                 },
1544//             )
1545//             .await
1546//             .unwrap();
1547//         }
1548//     }
1549// }
1550
1551// fn random_filename(rng: &mut impl Rng) -> String {
1552//     (0..6)
1553//         .map(|_| rng.sample(rand::distributions::Alphanumeric))
1554//         .map(char::from)
1555//         .collect()
1556// }
1557
1558// #[gpui::test]
1559// async fn test_rename_work_directory(cx: &mut TestAppContext) {
1560//     let root = temp_tree(json!({
1561//         "projects": {
1562//             "project1": {
1563//                 "a": "",
1564//                 "b": "",
1565//             }
1566//         },
1567
1568//     }));
1569//     let root_path = root.path();
1570
1571//     let tree = Worktree::local(
1572//         build_client(cx),
1573//         root_path,
1574//         true,
1575//         Arc::new(RealFs),
1576//         Default::default(),
1577//         &mut cx.to_async(),
1578//     )
1579//     .await
1580//     .unwrap();
1581
1582//     let repo = git_init(&root_path.join("projects/project1"));
1583//     git_add("a", &repo);
1584//     git_commit("init", &repo);
1585//     std::fs::write(root_path.join("projects/project1/a"), "aa").ok();
1586
1587//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1588//         .await;
1589
1590//     tree.flush_fs_events(cx).await;
1591
1592//     cx.read(|cx| {
1593//         let tree = tree.read(cx);
1594//         let (work_dir, _) = tree.repositories().next().unwrap();
1595//         assert_eq!(work_dir.as_ref(), Path::new("projects/project1"));
1596//         assert_eq!(
1597//             tree.status_for_file(Path::new("projects/project1/a")),
1598//             Some(GitFileStatus::Modified)
1599//         );
1600//         assert_eq!(
1601//             tree.status_for_file(Path::new("projects/project1/b")),
1602//             Some(GitFileStatus::Added)
1603//         );
1604//     });
1605
1606//     std::fs::rename(
1607//         root_path.join("projects/project1"),
1608//         root_path.join("projects/project2"),
1609//     )
1610//     .ok();
1611//     tree.flush_fs_events(cx).await;
1612
1613//     cx.read(|cx| {
1614//         let tree = tree.read(cx);
1615//         let (work_dir, _) = tree.repositories().next().unwrap();
1616//         assert_eq!(work_dir.as_ref(), Path::new("projects/project2"));
1617//         assert_eq!(
1618//             tree.status_for_file(Path::new("projects/project2/a")),
1619//             Some(GitFileStatus::Modified)
1620//         );
1621//         assert_eq!(
1622//             tree.status_for_file(Path::new("projects/project2/b")),
1623//             Some(GitFileStatus::Added)
1624//         );
1625//     });
1626// }
1627
1628// #[gpui::test]
1629// async fn test_git_repository_for_path(cx: &mut TestAppContext) {
1630//     let root = temp_tree(json!({
1631//         "c.txt": "",
1632//         "dir1": {
1633//             ".git": {},
1634//             "deps": {
1635//                 "dep1": {
1636//                     ".git": {},
1637//                     "src": {
1638//                         "a.txt": ""
1639//                     }
1640//                 }
1641//             },
1642//             "src": {
1643//                 "b.txt": ""
1644//             }
1645//         },
1646//     }));
1647
1648//     let tree = Worktree::local(
1649//         build_client(cx),
1650//         root.path(),
1651//         true,
1652//         Arc::new(RealFs),
1653//         Default::default(),
1654//         &mut cx.to_async(),
1655//     )
1656//     .await
1657//     .unwrap();
1658
1659//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1660//         .await;
1661//     tree.flush_fs_events(cx).await;
1662
1663//     tree.read_with(cx, |tree, _cx| {
1664//         let tree = tree.as_local().unwrap();
1665
1666//         assert!(tree.repository_for_path("c.txt".as_ref()).is_none());
1667
1668//         let entry = tree.repository_for_path("dir1/src/b.txt".as_ref()).unwrap();
1669//         assert_eq!(
1670//             entry
1671//                 .work_directory(tree)
1672//                 .map(|directory| directory.as_ref().to_owned()),
1673//             Some(Path::new("dir1").to_owned())
1674//         );
1675
1676//         let entry = tree
1677//             .repository_for_path("dir1/deps/dep1/src/a.txt".as_ref())
1678//             .unwrap();
1679//         assert_eq!(
1680//             entry
1681//                 .work_directory(tree)
1682//                 .map(|directory| directory.as_ref().to_owned()),
1683//             Some(Path::new("dir1/deps/dep1").to_owned())
1684//         );
1685
1686//         let entries = tree.files(false, 0);
1687
1688//         let paths_with_repos = tree
1689//             .entries_with_repositories(entries)
1690//             .map(|(entry, repo)| {
1691//                 (
1692//                     entry.path.as_ref(),
1693//                     repo.and_then(|repo| {
1694//                         repo.work_directory(&tree)
1695//                             .map(|work_directory| work_directory.0.to_path_buf())
1696//                     }),
1697//                 )
1698//             })
1699//             .collect::<Vec<_>>();
1700
1701//         assert_eq!(
1702//             paths_with_repos,
1703//             &[
1704//                 (Path::new("c.txt"), None),
1705//                 (
1706//                     Path::new("dir1/deps/dep1/src/a.txt"),
1707//                     Some(Path::new("dir1/deps/dep1").into())
1708//                 ),
1709//                 (Path::new("dir1/src/b.txt"), Some(Path::new("dir1").into())),
1710//             ]
1711//         );
1712//     });
1713
1714//     let repo_update_events = Arc::new(Mutex::new(vec![]));
1715//     tree.update(cx, |_, cx| {
1716//         let repo_update_events = repo_update_events.clone();
1717//         cx.subscribe(&tree, move |_, _, event, _| {
1718//             if let Event::UpdatedGitRepositories(update) = event {
1719//                 repo_update_events.lock().push(update.clone());
1720//             }
1721//         })
1722//         .detach();
1723//     });
1724
1725//     std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
1726//     tree.flush_fs_events(cx).await;
1727
1728//     assert_eq!(
1729//         repo_update_events.lock()[0]
1730//             .iter()
1731//             .map(|e| e.0.clone())
1732//             .collect::<Vec<Arc<Path>>>(),
1733//         vec![Path::new("dir1").into()]
1734//     );
1735
1736//     std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
1737//     tree.flush_fs_events(cx).await;
1738
1739//     tree.read_with(cx, |tree, _cx| {
1740//         let tree = tree.as_local().unwrap();
1741
1742//         assert!(tree
1743//             .repository_for_path("dir1/src/b.txt".as_ref())
1744//             .is_none());
1745//     });
1746// }
1747
1748// #[gpui::test]
1749// async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
1750//     const IGNORE_RULE: &'static str = "**/target";
1751
1752//     let root = temp_tree(json!({
1753//         "project": {
1754//             "a.txt": "a",
1755//             "b.txt": "bb",
1756//             "c": {
1757//                 "d": {
1758//                     "e.txt": "eee"
1759//                 }
1760//             },
1761//             "f.txt": "ffff",
1762//             "target": {
1763//                 "build_file": "???"
1764//             },
1765//             ".gitignore": IGNORE_RULE
1766//         },
1767
1768//     }));
1769
1770//     const A_TXT: &'static str = "a.txt";
1771//     const B_TXT: &'static str = "b.txt";
1772//     const E_TXT: &'static str = "c/d/e.txt";
1773//     const F_TXT: &'static str = "f.txt";
1774//     const DOTGITIGNORE: &'static str = ".gitignore";
1775//     const BUILD_FILE: &'static str = "target/build_file";
1776//     let project_path = Path::new("project");
1777
1778//     // Set up git repository before creating the worktree.
1779//     let work_dir = root.path().join("project");
1780//     let mut repo = git_init(work_dir.as_path());
1781//     repo.add_ignore_rule(IGNORE_RULE).unwrap();
1782//     git_add(A_TXT, &repo);
1783//     git_add(E_TXT, &repo);
1784//     git_add(DOTGITIGNORE, &repo);
1785//     git_commit("Initial commit", &repo);
1786
1787//     let tree = Worktree::local(
1788//         build_client(cx),
1789//         root.path(),
1790//         true,
1791//         Arc::new(RealFs),
1792//         Default::default(),
1793//         &mut cx.to_async(),
1794//     )
1795//     .await
1796//     .unwrap();
1797
1798//     tree.flush_fs_events(cx).await;
1799//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1800//         .await;
1801//     deterministic.run_until_parked();
1802
1803//     // Check that the right git state is observed on startup
1804//     tree.read_with(cx, |tree, _cx| {
1805//         let snapshot = tree.snapshot();
1806//         assert_eq!(snapshot.repositories().count(), 1);
1807//         let (dir, _) = snapshot.repositories().next().unwrap();
1808//         assert_eq!(dir.as_ref(), Path::new("project"));
1809
1810//         assert_eq!(
1811//             snapshot.status_for_file(project_path.join(B_TXT)),
1812//             Some(GitFileStatus::Added)
1813//         );
1814//         assert_eq!(
1815//             snapshot.status_for_file(project_path.join(F_TXT)),
1816//             Some(GitFileStatus::Added)
1817//         );
1818//     });
1819
1820//     // Modify a file in the working copy.
1821//     std::fs::write(work_dir.join(A_TXT), "aa").unwrap();
1822//     tree.flush_fs_events(cx).await;
1823//     deterministic.run_until_parked();
1824
1825//     // The worktree detects that the file's git status has changed.
1826//     tree.read_with(cx, |tree, _cx| {
1827//         let snapshot = tree.snapshot();
1828//         assert_eq!(
1829//             snapshot.status_for_file(project_path.join(A_TXT)),
1830//             Some(GitFileStatus::Modified)
1831//         );
1832//     });
1833
1834//     // Create a commit in the git repository.
1835//     git_add(A_TXT, &repo);
1836//     git_add(B_TXT, &repo);
1837//     git_commit("Committing modified and added", &repo);
1838//     tree.flush_fs_events(cx).await;
1839//     deterministic.run_until_parked();
1840
1841//     // The worktree detects that the files' git status have changed.
1842//     tree.read_with(cx, |tree, _cx| {
1843//         let snapshot = tree.snapshot();
1844//         assert_eq!(
1845//             snapshot.status_for_file(project_path.join(F_TXT)),
1846//             Some(GitFileStatus::Added)
1847//         );
1848//         assert_eq!(snapshot.status_for_file(project_path.join(B_TXT)), None);
1849//         assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
1850//     });
1851
1852//     // Modify files in the working copy and perform git operations on other files.
1853//     git_reset(0, &repo);
1854//     git_remove_index(Path::new(B_TXT), &repo);
1855//     git_stash(&mut repo);
1856//     std::fs::write(work_dir.join(E_TXT), "eeee").unwrap();
1857//     std::fs::write(work_dir.join(BUILD_FILE), "this should be ignored").unwrap();
1858//     tree.flush_fs_events(cx).await;
1859//     deterministic.run_until_parked();
1860
1861//     // Check that more complex repo changes are tracked
1862//     tree.read_with(cx, |tree, _cx| {
1863//         let snapshot = tree.snapshot();
1864
1865//         assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
1866//         assert_eq!(
1867//             snapshot.status_for_file(project_path.join(B_TXT)),
1868//             Some(GitFileStatus::Added)
1869//         );
1870//         assert_eq!(
1871//             snapshot.status_for_file(project_path.join(E_TXT)),
1872//             Some(GitFileStatus::Modified)
1873//         );
1874//     });
1875
1876//     std::fs::remove_file(work_dir.join(B_TXT)).unwrap();
1877//     std::fs::remove_dir_all(work_dir.join("c")).unwrap();
1878//     std::fs::write(
1879//         work_dir.join(DOTGITIGNORE),
1880//         [IGNORE_RULE, "f.txt"].join("\n"),
1881//     )
1882//     .unwrap();
1883
1884//     git_add(Path::new(DOTGITIGNORE), &repo);
1885//     git_commit("Committing modified git ignore", &repo);
1886
1887//     tree.flush_fs_events(cx).await;
1888//     deterministic.run_until_parked();
1889
1890//     let mut renamed_dir_name = "first_directory/second_directory";
1891//     const RENAMED_FILE: &'static str = "rf.txt";
1892
1893//     std::fs::create_dir_all(work_dir.join(renamed_dir_name)).unwrap();
1894//     std::fs::write(
1895//         work_dir.join(renamed_dir_name).join(RENAMED_FILE),
1896//         "new-contents",
1897//     )
1898//     .unwrap();
1899
1900//     tree.flush_fs_events(cx).await;
1901//     deterministic.run_until_parked();
1902
1903//     tree.read_with(cx, |tree, _cx| {
1904//         let snapshot = tree.snapshot();
1905//         assert_eq!(
1906//             snapshot.status_for_file(&project_path.join(renamed_dir_name).join(RENAMED_FILE)),
1907//             Some(GitFileStatus::Added)
1908//         );
1909//     });
1910
1911//     renamed_dir_name = "new_first_directory/second_directory";
1912
1913//     std::fs::rename(
1914//         work_dir.join("first_directory"),
1915//         work_dir.join("new_first_directory"),
1916//     )
1917//     .unwrap();
1918
1919//     tree.flush_fs_events(cx).await;
1920//     deterministic.run_until_parked();
1921
1922//     tree.read_with(cx, |tree, _cx| {
1923//         let snapshot = tree.snapshot();
1924
1925//         assert_eq!(
1926//             snapshot.status_for_file(
1927//                 project_path
1928//                     .join(Path::new(renamed_dir_name))
1929//                     .join(RENAMED_FILE)
1930//             ),
1931//             Some(GitFileStatus::Added)
1932//         );
1933//     });
1934// }
1935
1936// #[gpui::test]
1937// async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
1938//     let fs = FakeFs::new(cx.background());
1939//     fs.insert_tree(
1940//         "/root",
1941//         json!({
1942//             ".git": {},
1943//             "a": {
1944//                 "b": {
1945//                     "c1.txt": "",
1946//                     "c2.txt": "",
1947//                 },
1948//                 "d": {
1949//                     "e1.txt": "",
1950//                     "e2.txt": "",
1951//                     "e3.txt": "",
1952//                 }
1953//             },
1954//             "f": {
1955//                 "no-status.txt": ""
1956//             },
1957//             "g": {
1958//                 "h1.txt": "",
1959//                 "h2.txt": ""
1960//             },
1961
1962//         }),
1963//     )
1964//     .await;
1965
1966//     fs.set_status_for_repo_via_git_operation(
1967//         &Path::new("/root/.git"),
1968//         &[
1969//             (Path::new("a/b/c1.txt"), GitFileStatus::Added),
1970//             (Path::new("a/d/e2.txt"), GitFileStatus::Modified),
1971//             (Path::new("g/h2.txt"), GitFileStatus::Conflict),
1972//         ],
1973//     );
1974
1975//     let tree = Worktree::local(
1976//         build_client(cx),
1977//         Path::new("/root"),
1978//         true,
1979//         fs.clone(),
1980//         Default::default(),
1981//         &mut cx.to_async(),
1982//     )
1983//     .await
1984//     .unwrap();
1985
1986//     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1987//         .await;
1988
1989//     cx.foreground().run_until_parked();
1990//     let snapshot = tree.read_with(cx, |tree, _| tree.snapshot());
1991
1992//     check_propagated_statuses(
1993//         &snapshot,
1994//         &[
1995//             (Path::new(""), Some(GitFileStatus::Conflict)),
1996//             (Path::new("a"), Some(GitFileStatus::Modified)),
1997//             (Path::new("a/b"), Some(GitFileStatus::Added)),
1998//             (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
1999//             (Path::new("a/b/c2.txt"), None),
2000//             (Path::new("a/d"), Some(GitFileStatus::Modified)),
2001//             (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
2002//             (Path::new("f"), None),
2003//             (Path::new("f/no-status.txt"), None),
2004//             (Path::new("g"), Some(GitFileStatus::Conflict)),
2005//             (Path::new("g/h2.txt"), Some(GitFileStatus::Conflict)),
2006//         ],
2007//     );
2008
2009//     check_propagated_statuses(
2010//         &snapshot,
2011//         &[
2012//             (Path::new("a/b"), Some(GitFileStatus::Added)),
2013//             (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
2014//             (Path::new("a/b/c2.txt"), None),
2015//             (Path::new("a/d"), Some(GitFileStatus::Modified)),
2016//             (Path::new("a/d/e1.txt"), None),
2017//             (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
2018//             (Path::new("f"), None),
2019//             (Path::new("f/no-status.txt"), None),
2020//             (Path::new("g"), Some(GitFileStatus::Conflict)),
2021//         ],
2022//     );
2023
2024//     check_propagated_statuses(
2025//         &snapshot,
2026//         &[
2027//             (Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
2028//             (Path::new("a/b/c2.txt"), None),
2029//             (Path::new("a/d/e1.txt"), None),
2030//             (Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
2031//             (Path::new("f/no-status.txt"), None),
2032//         ],
2033//     );
2034
2035//     #[track_caller]
2036//     fn check_propagated_statuses(
2037//         snapshot: &Snapshot,
2038//         expected_statuses: &[(&Path, Option<GitFileStatus>)],
2039//     ) {
2040//         let mut entries = expected_statuses
2041//             .iter()
2042//             .map(|(path, _)| snapshot.entry_for_path(path).unwrap().clone())
2043//             .collect::<Vec<_>>();
2044//         snapshot.propagate_git_statuses(&mut entries);
2045//         assert_eq!(
2046//             entries
2047//                 .iter()
2048//                 .map(|e| (e.path.as_ref(), e.git_status))
2049//                 .collect::<Vec<_>>(),
2050//             expected_statuses
2051//         );
2052//     }
2053// }
2054
2055// fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
2056//     let http_client = FakeHttpClient::with_404_response();
2057//     cx.read(|cx| Client::new(http_client, cx))
2058// }
2059
2060// #[track_caller]
2061// fn git_init(path: &Path) -> git2::Repository {
2062//     git2::Repository::init(path).expect("Failed to initialize git repository")
2063// }
2064
2065// #[track_caller]
2066// fn git_add<P: AsRef<Path>>(path: P, repo: &git2::Repository) {
2067//     let path = path.as_ref();
2068//     let mut index = repo.index().expect("Failed to get index");
2069//     index.add_path(path).expect("Failed to add a.txt");
2070//     index.write().expect("Failed to write index");
2071// }
2072
2073// #[track_caller]
2074// fn git_remove_index(path: &Path, repo: &git2::Repository) {
2075//     let mut index = repo.index().expect("Failed to get index");
2076//     index.remove_path(path).expect("Failed to add a.txt");
2077//     index.write().expect("Failed to write index");
2078// }
2079
2080// #[track_caller]
2081// fn git_commit(msg: &'static str, repo: &git2::Repository) {
2082//     use git2::Signature;
2083
2084//     let signature = Signature::now("test", "test@zed.dev").unwrap();
2085//     let oid = repo.index().unwrap().write_tree().unwrap();
2086//     let tree = repo.find_tree(oid).unwrap();
2087//     if let Some(head) = repo.head().ok() {
2088//         let parent_obj = head.peel(git2::ObjectType::Commit).unwrap();
2089
2090//         let parent_commit = parent_obj.as_commit().unwrap();
2091
2092//         repo.commit(
2093//             Some("HEAD"),
2094//             &signature,
2095//             &signature,
2096//             msg,
2097//             &tree,
2098//             &[parent_commit],
2099//         )
2100//         .expect("Failed to commit with parent");
2101//     } else {
2102//         repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[])
2103//             .expect("Failed to commit");
2104//     }
2105// }
2106
2107// #[track_caller]
2108// fn git_stash(repo: &mut git2::Repository) {
2109//     use git2::Signature;
2110
2111//     let signature = Signature::now("test", "test@zed.dev").unwrap();
2112//     repo.stash_save(&signature, "N/A", None)
2113//         .expect("Failed to stash");
2114// }
2115
2116// #[track_caller]
2117// fn git_reset(offset: usize, repo: &git2::Repository) {
2118//     let head = repo.head().expect("Couldn't get repo head");
2119//     let object = head.peel(git2::ObjectType::Commit).unwrap();
2120//     let commit = object.as_commit().unwrap();
2121//     let new_head = commit
2122//         .parents()
2123//         .inspect(|parnet| {
2124//             parnet.message();
2125//         })
2126//         .skip(offset)
2127//         .next()
2128//         .expect("Not enough history");
2129//     repo.reset(&new_head.as_object(), git2::ResetType::Soft, None)
2130//         .expect("Could not reset");
2131// }
2132
2133// #[allow(dead_code)]
2134// #[track_caller]
2135// fn git_status(repo: &git2::Repository) -> collections::HashMap<String, git2::Status> {
2136//     repo.statuses(None)
2137//         .unwrap()
2138//         .iter()
2139//         .map(|status| (status.path().unwrap().to_string(), status.status()))
2140//         .collect()
2141// }