project: Always allocate WorktreeIDs on the remote client (#47936)

Lukas Wirth created

Closes https://github.com/zed-industries/zed/issues/40342

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/agent_ui/src/completion_provider.rs                |  2 
crates/edit_prediction/src/license_detection.rs           |  4 
crates/file_finder/src/file_finder_tests.rs               | 27 +-
crates/project/src/project.rs                             | 11 
crates/project/src/worktree_store.rs                      | 82 ++++++++
crates/project/tests/integration/extension_agent_tests.rs | 11 
crates/proto/proto/worktree.proto                         | 31 ++-
crates/proto/proto/zed.proto                              |  5 
crates/proto/src/proto.rs                                 |  4 
crates/remote_server/src/headless_project.rs              | 14 +
crates/worktree/src/worktree.rs                           | 11 
crates/worktree/tests/integration/main.rs                 | 31 +++
crates/worktree_benchmarks/src/main.rs                    |  2 
13 files changed, 187 insertions(+), 48 deletions(-)

Detailed changes

crates/agent_ui/src/completion_provider.rs 🔗

@@ -2575,7 +2575,7 @@ mod tests {
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
-            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+            worktrees[0].read(cx).id()
         });
 
         // Open a file in dir2 to create navigation history.

crates/edit_prediction/src/license_detection.rs 🔗

@@ -388,6 +388,7 @@ mod tests {
 
     use fs::FakeFs;
     use gpui::TestAppContext;
+    use project::WorktreeId;
     use rand::Rng as _;
     use serde_json::json;
     use settings::SettingsStore;
@@ -736,6 +737,7 @@ mod tests {
             fs.clone(),
             Default::default(),
             true,
+            WorktreeId::from_proto(0),
             &mut cx.to_async(),
         )
         .await
@@ -760,6 +762,7 @@ mod tests {
             fs.clone(),
             Default::default(),
             true,
+            WorktreeId::from_proto(0),
             &mut cx.to_async(),
         )
         .await
@@ -819,6 +822,7 @@ mod tests {
             fs.clone(),
             Default::default(),
             true,
+            WorktreeId::from_proto(0),
             &mut cx.to_async(),
         )
         .await

crates/file_finder/src/file_finder_tests.rs 🔗

@@ -1210,10 +1210,7 @@ async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) {
     let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
     let (_worktree_id1, worktree_id2) = cx.read(|cx| {
         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-        (
-            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize),
-            WorktreeId::from_usize(worktrees[1].entity_id().as_u64() as usize),
-        )
+        (worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
     });
 
     let b_path = ProjectPath {
@@ -1342,7 +1339,7 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
     let worktree_id = cx.read(|cx| {
         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
         assert_eq!(worktrees.len(), 1);
-        WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        worktrees[0].read(cx).id()
     });
 
     // When workspace has an active item, sort items which are closer to that item
@@ -1430,7 +1427,7 @@ async fn test_query_history(cx: &mut gpui::TestAppContext) {
     let worktree_id = cx.read(|cx| {
         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
         assert_eq!(worktrees.len(), 1);
-        WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        worktrees[0].read(cx).id()
     });
 
     // Open and close panels, getting their history items afterwards.
@@ -1650,7 +1647,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
         assert_eq!(worktrees.len(), 1,);
 
-        WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        worktrees[0].read(cx).id()
     });
     workspace
         .update_in(cx, |workspace, window, cx| {
@@ -1674,14 +1671,12 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
             "External file should get opened in a new worktree"
         );
 
-        WorktreeId::from_usize(
-            worktrees
-                .into_iter()
-                .find(|worktree| worktree.entity_id().as_u64() as usize != worktree_id.to_usize())
-                .expect("New worktree should have a different id")
-                .entity_id()
-                .as_u64() as usize,
-        )
+        worktrees
+            .into_iter()
+            .find(|worktree| worktree.read(cx).id() != worktree_id)
+            .expect("New worktree should have a different id")
+            .read(cx)
+            .id()
     });
     cx.dispatch_action(workspace::CloseActiveItem {
         save_intent: None,
@@ -1807,7 +1802,7 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
         assert_eq!(worktrees.len(), 1,);
 
-        WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        worktrees[0].read(cx).id()
     });
 
     // generate some history to select from

