Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:44:19.891Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

trusted_worktrees.rs

710 lines · 29.3 KB · rust
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    /// Whether the given project store has any restricted worktrees.
118    pub fn has_restricted_worktrees(worktree_store: &Entity<WorktreeStore>, cx: &App) -> bool {
119        Self::try_get_global(cx)
120            .map(|trusted| {
121                trusted
122                    .read(cx)
123                    .has_restricted_worktrees(worktree_store, cx)
124            })
125            .unwrap_or(false)
126    }
127}
128
129/// A collection of worktrees that are considered trusted and not trusted.
130/// This can be used when checking for this criteria before enabling certain features.
131///
132/// Emits an event each time the worktree was checked and found not trusted,
133/// or a certain worktree had been trusted.
134#[derive(Debug)]
135pub struct TrustedWorktreesStore {
136    worktree_stores: HashMap<WeakEntity<WorktreeStore>, StoreData>,
137    db_trusted_paths: DbTrustedPaths,
138    trusted_paths: TrustedPaths,
139    restricted: HashMap<WeakEntity<WorktreeStore>, HashSet<WorktreeId>>,
140    worktree_trust_serialization: Task<()>,
141}
142
143#[derive(Debug, Default)]
144struct StoreData {
145    upstream_client: Option<(AnyProtoClient, ProjectId)>,
146    downstream_client: Option<(AnyProtoClient, ProjectId)>,
147    host: Option<RemoteHostLocation>,
148}
149
150/// An identifier of a host to split the trust questions by.
151/// Each trusted data change and event is done for a particular host.
152/// A host may contain more than one worktree or even project open concurrently.
153#[derive(Debug, PartialEq, Eq, Clone, Hash)]
154pub struct RemoteHostLocation {
155    pub user_name: Option<SharedString>,
156    pub host_identifier: SharedString,
157}
158
159impl From<RemoteConnectionOptions> for RemoteHostLocation {
160    fn from(options: RemoteConnectionOptions) -> Self {
161        let (user_name, host_name) = match options {
162            RemoteConnectionOptions::Ssh(ssh) => (
163                ssh.username.map(SharedString::new),
164                SharedString::new(ssh.host.to_string()),
165            ),
166            RemoteConnectionOptions::Wsl(wsl) => (
167                wsl.user.map(SharedString::new),
168                SharedString::new(wsl.distro_name),
169            ),
170            RemoteConnectionOptions::Docker(docker_connection_options) => (
171                Some(SharedString::new(docker_connection_options.name)),
172                SharedString::new(docker_connection_options.container_id),
173            ),
174            #[cfg(feature = "test-support")]
175            RemoteConnectionOptions::Mock(mock) => {
176                (None, SharedString::new(format!("mock-{}", mock.id)))
177            }
178        };
179        Self {
180            user_name,
181            host_identifier: host_name,
182        }
183    }
184}
185
186/// A unit of trust consideration inside a particular host:
187/// either a familiar worktree, or a path that may influence other worktrees' trust.
188/// See module-level documentation on the trust model.
189#[derive(Debug, PartialEq, Eq, Clone, Hash)]
190pub enum PathTrust {
191    /// A worktree that is familiar to this workspace.
192    /// Either a single file or a directory worktree.
193    Worktree(WorktreeId),
194    /// A path that may be another worktree yet not loaded into any workspace (hence, without any `WorktreeId`),
195    /// or a parent path coming out of the security modal.
196    AbsPath(PathBuf),
197}
198
199impl PathTrust {
200    fn to_proto(&self) -> proto::PathTrust {
201        match self {
202            Self::Worktree(worktree_id) => proto::PathTrust {
203                content: Some(proto::path_trust::Content::WorktreeId(
204                    worktree_id.to_proto(),
205                )),
206            },
207            Self::AbsPath(path_buf) => proto::PathTrust {
208                content: Some(proto::path_trust::Content::AbsPath(
209                    path_buf.to_string_lossy().to_string(),
210                )),
211            },
212        }
213    }
214
215    pub fn from_proto(proto: proto::PathTrust) -> Option<Self> {
216        Some(match proto.content? {
217            proto::path_trust::Content::WorktreeId(id) => {
218                Self::Worktree(WorktreeId::from_proto(id))
219            }
220            proto::path_trust::Content::AbsPath(path) => Self::AbsPath(PathBuf::from(path)),
221        })
222    }
223}
224
225/// A change of trust on a certain host.
226#[derive(Debug)]
227pub enum TrustedWorktreesEvent {
228    Trusted(WeakEntity<WorktreeStore>, HashSet<PathTrust>),
229    Restricted(WeakEntity<WorktreeStore>, HashSet<PathTrust>),
230}
231
232impl EventEmitter<TrustedWorktreesEvent> for TrustedWorktreesStore {}
233
234type TrustedPaths = HashMap<WeakEntity<WorktreeStore>, HashSet<PathTrust>>;
235pub type DbTrustedPaths = HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>>;
236
237impl TrustedWorktreesStore {
238    fn new(db_trusted_paths: DbTrustedPaths) -> Self {
239        Self {
240            db_trusted_paths,
241            trusted_paths: HashMap::default(),
242            worktree_stores: HashMap::default(),
243            restricted: HashMap::default(),
244            worktree_trust_serialization: Task::ready(()),
245        }
246    }
247
248    /// Whether a particular worktree store has associated worktrees that are restricted, or an associated host is restricted.
249    pub fn has_restricted_worktrees(
250        &self,
251        worktree_store: &Entity<WorktreeStore>,
252        cx: &App,
253    ) -> bool {
254        self.restricted
255            .get(&worktree_store.downgrade())
256            .is_some_and(|restricted_worktrees| {
257                restricted_worktrees.iter().any(|restricted_worktree| {
258                    worktree_store
259                        .read(cx)
260                        .worktree_for_id(*restricted_worktree, cx)
261                        .is_some()
262                })
263            })
264    }
265
266    #[cfg(feature = "test-support")]
267    pub fn restricted_worktrees_for_store(
268        &self,
269        worktree_store: &Entity<WorktreeStore>,
270    ) -> HashSet<WorktreeId> {
271        self.restricted
272            .get(&worktree_store.downgrade())
273            .unwrap()
274            .clone()
275    }
276
277    /// Adds certain entities on this host to the trusted list.
278    /// This will emit [`TrustedWorktreesEvent::Trusted`] event for all passed entries
279    /// and the ones that got auto trusted based on trust hierarchy (see module-level docs).
280    pub fn trust(
281        &mut self,
282        worktree_store: &Entity<WorktreeStore>,
283        mut trusted_paths: HashSet<PathTrust>,
284        cx: &mut Context<Self>,
285    ) {
286        let weak_worktree_store = worktree_store.downgrade();
287        let mut new_trusted_single_file_worktrees = HashSet::default();
288        let mut new_trusted_other_worktrees = HashSet::default();
289        let mut new_trusted_abs_paths = HashSet::default();
290        for trusted_path in trusted_paths.iter().chain(
291            self.trusted_paths
292                .remove(&weak_worktree_store)
293                .iter()
294                .flat_map(|current_trusted| current_trusted.iter()),
295        ) {
296            match trusted_path {
297                PathTrust::Worktree(worktree_id) => {
298                    if let Some(restricted_worktrees) =
299                        self.restricted.get_mut(&weak_worktree_store)
300                    {
301                        restricted_worktrees.remove(worktree_id);
302                        if restricted_worktrees.is_empty() {
303                            self.restricted.remove(&weak_worktree_store);
304                        }
305                    };
306
307                    if let Some(worktree) =
308                        worktree_store.read(cx).worktree_for_id(*worktree_id, cx)
309                    {
310                        if worktree.read(cx).is_single_file() {
311                            new_trusted_single_file_worktrees.insert(*worktree_id);
312                        } else {
313                            new_trusted_other_worktrees
314                                .insert((worktree.read(cx).abs_path(), *worktree_id));
315                        }
316                    }
317                }
318                PathTrust::AbsPath(abs_path) => {
319                    debug_assert!(
320                        util::paths::is_absolute(
321                            &abs_path.to_string_lossy(),
322                            worktree_store.read(cx).path_style()
323                        ),
324                        "Cannot trust non-absolute path {abs_path:?} on path style {style:?}",
325                        style = worktree_store.read(cx).path_style()
326                    );
327                    if let Some((worktree_id, is_file)) =
328                        find_worktree_in_store(worktree_store.read(cx), abs_path, cx)
329                    {
330                        if is_file {
331                            new_trusted_single_file_worktrees.insert(worktree_id);
332                        } else {
333                            new_trusted_other_worktrees
334                                .insert((Arc::from(abs_path.as_path()), worktree_id));
335                        }
336                    }
337                    new_trusted_abs_paths.insert(abs_path.clone());
338                }
339            }
340        }
341
342        new_trusted_other_worktrees.retain(|(worktree_abs_path, _)| {
343            new_trusted_abs_paths
344                .iter()
345                .all(|new_trusted_path| !worktree_abs_path.starts_with(new_trusted_path))
346        });
347        if !new_trusted_other_worktrees.is_empty() {
348            new_trusted_single_file_worktrees.clear();
349        }
350
351        if let Some(restricted_worktrees) = self.restricted.remove(&weak_worktree_store) {
352            let new_restricted_worktrees = restricted_worktrees
353                .into_iter()
354                .filter(|restricted_worktree| {
355                    let Some(worktree) = worktree_store
356                        .read(cx)
357                        .worktree_for_id(*restricted_worktree, cx)
358                    else {
359                        return false;
360                    };
361                    let is_file = worktree.read(cx).is_single_file();
362
363                    // When trusting an abs path on the host, we transitively trust all single file worktrees on this host too.
364                    if is_file && !new_trusted_abs_paths.is_empty() {
365                        trusted_paths.insert(PathTrust::Worktree(*restricted_worktree));
366                        return false;
367                    }
368
369                    let restricted_worktree_path = worktree.read(cx).abs_path();
370                    let retain = (!is_file || new_trusted_other_worktrees.is_empty())
371                        && new_trusted_abs_paths.iter().all(|new_trusted_path| {
372                            !restricted_worktree_path.starts_with(new_trusted_path)
373                        });
374                    if !retain {
375                        trusted_paths.insert(PathTrust::Worktree(*restricted_worktree));
376                    }
377                    retain
378                })
379                .collect();
380            self.restricted
381                .insert(weak_worktree_store.clone(), new_restricted_worktrees);
382        }
383
384        {
385            let trusted_paths = self
386                .trusted_paths
387                .entry(weak_worktree_store.clone())
388                .or_default();
389            trusted_paths.extend(new_trusted_abs_paths.into_iter().map(PathTrust::AbsPath));
390            trusted_paths.extend(
391                new_trusted_other_worktrees
392                    .into_iter()
393                    .map(|(_, worktree_id)| PathTrust::Worktree(worktree_id)),
394            );
395            trusted_paths.extend(
396                new_trusted_single_file_worktrees
397                    .into_iter()
398                    .map(PathTrust::Worktree),
399            );
400        }
401
402        if let Some(store_data) = self.worktree_stores.get(&weak_worktree_store) {
403            if let Some((upstream_client, upstream_project_id)) = &store_data.upstream_client {
404                let trusted_paths = trusted_paths
405                    .iter()
406                    .map(|trusted_path| trusted_path.to_proto())
407                    .collect::<Vec<_>>();
408                if !trusted_paths.is_empty() {
409                    upstream_client
410                        .send(proto::TrustWorktrees {
411                            project_id: upstream_project_id.0,
412                            trusted_paths,
413                        })
414                        .ok();
415                }
416            }
417        }
418        cx.emit(TrustedWorktreesEvent::Trusted(
419            weak_worktree_store,
420            trusted_paths,
421        ));
422    }
423
424    /// Restricts certain entities on this host.
425    /// This will emit [`TrustedWorktreesEvent::Restricted`] event for all passed entries.
426    pub fn restrict(
427        &mut self,
428        worktree_store: WeakEntity<WorktreeStore>,
429        restricted_paths: HashSet<PathTrust>,
430        cx: &mut Context<Self>,
431    ) {
432        let mut restricted = HashSet::default();
433        for restricted_path in restricted_paths {
434            match restricted_path {
435                PathTrust::Worktree(worktree_id) => {
436                    self.restricted
437                        .entry(worktree_store.clone())
438                        .or_default()
439                        .insert(worktree_id);
440                    restricted.insert(PathTrust::Worktree(worktree_id));
441                }
442                PathTrust::AbsPath(..) => debug_panic!("Unexpected: cannot restrict an abs path"),
443            }
444        }
445
446        cx.emit(TrustedWorktreesEvent::Restricted(
447            worktree_store,
448            restricted,
449        ));
450    }
451
452    /// Erases all trust information.
453    /// Requires Zed's restart to take proper effect.
454    pub fn clear_trusted_paths(&mut self) {
455        self.trusted_paths.clear();
456        self.db_trusted_paths.clear();
457    }
458
459    /// Checks whether a certain worktree is trusted (or on a larger trust level).
460    /// If not, emits [`TrustedWorktreesEvent::Restricted`] event if for the first time and not trusted, or no corresponding worktree store was found.
461    ///
462    /// No events or data adjustment happens when `trust_all_worktrees` auto trust is enabled.
463    pub fn can_trust(
464        &mut self,
465        worktree_store: &Entity<WorktreeStore>,
466        worktree_id: WorktreeId,
467        cx: &mut Context<Self>,
468    ) -> bool {
469        if ProjectSettings::get_global(cx).session.trust_all_worktrees {
470            return true;
471        }
472
473        let weak_worktree_store = worktree_store.downgrade();
474        let Some(worktree) = worktree_store.read(cx).worktree_for_id(worktree_id, cx) else {
475            return false;
476        };
477        let worktree_path = worktree.read(cx).abs_path();
478        // Zed opened an "internal" directory: e.g. a tmp dir for `keymap_editor.rs` needs.
479        if !worktree.read(cx).is_visible() {
480            log::debug!("Skipping worktree trust checks for not visible {worktree_path:?}");
481            return true;
482        }
483
484        let is_file = worktree.read(cx).is_single_file();
485        if self
486            .restricted
487            .get(&weak_worktree_store)
488            .is_some_and(|restricted_worktrees| restricted_worktrees.contains(&worktree_id))
489        {
490            return false;
491        }
492
493        if self
494            .trusted_paths
495            .get(&weak_worktree_store)
496            .is_some_and(|trusted_paths| trusted_paths.contains(&PathTrust::Worktree(worktree_id)))
497        {
498            return true;
499        }
500
501        // * Single files are auto-approved when something else (not a single file) was approved on this host already.
502        // * If parent path is trusted already, this worktree is stusted also.
503        //
504        // See module documentation for details on trust level.
505        if let Some(trusted_paths) = self.trusted_paths.get(&weak_worktree_store) {
506            let auto_trusted = worktree_store.read_with(cx, |worktree_store, cx| {
507                trusted_paths.iter().any(|trusted_path| match trusted_path {
508                    PathTrust::Worktree(worktree_id) => worktree_store
509                        .worktree_for_id(*worktree_id, cx)
510                        .is_some_and(|worktree| {
511                            let worktree = worktree.read(cx);
512                            worktree_path.starts_with(&worktree.abs_path())
513                                || (is_file && !worktree.is_single_file())
514                        }),
515                    PathTrust::AbsPath(trusted_path) => {
516                        is_file || worktree_path.starts_with(trusted_path)
517                    }
518                })
519            });
520            if auto_trusted {
521                return true;
522            }
523        }
524
525        self.restricted
526            .entry(weak_worktree_store.clone())
527            .or_default()
528            .insert(worktree_id);
529        log::info!("Worktree {worktree_path:?} is not trusted");
530        if let Some(store_data) = self.worktree_stores.get(&weak_worktree_store) {
531            if let Some((downstream_client, downstream_project_id)) = &store_data.downstream_client
532            {
533                downstream_client
534                    .send(proto::RestrictWorktrees {
535                        project_id: downstream_project_id.0,
536                        worktree_ids: vec![worktree_id.to_proto()],
537                    })
538                    .ok();
539            }
540            if let Some((upstream_client, upstream_project_id)) = &store_data.upstream_client {
541                upstream_client
542                    .send(proto::RestrictWorktrees {
543                        project_id: upstream_project_id.0,
544                        worktree_ids: vec![worktree_id.to_proto()],
545                    })
546                    .ok();
547            }
548        }
549        cx.emit(TrustedWorktreesEvent::Restricted(
550            weak_worktree_store,
551            HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
552        ));
553        false
554    }
555
556    /// Lists all explicitly restricted worktrees (via [`TrustedWorktreesStore::can_trust`] method calls) for a particular worktree store on a particular host.
557    pub fn restricted_worktrees(
558        &self,
559        worktree_store: &Entity<WorktreeStore>,
560        cx: &App,
561    ) -> HashSet<(WorktreeId, Arc<Path>)> {
562        let mut single_file_paths = HashSet::default();
563
564        let other_paths = self
565            .restricted
566            .get(&worktree_store.downgrade())
567            .into_iter()
568            .flatten()
569            .filter_map(|&restricted_worktree_id| {
570                let worktree = worktree_store
571                    .read(cx)
572                    .worktree_for_id(restricted_worktree_id, cx)?;
573                let worktree = worktree.read(cx);
574                let abs_path = worktree.abs_path();
575                if worktree.is_single_file() {
576                    single_file_paths.insert((restricted_worktree_id, abs_path));
577                    None
578                } else {
579                    Some((restricted_worktree_id, abs_path))
580                }
581            })
582            .collect::<HashSet<_>>();
583
584        if !other_paths.is_empty() {
585            return other_paths;
586        } else {
587            single_file_paths
588        }
589    }
590
591    /// Switches the "trust nothing" mode to "automatically trust everything".
592    /// This does not influence already persisted data, but stops adding new worktrees there.
593    pub fn auto_trust_all(&mut self, cx: &mut Context<Self>) {
594        for (worktree_store, worktrees) in std::mem::take(&mut self.restricted).into_iter().fold(
595            HashMap::default(),
596            |mut acc, (remote_host, worktrees)| {
597                acc.entry(remote_host)
598                    .or_insert_with(HashSet::default)
599                    .extend(worktrees.into_iter().map(PathTrust::Worktree));
600                acc
601            },
602        ) {
603            if let Some(worktree_store) = worktree_store.upgrade() {
604                self.trust(&worktree_store, worktrees, cx);
605            }
606        }
607    }
608
609    pub fn schedule_serialization<S>(&mut self, cx: &mut Context<Self>, serialize: S)
610    where
611        S: FnOnce(HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>>, &App) -> Task<()>
612            + 'static,
613    {
614        self.worktree_trust_serialization = serialize(self.trusted_paths_for_serialization(cx), cx);
615    }
616
617    fn trusted_paths_for_serialization(
618        &mut self,
619        cx: &mut Context<Self>,
620    ) -> HashMap<Option<RemoteHostLocation>, HashSet<PathBuf>> {
621        let new_trusted_paths = self
622            .trusted_paths
623            .iter()
624            .filter_map(|(worktree_store, paths)| {
625                let host = self.worktree_stores.get(&worktree_store)?.host.clone();
626                let abs_paths = paths
627                    .iter()
628                    .flat_map(|path| match path {
629                        PathTrust::Worktree(worktree_id) => worktree_store
630                            .upgrade()
631                            .and_then(|worktree_store| {
632                                worktree_store.read(cx).worktree_for_id(*worktree_id, cx)
633                            })
634                            .map(|worktree| worktree.read(cx).abs_path().to_path_buf()),
635                        PathTrust::AbsPath(abs_path) => Some(abs_path.clone()),
636                    })
637                    .collect::<HashSet<_>>();
638                Some((host, abs_paths))
639            })
640            .chain(self.db_trusted_paths.drain())
641            .fold(HashMap::default(), |mut acc, (host, paths)| {
642                acc.entry(host)
643                    .or_insert_with(HashSet::default)
644                    .extend(paths);
645                acc
646            });
647
648        self.db_trusted_paths = new_trusted_paths.clone();
649        new_trusted_paths
650    }
651
652    fn add_worktree_store(
653        &mut self,
654        worktree_store: Entity<WorktreeStore>,
655        remote_host: Option<RemoteHostLocation>,
656        downstream_client: Option<(AnyProtoClient, ProjectId)>,
657        upstream_client: Option<(AnyProtoClient, ProjectId)>,
658        cx: &mut Context<Self>,
659    ) {
660        self.worktree_stores
661            .retain(|worktree_store, _| worktree_store.is_upgradable());
662        let weak_worktree_store = worktree_store.downgrade();
663        self.worktree_stores.insert(
664            weak_worktree_store.clone(),
665            StoreData {
666                host: remote_host.clone(),
667                downstream_client,
668                upstream_client,
669            },
670        );
671
672        let mut new_trusted_paths = HashSet::default();
673        if let Some(db_trusted_paths) = self.db_trusted_paths.get(&remote_host) {
674            new_trusted_paths.extend(db_trusted_paths.clone().into_iter().map(PathTrust::AbsPath));
675        }
676        if let Some(trusted_paths) = self.trusted_paths.remove(&weak_worktree_store) {
677            new_trusted_paths.extend(trusted_paths);
678        }
679        if !new_trusted_paths.is_empty() {
680            self.trusted_paths.insert(
681                weak_worktree_store,
682                new_trusted_paths
683                    .into_iter()
684                    .map(|path_trust| match path_trust {
685                        PathTrust::AbsPath(abs_path) => {
686                            find_worktree_in_store(worktree_store.read(cx), &abs_path, cx)
687                                .map(|(worktree_id, _)| PathTrust::Worktree(worktree_id))
688                                .unwrap_or_else(|| PathTrust::AbsPath(abs_path))
689                        }
690                        other => other,
691                    })
692                    .collect(),
693            );
694        }
695    }
696}
697
698fn find_worktree_in_store(
699    worktree_store: &WorktreeStore,
700    abs_path: &Path,
701    cx: &App,
702) -> Option<(WorktreeId, bool)> {
703    let (worktree, path_in_worktree) = worktree_store.find_worktree(&abs_path, cx)?;
704    if path_in_worktree.is_empty() {
705        Some((worktree.read(cx).id(), worktree.read(cx).is_single_file()))
706    } else {
707        None
708    }
709}
710
Served at tenant.openagents/omega Member data and write actions are omitted.