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//!
  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//! * "workspace"
  31//!
  32//! Even an empty Zed instance with no files or directories open is potentially dangerous: opening an Assistant Panel and creating new external agent thread might require installing and running MCP servers.
  33//!
  34//! Disabling the entire panel is possible with ai-related settings.
  35//! Yet when it's enabled, it's still reasonably safe to use remote AI agents and control their permissions in the Assistant Panel.
  36//!
  37//! Unlike that, MCP servers are similar to language servers and may require fetching, installing and running packages or binaries.
  38//! Given that those servers are not tied to any particular worktree, this level of trust is required to operate any MCP server.
  39//!
  40//! Workspace level of trust assumes all single file worktrees are trusted too, for the same host: if we allow global MCP server-related functionality, we can already allow spawning language servers for single file worktrees as well.
  41//!
  42//! * "directory worktree"
  43//!
  44//! If a directory is open in Zed, it's a full worktree which may spawn multiple language servers associated with it.
  45//! 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.
  46//!
  47//! When a directory worktree is trusted and language servers are allowed to be downloaded and started, hence we also allow workspace level of trust (hence, "single file worktree" level of trust also).
  48//!
  49//! * "path override"
  50//!
  51//! To ease trusting multiple directory worktrees at once, it's possible to trust a parent directory of a certain directory worktree opened in Zed.
  52//! Trusting a directory means trusting all its subdirectories as well, including all current and potential directory worktrees.
  53//!
  54//! If we trust multiple projects to install and spawn various language server processes, we can also allow workspace trust requests for MCP servers installation and spawning.
  55
  56use collections::{HashMap, HashSet};
  57use gpui::{
  58    App, AppContext as _, Context, Entity, EventEmitter, Global, SharedString, Task, WeakEntity,
  59};
  60use remote::RemoteConnectionOptions;
  61use rpc::{AnyProtoClient, proto};
  62use settings::{Settings as _, WorktreeId};
  63use std::{
  64    path::{Path, PathBuf},
  65    sync::Arc,
  66};
  67use util::debug_panic;
  68
  69use crate::{project_settings::ProjectSettings, worktree_store::WorktreeStore};
  70
  71#[cfg(not(any(test, feature = "test-support")))]
  72use crate::persistence::PROJECT_DB;
  73#[cfg(not(any(test, feature = "test-support")))]
  74use util::ResultExt as _;
  75
  76pub fn init(
  77    downstream_client: Option<(AnyProtoClient, u64)>,
  78    upstream_client: Option<(AnyProtoClient, u64)>,
  79    cx: &mut App,
  80) {
  81    if TrustedWorktrees::try_get_global(cx).is_none() {
  82        let trusted_worktrees = cx.new(|cx| {
  83            TrustedWorktreesStore::new(None, None, downstream_client, upstream_client, cx)
  84        });
  85        cx.set_global(TrustedWorktrees(trusted_worktrees))
  86    }
  87}
  88
  89/// An initialization call to set up trust global for a particular project (remote or local).
  90pub fn track_worktree_trust(
  91    worktree_store: Entity<WorktreeStore>,
  92    remote_host: Option<RemoteHostLocation>,
  93    downstream_client: Option<(AnyProtoClient, u64)>,
  94    upstream_client: Option<(AnyProtoClient, u64)>,
  95    cx: &mut App,
  96) {
  97    match TrustedWorktrees::try_get_global(cx) {
  98        Some(trusted_worktrees) => {
  99            trusted_worktrees.update(cx, |trusted_worktrees, cx| {
 100                let sync_upstream = trusted_worktrees.upstream_client.as_ref().map(|(_, id)| id)
 101                    != upstream_client.as_ref().map(|(_, id)| id);
 102                trusted_worktrees.downstream_client = downstream_client;
 103                trusted_worktrees.upstream_client = upstream_client;
 104                trusted_worktrees.add_worktree_store(worktree_store, remote_host, cx);
 105
 106                if sync_upstream {
 107                    if let Some((upstream_client, upstream_project_id)) =
 108                        &trusted_worktrees.upstream_client
 109                    {
 110                        let trusted_paths = trusted_worktrees
 111                            .trusted_paths
 112                            .iter()
 113                            .flat_map(|(_, paths)| {
 114                                paths.iter().map(|trusted_path| trusted_path.to_proto())
 115                            })
 116                            .collect::<Vec<_>>();
 117                        if !trusted_paths.is_empty() {
 118                            upstream_client
 119                                .send(proto::TrustWorktrees {
 120                                    project_id: *upstream_project_id,
 121                                    trusted_paths,
 122                                })
 123                                .ok();
 124                        }
 125                    }
 126                }
 127            });
 128        }
 129        None => {
 130            let trusted_worktrees = cx.new(|cx| {
 131                TrustedWorktreesStore::new(
 132                    Some(worktree_store.clone()),
 133                    remote_host,
 134                    downstream_client,
 135                    upstream_client,
 136                    cx,
 137                )
 138            });
 139            cx.set_global(TrustedWorktrees(trusted_worktrees))
 140        }
 141    }
 142}
 143
 144/// Waits until at least [`PathTrust::Workspace`] level of trust is granted for the host the [`TrustedWorktrees`] was initialized with.
 145pub fn wait_for_default_workspace_trust(
 146    what_waits: &'static str,
 147    cx: &mut App,
 148) -> Option<Task<()>> {
 149    let trusted_worktrees = TrustedWorktrees::try_get_global(cx)?;
 150    wait_for_workspace_trust(
 151        trusted_worktrees.read(cx).remote_host.clone(),
 152        what_waits,
 153        cx,
 154    )
 155}
 156
 157/// Waits until at least [`PathTrust::Workspace`] level of trust is granted for a particular host.
 158pub fn wait_for_workspace_trust(
 159    remote_host: Option<impl Into<RemoteHostLocation>>,
 160    what_waits: &'static str,
 161    cx: &mut App,
 162) -> Option<Task<()>> {
 163    let trusted_worktrees = TrustedWorktrees::try_get_global(cx)?;
 164    let remote_host = remote_host.map(|host| host.into());
 165
 166    let remote_host = if trusted_worktrees.update(cx, |trusted_worktrees, cx| {
 167        trusted_worktrees.can_trust_workspace(remote_host.clone(), cx)
 168    }) {
 169        None
 170    } else {
 171        Some(remote_host)
 172    }?;
 173
 174    Some(cx.spawn(async move |cx| {
 175        log::info!("Waiting for workspace to be trusted before starting {what_waits}");
 176        let (tx, restricted_worktrees_task) = smol::channel::bounded::<()>(1);
 177        let Ok(_subscription) = cx.update(|cx| {
 178            cx.subscribe(&trusted_worktrees, move |_, e, _| {
 179                if let TrustedWorktreesEvent::Trusted(trusted_host, trusted_paths) = e {
 180                    if trusted_host == &remote_host && trusted_paths.contains(&PathTrust::Workspace)
 181                    {
 182                        log::info!("Workspace is trusted for {what_waits}");
 183                        tx.send_blocking(()).ok();
 184                    }
 185                }
 186            })
 187        }) else {
 188            return;
 189        };
 190
 191        restricted_worktrees_task.recv().await.ok();
 192    }))
 193}
 194
 195/// A collection of worktree trust metadata, can be accessed globally (if initialized) and subscribed to.
 196pub struct TrustedWorktrees(Entity<TrustedWorktreesStore>);
 197
 198impl Global for TrustedWorktrees {}
 199
 200impl TrustedWorktrees {
 201    pub fn try_get_global(cx: &App) -> Option<Entity<TrustedWorktreesStore>> {
 202        cx.try_global::<Self>().map(|this| this.0.clone())
 203    }
 204}
 205
 206/// A collection of worktrees that are considered trusted and not trusted.
 207/// This can be used when checking for this criteria before enabling certain features.
 208///
 209/// Emits an event each time the worktree was checked and found not trusted,
 210/// or a certain worktree had been trusted.
 211pub struct TrustedWorktreesStore {
 212    downstream_client: Option<(AnyProtoClient, u64)>,
 213    upstream_client: Option<(AnyProtoClient, u64)>,
 214    worktree_stores: HashMap<WeakEntity<WorktreeStore>, Option<RemoteHostLocation>>,
 215    trusted_paths: HashMap<Option<RemoteHostLocation>, HashSet<PathTrust>>,
 216    #[cfg(not(any(test, feature = "test-support")))]
 217    serialization_task: Task<()>,
 218    restricted: HashSet<WorktreeId>,
 219    remote_host: Option<RemoteHostLocation>,
 220    restricted_workspaces: HashSet<Option<RemoteHostLocation>>,
 221}
 222
 223/// An identifier of a host to split the trust questions by.
 224/// Each trusted data change and event is done for a particular host.
 225/// A host may contain more than one worktree or even project open concurrently.
 226#[derive(Debug, PartialEq, Eq, Clone, Hash)]
 227pub struct RemoteHostLocation {
 228    pub user_name: Option<SharedString>,
 229    pub host_identifier: SharedString,
 230}
 231
 232impl From<RemoteConnectionOptions> for RemoteHostLocation {
 233    fn from(options: RemoteConnectionOptions) -> Self {
 234        let (user_name, host_name) = match options {
 235            RemoteConnectionOptions::Ssh(ssh) => (
 236                ssh.username.map(SharedString::new),
 237                SharedString::new(ssh.host),
 238            ),
 239            RemoteConnectionOptions::Wsl(wsl) => (
 240                wsl.user.map(SharedString::new),
 241                SharedString::new(wsl.distro_name),
 242            ),
 243            RemoteConnectionOptions::Docker(docker_connection_options) => (
 244                Some(SharedString::new(docker_connection_options.name)),
 245                SharedString::new(docker_connection_options.container_id),
 246            ),
 247        };
 248        RemoteHostLocation {
 249            user_name,
 250            host_identifier: host_name,
 251        }
 252    }
 253}
 254
 255/// A unit of trust consideration inside a particular host:
 256/// either a familiar worktree, or a path that may influence other worktrees' trust.
 257/// See module-level documentation on the trust model.
 258#[derive(Debug, PartialEq, Eq, Clone, Hash)]
 259pub enum PathTrust {
 260    /// General, no worktrees or files open case.
 261    /// E.g. MCP servers can be spawned from a blank Zed instance, but will do `npm i` and other potentially malicious actions.
 262    Workspace,
 263    /// A worktree that is familiar to this workspace.
 264    /// Either a single file or a directory worktree.
 265    Worktree(WorktreeId),
 266    /// A path that may be another worktree yet not loaded into any workspace (hence, without any `WorktreeId`),
 267    /// or a parent path coming out of the security modal.
 268    AbsPath(PathBuf),
 269}
 270
 271impl PathTrust {
 272    fn to_proto(&self) -> proto::PathTrust {
 273        match self {
 274            Self::Workspace => proto::PathTrust {
 275                content: Some(proto::path_trust::Content::Workspace(0)),
 276            },
 277            Self::Worktree(worktree_id) => proto::PathTrust {
 278                content: Some(proto::path_trust::Content::WorktreeId(
 279                    worktree_id.to_proto(),
 280                )),
 281            },
 282            Self::AbsPath(path_buf) => proto::PathTrust {
 283                content: Some(proto::path_trust::Content::AbsPath(
 284                    path_buf.to_string_lossy().to_string(),
 285                )),
 286            },
 287        }
 288    }
 289
 290    pub fn from_proto(proto: proto::PathTrust) -> Option<Self> {
 291        Some(match proto.content? {
 292            proto::path_trust::Content::WorktreeId(id) => {
 293                Self::Worktree(WorktreeId::from_proto(id))
 294            }
 295            proto::path_trust::Content::AbsPath(path) => Self::AbsPath(PathBuf::from(path)),
 296            proto::path_trust::Content::Workspace(_) => Self::Workspace,
 297        })
 298    }
 299}
 300
 301/// A change of trust on a certain host.
 302#[derive(Debug)]
 303pub enum TrustedWorktreesEvent {
 304    Trusted(Option<RemoteHostLocation>, HashSet<PathTrust>),
 305    Restricted(Option<RemoteHostLocation>, HashSet<PathTrust>),
 306}
 307
 308impl EventEmitter<TrustedWorktreesEvent> for TrustedWorktreesStore {}
 309
 310impl TrustedWorktreesStore {
 311    fn new(
 312        worktree_store: Option<Entity<WorktreeStore>>,
 313        remote_host: Option<RemoteHostLocation>,
 314        downstream_client: Option<(AnyProtoClient, u64)>,
 315        upstream_client: Option<(AnyProtoClient, u64)>,
 316        cx: &App,
 317    ) -> Self {
 318        #[cfg(any(test, feature = "test-support"))]
 319        let _ = cx;
 320
 321        #[cfg(not(any(test, feature = "test-support")))]
 322        let trusted_paths = if downstream_client.is_none() {
 323            match PROJECT_DB.fetch_trusted_worktrees(
 324                worktree_store.clone(),
 325                remote_host.clone(),
 326                cx,
 327            ) {
 328                Ok(trusted_paths) => trusted_paths,
 329                Err(e) => {
 330                    log::error!("Failed to do initial trusted worktrees fetch: {e:#}");
 331                    HashMap::default()
 332                }
 333            }
 334        } else {
 335            HashMap::default()
 336        };
 337        #[cfg(any(test, feature = "test-support"))]
 338        let trusted_paths = HashMap::<Option<RemoteHostLocation>, HashSet<PathTrust>>::default();
 339
 340        if let Some((upstream_client, upstream_project_id)) = &upstream_client {
 341            let trusted_paths = trusted_paths
 342                .iter()
 343                .flat_map(|(_, paths)| paths.iter().map(|trusted_path| trusted_path.to_proto()))
 344                .collect::<Vec<_>>();
 345            if !trusted_paths.is_empty() {
 346                upstream_client
 347                    .send(proto::TrustWorktrees {
 348                        project_id: *upstream_project_id,
 349                        trusted_paths,
 350                    })
 351                    .ok();
 352            }
 353        }
 354
 355        let worktree_stores = match worktree_store {
 356            Some(worktree_store) => {
 357                HashMap::from_iter([(worktree_store.downgrade(), remote_host.clone())])
 358            }
 359            None => HashMap::default(),
 360        };
 361
 362        Self {
 363            trusted_paths,
 364            downstream_client,
 365            upstream_client,
 366            remote_host,
 367            restricted_workspaces: HashSet::default(),
 368            restricted: HashSet::default(),
 369            #[cfg(not(any(test, feature = "test-support")))]
 370            serialization_task: Task::ready(()),
 371            worktree_stores,
 372        }
 373    }
 374
 375    /// Whether a particular worktree store has associated worktrees that are restricted, or an associated host is restricted.
 376    pub fn has_restricted_worktrees(
 377        &self,
 378        worktree_store: &Entity<WorktreeStore>,
 379        cx: &App,
 380    ) -> bool {
 381        let Some(remote_host) = self.worktree_stores.get(&worktree_store.downgrade()) else {
 382            return false;
 383        };
 384        self.restricted_workspaces.contains(remote_host)
 385            || self.restricted.iter().any(|restricted_worktree| {
 386                worktree_store
 387                    .read(cx)
 388                    .worktree_for_id(*restricted_worktree, cx)
 389                    .is_some()
 390            })
 391    }
 392
 393    /// Adds certain entities on this host to the trusted list.
 394    /// This will emit [`TrustedWorktreesEvent::Trusted`] event for all passed entries
 395    /// and the ones that got auto trusted based on trust hierarchy (see module-level docs).
 396    pub fn trust(
 397        &mut self,
 398        mut trusted_paths: HashSet<PathTrust>,
 399        remote_host: Option<RemoteHostLocation>,
 400        cx: &mut Context<Self>,
 401    ) {
 402        let mut new_workspace_trusted = false;
 403        let mut new_trusted_single_file_worktrees = HashSet::default();
 404        let mut new_trusted_other_worktrees = HashSet::default();
 405        let mut new_trusted_abs_paths = HashSet::default();
 406        for trusted_path in trusted_paths.iter().chain(
 407            self.trusted_paths
 408                .remove(&remote_host)
 409                .iter()
 410                .flat_map(|current_trusted| current_trusted.iter()),
 411        ) {
 412            match trusted_path {
 413                PathTrust::Workspace => new_workspace_trusted = true,
 414                PathTrust::Worktree(worktree_id) => {
 415                    self.restricted.remove(worktree_id);
 416                    if let Some((abs_path, is_file, host)) =
 417                        self.find_worktree_data(*worktree_id, cx)
 418                    {
 419                        if host == remote_host {
 420                            if is_file {
 421                                new_trusted_single_file_worktrees.insert(*worktree_id);
 422                            } else {
 423                                new_trusted_other_worktrees.insert((abs_path, *worktree_id));
 424                                new_workspace_trusted = true;
 425                            }
 426                        }
 427                    }
 428                }
 429                PathTrust::AbsPath(path) => {
 430                    new_workspace_trusted = true;
 431                    debug_assert!(
 432                        path.is_absolute(),
 433                        "Cannot trust non-absolute path {path:?}"
 434                    );
 435                    new_trusted_abs_paths.insert(path.clone());
 436                }
 437            }
 438        }
 439
 440        if new_workspace_trusted {
 441            new_trusted_single_file_worktrees.clear();
 442            self.restricted_workspaces.remove(&remote_host);
 443            trusted_paths.insert(PathTrust::Workspace);
 444        }
 445        new_trusted_other_worktrees.retain(|(worktree_abs_path, _)| {
 446            new_trusted_abs_paths
 447                .iter()
 448                .all(|new_trusted_path| !worktree_abs_path.starts_with(new_trusted_path))
 449        });
 450        if !new_trusted_other_worktrees.is_empty() {
 451            new_trusted_single_file_worktrees.clear();
 452        }
 453        self.restricted = std::mem::take(&mut self.restricted)
 454            .into_iter()
 455            .filter(|restricted_worktree| {
 456                let Some((restricted_worktree_path, is_file, restricted_host)) =
 457                    self.find_worktree_data(*restricted_worktree, cx)
 458                else {
 459                    return false;
 460                };
 461                if restricted_host != remote_host {
 462                    return true;
 463                }
 464                let retain = (!is_file
 465                    || (!new_workspace_trusted && new_trusted_other_worktrees.is_empty()))
 466                    && new_trusted_abs_paths.iter().all(|new_trusted_path| {
 467                        !restricted_worktree_path.starts_with(new_trusted_path)
 468                    });
 469                if !retain {
 470                    trusted_paths.insert(PathTrust::Worktree(*restricted_worktree));
 471                }
 472                retain
 473            })
 474            .collect();
 475
 476        {
 477            let trusted_paths = self.trusted_paths.entry(remote_host.clone()).or_default();
 478            trusted_paths.extend(new_trusted_abs_paths.into_iter().map(PathTrust::AbsPath));
 479            trusted_paths.extend(
 480                new_trusted_other_worktrees
 481                    .into_iter()
 482                    .map(|(_, worktree_id)| PathTrust::Worktree(worktree_id)),
 483            );
 484            trusted_paths.extend(
 485                new_trusted_single_file_worktrees
 486                    .into_iter()
 487                    .map(PathTrust::Worktree),
 488            );
 489            if trusted_paths.is_empty() && new_workspace_trusted {
 490                trusted_paths.insert(PathTrust::Workspace);
 491            }
 492        }
 493
 494        cx.emit(TrustedWorktreesEvent::Trusted(
 495            remote_host,
 496            trusted_paths.clone(),
 497        ));
 498
 499        #[cfg(not(any(test, feature = "test-support")))]
 500        if self.downstream_client.is_none() {
 501            let mut new_trusted_workspaces = HashSet::default();
 502            let new_trusted_worktrees = self
 503                .trusted_paths
 504                .clone()
 505                .into_iter()
 506                .map(|(host, paths)| {
 507                    let abs_paths = paths
 508                        .into_iter()
 509                        .flat_map(|path| match path {
 510                            PathTrust::Worktree(worktree_id) => self
 511                                .find_worktree_data(worktree_id, cx)
 512                                .map(|(abs_path, ..)| abs_path.to_path_buf()),
 513                            PathTrust::AbsPath(abs_path) => Some(abs_path),
 514                            PathTrust::Workspace => {
 515                                new_trusted_workspaces.insert(host.clone());
 516                                None
 517                            }
 518                        })
 519                        .collect();
 520                    (host, abs_paths)
 521                })
 522                .collect();
 523            // Do not persist auto trusted worktrees
 524            if !ProjectSettings::get_global(cx).session.trust_all_worktrees {
 525                self.serialization_task = cx.background_spawn(async move {
 526                    PROJECT_DB
 527                        .save_trusted_worktrees(new_trusted_worktrees, new_trusted_workspaces)
 528                        .await
 529                        .log_err();
 530                });
 531            }
 532        }
 533
 534        if let Some((upstream_client, upstream_project_id)) = &self.upstream_client {
 535            let trusted_paths = trusted_paths
 536                .iter()
 537                .map(|trusted_path| trusted_path.to_proto())
 538                .collect::<Vec<_>>();
 539            if !trusted_paths.is_empty() {
 540                upstream_client
 541                    .send(proto::TrustWorktrees {
 542                        project_id: *upstream_project_id,
 543                        trusted_paths,
 544                    })
 545                    .ok();
 546            }
 547        }
 548    }
 549
 550    /// Restricts certain entities on this host.
 551    /// This will emit [`TrustedWorktreesEvent::Restricted`] event for all passed entries.
 552    pub fn restrict(
 553        &mut self,
 554        restricted_paths: HashSet<PathTrust>,
 555        remote_host: Option<RemoteHostLocation>,
 556        cx: &mut Context<Self>,
 557    ) {
 558        for restricted_path in restricted_paths {
 559            match restricted_path {
 560                PathTrust::Workspace => {
 561                    self.restricted_workspaces.insert(remote_host.clone());
 562                    cx.emit(TrustedWorktreesEvent::Restricted(
 563                        remote_host.clone(),
 564                        HashSet::from_iter([PathTrust::Workspace]),
 565                    ));
 566                }
 567                PathTrust::Worktree(worktree_id) => {
 568                    self.restricted.insert(worktree_id);
 569                    cx.emit(TrustedWorktreesEvent::Restricted(
 570                        remote_host.clone(),
 571                        HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
 572                    ));
 573                }
 574                PathTrust::AbsPath(..) => debug_panic!("Unexpected: cannot restrict an abs path"),
 575            }
 576        }
 577    }
 578
 579    /// Erases all trust information.
 580    /// Requires Zed's restart to take proper effect.
 581    pub fn clear_trusted_paths(&mut self, cx: &App) -> Task<()> {
 582        if self.downstream_client.is_none() {
 583            self.trusted_paths.clear();
 584
 585            #[cfg(not(any(test, feature = "test-support")))]
 586            {
 587                let (tx, rx) = smol::channel::bounded(1);
 588                self.serialization_task = cx.background_spawn(async move {
 589                    PROJECT_DB.clear_trusted_worktrees().await.log_err();
 590                    tx.send(()).await.ok();
 591                });
 592
 593                return cx.background_spawn(async move {
 594                    rx.recv().await.ok();
 595                });
 596            }
 597
 598            #[cfg(any(test, feature = "test-support"))]
 599            {
 600                let _ = cx;
 601                Task::ready(())
 602            }
 603        } else {
 604            Task::ready(())
 605        }
 606    }
 607
 608    /// Checks whether a certain worktree is trusted (or on a larger trust level).
 609    /// If not, emits [`TrustedWorktreesEvent::Restricted`] event if for the first time and not trusted, or no corresponding worktree store was found.
 610    ///
 611    /// No events or data adjustment happens when `trust_all_worktrees` auto trust is enabled.
 612    pub fn can_trust(&mut self, worktree_id: WorktreeId, cx: &mut Context<Self>) -> bool {
 613        if ProjectSettings::get_global(cx).session.trust_all_worktrees {
 614            return true;
 615        }
 616        if self.restricted.contains(&worktree_id) {
 617            return false;
 618        }
 619
 620        let Some((worktree_path, is_file, remote_host)) = self.find_worktree_data(worktree_id, cx)
 621        else {
 622            return false;
 623        };
 624
 625        if self
 626            .trusted_paths
 627            .get(&remote_host)
 628            .is_some_and(|trusted_paths| trusted_paths.contains(&PathTrust::Worktree(worktree_id)))
 629        {
 630            return true;
 631        }
 632
 633        // See module documentation for details on trust level.
 634        if is_file && self.trusted_paths.contains_key(&remote_host) {
 635            return true;
 636        }
 637
 638        let parent_path_trusted =
 639            self.trusted_paths
 640                .get(&remote_host)
 641                .is_some_and(|trusted_paths| {
 642                    trusted_paths.iter().any(|trusted_path| {
 643                        let PathTrust::AbsPath(trusted_path) = trusted_path else {
 644                            return false;
 645                        };
 646                        worktree_path.starts_with(trusted_path)
 647                    })
 648                });
 649        if parent_path_trusted {
 650            return true;
 651        }
 652
 653        self.restricted.insert(worktree_id);
 654        cx.emit(TrustedWorktreesEvent::Restricted(
 655            remote_host,
 656            HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
 657        ));
 658        if let Some((downstream_client, downstream_project_id)) = &self.downstream_client {
 659            downstream_client
 660                .send(proto::RestrictWorktrees {
 661                    project_id: *downstream_project_id,
 662                    restrict_workspace: false,
 663                    worktree_ids: vec![worktree_id.to_proto()],
 664                })
 665                .ok();
 666        }
 667        if let Some((upstream_client, upstream_project_id)) = &self.upstream_client {
 668            upstream_client
 669                .send(proto::RestrictWorktrees {
 670                    project_id: *upstream_project_id,
 671                    restrict_workspace: false,
 672                    worktree_ids: vec![worktree_id.to_proto()],
 673                })
 674                .ok();
 675        }
 676        false
 677    }
 678
 679    /// Checks whether a certain worktree is trusted globally (or on a larger trust level).
 680    /// If not, emits [`TrustedWorktreesEvent::Restricted`] event if checked for the first time and not trusted.
 681    ///
 682    /// No events or data adjustment happens when `trust_all_worktrees` auto trust is enabled.
 683    pub fn can_trust_workspace(
 684        &mut self,
 685        remote_host: Option<RemoteHostLocation>,
 686        cx: &mut Context<Self>,
 687    ) -> bool {
 688        if ProjectSettings::get_global(cx).session.trust_all_worktrees {
 689            return true;
 690        }
 691        if self.restricted_workspaces.contains(&remote_host) {
 692            return false;
 693        }
 694        if self.trusted_paths.contains_key(&remote_host) {
 695            return true;
 696        }
 697
 698        self.restricted_workspaces.insert(remote_host.clone());
 699        cx.emit(TrustedWorktreesEvent::Restricted(
 700            remote_host.clone(),
 701            HashSet::from_iter([PathTrust::Workspace]),
 702        ));
 703
 704        if remote_host == self.remote_host {
 705            if let Some((downstream_client, downstream_project_id)) = &self.downstream_client {
 706                downstream_client
 707                    .send(proto::RestrictWorktrees {
 708                        project_id: *downstream_project_id,
 709                        restrict_workspace: true,
 710                        worktree_ids: Vec::new(),
 711                    })
 712                    .ok();
 713            }
 714            if let Some((upstream_client, upstream_project_id)) = &self.upstream_client {
 715                upstream_client
 716                    .send(proto::RestrictWorktrees {
 717                        project_id: *upstream_project_id,
 718                        restrict_workspace: true,
 719                        worktree_ids: Vec::new(),
 720                    })
 721                    .ok();
 722            }
 723        }
 724        false
 725    }
 726
 727    /// Lists all explicitly restricted worktrees (via [`TrustedWorktreesStore::can_trust`] and [`TrustedWorktreesStore::can_trust_workspace`] method calls) for a particular worktree store on a particular host.
 728    pub fn restricted_worktrees(
 729        &self,
 730        worktree_store: &WorktreeStore,
 731        remote_host: Option<RemoteHostLocation>,
 732        cx: &App,
 733    ) -> HashSet<Option<(WorktreeId, Arc<Path>)>> {
 734        let mut single_file_paths = HashSet::default();
 735        let other_paths = self
 736            .restricted
 737            .iter()
 738            .filter_map(|&restricted_worktree_id| {
 739                let worktree = worktree_store.worktree_for_id(restricted_worktree_id, cx)?;
 740                let worktree = worktree.read(cx);
 741                let abs_path = worktree.abs_path();
 742                if worktree.is_single_file() {
 743                    single_file_paths.insert(Some((restricted_worktree_id, abs_path)));
 744                    None
 745                } else {
 746                    Some((restricted_worktree_id, abs_path))
 747                }
 748            })
 749            .map(Some)
 750            .collect::<HashSet<_>>();
 751
 752        if !other_paths.is_empty() {
 753            return other_paths;
 754        } else if self.restricted_workspaces.contains(&remote_host) {
 755            return HashSet::from_iter([None]);
 756        } else {
 757            single_file_paths
 758        }
 759    }
 760
 761    /// Switches the "trust nothing" mode to "automatically trust everything".
 762    /// This does not influence already persisted data, but stops adding new worktrees there.
 763    pub fn auto_trust_all(&mut self, cx: &mut Context<Self>) {
 764        for (remote_host, mut worktrees) in std::mem::take(&mut self.restricted)
 765            .into_iter()
 766            .flat_map(|restricted_worktree| {
 767                let (_, _, host) = self.find_worktree_data(restricted_worktree, cx)?;
 768                Some((restricted_worktree, host))
 769            })
 770            .fold(HashMap::default(), |mut acc, (worktree_id, remote_host)| {
 771                acc.entry(remote_host)
 772                    .or_insert_with(HashSet::default)
 773                    .insert(PathTrust::Worktree(worktree_id));
 774                acc
 775            })
 776        {
 777            if self.restricted_workspaces.remove(&remote_host) {
 778                worktrees.insert(PathTrust::Workspace);
 779            }
 780            self.trust(worktrees, remote_host, cx);
 781        }
 782
 783        for remote_host in std::mem::take(&mut self.restricted_workspaces) {
 784            self.trust(HashSet::from_iter([PathTrust::Workspace]), remote_host, cx);
 785        }
 786    }
 787
 788    fn find_worktree_data(
 789        &mut self,
 790        worktree_id: WorktreeId,
 791        cx: &mut Context<Self>,
 792    ) -> Option<(Arc<Path>, bool, Option<RemoteHostLocation>)> {
 793        let mut worktree_data = None;
 794        self.worktree_stores.retain(
 795            |worktree_store, remote_host| match worktree_store.upgrade() {
 796                Some(worktree_store) => {
 797                    if worktree_data.is_none() {
 798                        if let Some(worktree) =
 799                            worktree_store.read(cx).worktree_for_id(worktree_id, cx)
 800                        {
 801                            worktree_data = Some((
 802                                worktree.read(cx).abs_path(),
 803                                worktree.read(cx).is_single_file(),
 804                                remote_host.clone(),
 805                            ));
 806                        }
 807                    }
 808                    true
 809                }
 810                None => false,
 811            },
 812        );
 813        worktree_data
 814    }
 815
 816    fn add_worktree_store(
 817        &mut self,
 818        worktree_store: Entity<WorktreeStore>,
 819        remote_host: Option<RemoteHostLocation>,
 820        cx: &mut Context<Self>,
 821    ) {
 822        self.worktree_stores
 823            .insert(worktree_store.downgrade(), remote_host.clone());
 824
 825        if let Some(trusted_paths) = self.trusted_paths.remove(&remote_host) {
 826            self.trusted_paths.insert(
 827                remote_host.clone(),
 828                trusted_paths
 829                    .into_iter()
 830                    .map(|path_trust| match path_trust {
 831                        PathTrust::AbsPath(abs_path) => {
 832                            find_worktree_in_store(worktree_store.read(cx), &abs_path, cx)
 833                                .map(PathTrust::Worktree)
 834                                .unwrap_or_else(|| PathTrust::AbsPath(abs_path))
 835                        }
 836                        other => other,
 837                    })
 838                    .collect(),
 839            );
 840        }
 841    }
 842}
 843
 844pub(crate) fn find_worktree_in_store(
 845    worktree_store: &WorktreeStore,
 846    abs_path: &Path,
 847    cx: &App,
 848) -> Option<WorktreeId> {
 849    let (worktree, path_in_worktree) = worktree_store.find_worktree(&abs_path, cx)?;
 850    if path_in_worktree.is_empty() {
 851        Some(worktree.read(cx).id())
 852    } else {
 853        None
 854    }
 855}
 856
 857#[cfg(test)]
 858mod tests {
 859    use std::{cell::RefCell, path::PathBuf, rc::Rc};
 860
 861    use collections::HashSet;
 862    use gpui::TestAppContext;
 863    use serde_json::json;
 864    use settings::SettingsStore;
 865    use util::path;
 866
 867    use crate::{FakeFs, Project};
 868
 869    use super::*;
 870
 871    fn init_test(cx: &mut TestAppContext) {
 872        cx.update(|cx| {
 873            if cx.try_global::<SettingsStore>().is_none() {
 874                let settings_store = SettingsStore::test(cx);
 875                cx.set_global(settings_store);
 876            }
 877            if cx.try_global::<TrustedWorktrees>().is_some() {
 878                cx.remove_global::<TrustedWorktrees>();
 879            }
 880        });
 881    }
 882
 883    fn init_trust_global(
 884        worktree_store: Entity<WorktreeStore>,
 885        cx: &mut TestAppContext,
 886    ) -> Entity<TrustedWorktreesStore> {
 887        cx.update(|cx| {
 888            track_worktree_trust(worktree_store, None, None, None, cx);
 889            TrustedWorktrees::try_get_global(cx).expect("global should be set")
 890        })
 891    }
 892
 893    #[gpui::test]
 894    async fn test_single_worktree_trust(cx: &mut TestAppContext) {
 895        init_test(cx);
 896
 897        let fs = FakeFs::new(cx.executor());
 898        fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
 899            .await;
 900
 901        let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
 902        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
 903        let worktree_id = worktree_store.read_with(cx, |store, cx| {
 904            store.worktrees().next().unwrap().read(cx).id()
 905        });
 906
 907        let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
 908
 909        let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
 910        cx.update({
 911            let events = events.clone();
 912            |cx| {
 913                cx.subscribe(&trusted_worktrees, move |_, event, _| {
 914                    events.borrow_mut().push(match event {
 915                        TrustedWorktreesEvent::Trusted(host, paths) => {
 916                            TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
 917                        }
 918                        TrustedWorktreesEvent::Restricted(host, paths) => {
 919                            TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
 920                        }
 921                    });
 922                })
 923            }
 924        })
 925        .detach();
 926
 927        let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
 928        assert!(!can_trust, "worktree should be restricted by default");
 929
 930        {
 931            let events = events.borrow();
 932            assert_eq!(events.len(), 1);
 933            match &events[0] {
 934                TrustedWorktreesEvent::Restricted(host, paths) => {
 935                    assert!(host.is_none());
 936                    assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
 937                }
 938                _ => panic!("expected Restricted event"),
 939            }
 940        }
 941
 942        let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
 943            store.has_restricted_worktrees(&worktree_store, cx)
 944        });
 945        assert!(has_restricted, "should have restricted worktrees");
 946
 947        let restricted = worktree_store.read_with(cx, |ws, cx| {
 948            trusted_worktrees
 949                .read(cx)
 950                .restricted_worktrees(ws, None, cx)
 951        });
 952        assert!(
 953            restricted
 954                .iter()
 955                .any(|r| r.as_ref().map(|(id, _)| *id) == Some(worktree_id))
 956        );
 957
 958        events.borrow_mut().clear();
 959
 960        let can_trust_again =
 961            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
 962        assert!(!can_trust_again, "worktree should still be restricted");
 963        assert!(
 964            events.borrow().is_empty(),
 965            "no duplicate Restricted event on repeated can_trust"
 966        );
 967
 968        trusted_worktrees.update(cx, |store, cx| {
 969            store.trust(
 970                HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
 971                None,
 972                cx,
 973            );
 974        });
 975
 976        {
 977            let events = events.borrow();
 978            assert_eq!(events.len(), 1);
 979            match &events[0] {
 980                TrustedWorktreesEvent::Trusted(host, paths) => {
 981                    assert!(host.is_none());
 982                    assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
 983                }
 984                _ => panic!("expected Trusted event"),
 985            }
 986        }
 987
 988        let can_trust_after =
 989            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
 990        assert!(can_trust_after, "worktree should be trusted after trust()");
 991
 992        let has_restricted_after = trusted_worktrees.read_with(cx, |store, cx| {
 993            store.has_restricted_worktrees(&worktree_store, cx)
 994        });
 995        assert!(
 996            !has_restricted_after,
 997            "should have no restricted worktrees after trust"
 998        );
 999
