diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5111bc04fa256f35feae40464e405d3dd926b286..fc0779dd1f03729e4812c8cac09a06a6d56d5772 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1293,17 +1293,33 @@ impl Project { cx.subscribe(&worktree_store, Self::on_worktree_store_event) .detach(); if init_worktree_trust { - match &connection_options { - RemoteConnectionOptions::Wsl(..) | RemoteConnectionOptions::Ssh(..) => { - trusted_worktrees::track_worktree_trust( - worktree_store.clone(), - Some(RemoteHostLocation::from(connection_options)), - None, - Some((remote_proto.clone(), REMOTE_SERVER_PROJECT_ID)), - cx, - ); + let trust_remote_project = match &connection_options { + RemoteConnectionOptions::Ssh(..) | RemoteConnectionOptions::Wsl(..) => false, + RemoteConnectionOptions::Docker(..) => true, + }; + let remote_host = RemoteHostLocation::from(connection_options); + trusted_worktrees::track_worktree_trust( + worktree_store.clone(), + Some(remote_host.clone()), + None, + Some((remote_proto.clone(), REMOTE_SERVER_PROJECT_ID)), + cx, + ); + if trust_remote_project { + if let Some(trusted_worktres) = TrustedWorktrees::try_get_global(cx) { + trusted_worktres.update(cx, |trusted_worktres, cx| { + trusted_worktres.trust( + worktree_store + .read(cx) + .worktrees() + .map(|worktree| worktree.read(cx).id()) + .map(PathTrust::Worktree) + .collect(), + Some(remote_host), + cx, + ); + }) } - RemoteConnectionOptions::Docker(..) => {} } } diff --git a/crates/project/src/trusted_worktrees.rs b/crates/project/src/trusted_worktrees.rs index 0e1a8b4011bf56b150fe99a502eece905dcc9d78..2b7e73d1f23eefd5a568800c86b19f8f509faba7 100644 --- a/crates/project/src/trusted_worktrees.rs +++ b/crates/project/src/trusted_worktrees.rs @@ -337,6 +337,13 @@ impl TrustedWorktreesStore { if restricted_host != remote_host { return true; } + + // When trusting an abs path on the host, we transitively trust all single file worktrees on this host too. + if is_file && !new_trusted_abs_paths.is_empty() { + trusted_paths.insert(PathTrust::Worktree(*restricted_worktree)); + return false; + } + let retain = (!is_file || new_trusted_other_worktrees.is_empty()) && new_trusted_abs_paths.iter().all(|new_trusted_path| { !restricted_worktree_path.starts_with(new_trusted_path) @@ -1045,6 +1052,13 @@ mod tests { "single-file worktree should be restricted initially" ); + let can_trust_directory = + trusted_worktrees.update(cx, |store, cx| store.can_trust(dir_worktree_id, cx)); + assert!( + !can_trust_directory, + "directory worktree should be restricted initially" + ); + trusted_worktrees.update(cx, |store, cx| { store.trust( HashSet::from_iter([PathTrust::Worktree(dir_worktree_id)]), @@ -1064,6 +1078,78 @@ mod tests { ); } + #[gpui::test] + async fn test_parent_path_trust_enables_single_file(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/"), + json!({ + "project": { "main.rs": "fn main() {}" }, + "standalone.rs": "fn standalone() {}" + }), + ) + .await; + + let project = Project::test( + fs, + [path!("/project").as_ref(), path!("/standalone.rs").as_ref()], + cx, + ) + .await; + let worktree_store = project.read_with(cx, |project, _| project.worktree_store()); + let (dir_worktree_id, file_worktree_id) = worktree_store.read_with(cx, |store, cx| { + let worktrees: Vec<_> = store.worktrees().collect(); + assert_eq!(worktrees.len(), 2); + let (dir_worktree, file_worktree) = if worktrees[0].read(cx).is_single_file() { + (&worktrees[1], &worktrees[0]) + } else { + (&worktrees[0], &worktrees[1]) + }; + assert!(!dir_worktree.read(cx).is_single_file()); + assert!(file_worktree.read(cx).is_single_file()); + (dir_worktree.read(cx).id(), file_worktree.read(cx).id()) + }); + + let trusted_worktrees = init_trust_global(worktree_store, cx); + + let can_trust_file = + trusted_worktrees.update(cx, |store, cx| store.can_trust(file_worktree_id, cx)); + assert!( + !can_trust_file, + "single-file worktree should be restricted initially" + ); + + let can_trust_directory = + trusted_worktrees.update(cx, |store, cx| store.can_trust(dir_worktree_id, cx)); + assert!( + !can_trust_directory, + "directory worktree should be restricted initially" + ); + + trusted_worktrees.update(cx, |store, cx| { + store.trust( + HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/project")))]), + None, + cx, + ); + }); + + let can_trust_dir = + trusted_worktrees.update(cx, |store, cx| store.can_trust(dir_worktree_id, cx)); + let can_trust_file_after = + trusted_worktrees.update(cx, |store, cx| store.can_trust(file_worktree_id, cx)); + assert!( + can_trust_dir, + "directory worktree should be trusted after its parent is trusted" + ); + assert!( + can_trust_file_after, + "single-file worktree should be trusted after directory worktree trust via its parent directory trust" + ); + } + #[gpui::test] async fn test_abs_path_trust_covers_multiple_worktrees(cx: &mut TestAppContext) { init_test(cx);