1use std::{
2 future::Future,
3 path::{Path, PathBuf},
4 sync::{
5 Arc,
6 atomic::{AtomicU64, AtomicUsize},
7 },
8};
9
10use anyhow::{Context as _, Result, anyhow, bail};
11use collections::HashMap;
12use fs::{Fs, copy_recursive};
13use futures::{FutureExt, future::Shared};
14use gpui::{
15 App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task,
16 WeakEntity,
17};
18use itertools::Either;
19use postage::{prelude::Stream as _, watch};
20use rpc::{
21 AnyProtoClient, ErrorExt, TypedEnvelope,
22 proto::{self, REMOTE_SERVER_PROJECT_ID},
23};
24use text::ReplicaId;
25use util::{
26 ResultExt,
27 path_list::PathList,
28 paths::{PathStyle, RemotePathBuf, SanitizedPath},
29 rel_path::RelPath,
30};
31use worktree::{
32 CreatedEntry, Entry, ProjectEntryId, UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree,
33 WorktreeId,
34};
35
36use crate::{ProjectPath, trusted_worktrees::TrustedWorktrees};
37
38/// The current paths for a project's worktrees. Each folder path has a corresponding
39/// main worktree path at the same position. The two lists are always the
40/// same length and are modified together via `add_path` / `remove_main_path`.
41///
42/// For non-linked worktrees, the main path and folder path are identical.
43/// For linked worktrees, the main path is the original repo and the folder
44/// path is the linked worktree location.
45#[derive(Default, Debug, Clone)]
46pub struct WorktreePaths {
47 paths: PathList,
48 main_paths: PathList,
49}
50
51impl PartialEq for WorktreePaths {
52 fn eq(&self, other: &Self) -> bool {
53 self.paths == other.paths && self.main_paths == other.main_paths
54 }
55}
56
57impl WorktreePaths {
58 /// Build from two parallel `PathList`s that already share the same
59 /// insertion order. Used for deserialization from DB.
60 ///
61 /// Returns an error if the two lists have different lengths, which
62 /// indicates corrupted data from a prior migration bug.
63 pub fn from_path_lists(
64 main_worktree_paths: PathList,
65 folder_paths: PathList,
66 ) -> anyhow::Result<Self> {
67 anyhow::ensure!(
68 main_worktree_paths.paths().len() == folder_paths.paths().len(),
69 "main_worktree_paths has {} entries but folder_paths has {}",
70 main_worktree_paths.paths().len(),
71 folder_paths.paths().len(),
72 );
73 Ok(Self {
74 paths: folder_paths,
75 main_paths: main_worktree_paths,
76 })
77 }
78
79 /// Build for non-linked worktrees where main == folder for every path.
80 pub fn from_folder_paths(folder_paths: &PathList) -> Self {
81 Self {
82 paths: folder_paths.clone(),
83 main_paths: folder_paths.clone(),
84 }
85 }
86
87 pub fn is_empty(&self) -> bool {
88 self.paths.is_empty()
89 }
90
91 /// The folder paths (for workspace matching / `threads_by_paths` index).
92 pub fn folder_path_list(&self) -> &PathList {
93 &self.paths
94 }
95
96 /// The main worktree paths (for group key / `threads_by_main_paths` index).
97 pub fn main_worktree_path_list(&self) -> &PathList {
98 &self.main_paths
99 }
100
101 /// Iterate the (main_worktree_path, folder_path) pairs in insertion order.
102 pub fn ordered_pairs(&self) -> impl Iterator<Item = (&PathBuf, &PathBuf)> {
103 self.main_paths
104 .ordered_paths()
105 .zip(self.paths.ordered_paths())
106 }
107
108 /// Add a new path pair. If the exact (main, folder) pair already exists,
109 /// this is a no-op. Rebuilds both internal `PathList`s to maintain
110 /// consistent ordering.
111 pub fn add_path(&mut self, main_path: &Path, folder_path: &Path) {
112 let already_exists = self
113 .ordered_pairs()
114 .any(|(m, f)| m.as_path() == main_path && f.as_path() == folder_path);
115 if already_exists {
116 return;
117 }
118 let (mut mains, mut folders): (Vec<PathBuf>, Vec<PathBuf>) = self
119 .ordered_pairs()
120 .map(|(m, f)| (m.clone(), f.clone()))
121 .unzip();
122 mains.push(main_path.to_path_buf());
123 folders.push(folder_path.to_path_buf());
124 self.main_paths = PathList::new(&mains);
125 self.paths = PathList::new(&folders);
126 }
127
128 /// Remove all pairs whose main worktree path matches the given path.
129 /// This removes the corresponding entries from both lists.
130 pub fn remove_main_path(&mut self, main_path: &Path) {
131 let (mains, folders): (Vec<PathBuf>, Vec<PathBuf>) = self
132 .ordered_pairs()
133 .filter(|(m, _)| m.as_path() != main_path)
134 .map(|(m, f)| (m.clone(), f.clone()))
135 .unzip();
136 self.main_paths = PathList::new(&mains);
137 self.paths = PathList::new(&folders);
138 }
139
140 /// Remove all pairs whose folder path matches the given path.
141 /// This removes the corresponding entries from both lists.
142 pub fn remove_folder_path(&mut self, folder_path: &Path) {
143 let (mains, folders): (Vec<PathBuf>, Vec<PathBuf>) = self
144 .ordered_pairs()
145 .filter(|(_, f)| f.as_path() != folder_path)
146 .map(|(m, f)| (m.clone(), f.clone()))
147 .unzip();
148 self.main_paths = PathList::new(&mains);
149 self.paths = PathList::new(&folders);
150 }
151}
152
153enum WorktreeStoreState {
154 Local {
155 fs: Arc<dyn Fs>,
156 },
157 Remote {
158 upstream_client: AnyProtoClient,
159 upstream_project_id: u64,
160 path_style: PathStyle,
161 },
162}
163
164#[derive(Clone)]
165pub struct WorktreeIdCounter(Arc<AtomicU64>);
166
167impl Default for WorktreeIdCounter {
168 fn default() -> Self {
169 Self(Arc::new(AtomicU64::new(1)))
170 }
171}
172
173impl WorktreeIdCounter {
174 pub fn get(cx: &mut App) -> Self {
175 cx.default_global::<Self>().clone()
176 }
177
178 fn next(&self) -> u64 {
179 self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
180 }
181}
182
183impl Global for WorktreeIdCounter {}
184
185pub struct WorktreeStore {
186 next_entry_id: Arc<AtomicUsize>,
187 next_worktree_id: WorktreeIdCounter,
188 downstream_client: Option<(AnyProtoClient, u64)>,
189 retain_worktrees: bool,
190 worktrees: Vec<WorktreeHandle>,
191 worktrees_reordered: bool,
192 scanning_enabled: bool,
193 #[allow(clippy::type_complexity)]
194 loading_worktrees:
195 HashMap<Arc<SanitizedPath>, Shared<Task<Result<Entity<Worktree>, Arc<anyhow::Error>>>>>,
196 initial_scan_complete: (watch::Sender<bool>, watch::Receiver<bool>),
197 state: WorktreeStoreState,
198}
199
200#[derive(Debug)]
201pub enum WorktreeStoreEvent {
202 WorktreeAdded(Entity<Worktree>),
203 WorktreeRemoved(EntityId, WorktreeId),
204 WorktreeReleased(EntityId, WorktreeId),
205 WorktreeOrderChanged,
206 WorktreeUpdateSent(Entity<Worktree>),
207 WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
208 WorktreeUpdatedGitRepositories(WorktreeId, UpdatedGitRepositoriesSet),
209 WorktreeDeletedEntry(WorktreeId, ProjectEntryId),
210 WorktreeUpdatedRootRepoCommonDir(WorktreeId),
211}
212
213impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {}
214
215impl WorktreeStore {
216 pub fn init(client: &AnyProtoClient) {
217 client.add_entity_request_handler(Self::handle_create_project_entry);
218 client.add_entity_request_handler(Self::handle_copy_project_entry);
219 client.add_entity_request_handler(Self::handle_delete_project_entry);
220 client.add_entity_request_handler(Self::handle_expand_project_entry);
221 client.add_entity_request_handler(Self::handle_expand_all_for_project_entry);
222 }
223
224 pub fn init_remote(client: &AnyProtoClient) {
225 client.add_entity_request_handler(Self::handle_allocate_worktree_id);
226 }
227
228 pub fn local(
229 retain_worktrees: bool,
230 fs: Arc<dyn Fs>,
231 next_worktree_id: WorktreeIdCounter,
232 ) -> Self {
233 Self {
234 next_entry_id: Default::default(),
235 next_worktree_id,
236 loading_worktrees: Default::default(),
237 downstream_client: None,
238 worktrees: Vec::new(),
239 worktrees_reordered: false,
240 scanning_enabled: true,
241 retain_worktrees,
242 initial_scan_complete: watch::channel_with(true),
243 state: WorktreeStoreState::Local { fs },
244 }
245 }
246
247 pub fn remote(
248 retain_worktrees: bool,
249 upstream_client: AnyProtoClient,
250 upstream_project_id: u64,
251 path_style: PathStyle,
252 next_worktree_id: WorktreeIdCounter,
253 ) -> Self {
254 Self {
255 next_entry_id: Default::default(),
256 next_worktree_id,
257 loading_worktrees: Default::default(),
258 downstream_client: None,
259 worktrees: Vec::new(),
260 worktrees_reordered: false,
261 scanning_enabled: true,
262 retain_worktrees,
263 initial_scan_complete: watch::channel_with(true),
264 state: WorktreeStoreState::Remote {
265 upstream_client,
266 upstream_project_id,
267 path_style,
268 },
269 }
270 }
271
272 pub fn next_worktree_id(&self) -> impl Future<Output = Result<WorktreeId>> + use<> {
273 let strategy = match (&self.state, &self.downstream_client) {
274 // we are a remote server, the client is in charge of assigning worktree ids
275 (WorktreeStoreState::Local { .. }, Some((client, REMOTE_SERVER_PROJECT_ID))) => {
276 Either::Left(client.clone())
277 }
278 // we are just a local zed project, we can assign ids
279 (WorktreeStoreState::Local { .. }, _) => Either::Right(self.next_worktree_id.next()),
280 // we are connected to a remote server, we are in charge of assigning worktree ids
281 (WorktreeStoreState::Remote { .. }, _) => Either::Right(self.next_worktree_id.next()),
282 };
283 async move {
284 match strategy {
285 Either::Left(client) => Ok(client
286 .request(proto::AllocateWorktreeId {
287 project_id: REMOTE_SERVER_PROJECT_ID,
288 })
289 .await?
290 .worktree_id),
291 Either::Right(id) => Ok(id),
292 }
293 .map(WorktreeId::from_proto)
294 }
295 }
296
297 pub fn disable_scanner(&mut self) {
298 self.scanning_enabled = false;
299 *self.initial_scan_complete.0.borrow_mut() = true;
300 }
301
302 /// Returns a future that resolves when all visible worktrees have completed
303 /// their initial scan (entries populated, git repos detected).
304 pub fn wait_for_initial_scan(&self) -> impl Future<Output = ()> + use<> {
305 let mut rx = self.initial_scan_complete.1.clone();
306 async move {
307 let mut done = *rx.borrow();
308 while !done {
309 if let Some(value) = rx.recv().await {
310 done = value;
311 } else {
312 break;
313 }
314 }
315 }
316 }
317
318 /// Returns whether all visible worktrees have completed their initial scan.
319 pub fn initial_scan_completed(&self) -> bool {
320 *self.initial_scan_complete.1.borrow()
321 }
322
323 /// Checks whether all visible worktrees have completed their initial scan
324 /// and no worktree creations are pending, and updates the watch channel accordingly.
325 fn update_initial_scan_state(&mut self, cx: &App) {
326 let complete = self.loading_worktrees.is_empty()
327 && self
328 .visible_worktrees(cx)
329 .all(|wt| wt.read(cx).completed_scan_id() >= 1);
330 *self.initial_scan_complete.0.borrow_mut() = complete;
331 }
332
333 /// Spawns a detached task that waits for a worktree's initial scan to complete,
334 /// then rechecks and updates the aggregate initial scan state.
335 fn observe_worktree_scan_completion(
336 &mut self,
337 worktree: &Entity<Worktree>,
338 cx: &mut Context<Self>,
339 ) {
340 let await_scan = worktree.update(cx, |worktree, _cx| worktree.wait_for_snapshot(1));
341 cx.spawn(async move |this, cx| {
342 await_scan.await.ok();
343 this.update(cx, |this, cx| {
344 this.update_initial_scan_state(cx);
345 })
346 .ok();
347 anyhow::Ok(())
348 })
349 .detach();
350 }
351
352 /// Iterates through all worktrees, including ones that don't appear in the project panel
353 pub fn worktrees(&self) -> impl '_ + DoubleEndedIterator<Item = Entity<Worktree>> {
354 self.worktrees
355 .iter()
356 .filter_map(move |worktree| worktree.upgrade())
357 }
358
359 /// Iterates through all user-visible worktrees, the ones that appear in the project panel.
360 pub fn visible_worktrees<'a>(
361 &'a self,
362 cx: &'a App,
363 ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
364 self.worktrees()
365 .filter(|worktree| worktree.read(cx).is_visible())
366 }
367
368 /// Iterates through all user-visible worktrees (directories and files that appear in the project panel) and other, invisible single files that could appear e.g. due to drag and drop.
369 pub fn visible_worktrees_and_single_files<'a>(
370 &'a self,
371 cx: &'a App,
372 ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
373 self.worktrees()
374 .filter(|worktree| worktree.read(cx).is_visible() || worktree.read(cx).is_single_file())
375 }
376
377 pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> {
378 self.worktrees()
379 .find(|worktree| worktree.read(cx).id() == id)
380 }
381
382 pub fn worktree_for_entry(
383 &self,
384 entry_id: ProjectEntryId,
385 cx: &App,
386 ) -> Option<Entity<Worktree>> {
387 self.worktrees()
388 .find(|worktree| worktree.read(cx).contains_entry(entry_id))
389 }
390
391 pub fn find_worktree(
392 &self,
393 abs_path: impl AsRef<Path>,
394 cx: &App,
395 ) -> Option<(Entity<Worktree>, Arc<RelPath>)> {
396 let abs_path = SanitizedPath::new(abs_path.as_ref());
397 for tree in self.worktrees() {
398 let path_style = tree.read(cx).path_style();
399 if let Some(relative_path) =
400 path_style.strip_prefix(abs_path.as_ref(), tree.read(cx).abs_path().as_ref())
401 {
402 return Some((tree.clone(), relative_path.into_arc()));
403 }
404 }
405 None
406 }
407
408 pub fn project_path_for_absolute_path(&self, abs_path: &Path, cx: &App) -> Option<ProjectPath> {
409 self.find_worktree(abs_path, cx)
410 .map(|(worktree, relative_path)| ProjectPath {
411 worktree_id: worktree.read(cx).id(),
412 path: relative_path,
413 })
414 }
415
416 pub fn absolutize(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> {
417 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
418 Some(worktree.read(cx).absolutize(&project_path.path))
419 }
420
421 pub fn path_style(&self) -> PathStyle {
422 match &self.state {
423 WorktreeStoreState::Local { .. } => PathStyle::local(),
424 WorktreeStoreState::Remote { path_style, .. } => *path_style,
425 }
426 }
427
428 pub fn find_or_create_worktree(
429 &mut self,
430 abs_path: impl AsRef<Path>,
431 visible: bool,
432 cx: &mut Context<Self>,
433 ) -> Task<Result<(Entity<Worktree>, Arc<RelPath>)>> {
434 let abs_path = abs_path.as_ref();
435 if let Some((tree, relative_path)) = self.find_worktree(abs_path, cx) {
436 Task::ready(Ok((tree, relative_path)))
437 } else {
438 let worktree = self.create_worktree(abs_path, visible, cx);
439 cx.background_spawn(async move { Ok((worktree.await?, RelPath::empty().into())) })
440 }
441 }
442
443 pub fn entry_for_id<'a>(&'a self, entry_id: ProjectEntryId, cx: &'a App) -> Option<&'a Entry> {
444 self.worktrees()
445 .find_map(|worktree| worktree.read(cx).entry_for_id(entry_id))
446 }
447
448 pub fn worktree_and_entry_for_id<'a>(
449 &'a self,
450 entry_id: ProjectEntryId,
451 cx: &'a App,
452 ) -> Option<(Entity<Worktree>, &'a Entry)> {
453 self.worktrees().find_map(|worktree| {
454 worktree
455 .read(cx)
456 .entry_for_id(entry_id)
457 .map(|e| (worktree.clone(), e))
458 })
459 }
460
461 pub fn entry_for_path<'a>(&'a self, path: &ProjectPath, cx: &'a App) -> Option<&'a Entry> {
462 self.worktree_for_id(path.worktree_id, cx)?
463 .read(cx)
464 .entry_for_path(&path.path)
465 }
466
467 pub fn copy_entry(
468 &mut self,
469 entry_id: ProjectEntryId,
470 new_project_path: ProjectPath,
471 cx: &mut Context<Self>,
472 ) -> Task<Result<Option<Entry>>> {
473 let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else {
474 return Task::ready(Err(anyhow!("no such worktree")));
475 };
476 let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id) else {
477 return Task::ready(Err(anyhow!("no such entry")));
478 };
479 let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else {
480 return Task::ready(Err(anyhow!("no such worktree")));
481 };
482
483 match &self.state {
484 WorktreeStoreState::Local { fs } => {
485 let old_abs_path = old_worktree.read(cx).absolutize(&old_entry.path);
486 let new_abs_path = new_worktree.read(cx).absolutize(&new_project_path.path);
487 let fs = fs.clone();
488 let copy = cx.background_spawn(async move {
489 copy_recursive(
490 fs.as_ref(),
491 &old_abs_path,
492 &new_abs_path,
493 Default::default(),
494 )
495 .await
496 });
497
498 cx.spawn(async move |_, cx| {
499 copy.await?;
500 new_worktree
501 .update(cx, |this, cx| {
502 this.as_local_mut().unwrap().refresh_entry(
503 new_project_path.path,
504 None,
505 cx,
506 )
507 })
508 .await
509 })
510 }
511 WorktreeStoreState::Remote {
512 upstream_client,
513 upstream_project_id,
514 ..
515 } => {
516 let response = upstream_client.request(proto::CopyProjectEntry {
517 project_id: *upstream_project_id,
518 entry_id: entry_id.to_proto(),
519 new_path: new_project_path.path.to_proto(),
520 new_worktree_id: new_project_path.worktree_id.to_proto(),
521 });
522 cx.spawn(async move |_, cx| {
523 let response = response.await?;
524 match response.entry {
525 Some(entry) => new_worktree
526 .update(cx, |worktree, cx| {
527 worktree.as_remote_mut().unwrap().insert_entry(
528 entry,
529 response.worktree_scan_id as usize,
530 cx,
531 )
532 })
533 .await
534 .map(Some),
535 None => Ok(None),
536 }
537 })
538 }
539 }
540 }
541
542 pub fn rename_entry(
543 &mut self,
544 entry_id: ProjectEntryId,
545 new_project_path: ProjectPath,
546 cx: &mut Context<Self>,
547 ) -> Task<Result<CreatedEntry>> {
548 let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else {
549 return Task::ready(Err(anyhow!("no such worktree")));
550 };
551 let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id).cloned() else {
552 return Task::ready(Err(anyhow!("no such entry")));
553 };
554 let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else {
555 return Task::ready(Err(anyhow!("no such worktree")));
556 };
557
558 match &self.state {
559 WorktreeStoreState::Local { fs } => {
560 let abs_old_path = old_worktree.read(cx).absolutize(&old_entry.path);
561 let new_worktree_ref = new_worktree.read(cx);
562 let is_root_entry = new_worktree_ref
563 .root_entry()
564 .is_some_and(|e| e.id == entry_id);
565 let abs_new_path = if is_root_entry {
566 let abs_path = new_worktree_ref.abs_path();
567 let Some(root_parent_path) = abs_path.parent() else {
568 return Task::ready(Err(anyhow!("no parent for path {:?}", abs_path)));
569 };
570 root_parent_path.join(new_project_path.path.as_std_path())
571 } else {
572 new_worktree_ref.absolutize(&new_project_path.path)
573 };
574
575 let fs = fs.clone();
576 let case_sensitive = new_worktree
577 .read(cx)
578 .as_local()
579 .unwrap()
580 .fs_is_case_sensitive();
581
582 let do_rename =
583 async move |fs: &dyn Fs, old_path: &Path, new_path: &Path, overwrite| {
584 fs.rename(
585 &old_path,
586 &new_path,
587 fs::RenameOptions {
588 overwrite,
589 ..fs::RenameOptions::default()
590 },
591 )
592 .await
593 .with_context(|| format!("renaming {old_path:?} into {new_path:?}"))
594 };
595
596 let rename = cx.background_spawn({
597 let abs_new_path = abs_new_path.clone();
598 async move {
599 // If we're on a case-insensitive FS and we're doing a case-only rename (i.e. `foobar` to `FOOBAR`)
600 // we want to overwrite, because otherwise we run into a file-already-exists error.
601 let overwrite = !case_sensitive
602 && abs_old_path != abs_new_path
603 && abs_old_path.to_str().map(|p| p.to_lowercase())
604 == abs_new_path.to_str().map(|p| p.to_lowercase());
605
606 // The directory we're renaming into might not exist yet
607 if let Err(e) =
608 do_rename(fs.as_ref(), &abs_old_path, &abs_new_path, overwrite).await
609 {
610 if let Some(err) = e.downcast_ref::<std::io::Error>()
611 && err.kind() == std::io::ErrorKind::NotFound
612 {
613 if let Some(parent) = abs_new_path.parent() {
614 fs.create_dir(parent).await.with_context(|| {
615 format!("creating parent directory {parent:?}")
616 })?;
617 return do_rename(
618 fs.as_ref(),
619 &abs_old_path,
620 &abs_new_path,
621 overwrite,
622 )
623 .await;
624 }
625 }
626 return Err(e);
627 }
628 Ok(())
629 }
630 });
631
632 cx.spawn(async move |_, cx| {
633 rename.await?;
634 Ok(new_worktree
635 .update(cx, |this, cx| {
636 let local = this.as_local_mut().unwrap();
637 if is_root_entry {
638 // We eagerly update `abs_path` and refresh this worktree.
639 // Otherwise, the FS watcher would do it on the `RootUpdated` event,
640 // but with a noticeable delay, so we handle it proactively.
641 local.update_abs_path_and_refresh(
642 SanitizedPath::new_arc(&abs_new_path),
643 cx,
644 );
645 Task::ready(Ok(this.root_entry().cloned()))
646 } else {
647 // First refresh the parent directory (in case it was newly created)
648 if let Some(parent) = new_project_path.path.parent() {
649 let _ = local.refresh_entries_for_paths(vec![parent.into()]);
650 }
651 // Then refresh the new path
652 local.refresh_entry(
653 new_project_path.path.clone(),
654 Some(old_entry.path),
655 cx,
656 )
657 }
658 })
659 .await?
660 .map(CreatedEntry::Included)
661 .unwrap_or_else(|| CreatedEntry::Excluded {
662 abs_path: abs_new_path,
663 }))
664 })
665 }
666 WorktreeStoreState::Remote {
667 upstream_client,
668 upstream_project_id,
669 ..
670 } => {
671 let response = upstream_client.request(proto::RenameProjectEntry {
672 project_id: *upstream_project_id,
673 entry_id: entry_id.to_proto(),
674 new_path: new_project_path.path.to_proto(),
675 new_worktree_id: new_project_path.worktree_id.to_proto(),
676 });
677 cx.spawn(async move |_, cx| {
678 let response = response.await?;
679 match response.entry {
680 Some(entry) => new_worktree
681 .update(cx, |worktree, cx| {
682 worktree.as_remote_mut().unwrap().insert_entry(
683 entry,
684 response.worktree_scan_id as usize,
685 cx,
686 )
687 })
688 .await
689 .map(CreatedEntry::Included),
690 None => {
691 let abs_path = new_worktree.read_with(cx, |worktree, _| {
692 worktree.absolutize(&new_project_path.path)
693 });
694 Ok(CreatedEntry::Excluded { abs_path })
695 }
696 }
697 })
698 }
699 }
700 }
701 pub fn create_worktree(
702 &mut self,
703 abs_path: impl AsRef<Path>,
704 visible: bool,
705 cx: &mut Context<Self>,
706 ) -> Task<Result<Entity<Worktree>>> {
707 let abs_path: Arc<SanitizedPath> = SanitizedPath::new_arc(&abs_path);
708 let is_via_collab = matches!(&self.state, WorktreeStoreState::Remote { upstream_client, .. } if upstream_client.is_via_collab());
709 if !self.loading_worktrees.contains_key(&abs_path) {
710 let task = match &self.state {
711 WorktreeStoreState::Remote {
712 upstream_client,
713 path_style,
714 ..
715 } => {
716 if upstream_client.is_via_collab() {
717 Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab"))))
718 } else {
719 let abs_path = RemotePathBuf::new(abs_path.to_string(), *path_style);
720 self.create_remote_worktree(upstream_client.clone(), abs_path, visible, cx)
721 }
722 }
723 WorktreeStoreState::Local { fs } => {
724 self.create_local_worktree(fs.clone(), abs_path.clone(), visible, cx)
725 }
726 };
727
728 self.loading_worktrees
729 .insert(abs_path.clone(), task.shared());
730
731 if visible && self.scanning_enabled {
732 *self.initial_scan_complete.0.borrow_mut() = false;
733 }
734 }
735 let task = self.loading_worktrees.get(&abs_path).unwrap().clone();
736 cx.spawn(async move |this, cx| {
737 let result = task.await;
738 this.update(cx, |this, cx| {
739 this.loading_worktrees.remove(&abs_path);
740 if !visible || !this.scanning_enabled || result.is_err() {
741 this.update_initial_scan_state(cx);
742 }
743 })
744 .ok();
745
746 match result {
747 Ok(worktree) => {
748 if !is_via_collab {
749 if let Some((trusted_worktrees, worktree_store)) = this
750 .update(cx, |_, cx| {
751 TrustedWorktrees::try_get_global(cx).zip(Some(cx.entity()))
752 })
753 .ok()
754 .flatten()
755 {
756 trusted_worktrees.update(cx, |trusted_worktrees, cx| {
757 trusted_worktrees.can_trust(
758 &worktree_store,
759 worktree.read(cx).id(),
760 cx,
761 );
762 });
763 }
764
765 this.update(cx, |this, cx| {
766 if this.scanning_enabled && visible {
767 this.observe_worktree_scan_completion(&worktree, cx);
768 }
769 })
770 .ok();
771 }
772 Ok(worktree)
773 }
774 Err(err) => Err((*err).cloned()),
775 }
776 })
777 }
778
779 fn create_remote_worktree(
780 &mut self,
781 client: AnyProtoClient,
782 abs_path: RemotePathBuf,
783 visible: bool,
784 cx: &mut Context<Self>,
785 ) -> Task<Result<Entity<Worktree>, Arc<anyhow::Error>>> {
786 let path_style = abs_path.path_style();
787 let mut abs_path = abs_path.to_string();
788 // If we start with `/~` that means the ssh path was something like `ssh://user@host/~/home-dir-folder/`
789 // in which case want to strip the leading the `/`.
790 // On the host-side, the `~` will get expanded.
791 // That's what git does too: https://github.com/libgit2/libgit2/issues/3345#issuecomment-127050850
792 if abs_path.starts_with("/~") {
793 abs_path = abs_path[1..].to_string();
794 }
795 if abs_path.is_empty() {
796 abs_path = "~/".to_string();
797 }
798
799 cx.spawn(async move |this, cx| {
800 let this = this.upgrade().context("Dropped worktree store")?;
801
802 let path = RemotePathBuf::new(abs_path, path_style);
803 let response = client
804 .request(proto::AddWorktree {
805 project_id: REMOTE_SERVER_PROJECT_ID,
806 path: path.to_proto(),
807 visible,
808 })
809 .await?;
810
811 if let Some(existing_worktree) = this.read_with(cx, |this, cx| {
812 this.worktree_for_id(WorktreeId::from_proto(response.worktree_id), cx)
813 }) {
814 return Ok(existing_worktree);
815 }
816
817 let root_path_buf = PathBuf::from(response.canonicalized_path.clone());
818 let root_name = root_path_buf
819 .file_name()
820 .map(|n| n.to_string_lossy().into_owned())
821 .unwrap_or(root_path_buf.to_string_lossy().into_owned());
822
823 let worktree = cx.update(|cx| {
824 Worktree::remote(
825 REMOTE_SERVER_PROJECT_ID,
826 ReplicaId::REMOTE_SERVER,
827 proto::WorktreeMetadata {
828 id: response.worktree_id,
829 root_name,
830 visible,
831 abs_path: response.canonicalized_path,
832 root_repo_common_dir: response.root_repo_common_dir,
833 },
834 client,
835 path_style,
836 cx,
837 )
838 });
839
840 this.update(cx, |this, cx| {
841 this.add(&worktree, cx);
842 });
843 Ok(worktree)
844 })
845 }
846
847 fn create_local_worktree(
848 &mut self,
849 fs: Arc<dyn Fs>,
850 abs_path: Arc<SanitizedPath>,
851 visible: bool,
852 cx: &mut Context<Self>,
853 ) -> Task<Result<Entity<Worktree>, Arc<anyhow::Error>>> {
854 let next_entry_id = self.next_entry_id.clone();
855 let scanning_enabled = self.scanning_enabled;
856
857 let next_worktree_id = self.next_worktree_id();
858
859 cx.spawn(async move |this, cx| {
860 let worktree_id = next_worktree_id.await?;
861 let worktree = Worktree::local(
862 SanitizedPath::cast_arc(abs_path.clone()),
863 visible,
864 fs,
865 next_entry_id,
866 scanning_enabled,
867 worktree_id,
868 cx,
869 )
870 .await?;
871
872 this.update(cx, |this, cx| this.add(&worktree, cx))?;
873
874 if visible {
875 cx.update(|cx| {
876 cx.add_recent_document(abs_path.as_path());
877 });
878 }
879
880 Ok(worktree)
881 })
882 }
883
884 pub fn add(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
885 let worktree_id = worktree.read(cx).id();
886 debug_assert!(self.worktrees().all(|w| w.read(cx).id() != worktree_id));
887
888 let push_strong_handle = self.retain_worktrees || worktree.read(cx).is_visible();
889 let handle = if push_strong_handle {
890 WorktreeHandle::Strong(worktree.clone())
891 } else {
892 WorktreeHandle::Weak(worktree.downgrade())
893 };
894 if self.worktrees_reordered {
895 self.worktrees.push(handle);
896 } else {
897 let i = match self
898 .worktrees
899 .binary_search_by_key(&Some(worktree.read(cx).abs_path()), |other| {
900 other.upgrade().map(|worktree| worktree.read(cx).abs_path())
901 }) {
902 Ok(i) | Err(i) => i,
903 };
904 self.worktrees.insert(i, handle);
905 }
906
907 cx.emit(WorktreeStoreEvent::WorktreeAdded(worktree.clone()));
908 self.send_project_updates(cx);
909
910 let handle_id = worktree.entity_id();
911 cx.subscribe(worktree, |_, worktree, event, cx| {
912 let worktree_id = worktree.read(cx).id();
913 match event {
914 worktree::Event::UpdatedEntries(changes) => {
915 cx.emit(WorktreeStoreEvent::WorktreeUpdatedEntries(
916 worktree_id,
917 changes.clone(),
918 ));
919 }
920 worktree::Event::UpdatedGitRepositories(set) => {
921 cx.emit(WorktreeStoreEvent::WorktreeUpdatedGitRepositories(
922 worktree_id,
923 set.clone(),
924 ));
925 }
926 worktree::Event::DeletedEntry(id) => {
927 cx.emit(WorktreeStoreEvent::WorktreeDeletedEntry(worktree_id, *id))
928 }
929 worktree::Event::Deleted => {
930 // The worktree root itself has been deleted (for single-file worktrees)
931 // The worktree will be removed via the observe_release callback
932 }
933 worktree::Event::UpdatedRootRepoCommonDir { .. } => {
934 cx.emit(WorktreeStoreEvent::WorktreeUpdatedRootRepoCommonDir(
935 worktree_id,
936 ));
937 }
938 }
939 })
940 .detach();
941 cx.observe_release(worktree, move |this, worktree, cx| {
942 cx.emit(WorktreeStoreEvent::WorktreeReleased(
943 handle_id,
944 worktree.id(),
945 ));
946 cx.emit(WorktreeStoreEvent::WorktreeRemoved(
947 handle_id,
948 worktree.id(),
949 ));
950 this.send_project_updates(cx);
951 })
952 .detach();
953 }
954
955 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
956 self.worktrees.retain(|worktree| {
957 if let Some(worktree) = worktree.upgrade() {
958 if worktree.read(cx).id() == id_to_remove {
959 cx.emit(WorktreeStoreEvent::WorktreeRemoved(
960 worktree.entity_id(),
961 id_to_remove,
962 ));
963 false
964 } else {
965 true
966 }
967 } else {
968 false
969 }
970 });
971 self.update_initial_scan_state(cx);
972 self.send_project_updates(cx);
973 }
974
975 pub fn worktree_for_main_worktree_path(
976 &self,
977 path: &Path,
978 cx: &App,
979 ) -> Option<Entity<Worktree>> {
980 self.visible_worktrees(cx).find(|worktree| {
981 let worktree = worktree.read(cx);
982 if let Some(common_dir) = worktree.root_repo_common_dir() {
983 common_dir.parent() == Some(path)
984 } else {
985 worktree.abs_path().as_ref() == path
986 }
987 })
988 }
989
990 pub fn set_worktrees_reordered(&mut self, worktrees_reordered: bool) {
991 self.worktrees_reordered = worktrees_reordered;
992 }
993
994 fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
995 match &self.state {
996 WorktreeStoreState::Remote {
997 upstream_client,
998 upstream_project_id,
999 ..
1000 } => Some((upstream_client.clone(), *upstream_project_id)),
1001 WorktreeStoreState::Local { .. } => None,
1002 }
1003 }
1004
1005 pub fn set_worktrees_from_proto(
1006 &mut self,
1007 worktrees: Vec<proto::WorktreeMetadata>,
1008 replica_id: ReplicaId,
1009 cx: &mut Context<Self>,
1010 ) -> Result<()> {
1011 let mut old_worktrees_by_id = self
1012 .worktrees
1013 .drain(..)
1014 .filter_map(|worktree| {
1015 let worktree = worktree.upgrade()?;
1016 Some((worktree.read(cx).id(), worktree))
1017 })
1018 .collect::<HashMap<_, _>>();
1019
1020 let (client, project_id) = self.upstream_client().context("invalid project")?;
1021
1022 for worktree in worktrees {
1023 if let Some(old_worktree) =
1024 old_worktrees_by_id.remove(&WorktreeId::from_proto(worktree.id))
1025 {
1026 let push_strong_handle =
1027 self.retain_worktrees || old_worktree.read(cx).is_visible();
1028 let handle = if push_strong_handle {
1029 WorktreeHandle::Strong(old_worktree.clone())
1030 } else {
1031 WorktreeHandle::Weak(old_worktree.downgrade())
1032 };
1033 self.worktrees.push(handle);
1034 } else {
1035 self.add(
1036 &Worktree::remote(
1037 project_id,
1038 replica_id,
1039 worktree,
1040 client.clone(),
1041 self.path_style(),
1042 cx,
1043 ),
1044 cx,
1045 );
1046 }
1047 }
1048 self.send_project_updates(cx);
1049
1050 Ok(())
1051 }
1052
1053 pub fn move_worktree(
1054 &mut self,
1055 source: WorktreeId,
1056 destination: WorktreeId,
1057 cx: &mut Context<Self>,
1058 ) -> Result<()> {
1059 if source == destination {
1060 return Ok(());
1061 }
1062
1063 let mut source_index = None;
1064 let mut destination_index = None;
1065 for (i, worktree) in self.worktrees.iter().enumerate() {
1066 if let Some(worktree) = worktree.upgrade() {
1067 let worktree_id = worktree.read(cx).id();
1068 if worktree_id == source {
1069 source_index = Some(i);
1070 if destination_index.is_some() {
1071 break;
1072 }
1073 } else if worktree_id == destination {
1074 destination_index = Some(i);
1075 if source_index.is_some() {
1076 break;
1077 }
1078 }
1079 }
1080 }
1081
1082 let source_index =
1083 source_index.with_context(|| format!("Missing worktree for id {source}"))?;
1084 let destination_index =
1085 destination_index.with_context(|| format!("Missing worktree for id {destination}"))?;
1086
1087 if source_index == destination_index {
1088 return Ok(());
1089 }
1090
1091 let worktree_to_move = self.worktrees.remove(source_index);
1092 self.worktrees.insert(destination_index, worktree_to_move);
1093 self.worktrees_reordered = true;
1094 cx.emit(WorktreeStoreEvent::WorktreeOrderChanged);
1095 cx.notify();
1096 Ok(())
1097 }
1098
1099 pub fn disconnected_from_host(&mut self, cx: &mut App) {
1100 for worktree in &self.worktrees {
1101 if let Some(worktree) = worktree.upgrade() {
1102 worktree.update(cx, |worktree, _| {
1103 if let Some(worktree) = worktree.as_remote_mut() {
1104 worktree.disconnected_from_host();
1105 }
1106 });
1107 }
1108 }
1109 }
1110
1111 pub fn send_project_updates(&mut self, cx: &mut Context<Self>) {
1112 let Some((downstream_client, project_id)) = self.downstream_client.clone() else {
1113 return;
1114 };
1115
1116 let update = proto::UpdateProject {
1117 project_id,
1118 worktrees: self.worktree_metadata_protos(cx),
1119 };
1120
1121 // collab has bad concurrency guarantees, so we send requests in serial.
1122 let update_project = if downstream_client.is_via_collab() {
1123 Some(downstream_client.request(update))
1124 } else {
1125 downstream_client.send(update).log_err();
1126 None
1127 };
1128 cx.spawn(async move |this, cx| {
1129 if let Some(update_project) = update_project {
1130 update_project.await?;
1131 }
1132
1133 this.update(cx, |this, cx| {
1134 let worktrees = this.worktrees().collect::<Vec<_>>();
1135
1136 for worktree in worktrees {
1137 worktree.update(cx, |worktree, cx| {
1138 let client = downstream_client.clone();
1139 worktree.observe_updates(project_id, cx, {
1140 move |update| {
1141 let client = client.clone();
1142 async move {
1143 if client.is_via_collab() {
1144 client
1145 .request(update)
1146 .map(|result| result.log_err().is_some())
1147 .await
1148 } else {
1149 client.send(update).log_err().is_some()
1150 }
1151 }
1152 }
1153 });
1154 });
1155
1156 cx.emit(WorktreeStoreEvent::WorktreeUpdateSent(worktree.clone()))
1157 }
1158
1159 anyhow::Ok(())
1160 })
1161 })
1162 .detach_and_log_err(cx);
1163 }
1164
1165 pub fn worktree_metadata_protos(&self, cx: &App) -> Vec<proto::WorktreeMetadata> {
1166 self.worktrees()
1167 .map(|worktree| {
1168 let worktree = worktree.read(cx);
1169 proto::WorktreeMetadata {
1170 id: worktree.id().to_proto(),
1171 root_name: worktree.root_name_str().to_owned(),
1172 visible: worktree.is_visible(),
1173 abs_path: worktree.abs_path().to_string_lossy().into_owned(),
1174 root_repo_common_dir: worktree
1175 .root_repo_common_dir()
1176 .map(|p| p.to_string_lossy().into_owned()),
1177 }
1178 })
1179 .collect()
1180 }
1181
1182 pub fn shared(
1183 &mut self,
1184 remote_id: u64,
1185 downstream_client: AnyProtoClient,
1186 cx: &mut Context<Self>,
1187 ) {
1188 self.retain_worktrees = true;
1189 self.downstream_client = Some((downstream_client, remote_id));
1190
1191 // When shared, retain all worktrees
1192 for worktree_handle in self.worktrees.iter_mut() {
1193 match worktree_handle {
1194 WorktreeHandle::Strong(_) => {}
1195 WorktreeHandle::Weak(worktree) => {
1196 if let Some(worktree) = worktree.upgrade() {
1197 *worktree_handle = WorktreeHandle::Strong(worktree);
1198 }
1199 }
1200 }
1201 }
1202 // Only send project updates if we share in a collaborative mode.
1203 // Otherwise we are the remote server which is currently constructing
1204 // worktree store before the client actually has set up its message
1205 // handlers.
1206 if remote_id != REMOTE_SERVER_PROJECT_ID {
1207 self.send_project_updates(cx);
1208 }
1209 }
1210
1211 pub fn unshared(&mut self, cx: &mut Context<Self>) {
1212 self.retain_worktrees = false;
1213 self.downstream_client.take();
1214
1215 // When not shared, only retain the visible worktrees
1216 for worktree_handle in self.worktrees.iter_mut() {
1217 if let WorktreeHandle::Strong(worktree) = worktree_handle {
1218 let is_visible = worktree.update(cx, |worktree, _| {
1219 worktree.stop_observing_updates();
1220 worktree.is_visible()
1221 });
1222 if !is_visible {
1223 *worktree_handle = WorktreeHandle::Weak(worktree.downgrade());
1224 }
1225 }
1226 }
1227 }
1228
1229 pub async fn handle_create_project_entry(
1230 this: Entity<Self>,
1231 envelope: TypedEnvelope<proto::CreateProjectEntry>,
1232 mut cx: AsyncApp,
1233 ) -> Result<proto::ProjectEntryResponse> {
1234 let worktree = this.update(&mut cx, |this, cx| {
1235 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1236 this.worktree_for_id(worktree_id, cx)
1237 .context("worktree not found")
1238 })?;
1239 Worktree::handle_create_entry(worktree, envelope.payload, cx).await
1240 }
1241
1242 pub async fn handle_copy_project_entry(
1243 this: Entity<Self>,
1244 envelope: TypedEnvelope<proto::CopyProjectEntry>,
1245 mut cx: AsyncApp,
1246 ) -> Result<proto::ProjectEntryResponse> {
1247 let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
1248 let new_worktree_id = WorktreeId::from_proto(envelope.payload.new_worktree_id);
1249 let new_project_path = (
1250 new_worktree_id,
1251 RelPath::from_proto(&envelope.payload.new_path)?,
1252 );
1253 let (scan_id, entry) = this.update(&mut cx, |this, cx| {
1254 let Some((_, project_id)) = this.downstream_client else {
1255 bail!("no downstream client")
1256 };
1257 let Some(entry) = this.entry_for_id(entry_id, cx) else {
1258 bail!("no such entry");
1259 };
1260 if entry.is_private && project_id != REMOTE_SERVER_PROJECT_ID {
1261 bail!("entry is private")
1262 }
1263
1264 let new_worktree = this
1265 .worktree_for_id(new_worktree_id, cx)
1266 .context("no such worktree")?;
1267 let scan_id = new_worktree.read(cx).scan_id();
1268 anyhow::Ok((
1269 scan_id,
1270 this.copy_entry(entry_id, new_project_path.into(), cx),
1271 ))
1272 })?;
1273 let entry = entry.await?;
1274 Ok(proto::ProjectEntryResponse {
1275 entry: entry.as_ref().map(|entry| entry.into()),
1276 worktree_scan_id: scan_id as u64,
1277 })
1278 }
1279
1280 pub async fn handle_delete_project_entry(
1281 this: Entity<Self>,
1282 envelope: TypedEnvelope<proto::DeleteProjectEntry>,
1283 mut cx: AsyncApp,
1284 ) -> Result<proto::ProjectEntryResponse> {
1285 let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
1286 let worktree = this.update(&mut cx, |this, cx| {
1287 let Some((_, project_id)) = this.downstream_client else {
1288 bail!("no downstream client")
1289 };
1290 let Some(entry) = this.entry_for_id(entry_id, cx) else {
1291 bail!("no entry")
1292 };
1293 if entry.is_private && project_id != REMOTE_SERVER_PROJECT_ID {
1294 bail!("entry is private")
1295 }
1296 this.worktree_for_entry(entry_id, cx)
1297 .context("worktree not found")
1298 })?;
1299 Worktree::handle_delete_entry(worktree, envelope.payload, cx).await
1300 }
1301
1302 pub async fn handle_rename_project_entry(
1303 this: Entity<Self>,
1304 request: proto::RenameProjectEntry,
1305 mut cx: AsyncApp,
1306 ) -> Result<proto::ProjectEntryResponse> {
1307 let entry_id = ProjectEntryId::from_proto(request.entry_id);
1308 let new_worktree_id = WorktreeId::from_proto(request.new_worktree_id);
1309 let rel_path = RelPath::from_proto(&request.new_path)
1310 .with_context(|| format!("received invalid relative path {:?}", &request.new_path))?;
1311
1312 let (scan_id, task) = this.update(&mut cx, |this, cx| {
1313 let worktree = this
1314 .worktree_for_entry(entry_id, cx)
1315 .context("no such worktree")?;
1316
1317 let Some((_, project_id)) = this.downstream_client else {
1318 bail!("no downstream client")
1319 };
1320 let entry = worktree
1321 .read(cx)
1322 .entry_for_id(entry_id)
1323 .ok_or_else(|| anyhow!("missing entry"))?;
1324 if entry.is_private && project_id != REMOTE_SERVER_PROJECT_ID {
1325 bail!("entry is private")
1326 }
1327
1328 let scan_id = worktree.read(cx).scan_id();
1329 anyhow::Ok((
1330 scan_id,
1331 this.rename_entry(entry_id, (new_worktree_id, rel_path).into(), cx),
1332 ))
1333 })?;
1334 Ok(proto::ProjectEntryResponse {
1335 entry: match &task.await? {
1336 CreatedEntry::Included(entry) => Some(entry.into()),
1337 CreatedEntry::Excluded { .. } => None,
1338 },
1339 worktree_scan_id: scan_id as u64,
1340 })
1341 }
1342
1343 pub async fn handle_expand_project_entry(
1344 this: Entity<Self>,
1345 envelope: TypedEnvelope<proto::ExpandProjectEntry>,
1346 mut cx: AsyncApp,
1347 ) -> Result<proto::ExpandProjectEntryResponse> {
1348 let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
1349 let worktree = this
1350 .update(&mut cx, |this, cx| this.worktree_for_entry(entry_id, cx))
1351 .context("invalid request")?;
1352 Worktree::handle_expand_entry(worktree, envelope.payload, cx).await
1353 }
1354
1355 pub async fn handle_expand_all_for_project_entry(
1356 this: Entity<Self>,
1357 envelope: TypedEnvelope<proto::ExpandAllForProjectEntry>,
1358 mut cx: AsyncApp,
1359 ) -> Result<proto::ExpandAllForProjectEntryResponse> {
1360 let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
1361 let worktree = this
1362 .update(&mut cx, |this, cx| this.worktree_for_entry(entry_id, cx))
1363 .context("invalid request")?;
1364 Worktree::handle_expand_all_for_entry(worktree, envelope.payload, cx).await
1365 }
1366
1367 pub async fn handle_allocate_worktree_id(
1368 _this: Entity<Self>,
1369 _envelope: TypedEnvelope<proto::AllocateWorktreeId>,
1370 cx: AsyncApp,
1371 ) -> Result<proto::AllocateWorktreeIdResponse> {
1372 let worktree_id = cx.update(|cx| WorktreeIdCounter::get(cx).next());
1373 Ok(proto::AllocateWorktreeIdResponse { worktree_id })
1374 }
1375
1376 pub fn fs(&self) -> Option<Arc<dyn Fs>> {
1377 match &self.state {
1378 WorktreeStoreState::Local { fs } => Some(fs.clone()),
1379 WorktreeStoreState::Remote { .. } => None,
1380 }
1381 }
1382
1383 pub fn paths(&self, cx: &App) -> WorktreePaths {
1384 let (mains, folders): (Vec<PathBuf>, Vec<PathBuf>) = self
1385 .visible_worktrees(cx)
1386 .filter(|worktree| {
1387 let worktree = worktree.read(cx);
1388 // Remote worktrees that haven't received their first update
1389 // don't have enough data to contribute yet.
1390 !worktree.is_remote() || worktree.root_entry().is_some()
1391 })
1392 .map(|worktree| {
1393 let snapshot = worktree.read(cx).snapshot();
1394 let folder_path = snapshot.abs_path().to_path_buf();
1395 let main_path = snapshot
1396 .root_repo_common_dir()
1397 .and_then(|dir| Some(dir.parent()?.to_path_buf()))
1398 .unwrap_or_else(|| folder_path.clone());
1399 (main_path, folder_path)
1400 })
1401 .unzip();
1402
1403 WorktreePaths {
1404 paths: PathList::new(&folders),
1405 main_paths: PathList::new(&mains),
1406 }
1407 }
1408}
1409
1410#[derive(Clone, Debug)]
1411enum WorktreeHandle {
1412 Strong(Entity<Worktree>),
1413 Weak(WeakEntity<Worktree>),
1414}
1415
1416impl WorktreeHandle {
1417 fn upgrade(&self) -> Option<Entity<Worktree>> {
1418 match self {
1419 WorktreeHandle::Strong(handle) => Some(handle.clone()),
1420 WorktreeHandle::Weak(handle) => handle.upgrade(),
1421 }
1422 }
1423}