crates/project/src/project.rs 🔗

@@ -40,6 +40,7 @@ use crate::{
     lsp_store::{SymbolLocation, log_store::LogKind},
     project_search::SearchResultsHandle,
     trusted_worktrees::{PathTrust, RemoteHostLocation, TrustedWorktrees},
+    worktree_store::WorktreeIdCounter,
 };
 pub use agent_registry_store::{AgentRegistryStore, RegistryAgent};
 pub use agent_server_store::{
@@ -1118,7 +1119,8 @@ impl Project {
             cx.spawn(async move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx).await)
                 .detach();
             let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
-            let worktree_store = cx.new(|_| WorktreeStore::local(false, fs.clone()));
+            let worktree_store =
+                cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
             if flags.init_worktree_trust {
                 trusted_worktrees::track_worktree_trust(
                     worktree_store.clone(),
@@ -1325,12 +1327,13 @@ impl Project {
                         remote.connection_options(),
                     )
                 });
-            let worktree_store = cx.new(|_| {
+            let worktree_store = cx.new(|cx| {
                 WorktreeStore::remote(
                     false,
                     remote_proto.clone(),
                     REMOTE_SERVER_PROJECT_ID,
                     path_style,
+                    WorktreeIdCounter::get(cx),
                 )
             });
 
@@ -1553,6 +1556,7 @@ impl Project {
 
             remote_proto.add_entity_message_handler(Self::handle_find_search_candidates_cancel);
             BufferStore::init(&remote_proto);
+            WorktreeStore::init_remote(&remote_proto);
             LspStore::init(&remote_proto);
             SettingsObserver::init(&remote_proto);
             TaskStore::init(Some(&remote_proto));
@@ -1632,12 +1636,13 @@ impl Project {
             PathStyle::Posix
         };
 
-        let worktree_store = cx.new(|_| {
+        let worktree_store = cx.new(|cx| {
             WorktreeStore::remote(
                 true,
                 client.clone().into(),
                 response.payload.project_id,
                 path_style,
+                WorktreeIdCounter::get(cx),
             )
         });
         let buffer_store = cx.new(|cx| {

crates/project/src/worktree_store.rs 🔗

@@ -1,6 +1,9 @@
 use std::{
     path::{Path, PathBuf},
-    sync::{Arc, atomic::AtomicUsize},
+    sync::{
+        Arc,
+        atomic::{AtomicU64, AtomicUsize},
+    },
 };
 
 use anyhow::{Context as _, Result, anyhow, bail};
@@ -8,8 +11,10 @@ use collections::HashMap;
 use fs::{Fs, copy_recursive};
 use futures::{FutureExt, future::Shared};
 use gpui::{
-    App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity,
+    App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task,
+    WeakEntity,
 };
+use itertools::Either;
 use rpc::{
     AnyProtoClient, ErrorExt, TypedEnvelope,
     proto::{self, REMOTE_SERVER_PROJECT_ID},
@@ -38,8 +43,30 @@ enum WorktreeStoreState {
     },
 }
 
+#[derive(Clone)]
+pub struct WorktreeIdCounter(Arc<AtomicU64>);
+
+impl Default for WorktreeIdCounter {
+    fn default() -> Self {
+        Self(Arc::new(AtomicU64::new(1)))
+    }
+}
+
+impl WorktreeIdCounter {
+    pub fn get(cx: &mut App) -> Self {
+        cx.default_global::<Self>().clone()
+    }
+
+    fn next(&self) -> u64 {
+        self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
+    }
+}
+
+impl Global for WorktreeIdCounter {}
+
 pub struct WorktreeStore {
     next_entry_id: Arc<AtomicUsize>,
+    next_worktree_id: WorktreeIdCounter,
     downstream_client: Option<(AnyProtoClient, u64)>,
     retain_worktrees: bool,
     worktrees: Vec<WorktreeHandle>,
@@ -74,9 +101,18 @@ impl WorktreeStore {
         client.add_entity_request_handler(Self::handle_expand_all_for_project_entry);
     }
 
-    pub fn local(retain_worktrees: bool, fs: Arc<dyn Fs>) -> Self {
+    pub fn init_remote(client: &AnyProtoClient) {
+        client.add_entity_request_handler(Self::handle_allocate_worktree_id);
+    }
+
+    pub fn local(
+        retain_worktrees: bool,
+        fs: Arc<dyn Fs>,
+        next_worktree_id: WorktreeIdCounter,
+    ) -> Self {
         Self {
             next_entry_id: Default::default(),
+            next_worktree_id,
             loading_worktrees: Default::default(),
             downstream_client: None,
             worktrees: Vec::new(),
@@ -92,9 +128,11 @@ impl WorktreeStore {
         upstream_client: AnyProtoClient,
         upstream_project_id: u64,
         path_style: PathStyle,
+        next_worktree_id: WorktreeIdCounter,
     ) -> Self {
         Self {
             next_entry_id: Default::default(),
+            next_worktree_id,
             loading_worktrees: Default::default(),
             downstream_client: None,
             worktrees: Vec::new(),
@@ -109,6 +147,31 @@ impl WorktreeStore {
         }
     }
 
+    pub fn next_worktree_id(&self) -> impl Future<Output = Result<WorktreeId>> + use<> {
+        let strategy = match (&self.state, &self.downstream_client) {
+            // we are a remote server, the client is in charge of assigning worktree ids
+            (WorktreeStoreState::Local { .. }, Some((client, REMOTE_SERVER_PROJECT_ID))) => {
+                Either::Left(client.clone())
+            }
+            // we are just a local zed project, we can assign ids
+            (WorktreeStoreState::Local { .. }, _) => Either::Right(self.next_worktree_id.next()),
+            // we are connected to a remote server, we are in charge of assigning worktree ids
+            (WorktreeStoreState::Remote { .. }, _) => Either::Right(self.next_worktree_id.next()),
+        };
+        async move {
+            match strategy {
+                Either::Left(client) => Ok(client
+                    .request(proto::AllocateWorktreeId {
+                        project_id: REMOTE_SERVER_PROJECT_ID,
+                    })
+                    .await?
+                    .worktree_id),
+                Either::Right(id) => Ok(id),
+            }
+            .map(WorktreeId::from_proto)
+        }
+    }
+
     pub fn disable_scanner(&mut self) {
         self.scanning_enabled = false;
     }
@@ -600,13 +663,17 @@ impl WorktreeStore {
         let next_entry_id = self.next_entry_id.clone();
         let scanning_enabled = self.scanning_enabled;
 
+        let next_worktree_id = self.next_worktree_id();
+
         cx.spawn(async move |this, cx| {
+            let worktree_id = next_worktree_id.await?;
             let worktree = Worktree::local(
                 SanitizedPath::cast_arc(abs_path.clone()),
                 visible,
                 fs,
                 next_entry_id,
                 scanning_enabled,
+                worktree_id,
                 cx,
             )
             .await?;
@@ -1078,6 +1145,15 @@ impl WorktreeStore {
         Worktree::handle_expand_all_for_entry(worktree, envelope.payload, cx).await
     }
 
+    pub async fn handle_allocate_worktree_id(
+        _this: Entity<Self>,
+        _envelope: TypedEnvelope<proto::AllocateWorktreeId>,
+        cx: AsyncApp,
+    ) -> Result<proto::AllocateWorktreeIdResponse> {
+        let worktree_id = cx.update(|cx| WorktreeIdCounter::get(cx).next());
+        Ok(proto::AllocateWorktreeIdResponse { worktree_id })
+    }
+
     pub fn fs(&self) -> Option<Arc<dyn Fs>> {
         match &self.state {
             WorktreeStoreState::Local { fs } => Some(fs.clone()),

crates/project/tests/integration/extension_agent_tests.rs 🔗

@@ -2,8 +2,8 @@ use anyhow::Result;
 use collections::HashMap;
 use gpui::{AppContext, AsyncApp, SharedString, Task, TestAppContext};
 use node_runtime::NodeRuntime;
-use project::agent_server_store::*;
 use project::worktree_store::WorktreeStore;
+use project::{agent_server_store::*, worktree_store::WorktreeIdCounter};
 use std::{any::Any, path::PathBuf, sync::Arc};
 
 #[test]
@@ -131,7 +131,8 @@ fn archive_launcher_constructs_with_all_fields() {
 async fn archive_agent_uses_extension_and_agent_id_for_cache_key(cx: &mut TestAppContext) {
     let fs = fs::FakeFs::new(cx.background_executor.clone());
     let http_client = http_client::FakeHttpClient::with_404_response();
-    let worktree_store = cx.new(|_| WorktreeStore::local(false, fs.clone()));
+    let worktree_store =
+        cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
     let project_environment = cx.new(|cx| {
         crate::ProjectEnvironment::new(None, worktree_store.downgrade(), None, false, cx)
     });
@@ -212,7 +213,8 @@ async fn test_node_command_uses_managed_runtime(cx: &mut TestAppContext) {
     let fs = fs::FakeFs::new(cx.background_executor.clone());
     let http_client = http_client::FakeHttpClient::with_404_response();
     let node_runtime = NodeRuntime::unavailable();
-    let worktree_store = cx.new(|_| WorktreeStore::local(false, fs.clone()));
+    let worktree_store =
+        cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
     let project_environment = cx.new(|cx| {
         crate::ProjectEnvironment::new(None, worktree_store.downgrade(), None, false, cx)
     });
@@ -255,7 +257,8 @@ async fn test_commands_run_in_extraction_directory(cx: &mut TestAppContext) {
     let fs = fs::FakeFs::new(cx.background_executor.clone());
     let http_client = http_client::FakeHttpClient::with_404_response();
     let node_runtime = NodeRuntime::unavailable();
-    let worktree_store = cx.new(|_| WorktreeStore::local(false, fs.clone()));
+    let worktree_store =
+        cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
     let project_environment = cx.new(|cx| {
         crate::ProjectEnvironment::new(None, worktree_store.downgrade(), None, false, cx)
     });

crates/proto/proto/worktree.proto 🔗

@@ -162,22 +162,33 @@ message UpdateUserSettings {
 }
 
 message TrustWorktrees {
-    uint64 project_id = 1;
-    repeated PathTrust trusted_paths = 2;
+  uint64 project_id = 1;
+  repeated PathTrust trusted_paths = 2;
 }
 
 message PathTrust {
-    oneof content {
-        uint64 worktree_id = 2;
-        string abs_path = 3;
-    }
+  oneof content {
+    uint64 worktree_id = 2;
+    string abs_path = 3;
+  }
 
-    reserved 1;
+  reserved 1;
 }
 
 message RestrictWorktrees {
-    uint64 project_id = 1;
-    repeated uint64 worktree_ids = 3;
+  uint64 project_id = 1;
+  repeated uint64 worktree_ids = 3;
+
+  reserved 2;
+}
 
-    reserved 2;
+// Request from the remote server to the client to allocate a new worktree ID.
+// This allows the client to maintain a single WorktreeIdCounter that prevents
+// ID collisions when connected to multiple remote servers.
+message AllocateWorktreeId {
+  uint64 project_id = 1;
+}
+
+message AllocateWorktreeIdResponse {
+  uint64 worktree_id = 1;
 }

crates/proto/proto/zed.proto 🔗

@@ -455,7 +455,10 @@ message Envelope {
         FindSearchCandidatesChunk find_search_candidates_chunk = 409;
         FindSearchCandidatesCancelled find_search_candidates_cancelled = 410;
         GetContextServerCommand get_context_server_command = 411;
-        ContextServerCommand context_server_command = 412; // current max
+        ContextServerCommand context_server_command = 412;
+        
+        AllocateWorktreeId allocate_worktree_id = 413;
+        AllocateWorktreeIdResponse allocate_worktree_id_response = 414; // current max
     }
 
     reserved 87 to 88;

crates/proto/src/proto.rs 🔗

@@ -33,6 +33,8 @@ messages!(
     (AddWorktree, Foreground),
     (AddWorktreeResponse, Foreground),
     (AdvertiseContexts, Foreground),
+    (AllocateWorktreeId, Foreground),
+    (AllocateWorktreeIdResponse, Foreground),
     (ApplyCodeAction, Background),
     (ApplyCodeActionResponse, Background),
     (ApplyCompletionAdditionalEdits, Background),
@@ -352,6 +354,7 @@ messages!(
 );
 
 request_messages!(
+    (AllocateWorktreeId, AllocateWorktreeIdResponse),
     (ApplyCodeAction, ApplyCodeActionResponse),
     (
         ApplyCompletionAdditionalEdits,
@@ -561,6 +564,7 @@ entity_messages!(
     {project_id, ShareProject},
     AddProjectCollaborator,
     AddWorktree,
+    AllocateWorktreeId,
     ApplyCodeAction,
     ApplyCompletionAdditionalEdits,
     BlameBuffer,

crates/remote_server/src/headless_project.rs 🔗

@@ -25,7 +25,7 @@ use project::{
     search::SearchQuery,
     task_store::TaskStore,
     trusted_worktrees::{PathTrust, RemoteHostLocation, TrustedWorktrees},
-    worktree_store::WorktreeStore,
+    worktree_store::{WorktreeIdCounter, WorktreeStore},
 };
 use rpc::{
     AnyProtoClient, TypedEnvelope,
@@ -98,7 +98,7 @@ impl HeadlessProject {
         languages::init(languages.clone(), fs.clone(), node_runtime.clone(), cx);
 
         let worktree_store = cx.new(|cx| {
-            let mut store = WorktreeStore::local(true, fs.clone());
+            let mut store = WorktreeStore::local(true, fs.clone(), WorktreeIdCounter::get(cx));
             store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
             store
         });
@@ -482,7 +482,12 @@ impl HeadlessProject {
                 }
             }
         };
-
+        let next_worktree_id = this
+            .update(&mut cx, |this, cx| {
+                this.worktree_store
+                    .update(cx, |worktree_store, _| worktree_store.next_worktree_id())
+            })
+            .await?;
         let worktree = this
             .read_with(&cx.clone(), |this, _| {
                 Worktree::local(
@@ -491,6 +496,7 @@ impl HeadlessProject {
                     this.fs.clone(),
                     this.next_entry_id.clone(),
                     true,
+                    next_worktree_id,
                     &mut cx,
                 )
             })
@@ -754,7 +760,7 @@ impl HeadlessProject {
                 buffer_store.open_buffer(
                     ProjectPath {
                         worktree_id: worktree.read(cx).id(),
-                        path: path,
+                        path,
                     },
                     cx,
                 )

crates/worktree/src/worktree.rs 🔗

@@ -368,6 +368,7 @@ impl Worktree {
         fs: Arc<dyn Fs>,
         next_entry_id: Arc<AtomicUsize>,
         scanning_enabled: bool,
+        worktree_id: WorktreeId,
         cx: &mut AsyncApp,
     ) -> Result<Entity<Self>> {
         let abs_path = path.into();
@@ -404,7 +405,7 @@ impl Worktree {
                 repo_exclude_by_work_dir_abs_path: Default::default(),
                 git_repositories: Default::default(),
                 snapshot: Snapshot::new(
-                    cx.entity_id().as_u64(),
+                    worktree_id,
                     abs_path
                         .file_name()
                         .and_then(|f| f.to_str())
@@ -493,7 +494,7 @@ impl Worktree {
     ) -> Entity<Self> {
         cx.new(|cx: &mut Context<Self>| {
             let snapshot = Snapshot::new(
-                worktree.id,
+                WorktreeId::from_proto(worktree.id),
                 RelPath::from_proto(&worktree.root_name)
                     .unwrap_or_else(|_| RelPath::empty().into()),
                 Path::new(&worktree.abs_path).into(),
@@ -1831,7 +1832,7 @@ impl LocalWorktree {
             .unbounded_send((self.snapshot(), Arc::default()))
             .ok();
 
-        let worktree_id = cx.entity_id().as_u64();
+        let worktree_id = self.id.to_proto();
         let _maintain_remote_snapshot = cx.background_spawn(async move {
             let mut is_first = true;
             while let Some((snapshot, entry_changes)) = snapshots_rx.next().await {
@@ -2137,13 +2138,13 @@ impl RemoteWorktree {
 
 impl Snapshot {
     pub fn new(
-        id: u64,
+        id: WorktreeId,
         root_name: Arc<RelPath>,
         abs_path: Arc<Path>,
         path_style: PathStyle,
     ) -> Self {
         Snapshot {
-            id: WorktreeId::from_usize(id as usize),
+            id,
             abs_path: SanitizedPath::from_arc(abs_path),
             path_style,
             root_char_bag: root_name

crates/worktree/tests/integration/main.rs 🔗

@@ -12,7 +12,7 @@ use rand::prelude::*;
 use worktree::{Entry, EntryKind, Event, PathChange, Worktree, WorktreeModelHandle};
 
 use serde_json::json;
-use settings::SettingsStore;
+use settings::{SettingsStore, WorktreeId};
 use std::{
     env,
     fmt::Write,
@@ -49,6 +49,7 @@ async fn test_traversal(cx: &mut TestAppContext) {
         fs,
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -114,6 +115,7 @@ async fn test_circular_symlinks(cx: &mut TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -214,6 +216,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -365,6 +368,7 @@ async fn test_renaming_case_only(cx: &mut TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -443,6 +447,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -608,6 +613,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -709,6 +715,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -807,6 +814,7 @@ async fn test_file_scan_inclusions(cx: &mut TestAppContext) {
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -873,6 +881,7 @@ async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext)
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -932,6 +941,7 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1018,6 +1028,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1100,6 +1111,7 @@ async fn test_hidden_files(cx: &mut TestAppContext) {
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1211,6 +1223,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1323,6 +1336,7 @@ async fn test_fs_events_in_dot_git_worktree(cx: &mut TestAppContext) {
         Arc::new(RealFs::new(None, cx.executor())),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1362,6 +1376,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
         fs,
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1431,6 +1446,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         fs_fake,
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1473,6 +1489,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
         fs_real,
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1582,6 +1599,7 @@ async fn test_create_file_in_expanded_gitignored_dir(cx: &mut TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1678,6 +1696,7 @@ async fn test_fs_event_for_gitignored_dir_does_not_lose_contents(cx: &mut TestAp
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1756,6 +1775,7 @@ async fn test_random_worktree_operations_during_initial_scan(
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1847,6 +1867,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -1920,6 +1941,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
             fs.clone(),
             Default::default(),
             true,
+            WorktreeId::from_proto(0),
             &mut cx.to_async(),
         )
         .await
@@ -2244,6 +2266,7 @@ async fn test_private_single_file_worktree(cx: &mut TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -2277,6 +2300,7 @@ async fn test_repository_above_root(executor: BackgroundExecutor, cx: &mut TestA
         fs.clone(),
         Arc::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -2343,6 +2367,7 @@ async fn test_global_gitignore(executor: BackgroundExecutor, cx: &mut TestAppCon
         fs.clone(),
         Arc::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -2449,6 +2474,7 @@ async fn test_repo_exclude(executor: BackgroundExecutor, cx: &mut TestAppContext
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -2665,6 +2691,7 @@ async fn test_load_file_encoding(cx: &mut TestAppContext) {
         fs,
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -2728,6 +2755,7 @@ async fn test_write_file_encoding(cx: &mut gpui::TestAppContext) {
         fs.clone(),
         Default::default(),
         true,
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await
@@ -2866,6 +2894,7 @@ async fn test_refresh_entries_for_paths_creates_ancestors(cx: &mut TestAppContex
         fs.clone(),
         Default::default(),
         false, // Disable scanning so the initial scan doesn't discover any entries
+        WorktreeId::from_proto(0),
         &mut cx.to_async(),
     )
     .await

crates/worktree_benchmarks/src/main.rs 🔗

@@ -5,6 +5,7 @@ use std::{
 
 use fs::RealFs;
 use gpui::Application;
+use settings::WorktreeId;
 use worktree::Worktree;
 
 fn main() {
@@ -27,6 +28,7 @@ fn main() {
                 fs,
                 Arc::new(AtomicUsize::new(0)),
                 true,
+                WorktreeId::from_proto(0),
                 cx,
             )
             .await