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