trusted_worktrees.rs

  1use std::{cell::RefCell, path::PathBuf, rc::Rc};
  2
  3use collections::HashSet;
  4use gpui::{Entity, TestAppContext};
  5use serde_json::json;
  6use settings::SettingsStore;
  7use util::path;
  8
  9use crate::{FakeFs, Project};
 10
 11use project::{trusted_worktrees::*, worktree_store::WorktreeStore};
 12
 13fn init_test(cx: &mut TestAppContext) {
 14    cx.update(|cx| {
 15        if cx.try_global::<SettingsStore>().is_none() {
 16            let settings_store = SettingsStore::test(cx);
 17            cx.set_global(settings_store);
 18        }
 19        if cx.try_global::<TrustedWorktrees>().is_some() {
 20            cx.remove_global::<TrustedWorktrees>();
 21        }
 22    });
 23}
 24
 25fn init_trust_global(
 26    worktree_store: Entity<WorktreeStore>,
 27    cx: &mut TestAppContext,
 28) -> Entity<TrustedWorktreesStore> {
 29    cx.update(|cx| {
 30        init(DbTrustedPaths::default(), cx);
 31        track_worktree_trust(worktree_store, None, None, None, cx);
 32        TrustedWorktrees::try_get_global(cx).expect("global should be set")
 33    })
 34}
 35
 36#[gpui::test]
 37async fn test_single_worktree_trust(cx: &mut TestAppContext) {
 38    init_test(cx);
 39
 40    let fs = FakeFs::new(cx.executor());
 41    fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
 42        .await;
 43
 44    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
 45    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
 46    let worktree_id = worktree_store.read_with(cx, |store, cx| {
 47        store.worktrees().next().unwrap().read(cx).id()
 48    });
 49
 50    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
 51
 52    let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
 53    cx.update({
 54        let events = events.clone();
 55        |cx| {
 56            cx.subscribe(&trusted_worktrees, move |_, event, _| {
 57                events.borrow_mut().push(match event {
 58                    TrustedWorktreesEvent::Trusted(host, paths) => {
 59                        TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
 60                    }
 61                    TrustedWorktreesEvent::Restricted(host, paths) => {
 62                        TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
 63                    }
 64                });
 65            })
 66        }
 67    })
 68    .detach();
 69
 70    let can_trust = trusted_worktrees.update(cx, |store, cx| {
 71        store.can_trust(&worktree_store, worktree_id, cx)
 72    });
 73    assert!(!can_trust, "worktree should be restricted by default");
 74
 75    {
 76        let events = events.borrow();
 77        assert_eq!(events.len(), 1);
 78        match &events[0] {
 79            TrustedWorktreesEvent::Restricted(event_worktree_store, paths) => {
 80                assert_eq!(event_worktree_store, &worktree_store.downgrade());
 81                assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
 82            }
 83            _ => panic!("expected Restricted event"),
 84        }
 85    }
 86
 87    let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
 88        store.has_restricted_worktrees(&worktree_store, cx)
 89    });
 90    assert!(has_restricted, "should have restricted worktrees");
 91
 92    let restricted = trusted_worktrees.read_with(cx, |trusted_worktrees, cx| {
 93        trusted_worktrees.restricted_worktrees(&worktree_store, cx)
 94    });
 95    assert!(restricted.iter().any(|(id, _)| *id == worktree_id));
 96
 97    events.borrow_mut().clear();
 98
 99    let can_trust_again = trusted_worktrees.update(cx, |store, cx| {
100        store.can_trust(&worktree_store, worktree_id, cx)
101    });
102    assert!(!can_trust_again, "worktree should still be restricted");
103    assert!(
104        events.borrow().is_empty(),
105        "no duplicate Restricted event on repeated can_trust"
106    );
107
108    trusted_worktrees.update(cx, |store, cx| {
109        store.trust(
110            &worktree_store,
111            HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
112            cx,
113        );
114    });
115
116    {
117        let events = events.borrow();
118        assert_eq!(events.len(), 1);
119        match &events[0] {
120            TrustedWorktreesEvent::Trusted(event_worktree_store, paths) => {
121                assert_eq!(event_worktree_store, &worktree_store.downgrade());
122                assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
123            }
124            _ => panic!("expected Trusted event"),
125        }
126    }
127
128    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
129        store.can_trust(&worktree_store, worktree_id, cx)
130    });
131    assert!(can_trust_after, "worktree should be trusted after trust()");
132
133    let has_restricted_after = trusted_worktrees.read_with(cx, |store, cx| {
134        store.has_restricted_worktrees(&worktree_store, cx)
135    });
136    assert!(
137        !has_restricted_after,
138        "should have no restricted worktrees after trust"
139    );
140
141    let restricted_after = trusted_worktrees.read_with(cx, |trusted_worktrees, cx| {
142        trusted_worktrees.restricted_worktrees(&worktree_store, cx)
143    });
144    assert!(
145        restricted_after.is_empty(),
146        "restricted set should be empty"
147    );
148}
149
150#[gpui::test]
151async fn test_single_file_worktree_trust(cx: &mut TestAppContext) {
152    init_test(cx);
153
154    let fs = FakeFs::new(cx.executor());
155    fs.insert_tree(path!("/root"), json!({ "foo.rs": "fn foo() {}" }))
156        .await;
157
158    let project = Project::test(fs, [path!("/root/foo.rs").as_ref()], cx).await;
159    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
160    let worktree_id = worktree_store.read_with(cx, |store, cx| {
161        let worktree = store.worktrees().next().unwrap();
162        let worktree = worktree.read(cx);
163        assert!(worktree.is_single_file(), "expected single-file worktree");
164        worktree.id()
165    });
166
167    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
168
169    let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
170    cx.update({
171        let events = events.clone();
172        |cx| {
173            cx.subscribe(&trusted_worktrees, move |_, event, _| {
174                events.borrow_mut().push(match event {
175                    TrustedWorktreesEvent::Trusted(host, paths) => {
176                        TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
177                    }
178                    TrustedWorktreesEvent::Restricted(host, paths) => {
179                        TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
180                    }
181                });
182            })
183        }
184    })
185    .detach();
186
187    let can_trust = trusted_worktrees.update(cx, |store, cx| {
188        store.can_trust(&worktree_store, worktree_id, cx)
189    });
190    assert!(
191        !can_trust,
192        "single-file worktree should be restricted by default"
193    );
194
195    {
196        let events = events.borrow();
197        assert_eq!(events.len(), 1);
198        match &events[0] {
199            TrustedWorktreesEvent::Restricted(event_worktree_store, paths) => {
200                assert_eq!(event_worktree_store, &worktree_store.downgrade());
201                assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
202            }
203            _ => panic!("expected Restricted event"),
204        }
205    }
206
207    events.borrow_mut().clear();
208
209    trusted_worktrees.update(cx, |store, cx| {
210        store.trust(
211            &worktree_store,
212            HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
213            cx,
214        );
215    });
216
217    {
218        let events = events.borrow();
219        assert_eq!(events.len(), 1);
220        match &events[0] {
221            TrustedWorktreesEvent::Trusted(event_worktree_store, paths) => {
222                assert_eq!(event_worktree_store, &worktree_store.downgrade());
223                assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
224            }
225            _ => panic!("expected Trusted event"),
226        }
227    }
228
229    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
230        store.can_trust(&worktree_store, worktree_id, cx)
231    });
232    assert!(
233        can_trust_after,
234        "single-file worktree should be trusted after trust()"
235    );
236}
237
238#[gpui::test]
239async fn test_multiple_single_file_worktrees_trust_one(cx: &mut TestAppContext) {
240    init_test(cx);
241
242    let fs = FakeFs::new(cx.executor());
243    fs.insert_tree(
244        path!("/root"),
245        json!({
246            "a.rs": "fn a() {}",
247            "b.rs": "fn b() {}",
248            "c.rs": "fn c() {}"
249        }),
250    )
251    .await;
252
253    let project = Project::test(
254        fs,
255        [
256            path!("/root/a.rs").as_ref(),
257            path!("/root/b.rs").as_ref(),
258            path!("/root/c.rs").as_ref(),
259        ],
260        cx,
261    )
262    .await;
263    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
264    let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
265        store
266            .worktrees()
267            .map(|worktree| {
268                let worktree = worktree.read(cx);
269                assert!(worktree.is_single_file());
270                worktree.id()
271            })
272            .collect()
273    });
274    assert_eq!(worktree_ids.len(), 3);
275
276    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
277
278    for &worktree_id in &worktree_ids {
279        let can_trust = trusted_worktrees.update(cx, |store, cx| {
280            store.can_trust(&worktree_store, worktree_id, cx)
281        });
282        assert!(
283            !can_trust,
284            "worktree {worktree_id:?} should be restricted initially"
285        );
286    }
287
288    trusted_worktrees.update(cx, |store, cx| {
289        store.trust(
290            &worktree_store,
291            HashSet::from_iter([PathTrust::Worktree(worktree_ids[1])]),
292            cx,
293        );
294    });
295
296    let can_trust_0 = trusted_worktrees.update(cx, |store, cx| {
297        store.can_trust(&worktree_store, worktree_ids[0], cx)
298    });
299    let can_trust_1 = trusted_worktrees.update(cx, |store, cx| {
300        store.can_trust(&worktree_store, worktree_ids[1], cx)
301    });
302    let can_trust_2 = trusted_worktrees.update(cx, |store, cx| {
303        store.can_trust(&worktree_store, worktree_ids[2], cx)
304    });
305
306    assert!(!can_trust_0, "worktree 0 should still be restricted");
307    assert!(can_trust_1, "worktree 1 should be trusted");
308    assert!(!can_trust_2, "worktree 2 should still be restricted");
309}
310
311#[gpui::test]
312async fn test_two_directory_worktrees_separate_trust(cx: &mut TestAppContext) {
313    init_test(cx);
314
315    let fs = FakeFs::new(cx.executor());
316    fs.insert_tree(
317        path!("/projects"),
318        json!({
319            "project_a": { "main.rs": "fn main() {}" },
320            "project_b": { "lib.rs": "pub fn lib() {}" }
321        }),
322    )
323    .await;
324
325    let project = Project::test(
326        fs,
327        [
328            path!("/projects/project_a").as_ref(),
329            path!("/projects/project_b").as_ref(),
330        ],
331        cx,
332    )
333    .await;
334    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
335    let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
336        store
337            .worktrees()
338            .map(|worktree| {
339                let worktree = worktree.read(cx);
340                assert!(!worktree.is_single_file());
341                worktree.id()
342            })
343            .collect()
344    });
345    assert_eq!(worktree_ids.len(), 2);
346
347    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
348
349    let can_trust_a = trusted_worktrees.update(cx, |store, cx| {
350        store.can_trust(&worktree_store, worktree_ids[0], cx)
351    });
352    let can_trust_b = trusted_worktrees.update(cx, |store, cx| {
353        store.can_trust(&worktree_store, worktree_ids[1], cx)
354    });
355    assert!(!can_trust_a, "project_a should be restricted initially");
356    assert!(!can_trust_b, "project_b should be restricted initially");
357
358    trusted_worktrees.update(cx, |store, cx| {
359        store.trust(
360            &worktree_store,
361            HashSet::from_iter([PathTrust::Worktree(worktree_ids[0])]),
362            cx,
363        );
364    });
365
366    let can_trust_a = trusted_worktrees.update(cx, |store, cx| {
367        store.can_trust(&worktree_store, worktree_ids[0], cx)
368    });
369    let can_trust_b = trusted_worktrees.update(cx, |store, cx| {
370        store.can_trust(&worktree_store, worktree_ids[1], cx)
371    });
372    assert!(can_trust_a, "project_a should be trusted after trust()");
373    assert!(!can_trust_b, "project_b should still be restricted");
374
375    trusted_worktrees.update(cx, |store, cx| {
376        store.trust(
377            &worktree_store,
378            HashSet::from_iter([PathTrust::Worktree(worktree_ids[1])]),
379            cx,
380        );
381    });
382
383    let can_trust_a = trusted_worktrees.update(cx, |store, cx| {
384        store.can_trust(&worktree_store, worktree_ids[0], cx)
385    });
386    let can_trust_b = trusted_worktrees.update(cx, |store, cx| {
387        store.can_trust(&worktree_store, worktree_ids[1], cx)
388    });
389    assert!(can_trust_a, "project_a should remain trusted");
390    assert!(can_trust_b, "project_b should now be trusted");
391}
392
393#[gpui::test]
394async fn test_directory_worktree_trust_enables_single_file(cx: &mut TestAppContext) {
395    init_test(cx);
396
397    let fs = FakeFs::new(cx.executor());
398    fs.insert_tree(
399        path!("/"),
400        json!({
401            "project": { "main.rs": "fn main() {}" },
402            "standalone.rs": "fn standalone() {}"
403        }),
404    )
405    .await;
406
407    let project = Project::test(
408        fs,
409        [path!("/project").as_ref(), path!("/standalone.rs").as_ref()],
410        cx,
411    )
412    .await;
413    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
414    let (dir_worktree_id, file_worktree_id) = worktree_store.read_with(cx, |store, cx| {
415        let worktrees: Vec<_> = store.worktrees().collect();
416        assert_eq!(worktrees.len(), 2);
417        let (dir_worktree, file_worktree) = if worktrees[0].read(cx).is_single_file() {
418            (&worktrees[1], &worktrees[0])
419        } else {
420            (&worktrees[0], &worktrees[1])
421        };
422        assert!(!dir_worktree.read(cx).is_single_file());
423        assert!(file_worktree.read(cx).is_single_file());
424        (dir_worktree.read(cx).id(), file_worktree.read(cx).id())
425    });
426
427    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
428
429    let can_trust_file = trusted_worktrees.update(cx, |store, cx| {
430        store.can_trust(&worktree_store, file_worktree_id, cx)
431    });
432    assert!(
433        !can_trust_file,
434        "single-file worktree should be restricted initially"
435    );
436
437    let can_trust_directory = trusted_worktrees.update(cx, |store, cx| {
438        store.can_trust(&worktree_store, dir_worktree_id, cx)
439    });
440    assert!(
441        !can_trust_directory,
442        "directory worktree should be restricted initially"
443    );
444
445    trusted_worktrees.update(cx, |store, cx| {
446        store.trust(
447            &worktree_store,
448            HashSet::from_iter([PathTrust::Worktree(dir_worktree_id)]),
449            cx,
450        );
451    });
452
453    let can_trust_dir = trusted_worktrees.update(cx, |store, cx| {
454        store.can_trust(&worktree_store, dir_worktree_id, cx)
455    });
456    let can_trust_file_after = trusted_worktrees.update(cx, |store, cx| {
457        store.can_trust(&worktree_store, file_worktree_id, cx)
458    });
459    assert!(can_trust_dir, "directory worktree should be trusted");
460    assert!(
461        can_trust_file_after,
462        "single-file worktree should be trusted after directory worktree trust"
463    );
464}
465
466#[gpui::test]
467async fn test_parent_path_trust_enables_single_file(cx: &mut TestAppContext) {
468    init_test(cx);
469
470    let fs = FakeFs::new(cx.executor());
471    fs.insert_tree(
472        path!("/"),
473        json!({
474            "project": { "main.rs": "fn main() {}" },
475            "standalone.rs": "fn standalone() {}"
476        }),
477    )
478    .await;
479
480    let project = Project::test(
481        fs,
482        [path!("/project").as_ref(), path!("/standalone.rs").as_ref()],
483        cx,
484    )
485    .await;
486    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
487    let (dir_worktree_id, file_worktree_id) = worktree_store.read_with(cx, |store, cx| {
488        let worktrees: Vec<_> = store.worktrees().collect();
489        assert_eq!(worktrees.len(), 2);
490        let (dir_worktree, file_worktree) = if worktrees[0].read(cx).is_single_file() {
491            (&worktrees[1], &worktrees[0])
492        } else {
493            (&worktrees[0], &worktrees[1])
494        };
495        assert!(!dir_worktree.read(cx).is_single_file());
496        assert!(file_worktree.read(cx).is_single_file());
497        (dir_worktree.read(cx).id(), file_worktree.read(cx).id())
498    });
499
500    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
501
502    let can_trust_file = trusted_worktrees.update(cx, |store, cx| {
503        store.can_trust(&worktree_store, file_worktree_id, cx)
504    });
505    assert!(
506        !can_trust_file,
507        "single-file worktree should be restricted initially"
508    );
509
510    let can_trust_directory = trusted_worktrees.update(cx, |store, cx| {
511        store.can_trust(&worktree_store, dir_worktree_id, cx)
512    });
513    assert!(
514        !can_trust_directory,
515        "directory worktree should be restricted initially"
516    );
517
518    trusted_worktrees.update(cx, |store, cx| {
519        store.trust(
520            &worktree_store,
521            HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/project")))]),
522            cx,
523        );
524    });
525
526    let can_trust_dir = trusted_worktrees.update(cx, |store, cx| {
527        store.can_trust(&worktree_store, dir_worktree_id, cx)
528    });
529    let can_trust_file_after = trusted_worktrees.update(cx, |store, cx| {
530        store.can_trust(&worktree_store, file_worktree_id, cx)
531    });
532    assert!(
533        can_trust_dir,
534        "directory worktree should be trusted after its parent is trusted"
535    );
536    assert!(
537        can_trust_file_after,
538        "single-file worktree should be trusted after directory worktree trust via its parent directory trust"
539    );
540}
541
542#[gpui::test]
543async fn test_abs_path_trust_covers_multiple_worktrees(cx: &mut TestAppContext) {
544    init_test(cx);
545
546    let fs = FakeFs::new(cx.executor());
547    fs.insert_tree(
548        path!("/root"),
549        json!({
550            "project_a": { "main.rs": "fn main() {}" },
551            "project_b": { "lib.rs": "pub fn lib() {}" }
552        }),
553    )
554    .await;
555
556    let project = Project::test(
557        fs,
558        [
559            path!("/root/project_a").as_ref(),
560            path!("/root/project_b").as_ref(),
561        ],
562        cx,
563    )
564    .await;
565    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
566    let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
567        store
568            .worktrees()
569            .map(|worktree| worktree.read(cx).id())
570            .collect()
571    });
572    assert_eq!(worktree_ids.len(), 2);
573
574    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
575
576    for &worktree_id in &worktree_ids {
577        let can_trust = trusted_worktrees.update(cx, |store, cx| {
578            store.can_trust(&worktree_store, worktree_id, cx)
579        });
580        assert!(!can_trust, "worktree should be restricted initially");
581    }
582
583    trusted_worktrees.update(cx, |store, cx| {
584        store.trust(
585            &worktree_store,
586            HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/root")))]),
587            cx,
588        );
589    });
590
591    for &worktree_id in &worktree_ids {
592        let can_trust = trusted_worktrees.update(cx, |store, cx| {
593            store.can_trust(&worktree_store, worktree_id, cx)
594        });
595        assert!(
596            can_trust,
597            "worktree should be trusted after parent path trust"
598        );
599    }
600}
601
602#[gpui::test]
603async fn test_auto_trust_all(cx: &mut TestAppContext) {
604    init_test(cx);
605
606    let fs = FakeFs::new(cx.executor());
607    fs.insert_tree(
608        path!("/"),
609        json!({
610            "project_a": { "main.rs": "fn main() {}" },
611            "project_b": { "lib.rs": "pub fn lib() {}" },
612            "single.rs": "fn single() {}"
613        }),
614    )
615    .await;
616
617    let project = Project::test(
618        fs,
619        [
620            path!("/project_a").as_ref(),
621            path!("/project_b").as_ref(),
622            path!("/single.rs").as_ref(),
623        ],
624        cx,
625    )
626    .await;
627    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
628    let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
629        store
630            .worktrees()
631            .map(|worktree| worktree.read(cx).id())
632            .collect()
633    });
634    assert_eq!(worktree_ids.len(), 3);
635
636    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
637
638    let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
639    cx.update({
640        let events = events.clone();
641        |cx| {
642            cx.subscribe(&trusted_worktrees, move |_, event, _| {
643                events.borrow_mut().push(match event {
644                    TrustedWorktreesEvent::Trusted(host, paths) => {
645                        TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
646                    }
647                    TrustedWorktreesEvent::Restricted(host, paths) => {
648                        TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
649                    }
650                });
651            })
652        }
653    })
654    .detach();
655
656    for &worktree_id in &worktree_ids {
657        let can_trust = trusted_worktrees.update(cx, |store, cx| {
658            store.can_trust(&worktree_store, worktree_id, cx)
659        });
660        assert!(!can_trust, "worktree should be restricted initially");
661    }
662
663    let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
664        store.has_restricted_worktrees(&worktree_store, cx)
665    });
666    assert!(has_restricted, "should have restricted worktrees");
667
668    events.borrow_mut().clear();
669
670    trusted_worktrees.update(cx, |store, cx| {
671        store.auto_trust_all(cx);
672    });
673
674    for &worktree_id in &worktree_ids {
675        let can_trust = trusted_worktrees.update(cx, |store, cx| {
676            store.can_trust(&worktree_store, worktree_id, cx)
677        });
678        assert!(
679            can_trust,
680            "worktree {worktree_id:?} should be trusted after auto_trust_all"
681        );
682    }
683
684    let has_restricted_after = trusted_worktrees.read_with(cx, |store, cx| {
685        store.has_restricted_worktrees(&worktree_store, cx)
686    });
687    assert!(
688        !has_restricted_after,
689        "should have no restricted worktrees after auto_trust_all"
690    );
691
692    let trusted_event_count = events
693        .borrow()
694        .iter()
695        .filter(|e| matches!(e, TrustedWorktreesEvent::Trusted(..)))
696        .count();
697    assert!(
698        trusted_event_count > 0,
699        "should have emitted Trusted events"
700    );
701}
702
703#[gpui::test]
704async fn test_trust_restrict_trust_cycle(cx: &mut TestAppContext) {
705    init_test(cx);
706
707    let fs = FakeFs::new(cx.executor());
708    fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
709        .await;
710
711    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
712    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
713    let worktree_id = worktree_store.read_with(cx, |store, cx| {
714        store.worktrees().next().unwrap().read(cx).id()
715    });
716
717    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
718
719    let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
720    cx.update({
721        let events = events.clone();
722        |cx| {
723            cx.subscribe(&trusted_worktrees, move |_, event, _| {
724                events.borrow_mut().push(match event {
725                    TrustedWorktreesEvent::Trusted(host, paths) => {
726                        TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
727                    }
728                    TrustedWorktreesEvent::Restricted(host, paths) => {
729                        TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
730                    }
731                });
732            })
733        }
734    })
735    .detach();
736
737    let can_trust = trusted_worktrees.update(cx, |store, cx| {
738        store.can_trust(&worktree_store, worktree_id, cx)
739    });
740    assert!(!can_trust, "should be restricted initially");
741    assert_eq!(events.borrow().len(), 1);
742    events.borrow_mut().clear();
743
744    trusted_worktrees.update(cx, |store, cx| {
745        store.trust(
746            &worktree_store,
747            HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
748            cx,
749        );
750    });
751    let can_trust = trusted_worktrees.update(cx, |store, cx| {
752        store.can_trust(&worktree_store, worktree_id, cx)
753    });
754    assert!(can_trust, "should be trusted after trust()");
755    assert_eq!(events.borrow().len(), 1);
756    assert!(matches!(
757        &events.borrow()[0],
758        TrustedWorktreesEvent::Trusted(..)
759    ));
760    events.borrow_mut().clear();
761
762    trusted_worktrees.update(cx, |store, cx| {
763        store.restrict(
764            worktree_store.downgrade(),
765            HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
766            cx,
767        );
768    });
769    let can_trust = trusted_worktrees.update(cx, |store, cx| {
770        store.can_trust(&worktree_store, worktree_id, cx)
771    });
772    assert!(!can_trust, "should be restricted after restrict()");
773    assert_eq!(events.borrow().len(), 1);
774    assert!(matches!(
775        &events.borrow()[0],
776        TrustedWorktreesEvent::Restricted(..)
777    ));
778
779    let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
780        store.has_restricted_worktrees(&worktree_store, cx)
781    });
782    assert!(has_restricted);
783    events.borrow_mut().clear();
784
785    trusted_worktrees.update(cx, |store, cx| {
786        store.trust(
787            &worktree_store,
788            HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
789            cx,
790        );
791    });
792    let can_trust = trusted_worktrees.update(cx, |store, cx| {
793        store.can_trust(&worktree_store, worktree_id, cx)
794    });
795    assert!(can_trust, "should be trusted again after second trust()");
796    assert_eq!(events.borrow().len(), 1);
797    assert!(matches!(
798        &events.borrow()[0],
799        TrustedWorktreesEvent::Trusted(..)
800    ));
801
802    let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
803        store.has_restricted_worktrees(&worktree_store, cx)
804    });
805    assert!(!has_restricted);
806}
807
808#[gpui::test]
809async fn test_multi_host_trust_isolation(cx: &mut TestAppContext) {
810    init_test(cx);
811
812    let fs = FakeFs::new(cx.executor());
813    fs.insert_tree(
814        path!("/"),
815        json!({
816            "local_project": { "main.rs": "fn main() {}" },
817            "remote_project": { "lib.rs": "pub fn lib() {}" }
818        }),
819    )
820    .await;
821
822    let project = Project::test(
823        fs,
824        [
825            path!("/local_project").as_ref(),
826            path!("/remote_project").as_ref(),
827        ],
828        cx,
829    )
830    .await;
831    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
832    let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
833        store
834            .worktrees()
835            .map(|worktree| worktree.read(cx).id())
836            .collect()
837    });
838    assert_eq!(worktree_ids.len(), 2);
839    let local_worktree = worktree_ids[0];
840    let _remote_worktree = worktree_ids[1];
841
842    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
843
844    let can_trust_local = trusted_worktrees.update(cx, |store, cx| {
845        store.can_trust(&worktree_store, local_worktree, cx)
846    });
847    assert!(!can_trust_local, "local worktree restricted on host_a");
848
849    trusted_worktrees.update(cx, |store, cx| {
850        store.trust(
851            &worktree_store,
852            HashSet::from_iter([PathTrust::Worktree(local_worktree)]),
853            cx,
854        );
855    });
856
857    let can_trust_local_after = trusted_worktrees.update(cx, |store, cx| {
858        store.can_trust(&worktree_store, local_worktree, cx)
859    });
860    assert!(
861        can_trust_local_after,
862        "local worktree should be trusted on local host"
863    );
864}
865
866#[gpui::test]
867async fn test_invisible_worktree_stores_do_not_affect_trust(cx: &mut TestAppContext) {
868    init_test(cx);
869
870    let fs = FakeFs::new(cx.executor());
871    fs.insert_tree(
872        path!("/"),
873        json!({
874            "visible": { "main.rs": "fn main() {}" },
875            "other": { "a.rs": "fn other() {}" },
876            "invisible": { "b.rs": "fn invisible() {}" }
877        }),
878    )
879    .await;
880
881    let project = Project::test(fs, [path!("/visible").as_ref()], cx).await;
882    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
883    let visible_worktree_id = worktree_store.read_with(cx, |store, cx| {
884        store
885            .worktrees()
886            .find(|worktree| worktree.read(cx).root_dir().unwrap().ends_with("visible"))
887            .expect("visible worktree")
888            .read(cx)
889            .id()
890    });
891    let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
892
893    let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
894    cx.update({
895        let events = events.clone();
896        |cx| {
897            cx.subscribe(&trusted_worktrees, move |_, event, _| {
898                events.borrow_mut().push(match event {
899                    TrustedWorktreesEvent::Trusted(host, paths) => {
900                        TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
901                    }
902                    TrustedWorktreesEvent::Restricted(host, paths) => {
903                        TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
904                    }
905                });
906            })
907        }
908    })
909    .detach();
910
911    assert!(
912        !trusted_worktrees.update(cx, |store, cx| {
913            store.can_trust(&worktree_store, visible_worktree_id, cx)
914        }),
915        "visible worktree should be restricted initially"
916    );
917    assert_eq!(
918        HashSet::from_iter([(visible_worktree_id)]),
919        trusted_worktrees.read_with(cx, |store, _| {
920            store.restricted_worktrees_for_store(&worktree_store)
921        }),
922        "only visible worktree should be restricted",
923    );
924
925    let (new_visible_worktree, new_invisible_worktree) =
926        worktree_store.update(cx, |worktree_store, cx| {
927            let new_visible_worktree = worktree_store.create_worktree("/other", true, cx);
928            let new_invisible_worktree = worktree_store.create_worktree("/invisible", false, cx);
929            (new_visible_worktree, new_invisible_worktree)
930        });
931    let (new_visible_worktree, new_invisible_worktree) = (
932        new_visible_worktree.await.unwrap(),
933        new_invisible_worktree.await.unwrap(),
934    );
935
936    let new_visible_worktree_id =
937        new_visible_worktree.read_with(cx, |new_visible_worktree, _| new_visible_worktree.id());
938    assert!(
939        !trusted_worktrees.update(cx, |store, cx| {
940            store.can_trust(&worktree_store, new_visible_worktree_id, cx)
941        }),
942        "new visible worktree should be restricted initially",
943    );
944    assert!(
945        trusted_worktrees.update(cx, |store, cx| {
946            store.can_trust(&worktree_store, new_invisible_worktree.read(cx).id(), cx)
947        }),
948        "invisible worktree should be skipped",
949    );
950    assert_eq!(
951        HashSet::from_iter([visible_worktree_id, new_visible_worktree_id]),
952        trusted_worktrees.read_with(cx, |store, _| {
953            store.restricted_worktrees_for_store(&worktree_store)
954        }),
955        "only visible worktrees should be restricted"
956    );
957}