trusted_worktrees.rs

  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}