Skip to repository content710 lines · 29.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:32:45.433Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
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 /// 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