1000        let restricted_after = worktree_store.read_with(cx, |ws, cx| {
1001            trusted_worktrees
1002                .read(cx)
1003                .restricted_worktrees(ws, None, cx)
1004        });
1005        assert!(
1006            restricted_after.is_empty(),
1007            "restricted set should be empty"
1008        );
1009    }
1010
1011    #[gpui::test]
1012    async fn test_workspace_trust_no_worktrees(cx: &mut TestAppContext) {
1013        init_test(cx);
1014
1015        let fs = FakeFs::new(cx.executor());
1016        fs.insert_tree(path!("/root"), json!({})).await;
1017
1018        let project = Project::test(fs, Vec::<&Path>::new(), cx).await;
1019        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1020
1021        let trusted_worktrees = init_trust_global(worktree_store, cx);
1022
1023        let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
1024        cx.update({
1025            let events = events.clone();
1026            |cx| {
1027                cx.subscribe(&trusted_worktrees, move |_, event, _| {
1028                    events.borrow_mut().push(match event {
1029                        TrustedWorktreesEvent::Trusted(host, paths) => {
1030                            TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
1031                        }
1032                        TrustedWorktreesEvent::Restricted(host, paths) => {
1033                            TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
1034                        }
1035                    });
1036                })
1037            }
1038        })
1039        .detach();
1040
1041        let can_trust_workspace =
1042            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1043        assert!(
1044            !can_trust_workspace,
1045            "workspace should be restricted by default"
1046        );
1047
1048        {
1049            let events = events.borrow();
1050            assert_eq!(events.len(), 1);
1051            match &events[0] {
1052                TrustedWorktreesEvent::Restricted(host, paths) => {
1053                    assert!(host.is_none());
1054                    assert!(paths.contains(&PathTrust::Workspace));
1055                }
1056                _ => panic!("expected Restricted event"),
1057            }
1058        }
1059
1060        events.borrow_mut().clear();
1061
1062        let can_trust_workspace_again =
1063            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1064        assert!(
1065            !can_trust_workspace_again,
1066            "workspace should still be restricted"
1067        );
1068        assert!(
1069            events.borrow().is_empty(),
1070            "no duplicate Restricted event on repeated can_trust_workspace"
1071        );
1072
1073        trusted_worktrees.update(cx, |store, cx| {
1074            store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx);
1075        });
1076
1077        {
1078            let events = events.borrow();
1079            assert_eq!(events.len(), 1);
1080            match &events[0] {
1081                TrustedWorktreesEvent::Trusted(host, paths) => {
1082                    assert!(host.is_none());
1083                    assert!(paths.contains(&PathTrust::Workspace));
1084                }
1085                _ => panic!("expected Trusted event"),
1086            }
1087        }
1088
1089        let can_trust_workspace_after =
1090            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1091        assert!(
1092            can_trust_workspace_after,
1093            "workspace should be trusted after trust()"
1094        );
1095    }
1096
1097    #[gpui::test]
1098    async fn test_single_file_worktree_trust(cx: &mut TestAppContext) {
1099        init_test(cx);
1100
1101        let fs = FakeFs::new(cx.executor());
1102        fs.insert_tree(path!("/root"), json!({ "foo.rs": "fn foo() {}" }))
1103            .await;
1104
1105        let project = Project::test(fs, [path!("/root/foo.rs").as_ref()], cx).await;
1106        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1107        let worktree_id = worktree_store.read_with(cx, |store, cx| {
1108            let worktree = store.worktrees().next().unwrap();
1109            let worktree = worktree.read(cx);
1110            assert!(worktree.is_single_file(), "expected single-file worktree");
1111            worktree.id()
1112        });
1113
1114        let trusted_worktrees = init_trust_global(worktree_store, cx);
1115
1116        let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
1117        cx.update({
1118            let events = events.clone();
1119            |cx| {
1120                cx.subscribe(&trusted_worktrees, move |_, event, _| {
1121                    events.borrow_mut().push(match event {
1122                        TrustedWorktreesEvent::Trusted(host, paths) => {
1123                            TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
1124                        }
1125                        TrustedWorktreesEvent::Restricted(host, paths) => {
1126                            TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
1127                        }
1128                    });
1129                })
1130            }
1131        })
1132        .detach();
1133
1134        let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1135        assert!(
1136            !can_trust,
1137            "single-file worktree should be restricted by default"
1138        );
1139
1140        {
1141            let events = events.borrow();
1142            assert_eq!(events.len(), 1);
1143            match &events[0] {
1144                TrustedWorktreesEvent::Restricted(host, paths) => {
1145                    assert!(host.is_none());
1146                    assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
1147                }
1148                _ => panic!("expected Restricted event"),
1149            }
1150        }
1151
1152        events.borrow_mut().clear();
1153
1154        trusted_worktrees.update(cx, |store, cx| {
1155            store.trust(
1156                HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
1157                None,
1158                cx,
1159            );
1160        });
1161
1162        {
1163            let events = events.borrow();
1164            assert_eq!(events.len(), 1);
1165            match &events[0] {
1166                TrustedWorktreesEvent::Trusted(host, paths) => {
1167                    assert!(host.is_none());
1168                    assert!(paths.contains(&PathTrust::Worktree(worktree_id)));
1169                }
1170                _ => panic!("expected Trusted event"),
1171            }
1172        }
1173
1174        let can_trust_after =
1175            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1176        assert!(
1177            can_trust_after,
1178            "single-file worktree should be trusted after trust()"
1179        );
1180    }
1181
1182    #[gpui::test]
1183    async fn test_workspace_trust_unlocks_single_file_worktree(cx: &mut TestAppContext) {
1184        init_test(cx);
1185
1186        let fs = FakeFs::new(cx.executor());
1187        fs.insert_tree(path!("/root"), json!({ "foo.rs": "fn foo() {}" }))
1188            .await;
1189
1190        let project = Project::test(fs, [path!("/root/foo.rs").as_ref()], cx).await;
1191        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1192        let worktree_id = worktree_store.read_with(cx, |store, cx| {
1193            let worktree = store.worktrees().next().unwrap();
1194            let worktree = worktree.read(cx);
1195            assert!(worktree.is_single_file(), "expected single-file worktree");
1196            worktree.id()
1197        });
1198
1199        let trusted_worktrees = init_trust_global(worktree_store, cx);
1200
1201        let can_trust_workspace =
1202            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1203        assert!(
1204            !can_trust_workspace,
1205            "workspace should be restricted by default"
1206        );
1207
1208        let can_trust_file =
1209            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1210        assert!(
1211            !can_trust_file,
1212            "single-file worktree should be restricted by default"
1213        );
1214
1215        trusted_worktrees.update(cx, |store, cx| {
1216            store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx);
1217        });
1218
1219        let can_trust_workspace_after =
1220            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1221        assert!(
1222            can_trust_workspace_after,
1223            "workspace should be trusted after trust(Workspace)"
1224        );
1225
1226        let can_trust_file_after =
1227            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1228        assert!(
1229            can_trust_file_after,
1230            "single-file worktree should be trusted after workspace trust"
1231        );
1232    }
1233
1234    #[gpui::test]
1235    async fn test_multiple_single_file_worktrees_trust_one(cx: &mut TestAppContext) {
1236        init_test(cx);
1237
1238        let fs = FakeFs::new(cx.executor());
1239        fs.insert_tree(
1240            path!("/root"),
1241            json!({
1242                "a.rs": "fn a() {}",
1243                "b.rs": "fn b() {}",
1244                "c.rs": "fn c() {}"
1245            }),
1246        )
1247        .await;
1248
1249        let project = Project::test(
1250            fs,
1251            [
1252                path!("/root/a.rs").as_ref(),
1253                path!("/root/b.rs").as_ref(),
1254                path!("/root/c.rs").as_ref(),
1255            ],
1256            cx,
1257        )
1258        .await;
1259        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1260        let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
1261            store
1262                .worktrees()
1263                .map(|worktree| {
1264                    let worktree = worktree.read(cx);
1265                    assert!(worktree.is_single_file());
1266                    worktree.id()
1267                })
1268                .collect()
1269        });
1270        assert_eq!(worktree_ids.len(), 3);
1271
1272        let trusted_worktrees = init_trust_global(worktree_store, cx);
1273
1274        for &worktree_id in &worktree_ids {
1275            let can_trust =
1276                trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1277            assert!(
1278                !can_trust,
1279                "worktree {worktree_id:?} should be restricted initially"
1280            );
1281        }
1282
1283        trusted_worktrees.update(cx, |store, cx| {
1284            store.trust(
1285                HashSet::from_iter([PathTrust::Worktree(worktree_ids[1])]),
1286                None,
1287                cx,
1288            );
1289        });
1290
1291        let can_trust_0 =
1292            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[0], cx));
1293        let can_trust_1 =
1294            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[1], cx));
1295        let can_trust_2 =
1296            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[2], cx));
1297
1298        assert!(!can_trust_0, "worktree 0 should still be restricted");
1299        assert!(can_trust_1, "worktree 1 should be trusted");
1300        assert!(!can_trust_2, "worktree 2 should still be restricted");
1301    }
1302
1303    #[gpui::test]
1304    async fn test_two_directory_worktrees_separate_trust(cx: &mut TestAppContext) {
1305        init_test(cx);
1306
1307        let fs = FakeFs::new(cx.executor());
1308        fs.insert_tree(
1309            path!("/projects"),
1310            json!({
1311                "project_a": { "main.rs": "fn main() {}" },
1312                "project_b": { "lib.rs": "pub fn lib() {}" }
1313            }),
1314        )
1315        .await;
1316
1317        let project = Project::test(
1318            fs,
1319            [
1320                path!("/projects/project_a").as_ref(),
1321                path!("/projects/project_b").as_ref(),
1322            ],
1323            cx,
1324        )
1325        .await;
1326        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1327        let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
1328            store
1329                .worktrees()
1330                .map(|worktree| {
1331                    let worktree = worktree.read(cx);
1332                    assert!(!worktree.is_single_file());
1333                    worktree.id()
1334                })
1335                .collect()
1336        });
1337        assert_eq!(worktree_ids.len(), 2);
1338
1339        let trusted_worktrees = init_trust_global(worktree_store, cx);
1340
1341        let can_trust_a =
1342            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[0], cx));
1343        let can_trust_b =
1344            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[1], cx));
1345        assert!(!can_trust_a, "project_a should be restricted initially");
1346        assert!(!can_trust_b, "project_b should be restricted initially");
1347
1348        trusted_worktrees.update(cx, |store, cx| {
1349            store.trust(
1350                HashSet::from_iter([PathTrust::Worktree(worktree_ids[0])]),
1351                None,
1352                cx,
1353            );
1354        });
1355
1356        let can_trust_a =
1357            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[0], cx));
1358        let can_trust_b =
1359            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[1], cx));
1360        assert!(can_trust_a, "project_a should be trusted after trust()");
1361        assert!(!can_trust_b, "project_b should still be restricted");
1362
1363        trusted_worktrees.update(cx, |store, cx| {
1364            store.trust(
1365                HashSet::from_iter([PathTrust::Worktree(worktree_ids[1])]),
1366                None,
1367                cx,
1368            );
1369        });
1370
1371        let can_trust_a =
1372            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[0], cx));
1373        let can_trust_b =
1374            trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_ids[1], cx));
1375        assert!(can_trust_a, "project_a should remain trusted");
1376        assert!(can_trust_b, "project_b should now be trusted");
1377    }
1378
1379    #[gpui::test]
1380    async fn test_directory_worktree_trust_enables_workspace(cx: &mut TestAppContext) {
1381        init_test(cx);
1382
1383        let fs = FakeFs::new(cx.executor());
1384        fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
1385            .await;
1386
1387        let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
1388        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1389        let worktree_id = worktree_store.read_with(cx, |store, cx| {
1390            let worktree = store.worktrees().next().unwrap();
1391            assert!(!worktree.read(cx).is_single_file());
1392            worktree.read(cx).id()
1393        });
1394
1395        let trusted_worktrees = init_trust_global(worktree_store, cx);
1396
1397        let can_trust_workspace =
1398            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1399        assert!(
1400            !can_trust_workspace,
1401            "workspace should be restricted initially"
1402        );
1403
1404        trusted_worktrees.update(cx, |store, cx| {
1405            store.trust(
1406                HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
1407                None,
1408                cx,
1409            );
1410        });
1411
1412        let can_trust_workspace_after =
1413            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1414        assert!(
1415            can_trust_workspace_after,
1416            "workspace should be trusted after trusting directory worktree"
1417        );
1418    }
1419
1420    #[gpui::test]
1421    async fn test_directory_worktree_trust_enables_single_file(cx: &mut TestAppContext) {
1422        init_test(cx);
1423
1424        let fs = FakeFs::new(cx.executor());
1425        fs.insert_tree(
1426            path!("/"),
1427            json!({
1428                "project": { "main.rs": "fn main() {}" },
1429                "standalone.rs": "fn standalone() {}"
1430            }),
1431        )
1432        .await;
1433
1434        let project = Project::test(
1435            fs,
1436            [path!("/project").as_ref(), path!("/standalone.rs").as_ref()],
1437            cx,
1438        )
1439        .await;
1440        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1441        let (dir_worktree_id, file_worktree_id) = worktree_store.read_with(cx, |store, cx| {
1442            let worktrees: Vec<_> = store.worktrees().collect();
1443            assert_eq!(worktrees.len(), 2);
1444            let (dir_worktree, file_worktree) = if worktrees[0].read(cx).is_single_file() {
1445                (&worktrees[1], &worktrees[0])
1446            } else {
1447                (&worktrees[0], &worktrees[1])
1448            };
1449            assert!(!dir_worktree.read(cx).is_single_file());
1450            assert!(file_worktree.read(cx).is_single_file());
1451            (dir_worktree.read(cx).id(), file_worktree.read(cx).id())
1452        });
1453
1454        let trusted_worktrees = init_trust_global(worktree_store, cx);
1455
1456        let can_trust_file =
1457            trusted_worktrees.update(cx, |store, cx| store.can_trust(file_worktree_id, cx));
1458        assert!(
1459            !can_trust_file,
1460            "single-file worktree should be restricted initially"
1461        );
1462
1463        trusted_worktrees.update(cx, |store, cx| {
1464            store.trust(
1465                HashSet::from_iter([PathTrust::Worktree(dir_worktree_id)]),
1466                None,
1467                cx,
1468            );
1469        });
1470
1471        let can_trust_dir =
1472            trusted_worktrees.update(cx, |store, cx| store.can_trust(dir_worktree_id, cx));
1473        let can_trust_file_after =
1474            trusted_worktrees.update(cx, |store, cx| store.can_trust(file_worktree_id, cx));
1475        assert!(can_trust_dir, "directory worktree should be trusted");
1476        assert!(
1477            can_trust_file_after,
1478            "single-file worktree should be trusted after directory worktree trust"
1479        );
1480    }
1481
1482    #[gpui::test]
1483    async fn test_abs_path_trust_covers_multiple_worktrees(cx: &mut TestAppContext) {
1484        init_test(cx);
1485
1486        let fs = FakeFs::new(cx.executor());
1487        fs.insert_tree(
1488            path!("/workspace"),
1489            json!({
1490                "project_a": { "main.rs": "fn main() {}" },
1491                "project_b": { "lib.rs": "pub fn lib() {}" }
1492            }),
1493        )
1494        .await;
1495
1496        let project = Project::test(
1497            fs,
1498            [
1499                path!("/workspace/project_a").as_ref(),
1500                path!("/workspace/project_b").as_ref(),
1501            ],
1502            cx,
1503        )
1504        .await;
1505        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1506        let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
1507            store
1508                .worktrees()
1509                .map(|worktree| worktree.read(cx).id())
1510                .collect()
1511        });
1512        assert_eq!(worktree_ids.len(), 2);
1513
1514        let trusted_worktrees = init_trust_global(worktree_store, cx);
1515
1516        for &worktree_id in &worktree_ids {
1517            let can_trust =
1518                trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1519            assert!(!can_trust, "worktree should be restricted initially");
1520        }
1521
1522        trusted_worktrees.update(cx, |store, cx| {
1523            store.trust(
1524                HashSet::from_iter([PathTrust::AbsPath(PathBuf::from(path!("/workspace")))]),
1525                None,
1526                cx,
1527            );
1528        });
1529
1530        for &worktree_id in &worktree_ids {
1531            let can_trust =
1532                trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1533            assert!(
1534                can_trust,
1535                "worktree should be trusted after parent path trust"
1536            );
1537        }
1538    }
1539
1540    #[gpui::test]
1541    async fn test_auto_trust_all(cx: &mut TestAppContext) {
1542        init_test(cx);
1543
1544        let fs = FakeFs::new(cx.executor());
1545        fs.insert_tree(
1546            path!("/"),
1547            json!({
1548                "project_a": { "main.rs": "fn main() {}" },
1549                "project_b": { "lib.rs": "pub fn lib() {}" },
1550                "single.rs": "fn single() {}"
1551            }),
1552        )
1553        .await;
1554
1555        let project = Project::test(
1556            fs,
1557            [
1558                path!("/project_a").as_ref(),
1559                path!("/project_b").as_ref(),
1560                path!("/single.rs").as_ref(),
1561            ],
1562            cx,
1563        )
1564        .await;
1565        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1566        let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
1567            store
1568                .worktrees()
1569                .map(|worktree| worktree.read(cx).id())
1570                .collect()
1571        });
1572        assert_eq!(worktree_ids.len(), 3);
1573
1574        let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
1575
1576        let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
1577        cx.update({
1578            let events = events.clone();
1579            |cx| {
1580                cx.subscribe(&trusted_worktrees, move |_, event, _| {
1581                    events.borrow_mut().push(match event {
1582                        TrustedWorktreesEvent::Trusted(host, paths) => {
1583                            TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
1584                        }
1585                        TrustedWorktreesEvent::Restricted(host, paths) => {
1586                            TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
1587                        }
1588                    });
1589                })
1590            }
1591        })
1592        .detach();
1593
1594        for &worktree_id in &worktree_ids {
1595            let can_trust =
1596                trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1597            assert!(!can_trust, "worktree should be restricted initially");
1598        }
1599        let can_trust_workspace =
1600            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1601        assert!(
1602            !can_trust_workspace,
1603            "workspace should be restricted initially"
1604        );
1605
1606        let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
1607            store.has_restricted_worktrees(&worktree_store, cx)
1608        });
1609        assert!(has_restricted, "should have restricted worktrees");
1610
1611        events.borrow_mut().clear();
1612
1613        trusted_worktrees.update(cx, |store, cx| {
1614            store.auto_trust_all(cx);
1615        });
1616
1617        for &worktree_id in &worktree_ids {
1618            let can_trust =
1619                trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1620            assert!(
1621                can_trust,
1622                "worktree {worktree_id:?} should be trusted after auto_trust_all"
1623            );
1624        }
1625
1626        let can_trust_workspace =
1627            trusted_worktrees.update(cx, |store, cx| store.can_trust_workspace(None, cx));
1628        assert!(
1629            can_trust_workspace,
1630            "workspace should be trusted after auto_trust_all"
1631        );
1632
1633        let has_restricted_after = trusted_worktrees.read_with(cx, |store, cx| {
1634            store.has_restricted_worktrees(&worktree_store, cx)
1635        });
1636        assert!(
1637            !has_restricted_after,
1638            "should have no restricted worktrees after auto_trust_all"
1639        );
1640
1641        let trusted_event_count = events
1642            .borrow()
1643            .iter()
1644            .filter(|e| matches!(e, TrustedWorktreesEvent::Trusted(..)))
1645            .count();
1646        assert!(
1647            trusted_event_count > 0,
1648            "should have emitted Trusted events"
1649        );
1650    }
1651
1652    #[gpui::test]
1653    async fn test_wait_for_global_trust_already_trusted(cx: &mut TestAppContext) {
1654        init_test(cx);
1655
1656        let fs = FakeFs::new(cx.executor());
1657        fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
1658            .await;
1659
1660        let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
1661        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1662
1663        let trusted_worktrees = init_trust_global(worktree_store, cx);
1664
1665        trusted_worktrees.update(cx, |store, cx| {
1666            store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx);
1667        });
1668
1669        let task = cx.update(|cx| wait_for_workspace_trust(None::<RemoteHostLocation>, "test", cx));
1670        assert!(task.is_none(), "should return None when already trusted");
1671    }
1672
1673    #[gpui::test]
1674    async fn test_wait_for_workspace_trust_resolves_on_trust(cx: &mut TestAppContext) {
1675        init_test(cx);
1676
1677        let fs = FakeFs::new(cx.executor());
1678        fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
1679            .await;
1680
1681        let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
1682        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1683
1684        let trusted_worktrees = init_trust_global(worktree_store, cx);
1685
1686        let task = cx.update(|cx| wait_for_workspace_trust(None::<RemoteHostLocation>, "test", cx));
1687        assert!(
1688            task.is_some(),
1689            "should return Some(Task) when not yet trusted"
1690        );
1691
1692        let task = task.unwrap();
1693
1694        cx.executor().run_until_parked();
1695
1696        trusted_worktrees.update(cx, |store, cx| {
1697            store.trust(HashSet::from_iter([PathTrust::Workspace]), None, cx);
1698        });
1699
1700        cx.executor().run_until_parked();
1701        task.await;
1702    }
1703
1704    #[gpui::test]
1705    async fn test_wait_for_default_workspace_trust_resolves_on_directory_worktree_trust(
1706        cx: &mut TestAppContext,
1707    ) {
1708        init_test(cx);
1709
1710        let fs = FakeFs::new(cx.executor());
1711        fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
1712            .await;
1713
1714        let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
1715        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1716        let worktree_id = worktree_store.read_with(cx, |store, cx| {
1717            let worktree = store.worktrees().next().unwrap();
1718            assert!(!worktree.read(cx).is_single_file());
1719            worktree.read(cx).id()
1720        });
1721
1722        let trusted_worktrees = init_trust_global(worktree_store, cx);
1723
1724        let task = cx.update(|cx| wait_for_default_workspace_trust("test", cx));
1725        assert!(
1726            task.is_some(),
1727            "should return Some(Task) when not yet trusted"
1728        );
1729
1730        let task = task.unwrap();
1731
1732        cx.executor().run_until_parked();
1733
1734        trusted_worktrees.update(cx, |store, cx| {
1735            store.trust(
1736                HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
1737                None,
1738                cx,
1739            );
1740        });
1741
1742        cx.executor().run_until_parked();
1743        task.await;
1744    }
1745
1746    #[gpui::test]
1747    async fn test_trust_restrict_trust_cycle(cx: &mut TestAppContext) {
1748        init_test(cx);
1749
1750        let fs = FakeFs::new(cx.executor());
1751        fs.insert_tree(path!("/root"), json!({ "main.rs": "fn main() {}" }))
1752            .await;
1753
1754        let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
1755        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1756        let worktree_id = worktree_store.read_with(cx, |store, cx| {
1757            store.worktrees().next().unwrap().read(cx).id()
1758        });
1759
1760        let trusted_worktrees = init_trust_global(worktree_store.clone(), cx);
1761
1762        let events: Rc<RefCell<Vec<TrustedWorktreesEvent>>> = Rc::default();
1763        cx.update({
1764            let events = events.clone();
1765            |cx| {
1766                cx.subscribe(&trusted_worktrees, move |_, event, _| {
1767                    events.borrow_mut().push(match event {
1768                        TrustedWorktreesEvent::Trusted(host, paths) => {
1769                            TrustedWorktreesEvent::Trusted(host.clone(), paths.clone())
1770                        }
1771                        TrustedWorktreesEvent::Restricted(host, paths) => {
1772                            TrustedWorktreesEvent::Restricted(host.clone(), paths.clone())
1773                        }
1774                    });
1775                })
1776            }
1777        })
1778        .detach();
1779
1780        let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1781        assert!(!can_trust, "should be restricted initially");
1782        assert_eq!(events.borrow().len(), 1);
1783        events.borrow_mut().clear();
1784
1785        trusted_worktrees.update(cx, |store, cx| {
1786            store.trust(
1787                HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
1788                None,
1789                cx,
1790            );
1791        });
1792        let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1793        assert!(can_trust, "should be trusted after trust()");
1794        assert_eq!(events.borrow().len(), 1);
1795        assert!(matches!(
1796            &events.borrow()[0],
1797            TrustedWorktreesEvent::Trusted(..)
1798        ));
1799        events.borrow_mut().clear();
1800
1801        trusted_worktrees.update(cx, |store, cx| {
1802            store.restrict(
1803                HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
1804                None,
1805                cx,
1806            );
1807        });
1808        let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1809        assert!(!can_trust, "should be restricted after restrict()");
1810        assert_eq!(events.borrow().len(), 1);
1811        assert!(matches!(
1812            &events.borrow()[0],
1813            TrustedWorktreesEvent::Restricted(..)
1814        ));
1815
1816        let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
1817            store.has_restricted_worktrees(&worktree_store, cx)
1818        });
1819        assert!(has_restricted);
1820        events.borrow_mut().clear();
1821
1822        trusted_worktrees.update(cx, |store, cx| {
1823            store.trust(
1824                HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
1825                None,
1826                cx,
1827            );
1828        });
1829        let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
1830        assert!(can_trust, "should be trusted again after second trust()");
1831        assert_eq!(events.borrow().len(), 1);
1832        assert!(matches!(
1833            &events.borrow()[0],
1834            TrustedWorktreesEvent::Trusted(..)
1835        ));
1836
1837        let has_restricted = trusted_worktrees.read_with(cx, |store, cx| {
1838            store.has_restricted_worktrees(&worktree_store, cx)
1839        });
1840        assert!(!has_restricted);
1841    }
1842
1843    #[gpui::test]
1844    async fn test_multi_host_trust_isolation(cx: &mut TestAppContext) {
1845        init_test(cx);
1846
1847        let fs = FakeFs::new(cx.executor());
1848        fs.insert_tree(
1849            path!("/"),
1850            json!({
1851                "local_project": { "main.rs": "fn main() {}" },
1852                "remote_project": { "lib.rs": "pub fn lib() {}" }
1853            }),
1854        )
1855        .await;
1856
1857        let project = Project::test(
1858            fs,
1859            [
1860                path!("/local_project").as_ref(),
1861                path!("/remote_project").as_ref(),
1862            ],
1863            cx,
1864        )
1865        .await;
1866        let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
1867        let worktree_ids: Vec<_> = worktree_store.read_with(cx, |store, cx| {
1868            store
1869                .worktrees()
1870                .map(|worktree| worktree.read(cx).id())
1871                .collect()
1872        });
1873        assert_eq!(worktree_ids.len(), 2);
1874        let local_worktree = worktree_ids[0];
1875        let _remote_worktree = worktree_ids[1];
1876
1877        let trusted_worktrees = init_trust_global(worktree_store, cx);
1878
1879        let host_a: Option<RemoteHostLocation> = None;
1880        let host_b = Some(RemoteHostLocation {
1881            user_name: Some("user".into()),
1882            host_identifier: "remote-host".into(),
1883        });
1884
1885        let can_trust_local =
1886            trusted_worktrees.update(cx, |store, cx| store.can_trust(local_worktree, cx));
1887        assert!(!can_trust_local, "local worktree restricted on host_a");
1888
1889        trusted_worktrees.update(cx, |store, cx| {
1890            store.trust(
1891                HashSet::from_iter([PathTrust::Workspace]),
1892                host_b.clone(),
1893                cx,
1894            );
1895        });
1896
1897        let can_trust_workspace_a = trusted_worktrees.update(cx, |store, cx| {
1898            store.can_trust_workspace(host_a.clone(), cx)
1899        });
1900        assert!(
1901            !can_trust_workspace_a,
1902            "host_a workspace should still be restricted"
1903        );
1904
1905        let can_trust_workspace_b = trusted_worktrees.update(cx, |store, cx| {
1906            store.can_trust_workspace(host_b.clone(), cx)
1907        });
1908        assert!(can_trust_workspace_b, "host_b workspace should be trusted");
1909
1910        trusted_worktrees.update(cx, |store, cx| {
1911            store.trust(
1912                HashSet::from_iter([PathTrust::Worktree(local_worktree)]),
1913                host_a.clone(),
1914                cx,
1915            );
1916        });
1917
1918        let can_trust_local_after =
1919            trusted_worktrees.update(cx, |store, cx| store.can_trust(local_worktree, cx));
1920        assert!(
1921            can_trust_local_after,
1922            "local worktree should be trusted on host_a"
1923        );
1924
1925        let can_trust_workspace_a_after = trusted_worktrees.update(cx, |store, cx| {
1926            store.can_trust_workspace(host_a.clone(), cx)
1927        });
1928        assert!(
1929            can_trust_workspace_a_after,
1930            "host_a workspace should be trusted after directory trust"
1931        );
1932    }
1933}