1//! A module, responsible for managing the trust logic in Zed.
2//!
3//! It deals with multiple hosts, distinguished by [`RemoteHostLocation`].
4//! Each [`crate::Project`] and `HeadlessProject` should call [`init_global`], if wants to establish the trust mechanism.
5//! This will set up a [`gpui::Global`] with [`TrustedWorktrees`] entity that will persist, restore and allow querying for worktree trust.
6//! It's also possible to subscribe on [`TrustedWorktreesEvent`] events of this entity to track trust changes dynamically.
7//!
8//! The implementation can synchronize trust information with the remote hosts: currently, WSL and SSH.
9//! Docker and Collab remotes do not employ trust mechanism, as manage that themselves.
10//!
11//! Unless `trust_all_worktrees` auto trust is enabled, does not trust anything that was not persisted before.
12//! When dealing with "restricted" and other related concepts in the API, it means all explicitly restricted, after any of the [`TrustedWorktreesStore::can_trust`] and [`TrustedWorktreesStore::can_trust_global`] calls.
13//!
14//! Zed does not consider invisible, `worktree.is_visible() == false` worktrees in Zed, as those are programmatically created inside Zed for internal needs, e.g. a tmp dir for `keymap_editor.rs` needs.
15//!
16//!
17//! Path rust hierarchy.
18//!
19//! Zed has multiple layers of trust, based on the requests and [`PathTrust`] enum variants.
20//! From the least to the most trusted level:
21//!
22//! * "single file worktree"
23//!
24//! After opening an empty Zed it's possible to open just a file, same as after opening a directory in Zed it's possible to open a file outside of this directory.
25//! Usual scenario for both cases is opening Zed's settings.json file via `zed: open settings file` command: that starts a language server for a new file open, which originates from a newly created, single file worktree.
26//!
27//! Spawning a language server is potentially dangerous, and Zed needs to restrict that by default.
28//! Each single file worktree requires a separate trust permission, unless a more global level is trusted.
29//!
30//! * "directory worktree"
31//!
32//! If a directory is open in Zed, it's a full worktree which may spawn multiple language servers associated with it.
33//! Each such worktree requires a separate trust permission, so each separate directory worktree has to be trusted separately, unless a more global level is trusted.
34//!
35//! When a directory worktree is trusted and language servers are allowed to be downloaded and started, hence, "single file worktree" level of trust also.
36//!
37//! * "path override"
38//!
39//! To ease trusting multiple directory worktrees at once, it's possible to trust a parent directory of a certain directory worktree opened in Zed.
40//! Trusting a directory means trusting all its subdirectories as well, including all current and potential directory worktrees.
41
42use client::ProjectId;
43use collections::{HashMap, HashSet};
44use gpui::{
45 App, AppContext as _, Context, Entity, EventEmitter, Global, SharedString, Task, WeakEntity,
46};
47use remote::RemoteConnectionOptions;
48use rpc::{AnyProtoClient, proto};
49use settings::{Settings as _, WorktreeId};
50use std::{
51 path::{Path, PathBuf},
52 sync::Arc,
53};
54use util::debug_panic;
55
56use crate::{project_settings::ProjectSettings, worktree_store::WorktreeStore};
57
58pub fn init(db_trusted_paths: DbTrustedPaths, cx: &mut App) {
59 if TrustedWorktrees::try_get_global(cx).is_none() {
60 let trusted_worktrees = cx.new(|_| TrustedWorktreesStore::new(db_trusted_paths));
61 cx.set_global(TrustedWorktrees(trusted_worktrees))
62 }
63}
64
65/// An initialization call to set up trust global for a particular project (remote or local).
66pub fn track_worktree_trust(
67 worktree_store: Entity<WorktreeStore>,
68 remote_host: Option<RemoteHostLocation>,
69 downstream_client: Option<(AnyProtoClient, ProjectId)>,
70 upstream_client: Option<(AnyProtoClient, ProjectId)>,
71 cx: &mut App,
72) {
73 match TrustedWorktrees::try_get_global(cx) {
74 Some(trusted_worktrees) => {
75 trusted_worktrees.update(cx, |trusted_worktrees, cx| {
76 trusted_worktrees.add_worktree_store(
77 worktree_store.clone(),
78 remote_host,
79 downstream_client,
80 upstream_client.clone(),
81 cx,
82 );
83
84 if let Some((upstream_client, upstream_project_id)) = upstream_client {
85 let trusted_paths = trusted_worktrees
86 .trusted_paths
87 .get(&worktree_store.downgrade())
88 .into_iter()
89 .flatten()
90 .map(|trusted_path| trusted_path.to_proto())
91 .collect::<Vec<_>>();
92 if !trusted_paths.is_empty() {
93 upstream_client
94 .send(proto::TrustWorktrees {
95 project_id: upstream_project_id.0,
96 trusted_paths,
97 })
98 .ok();
99 }
100 }
101 });
102 }
103 None => log::debug!("No TrustedWorktrees initialized, not tracking worktree trust"),
104 }
105}
106
107/// A collection of worktree trust metadata, can be accessed globally (if initialized) and subscribed to.
108pub struct TrustedWorktrees(Entity<TrustedWorktreesStore>);
109
110impl Global for TrustedWorktrees {}
111
112impl TrustedWorktrees {
113 pub fn try_get_global(cx: &App) -> Option<Entity<TrustedWorktreesStore>> {
114 cx.try_global::<Self>().map(|this| this.0.clone())
115 }
116}
117
118/// A collection of worktrees that are considered trusted and not trusted.
119/// This can be used when checking for this criteria before enabling certain features.
120///
121/// Emits an event each time the worktree was checked and found not trusted,
122/// or a certain worktree had been trusted.
123#[derive(Debug)]
124pub struct TrustedWorktreesStore {
125 worktree_stores: HashMap<WeakEntity<WorktreeStore>, StoreData>,
126 db_trusted_paths: DbTrustedPaths,
127 trusted_paths: TrustedPaths,
128 restricted: HashMap<WeakEntity<WorktreeStore>, HashSet<WorktreeId>>,
129 worktree_trust_serialization: Task<()>,
130}
131
132#[derive(Debug, Default)]
133struct StoreData {
134 upstream_client: Option<(AnyProtoClient, ProjectId)>,
135 downstream_client: Option<(AnyProtoClient, ProjectId)>,
136 host: Option<RemoteHostLocation>,
137}
138
139/// An identifier of a host to split the trust questions by.
140/// Each trusted data change and event is done for a particular host.
141/// A host may contain more than one worktree or even project open concurrently.
142#[derive(Debug, PartialEq, Eq, Clone, Hash)]
143pub struct RemoteHostLocation {
144 pub user_name: Option<SharedString>,
145 pub host_identifier: SharedString,
146}
147
148impl From<RemoteConnectionOptions> for RemoteHostLocation {
149 fn from(options: RemoteConnectionOptions) -> Self {
150 let (user_name, host_name) = match options {
151 RemoteConnectionOptions::Ssh(ssh) => (
152 ssh.username.map(SharedString::new),
153 SharedString::new(ssh.host.to_string()),
154 ),
155 RemoteConnectionOptions::Wsl(wsl) => (
156 wsl.user.map(SharedString::new),
157 SharedString::new(wsl.distro_name),
158 ),
159 RemoteConnectionOptions::Docker(docker_connection_options) => (
160 Some(SharedString::new(docker_connection_options.name)),
161 SharedString::new(docker_connection_options.container_id),
162 ),
163 #[cfg(feature = "test-support")]
164 RemoteConnectionOptions::Mock(mock) => {
165 (None, SharedString::new(format!("mock-{}", mock.id)))
166 }
167 };
168 Self {
169 user_name,
170 host_identifier: host_name,
171 }
172 }
173}
174
175/// A unit of trust consideration inside a particular host:
176/// either a familiar worktree, or a path that may influence other worktrees' trust.
177/// See module-level documentation on the trust model.
178#[derive(Debug, PartialEq, Eq, Clone, Hash)]
179pub enum PathTrust {
180 /// A worktree that is familiar to this workspace.
181 /// Either a single file or a directory worktree.
182 Worktree(WorktreeId),
183 /// A path that may be another worktree yet not loaded into any workspace (hence, without any `WorktreeId`),
184 /// or a parent path coming out of the security modal.
185 AbsPath(PathBuf),
186}
187
188impl PathTrust {
189 fn to_proto(&self) -> proto::PathTrust {
190 match self {
191 Self::Worktree(worktree_id) => proto::PathTrust {
192 content: Some(proto::path_trust::Content::WorktreeId(
193 worktree_id.to_proto(),
194 )),
195 },
196 Self::AbsPath(path_buf) => proto::PathTrust {
197 content: Some(proto::path_trust::Content::AbsPath(
198 path_buf.to_string_lossy().to_string(),
199 )),
200 },
201 }
202 }
203
204 pub fn from_proto(proto: proto::PathTrust) -> Option<Self> {
205 Some(match proto.content? {
206 proto::path_trust::Content::WorktreeId(id) => {
207 Self::Worktree(WorktreeId::from_proto(id))
208 }
209 proto::path_trust::Content::AbsPath(path) => Self::AbsPath(PathBuf::from(path)),
210 })
211 }
212}
213
214/// A change of trust on a certain host.
215#[derive(Debug)]
216pub enum TrustedWorktreesEvent {
217 Trusted(WeakEntity<WorktreeStore>, HashSet<PathTrust>),
218 Restricted(WeakEntity<WorktreeStore>, HashSet<PathTrust>),
219}
220
221impl EventEmitter<TrustedWorktreesEvent> for TrustedWorktreesStore {}
222
223type TrustedPaths = HashMap<WeakEntity<WorktreeStore>, HashSet<PathTrust>>;
224pub type DbTrustedPaths = HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>>;
225
226impl TrustedWorktreesStore {
227 fn new(db_trusted_paths: DbTrustedPaths) -> Self {
228 Self {
229 db_trusted_paths,
230 trusted_paths: HashMap::default(),
231 worktree_stores: HashMap::default(),
232 restricted: HashMap::default(),
233 worktree_trust_serialization: Task::ready(()),
234 }
235 }
236
237 /// Whether a particular worktree store has associated worktrees that are restricted, or an associated host is restricted.
238 pub fn has_restricted_worktrees(
239 &self,
240 worktree_store: &Entity<WorktreeStore>,
241 cx: &App,
242 ) -> bool {
243 self.restricted
244 .get(&worktree_store.downgrade())
245 .is_some_and(|restricted_worktrees| {
246 restricted_worktrees.iter().any(|restricted_worktree| {
247 worktree_store
248 .read(cx)
249 .worktree_for_id(*restricted_worktree, cx)
250 .is_some()
251 })
252 })
253 }
254
255 #[cfg(feature = "test-support")]
256 pub fn restricted_worktrees_for_store(
257 &self,
258 worktree_store: &Entity<WorktreeStore>,
259 ) -> HashSet<WorktreeId> {
260 self.restricted
261 .get(&worktree_store.downgrade())
262 .unwrap()
263 .clone()
264 }
265
266 /// Adds certain entities on this host to the trusted list.
267 /// This will emit [`TrustedWorktreesEvent::Trusted`] event for all passed entries
268 /// and the ones that got auto trusted based on trust hierarchy (see module-level docs).
269 pub fn trust(
270 &mut self,
271 worktree_store: &Entity<WorktreeStore>,
272 mut trusted_paths: HashSet<PathTrust>,
273 cx: &mut Context<Self>,
274 ) {
275 let weak_worktree_store = worktree_store.downgrade();
276 let mut new_trusted_single_file_worktrees = HashSet::default();
277 let mut new_trusted_other_worktrees = HashSet::default();
278 let mut new_trusted_abs_paths = HashSet::default();
279 for trusted_path in trusted_paths.iter().chain(
280 self.trusted_paths
281 .remove(&weak_worktree_store)
282 .iter()
283 .flat_map(|current_trusted| current_trusted.iter()),
284 ) {
285 match trusted_path {
286 PathTrust::Worktree(worktree_id) => {
287 if let Some(restricted_worktrees) =
288 self.restricted.get_mut(&weak_worktree_store)
289 {
290 restricted_worktrees.remove(worktree_id);
291 if restricted_worktrees.is_empty() {
292 self.restricted.remove(&weak_worktree_store);
293 }
294 };
295
296 if let Some(worktree) =
297 worktree_store.read(cx).worktree_for_id(*worktree_id, cx)
298 {
299 if worktree.read(cx).is_single_file() {
300 new_trusted_single_file_worktrees.insert(*worktree_id);
301 } else {
302 new_trusted_other_worktrees
303 .insert((worktree.read(cx).abs_path(), *worktree_id));
304 }
305 }
306 }
307 PathTrust::AbsPath(abs_path) => {
308 debug_assert!(
309 util::paths::is_absolute(
310 &abs_path.to_string_lossy(),
311 worktree_store.read(cx).path_style()
312 ),
313 "Cannot trust non-absolute path {abs_path:?} on path style {style:?}",
314 style = worktree_store.read(cx).path_style()
315 );
316 if let Some((worktree_id, is_file)) =
317 find_worktree_in_store(worktree_store.read(cx), abs_path, cx)
318 {
319 if is_file {
320 new_trusted_single_file_worktrees.insert(worktree_id);
321 } else {
322 new_trusted_other_worktrees
323 .insert((Arc::from(abs_path.as_path()), worktree_id));
324 }
325 }
326 new_trusted_abs_paths.insert(abs_path.clone());
327 }
328 }
329 }
330
331 new_trusted_other_worktrees.retain(|(worktree_abs_path, _)| {
332 new_trusted_abs_paths
333 .iter()
334 .all(|new_trusted_path| !worktree_abs_path.starts_with(new_trusted_path))
335 });
336 if !new_trusted_other_worktrees.is_empty() {
337 new_trusted_single_file_worktrees.clear();
338 }
339
340 if let Some(restricted_worktrees) = self.restricted.remove(&weak_worktree_store) {
341 let new_restricted_worktrees = restricted_worktrees
342 .into_iter()
343 .filter(|restricted_worktree| {
344 let Some(worktree) = worktree_store
345 .read(cx)
346 .worktree_for_id(*restricted_worktree, cx)
347 else {
348 return false;
349 };
350 let is_file = worktree.read(cx).is_single_file();
351
352 // When trusting an abs path on the host, we transitively trust all single file worktrees on this host too.
353 if is_file && !new_trusted_abs_paths.is_empty() {
354 trusted_paths.insert(PathTrust::Worktree(*restricted_worktree));
355 return false;
356 }
357
358 let restricted_worktree_path = worktree.read(cx).abs_path();
359 let retain = (!is_file || new_trusted_other_worktrees.is_empty())
360 && new_trusted_abs_paths.iter().all(|new_trusted_path| {
361 !restricted_worktree_path.starts_with(new_trusted_path)
362 });
363 if !retain {
364 trusted_paths.insert(PathTrust::Worktree(*restricted_worktree));
365 }
366 retain
367 })
368 .collect();
369 self.restricted
370 .insert(weak_worktree_store.clone(), new_restricted_worktrees);
371 }
372
373 {
374 let trusted_paths = self
375 .trusted_paths
376 .entry(weak_worktree_store.clone())
377 .or_default();
378 trusted_paths.extend(new_trusted_abs_paths.into_iter().map(PathTrust::AbsPath));
379 trusted_paths.extend(
380 new_trusted_other_worktrees
381 .into_iter()
382 .map(|(_, worktree_id)| PathTrust::Worktree(worktree_id)),
383 );
384 trusted_paths.extend(
385 new_trusted_single_file_worktrees
386 .into_iter()
387 .map(PathTrust::Worktree),
388 );
389 }
390
391 if let Some(store_data) = self.worktree_stores.get(&weak_worktree_store) {
392 if let Some((upstream_client, upstream_project_id)) = &store_data.upstream_client {
393 let trusted_paths = trusted_paths
394 .iter()
395 .map(|trusted_path| trusted_path.to_proto())
396 .collect::<Vec<_>>();
397 if !trusted_paths.is_empty() {
398 upstream_client
399 .send(proto::TrustWorktrees {
400 project_id: upstream_project_id.0,
401 trusted_paths,
402 })
403 .ok();
404 }
405 }
406 }
407 cx.emit(TrustedWorktreesEvent::Trusted(
408 weak_worktree_store,
409 trusted_paths,
410 ));
411 }
412
413 /// Restricts certain entities on this host.
414 /// This will emit [`TrustedWorktreesEvent::Restricted`] event for all passed entries.
415 pub fn restrict(
416 &mut self,
417 worktree_store: WeakEntity<WorktreeStore>,
418 restricted_paths: HashSet<PathTrust>,
419 cx: &mut Context<Self>,
420 ) {
421 let mut restricted = HashSet::default();
422 for restricted_path in restricted_paths {
423 match restricted_path {
424 PathTrust::Worktree(worktree_id) => {
425 self.restricted
426 .entry(worktree_store.clone())
427 .or_default()
428 .insert(worktree_id);
429 restricted.insert(PathTrust::Worktree(worktree_id));
430 }
431 PathTrust::AbsPath(..) => debug_panic!("Unexpected: cannot restrict an abs path"),
432 }
433 }
434
435 cx.emit(TrustedWorktreesEvent::Restricted(
436 worktree_store,
437 restricted,
438 ));
439 }
440
441 /// Erases all trust information.
442 /// Requires Zed's restart to take proper effect.
443 pub fn clear_trusted_paths(&mut self) {
444 self.trusted_paths.clear();
445 self.db_trusted_paths.clear();
446 }
447
448 /// Checks whether a certain worktree is trusted (or on a larger trust level).
449 /// If not, emits [`TrustedWorktreesEvent::Restricted`] event if for the first time and not trusted, or no corresponding worktree store was found.
450 ///
451 /// No events or data adjustment happens when `trust_all_worktrees` auto trust is enabled.
452 pub fn can_trust(
453 &mut self,
454 worktree_store: &Entity<WorktreeStore>,
455 worktree_id: WorktreeId,
456 cx: &mut Context<Self>,
457 ) -> bool {
458 if ProjectSettings::get_global(cx).session.trust_all_worktrees {
459 return true;
460 }
461
462 let weak_worktree_store = worktree_store.downgrade();
463 let Some(worktree) = worktree_store.read(cx).worktree_for_id(worktree_id, cx) else {
464 return false;
465 };
466 let worktree_path = worktree.read(cx).abs_path();
467 // Zed opened an "internal" directory: e.g. a tmp dir for `keymap_editor.rs` needs.
468 if !worktree.read(cx).is_visible() {
469 log::debug!("Skipping worktree trust checks for not visible {worktree_path:?}");
470 return true;
471 }
472
473 let is_file = worktree.read(cx).is_single_file();
474 if self
475 .restricted
476 .get(&weak_worktree_store)
477 .is_some_and(|restricted_worktrees| restricted_worktrees.contains(&worktree_id))
478 {
479 return false;
480 }
481
482 if self
483 .trusted_paths
484 .get(&weak_worktree_store)
485 .is_some_and(|trusted_paths| trusted_paths.contains(&PathTrust::Worktree(worktree_id)))
486 {
487 return true;
488 }
489
490 // * Single files are auto-approved when something else (not a single file) was approved on this host already.
491 // * If parent path is trusted already, this worktree is stusted also.
492 //
493 // See module documentation for details on trust level.
494 if let Some(trusted_paths) = self.trusted_paths.get(&weak_worktree_store) {
495 let auto_trusted = worktree_store.read_with(cx, |worktree_store, cx| {
496 trusted_paths.iter().any(|trusted_path| match trusted_path {
497 PathTrust::Worktree(worktree_id) => worktree_store
498 .worktree_for_id(*worktree_id, cx)
499 .is_some_and(|worktree| {
500 let worktree = worktree.read(cx);
501 worktree_path.starts_with(&worktree.abs_path())
502 || (is_file && !worktree.is_single_file())
503 }),
504 PathTrust::AbsPath(trusted_path) => {
505 is_file || worktree_path.starts_with(trusted_path)
506 }
507 })
508 });
509 if auto_trusted {
510 return true;
511 }
512 }
513
514 self.restricted
515 .entry(weak_worktree_store.clone())
516 .or_default()
517 .insert(worktree_id);
518 log::info!("Worktree {worktree_path:?} is not trusted");
519 if let Some(store_data) = self.worktree_stores.get(&weak_worktree_store) {
520 if let Some((downstream_client, downstream_project_id)) = &store_data.downstream_client
521 {
522 downstream_client
523 .send(proto::RestrictWorktrees {
524 project_id: downstream_project_id.0,
525 worktree_ids: vec![worktree_id.to_proto()],
526 })
527 .ok();
528 }
529 if let Some((upstream_client, upstream_project_id)) = &store_data.upstream_client {
530 upstream_client
531 .send(proto::RestrictWorktrees {
532 project_id: upstream_project_id.0,
533 worktree_ids: vec![worktree_id.to_proto()],
534 })
535 .ok();
536 }
537 }
538 cx.emit(TrustedWorktreesEvent::Restricted(
539 weak_worktree_store,
540 HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
541 ));
542 false
543 }
544
545 /// Lists all explicitly restricted worktrees (via [`TrustedWorktreesStore::can_trust`] method calls) for a particular worktree store on a particular host.
546 pub fn restricted_worktrees(
547 &self,
548 worktree_store: &Entity<WorktreeStore>,
549 cx: &App,
550 ) -> HashSet<(WorktreeId, Arc<Path>)> {
551 let mut single_file_paths = HashSet::default();
552
553 let other_paths = self
554 .restricted
555 .get(&worktree_store.downgrade())
556 .into_iter()
557 .flatten()
558 .filter_map(|&restricted_worktree_id| {
559 let worktree = worktree_store
560 .read(cx)
561 .worktree_for_id(restricted_worktree_id, cx)?;
562 let worktree = worktree.read(cx);
563 let abs_path = worktree.abs_path();
564 if worktree.is_single_file() {
565 single_file_paths.insert((restricted_worktree_id, abs_path));
566 None
567 } else {
568 Some((restricted_worktree_id, abs_path))
569 }
570 })
571 .collect::<HashSet<_>>();
572
573 if !other_paths.is_empty() {
574 return other_paths;
575 } else {
576 single_file_paths
577 }
578 }
579
580 /// Switches the "trust nothing" mode to "automatically trust everything".
581 /// This does not influence already persisted data, but stops adding new worktrees there.
582 pub fn auto_trust_all(&mut self, cx: &mut Context<Self>) {
583 for (worktree_store, worktrees) in std::mem::take(&mut self.restricted).into_iter().fold(
584 HashMap::default(),
585 |mut acc, (remote_host, worktrees)| {
586 acc.entry(remote_host)
587 .or_insert_with(HashSet::default)
588 .extend(worktrees.into_iter().map(PathTrust::Worktree));
589 acc
590 },
591 ) {
592 if let Some(worktree_store) = worktree_store.upgrade() {
593 self.trust(&worktree_store, worktrees, cx);
594 }
595 }
596 }
597
598 pub fn schedule_serialization<S>(&mut self, cx: &mut Context<Self>, serialize: S)
599 where
600 S: FnOnce(HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>>, &App) -> Task<()>
601 + 'static,
602 {
603 self.worktree_trust_serialization = serialize(self.trusted_paths_for_serialization(cx), cx);
604 }
605
606 fn trusted_paths_for_serialization(
607 &mut self,
608 cx: &mut Context<Self>,
609 ) -> HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>> {
610 let new_trusted_paths = self
611 .trusted_paths
612 .iter()
613 .filter_map(|(worktree_store, paths)| {
614 let host = self.worktree_stores.get(&worktree_store)?.host.clone();
615 let abs_paths = paths
616 .iter()
617 .flat_map(|path| match path {
618 PathTrust::Worktree(worktree_id) => worktree_store
619 .upgrade()
620 .and_then(|worktree_store| {
621 worktree_store.read(cx).worktree_for_id(*worktree_id, cx)
622 })
623 .map(|worktree| worktree.read(cx).abs_path().to_path_buf()),
624 PathTrust::AbsPath(abs_path) => Some(abs_path.clone()),
625 })
626 .collect::<HashSet<_>>();
627 Some((host, abs_paths))
628 })
629 .chain(self.db_trusted_paths.drain())
630 .fold(HashMap::default(), |mut acc, (host, paths)| {
631 acc.entry(host)
632 .or_insert_with(HashSet::default)
633 .extend(paths);
634 acc
635 });
636
637 self.db_trusted_paths = new_trusted_paths.clone();
638 new_trusted_paths
639 }
640
641 fn add_worktree_store(
642 &mut self,
643 worktree_store: Entity<WorktreeStore>,
644 remote_host: Option<RemoteHostLocation>,
645 downstream_client: Option<(AnyProtoClient, ProjectId)>,
646 upstream_client: Option<(AnyProtoClient, ProjectId)>,
647 cx: &mut Context<Self>,
648 ) {
649 self.worktree_stores
650 .retain(|worktree_store, _| worktree_store.is_upgradable());
651 let weak_worktree_store = worktree_store.downgrade();
652 self.worktree_stores.insert(
653 weak_worktree_store.clone(),
654 StoreData {
655 host: remote_host.clone(),
656 downstream_client,
657 upstream_client,
658 },
659 );
660
661 let mut new_trusted_paths = HashSet::default();
662 if let Some(db_trusted_paths) = self.db_trusted_paths.get(&remote_host) {
663 new_trusted_paths.extend(db_trusted_paths.clone().into_iter().map(PathTrust::AbsPath));
664 }
665 if let Some(trusted_paths) = self.trusted_paths.remove(&weak_worktree_store) {
666 new_trusted_paths.extend(trusted_paths);
667 }
668 if !new_trusted_paths.is_empty() {
669 self.trusted_paths.insert(
670 weak_worktree_store,
671 new_trusted_paths
672 .into_iter()
673 .map(|path_trust| match path_trust {
674 PathTrust::AbsPath(abs_path) => {
675 find_worktree_in_store(worktree_store.read(cx), &abs_path, cx)
676 .map(|(worktree_id, _)| PathTrust::Worktree(worktree_id))
677 .unwrap_or_else(|| PathTrust::AbsPath(abs_path))
678 }
679 other => other,
680 })
681 .collect(),
682 );
683 }
684 }
685}
686
687fn find_worktree_in_store(
688 worktree_store: &WorktreeStore,
689 abs_path: &Path,
690 cx: &App,
691) -> Option<(WorktreeId, bool)> {
692 let (worktree, path_in_worktree) = worktree_store.find_worktree(&abs_path, cx)?;
693 if path_in_worktree.is_empty() {
694 Some((worktree.read(cx).id(), worktree.read(cx).is_single_file()))
695 } else {
696 None
697 }
698}