use std::{ io::{BufRead, BufReader}, path::{Path, PathBuf}, pin::pin, sync::{Arc, atomic::AtomicUsize},}; use anyhow::{Context as _, Result, anyhow, bail};use collections::{HashMap, HashSet};use fs::{Fs, copy_recursive};use futures::{FutureExt, SinkExt, future::Shared};use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity,};use postage::oneshot;use rpc::{ AnyProtoClient, ErrorExt, TypedEnvelope, proto::{self, REMOTE_SERVER_PROJECT_ID},};use smol::{ channel::{Receiver, Sender}, stream::StreamExt,};use text::ReplicaId;use util::{ ResultExt, paths::{PathStyle, RemotePathBuf, SanitizedPath}, rel_path::RelPath,};use worktree::{ CreatedEntry, Entry, ProjectEntryId, UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,}; use crate::{ProjectPath, search::SearchQuery}; struct MatchingEntry { worktree_root: Arc<Path>, path: ProjectPath, respond: oneshot::Sender<ProjectPath>,} enum WorktreeStoreState { Local { fs: Arc<dyn Fs>, }, Remote { upstream_client: AnyProtoClient, upstream_project_id: u64, path_style: PathStyle, },} pub struct WorktreeStore { next_entry_id: Arc<AtomicUsize>, downstream_client: Option<(AnyProtoClient, u64)>, retain_worktrees: bool, worktrees: Vec<WorktreeHandle>, worktrees_reordered: bool, scanning_enabled: bool, #[allow(clippy::type_complexity)] loading_worktrees: HashMap<Arc<SanitizedPath>, Shared<Task<Result<Entity<Worktree>, Arc<anyhow::Error>>>>>, state: WorktreeStoreState,} #[derive(Debug)]pub enum WorktreeStoreEvent { WorktreeAdded(Entity<Worktree>), WorktreeRemoved(EntityId, WorktreeId), WorktreeReleased(EntityId, WorktreeId), WorktreeOrderChanged, WorktreeUpdateSent(Entity<Worktree>), WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet), WorktreeUpdatedGitRepositories(WorktreeId, UpdatedGitRepositoriesSet), WorktreeDeletedEntry(WorktreeId, ProjectEntryId),} impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {} impl WorktreeStore { pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_create_project_entry); client.add_entity_request_handler(Self::handle_copy_project_entry); client.add_entity_request_handler(Self::handle_delete_project_entry); client.add_entity_request_handler(Self::handle_expand_project_entry); client.add_entity_request_handler(Self::handle_expand_all_for_project_entry); } pub fn local(retain_worktrees: bool, fs: Arc<dyn Fs>) -> Self { Self { next_entry_id: Default::default(), loading_worktrees: Default::default(), downstream_client: None, worktrees: Vec::new(), worktrees_reordered: false, scanning_enabled: true, retain_worktrees, state: WorktreeStoreState::Local { fs }, } } pub fn remote( retain_worktrees: bool, upstream_client: AnyProtoClient, upstream_project_id: u64, path_style: PathStyle, ) -> Self { Self { next_entry_id: Default::default(), loading_worktrees: Default::default(), downstream_client: None, worktrees: Vec::new(), worktrees_reordered: false, scanning_enabled: true, retain_worktrees, state: WorktreeStoreState::Remote { upstream_client, upstream_project_id, path_style, }, } } pub fn disable_scanner(&mut self) { self.scanning_enabled = false; } /// Iterates through all worktrees, including ones that don't appear in the project panel pub fn worktrees(&self) -> impl '_ + DoubleEndedIterator<Item = Entity<Worktree>> { self.worktrees .iter() .filter_map(move |worktree| worktree.upgrade()) } /// Iterates through all user-visible worktrees, the ones that appear in the project panel. pub fn visible_worktrees<'a>( &'a self, cx: &'a App, ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> { self.worktrees() .filter(|worktree| worktree.read(cx).is_visible()) } /// 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. pub fn visible_worktrees_and_single_files<'a>( &'a self, cx: &'a App, ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> { self.worktrees() .filter(|worktree| worktree.read(cx).is_visible() || worktree.read(cx).is_single_file()) } pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> { self.worktrees() .find(|worktree| worktree.read(cx).id() == id) } pub fn worktree_for_entry( &self, entry_id: ProjectEntryId, cx: &App, ) -> Option<Entity<Worktree>> { self.worktrees() .find(|worktree| worktree.read(cx).contains_entry(entry_id)) } pub fn find_worktree( &self, abs_path: impl AsRef<Path>, cx: &App, ) -> Option<(Entity<Worktree>, Arc<RelPath>)> { let abs_path = SanitizedPath::new(abs_path.as_ref()); for tree in self.worktrees() { let path_style = tree.read(cx).path_style(); if let Ok(relative_path) = abs_path.as_ref().strip_prefix(tree.read(cx).abs_path()) && let Ok(relative_path) = RelPath::new(relative_path, path_style) { return Some((tree.clone(), relative_path.into_arc())); } } None } pub fn absolutize(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> { let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; Some(worktree.read(cx).absolutize(&project_path.path)) } pub fn path_style(&self) -> PathStyle { match &self.state { WorktreeStoreState::Local { .. } => PathStyle::local(), WorktreeStoreState::Remote { path_style, .. } => *path_style, } } pub fn find_or_create_worktree( &mut self, abs_path: impl AsRef<Path>, visible: bool, cx: &mut Context<Self>, ) -> Task<Result<(Entity<Worktree>, Arc<RelPath>)>> { let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_worktree(abs_path, cx) { Task::ready(Ok((tree, relative_path))) } else { let worktree = self.create_worktree(abs_path, visible, cx); cx.background_spawn(async move { Ok((worktree.await?, RelPath::empty().into())) }) } } pub fn entry_for_id<'a>(&'a self, entry_id: ProjectEntryId, cx: &'a App) -> Option<&'a Entry> { self.worktrees() .find_map(|worktree| worktree.read(cx).entry_for_id(entry_id)) } pub fn worktree_and_entry_for_id<'a>( &'a self, entry_id: ProjectEntryId, cx: &'a App, ) -> Option<(Entity<Worktree>, &'a Entry)> { self.worktrees().find_map(|worktree| { worktree .read(cx) .entry_for_id(entry_id) .map(|e| (worktree.clone(), e)) }) } pub fn entry_for_path<'a>(&'a self, path: &ProjectPath, cx: &'a App) -> Option<&'a Entry> { self.worktree_for_id(path.worktree_id, cx)? .read(cx) .entry_for_path(&path.path) } pub fn copy_entry( &mut self, entry_id: ProjectEntryId, new_project_path: ProjectPath, cx: &mut Context<Self>, ) -> Task<Result<Option<Entry>>> { let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id) else { return Task::ready(Err(anyhow!("no such entry"))); }; let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; match &self.state { WorktreeStoreState::Local { fs } => { let old_abs_path = old_worktree.read(cx).absolutize(&old_entry.path); let new_abs_path = new_worktree.read(cx).absolutize(&new_project_path.path); let fs = fs.clone(); let copy = cx.background_spawn(async move { copy_recursive( fs.as_ref(), &old_abs_path, &new_abs_path, Default::default(), ) .await }); cx.spawn(async move |_, cx| { copy.await?; new_worktree .update(cx, |this, cx| { this.as_local_mut().unwrap().refresh_entry( new_project_path.path, None, cx, ) })? .await }) } WorktreeStoreState::Remote { upstream_client, upstream_project_id, .. } => { let response = upstream_client.request(proto::CopyProjectEntry { project_id: *upstream_project_id, entry_id: entry_id.to_proto(), new_path: new_project_path.path.to_proto(), new_worktree_id: new_project_path.worktree_id.to_proto(), }); cx.spawn(async move |_, cx| { let response = response.await?; match response.entry { Some(entry) => new_worktree .update(cx, |worktree, cx| { worktree.as_remote_mut().unwrap().insert_entry( entry, response.worktree_scan_id as usize, cx, ) })? .await .map(Some), None => Ok(None), } }) } } } pub fn rename_entry( &mut self, entry_id: ProjectEntryId, new_project_path: ProjectPath, cx: &mut Context<Self>, ) -> Task<Result<CreatedEntry>> { let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id).cloned() else { return Task::ready(Err(anyhow!("no such entry"))); }; let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; match &self.state { WorktreeStoreState::Local { fs } => { let abs_old_path = old_worktree.read(cx).absolutize(&old_entry.path); let new_worktree_ref = new_worktree.read(cx); let is_root_entry = new_worktree_ref .root_entry() .is_some_and(|e| e.id == entry_id); let abs_new_path = if is_root_entry { let abs_path = new_worktree_ref.abs_path(); let Some(root_parent_path) = abs_path.parent() else { return Task::ready(Err(anyhow!("no parent for path {:?}", abs_path))); }; root_parent_path.join(new_project_path.path.as_std_path()) } else { new_worktree_ref.absolutize(&new_project_path.path) }; let fs = fs.clone(); let case_sensitive = new_worktree .read(cx) .as_local() .unwrap() .fs_is_case_sensitive(); let do_rename = async move |fs: &dyn Fs, old_path: &Path, new_path: &Path, overwrite| { fs.rename( &old_path, &new_path, fs::RenameOptions { overwrite, ..fs::RenameOptions::default() }, ) .await .with_context(|| format!("renaming {old_path:?} into {new_path:?}")) }; let rename = cx.background_spawn({ let abs_new_path = abs_new_path.clone(); async move { // If we're on a case-insensitive FS and we're doing a case-only rename (i.e. `foobar` to `FOOBAR`) // we want to overwrite, because otherwise we run into a file-already-exists error. let overwrite = !case_sensitive && abs_old_path != abs_new_path && abs_old_path.to_str().map(|p| p.to_lowercase()) == abs_new_path.to_str().map(|p| p.to_lowercase()); // The directory we're renaming into might not exist yet if let Err(e) = do_rename(fs.as_ref(), &abs_old_path, &abs_new_path, overwrite).await { if let Some(err) = e.downcast_ref::<std::io::Error>() && err.kind() == std::io::ErrorKind::NotFound { if let Some(parent) = abs_new_path.parent() { fs.create_dir(parent).await.with_context(|| { format!("creating parent directory {parent:?}") })?; return do_rename( fs.as_ref(), &abs_old_path, &abs_new_path, overwrite, ) .await; } } return Err(e); } Ok(()) } }); cx.spawn(async move |_, cx| { rename.await?; Ok(new_worktree .update(cx, |this, cx| { let local = this.as_local_mut().unwrap(); if is_root_entry { // We eagerly update `abs_path` and refresh this worktree. // Otherwise, the FS watcher would do it on the `RootUpdated` event, // but with a noticeable delay, so we handle it proactively. local.update_abs_path_and_refresh( SanitizedPath::new_arc(&abs_new_path), cx, ); Task::ready(Ok(this.root_entry().cloned())) } else { // First refresh the parent directory (in case it was newly created) if let Some(parent) = new_project_path.path.parent() { let _ = local.refresh_entries_for_paths(vec![parent.into()]); } // Then refresh the new path local.refresh_entry( new_project_path.path.clone(), Some(old_entry.path), cx, ) } })? .await? .map(CreatedEntry::Included) .unwrap_or_else(|| CreatedEntry::Excluded { abs_path: abs_new_path, })) }) } WorktreeStoreState::Remote { upstream_client, upstream_project_id, .. } => { let response = upstream_client.request(proto::RenameProjectEntry { project_id: *upstream_project_id, entry_id: entry_id.to_proto(), new_path: new_project_path.path.to_proto(), new_worktree_id: new_project_path.worktree_id.to_proto(), }); cx.spawn(async move |_, cx| { let response = response.await?; match response.entry { Some(entry) => new_worktree .update(cx, |worktree, cx| { worktree.as_remote_mut().unwrap().insert_entry( entry, response.worktree_scan_id as usize, cx, ) })? .await .map(CreatedEntry::Included), None => { let abs_path = new_worktree.read_with(cx, |worktree, _| { worktree.absolutize(&new_project_path.path) })?; Ok(CreatedEntry::Excluded { abs_path }) } } }) } } } pub fn create_worktree( &mut self, abs_path: impl AsRef<Path>, visible: bool, cx: &mut Context<Self>, ) -> Task<Result<Entity<Worktree>>> { let abs_path: Arc<SanitizedPath> = SanitizedPath::new_arc(&abs_path); if !self.loading_worktrees.contains_key(&abs_path) { let task = match &self.state { WorktreeStoreState::Remote { upstream_client, path_style, .. } => { if upstream_client.is_via_collab() { Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab")))) } else { let abs_path = RemotePathBuf::new(abs_path.to_string(), *path_style); self.create_remote_worktree(upstream_client.clone(), abs_path, visible, cx) } } WorktreeStoreState::Local { fs } => { self.create_local_worktree(fs.clone(), abs_path.clone(), visible, cx) } }; self.loading_worktrees .insert(abs_path.clone(), task.shared()); } let task = self.loading_worktrees.get(&abs_path).unwrap().clone(); cx.spawn(async move |this, cx| {Lines 1–500 of 1212. Load more
use std::{ io::{BufRead, BufReader}, path::{Path, PathBuf}, pin::pin, sync::{Arc, atomic::AtomicUsize}, }; use anyhow::{Context as _, Result, anyhow, bail}; use collections::{HashMap, HashSet}; use fs::{Fs, copy_recursive}; use futures::{FutureExt, SinkExt, future::Shared}; use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity, }; use postage::oneshot; use rpc::{ AnyProtoClient, ErrorExt, TypedEnvelope, proto::{self, REMOTE_SERVER_PROJECT_ID}, }; use smol::{ channel::{Receiver, Sender}, stream::StreamExt, }; use text::ReplicaId; use util::{ ResultExt, paths::{PathStyle, RemotePathBuf, SanitizedPath}, rel_path::RelPath, }; use worktree::{ CreatedEntry, Entry, ProjectEntryId, UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, }; use crate::{ProjectPath, search::SearchQuery}; struct MatchingEntry { worktree_root: Arc<Path>, path: ProjectPath, respond: oneshot::Sender<ProjectPath>, } enum WorktreeStoreState { Local { fs: Arc<dyn Fs>, }, Remote { upstream_client: AnyProtoClient, upstream_project_id: u64, path_style: PathStyle, }, } pub struct WorktreeStore { next_entry_id: Arc<AtomicUsize>, downstream_client: Option<(AnyProtoClient, u64)>, retain_worktrees: bool, worktrees: Vec<WorktreeHandle>, worktrees_reordered: bool, scanning_enabled: bool, #[allow(clippy::type_complexity)] loading_worktrees: HashMap<Arc<SanitizedPath>, Shared<Task<Result<Entity<Worktree>, Arc<anyhow::Error>>>>>, state: WorktreeStoreState, } #[derive(Debug)] pub enum WorktreeStoreEvent { WorktreeAdded(Entity<Worktree>), WorktreeRemoved(EntityId, WorktreeId), WorktreeReleased(EntityId, WorktreeId), WorktreeOrderChanged, WorktreeUpdateSent(Entity<Worktree>), WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet), WorktreeUpdatedGitRepositories(WorktreeId, UpdatedGitRepositoriesSet), WorktreeDeletedEntry(WorktreeId, ProjectEntryId), } impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {} impl WorktreeStore { pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_create_project_entry); client.add_entity_request_handler(Self::handle_copy_project_entry); client.add_entity_request_handler(Self::handle_delete_project_entry); client.add_entity_request_handler(Self::handle_expand_project_entry); client.add_entity_request_handler(Self::handle_expand_all_for_project_entry); } pub fn local(retain_worktrees: bool, fs: Arc<dyn Fs>) -> Self { Self { next_entry_id: Default::default(), loading_worktrees: Default::default(), downstream_client: None, worktrees: Vec::new(), worktrees_reordered: false, scanning_enabled: true, retain_worktrees, state: WorktreeStoreState::Local { fs }, } } pub fn remote( retain_worktrees: bool, upstream_client: AnyProtoClient, upstream_project_id: u64, path_style: PathStyle, ) -> Self { Self { next_entry_id: Default::default(), loading_worktrees: Default::default(), downstream_client: None, worktrees: Vec::new(), worktrees_reordered: false, scanning_enabled: true, retain_worktrees, state: WorktreeStoreState::Remote { upstream_client, upstream_project_id, path_style, }, } } pub fn disable_scanner(&mut self) { self.scanning_enabled = false; } /// Iterates through all worktrees, including ones that don't appear in the project panel pub fn worktrees(&self) -> impl '_ + DoubleEndedIterator<Item = Entity<Worktree>> { self.worktrees .iter() .filter_map(move |worktree| worktree.upgrade()) } /// Iterates through all user-visible worktrees, the ones that appear in the project panel. pub fn visible_worktrees<'a>( &'a self, cx: &'a App, ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> { self.worktrees() .filter(|worktree| worktree.read(cx).is_visible()) } /// 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. pub fn visible_worktrees_and_single_files<'a>( &'a self, cx: &'a App, ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> { self.worktrees() .filter(|worktree| worktree.read(cx).is_visible() || worktree.read(cx).is_single_file()) } pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> { self.worktrees() .find(|worktree| worktree.read(cx).id() == id) } pub fn worktree_for_entry( &self, entry_id: ProjectEntryId, cx: &App, ) -> Option<Entity<Worktree>> { self.worktrees() .find(|worktree| worktree.read(cx).contains_entry(entry_id)) } pub fn find_worktree( &self, abs_path: impl AsRef<Path>, cx: &App, ) -> Option<(Entity<Worktree>, Arc<RelPath>)> { let abs_path = SanitizedPath::new(abs_path.as_ref()); for tree in self.worktrees() { let path_style = tree.read(cx).path_style(); if let Ok(relative_path) = abs_path.as_ref().strip_prefix(tree.read(cx).abs_path()) && let Ok(relative_path) = RelPath::new(relative_path, path_style) { return Some((tree.clone(), relative_path.into_arc())); } } None } pub fn absolutize(&self, project_path: &ProjectPath, cx: &App) -> Option<PathBuf> { let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; Some(worktree.read(cx).absolutize(&project_path.path)) } pub fn path_style(&self) -> PathStyle { match &self.state { WorktreeStoreState::Local { .. } => PathStyle::local(), WorktreeStoreState::Remote { path_style, .. } => *path_style, } } pub fn find_or_create_worktree( &mut self, abs_path: impl AsRef<Path>, visible: bool, cx: &mut Context<Self>, ) -> Task<Result<(Entity<Worktree>, Arc<RelPath>)>> { let abs_path = abs_path.as_ref(); if let Some((tree, relative_path)) = self.find_worktree(abs_path, cx) { Task::ready(Ok((tree, relative_path))) } else { let worktree = self.create_worktree(abs_path, visible, cx); cx.background_spawn(async move { Ok((worktree.await?, RelPath::empty().into())) }) } } pub fn entry_for_id<'a>(&'a self, entry_id: ProjectEntryId, cx: &'a App) -> Option<&'a Entry> { self.worktrees() .find_map(|worktree| worktree.read(cx).entry_for_id(entry_id)) } pub fn worktree_and_entry_for_id<'a>( &'a self, entry_id: ProjectEntryId, cx: &'a App, ) -> Option<(Entity<Worktree>, &'a Entry)> { self.worktrees().find_map(|worktree| { worktree .read(cx) .entry_for_id(entry_id) .map(|e| (worktree.clone(), e)) }) } pub fn entry_for_path<'a>(&'a self, path: &ProjectPath, cx: &'a App) -> Option<&'a Entry> { self.worktree_for_id(path.worktree_id, cx)? .read(cx) .entry_for_path(&path.path) } pub fn copy_entry( &mut self, entry_id: ProjectEntryId, new_project_path: ProjectPath, cx: &mut Context<Self>, ) -> Task<Result<Option<Entry>>> { let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id) else { return Task::ready(Err(anyhow!("no such entry"))); }; let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; match &self.state { WorktreeStoreState::Local { fs } => { let old_abs_path = old_worktree.read(cx).absolutize(&old_entry.path); let new_abs_path = new_worktree.read(cx).absolutize(&new_project_path.path); let fs = fs.clone(); let copy = cx.background_spawn(async move { copy_recursive( fs.as_ref(), &old_abs_path, &new_abs_path, Default::default(), ) .await }); cx.spawn(async move |_, cx| { copy.await?; new_worktree .update(cx, |this, cx| { this.as_local_mut().unwrap().refresh_entry( new_project_path.path, None, cx, ) })? .await }) } WorktreeStoreState::Remote { upstream_client, upstream_project_id, .. } => { let response = upstream_client.request(proto::CopyProjectEntry { project_id: *upstream_project_id, entry_id: entry_id.to_proto(), new_path: new_project_path.path.to_proto(), new_worktree_id: new_project_path.worktree_id.to_proto(), }); cx.spawn(async move |_, cx| { let response = response.await?; match response.entry { Some(entry) => new_worktree .update(cx, |worktree, cx| { worktree.as_remote_mut().unwrap().insert_entry( entry, response.worktree_scan_id as usize, cx, ) })? .await .map(Some), None => Ok(None), } }) } } } pub fn rename_entry( &mut self, entry_id: ProjectEntryId, new_project_path: ProjectPath, cx: &mut Context<Self>, ) -> Task<Result<CreatedEntry>> { let Some(old_worktree) = self.worktree_for_entry(entry_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; let Some(old_entry) = old_worktree.read(cx).entry_for_id(entry_id).cloned() else { return Task::ready(Err(anyhow!("no such entry"))); }; let Some(new_worktree) = self.worktree_for_id(new_project_path.worktree_id, cx) else { return Task::ready(Err(anyhow!("no such worktree"))); }; match &self.state { WorktreeStoreState::Local { fs } => { let abs_old_path = old_worktree.read(cx).absolutize(&old_entry.path); let new_worktree_ref = new_worktree.read(cx); let is_root_entry = new_worktree_ref .root_entry() .is_some_and(|e| e.id == entry_id); let abs_new_path = if is_root_entry { let abs_path = new_worktree_ref.abs_path(); let Some(root_parent_path) = abs_path.parent() else { return Task::ready(Err(anyhow!("no parent for path {:?}", abs_path))); }; root_parent_path.join(new_project_path.path.as_std_path()) } else { new_worktree_ref.absolutize(&new_project_path.path) }; let fs = fs.clone(); let case_sensitive = new_worktree .read(cx) .as_local() .unwrap() .fs_is_case_sensitive(); let do_rename = async move |fs: &dyn Fs, old_path: &Path, new_path: &Path, overwrite| { fs.rename( &old_path, &new_path, fs::RenameOptions { overwrite, ..fs::RenameOptions::default() }, ) .await .with_context(|| format!("renaming {old_path:?} into {new_path:?}")) }; let rename = cx.background_spawn({ let abs_new_path = abs_new_path.clone(); async move { // If we're on a case-insensitive FS and we're doing a case-only rename (i.e. `foobar` to `FOOBAR`) // we want to overwrite, because otherwise we run into a file-already-exists error. let overwrite = !case_sensitive && abs_old_path != abs_new_path && abs_old_path.to_str().map(|p| p.to_lowercase()) == abs_new_path.to_str().map(|p| p.to_lowercase()); // The directory we're renaming into might not exist yet if let Err(e) = do_rename(fs.as_ref(), &abs_old_path, &abs_new_path, overwrite).await { if let Some(err) = e.downcast_ref::<std::io::Error>() && err.kind() == std::io::ErrorKind::NotFound { if let Some(parent) = abs_new_path.parent() { fs.create_dir(parent).await.with_context(|| { format!("creating parent directory {parent:?}") })?; return do_rename( fs.as_ref(), &abs_old_path, &abs_new_path, overwrite, ) .await; } } return Err(e); } Ok(()) } }); cx.spawn(async move |_, cx| { rename.await?; Ok(new_worktree .update(cx, |this, cx| { let local = this.as_local_mut().unwrap(); if is_root_entry { // We eagerly update `abs_path` and refresh this worktree. // Otherwise, the FS watcher would do it on the `RootUpdated` event, // but with a noticeable delay, so we handle it proactively. local.update_abs_path_and_refresh( SanitizedPath::new_arc(&abs_new_path), cx, ); Task::ready(Ok(this.root_entry().cloned())) } else { // First refresh the parent directory (in case it was newly created) if let Some(parent) = new_project_path.path.parent() { let _ = local.refresh_entries_for_paths(vec![parent.into()]); } // Then refresh the new path local.refresh_entry( new_project_path.path.clone(), Some(old_entry.path), cx, ) } })? .await? .map(CreatedEntry::Included) .unwrap_or_else(|| CreatedEntry::Excluded { abs_path: abs_new_path, })) }) } WorktreeStoreState::Remote { upstream_client, upstream_project_id, .. } => { let response = upstream_client.request(proto::RenameProjectEntry { project_id: *upstream_project_id, entry_id: entry_id.to_proto(), new_path: new_project_path.path.to_proto(), new_worktree_id: new_project_path.worktree_id.to_proto(), }); cx.spawn(async move |_, cx| { let response = response.await?; match response.entry { Some(entry) => new_worktree .update(cx, |worktree, cx| { worktree.as_remote_mut().unwrap().insert_entry( entry, response.worktree_scan_id as usize, cx, ) })? .await .map(CreatedEntry::Included), None => { let abs_path = new_worktree.read_with(cx, |worktree, _| { worktree.absolutize(&new_project_path.path) })?; Ok(CreatedEntry::Excluded { abs_path }) } } }) } } } pub fn create_worktree( &mut self, abs_path: impl AsRef<Path>, visible: bool, cx: &mut Context<Self>, ) -> Task<Result<Entity<Worktree>>> { let abs_path: Arc<SanitizedPath> = SanitizedPath::new_arc(&abs_path); if !self.loading_worktrees.contains_key(&abs_path) { let task = match &self.state { WorktreeStoreState::Remote { upstream_client, path_style, .. } => { if upstream_client.is_via_collab() { Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab")))) } else { let abs_path = RemotePathBuf::new(abs_path.to_string(), *path_style); self.create_remote_worktree(upstream_client.clone(), abs_path, visible, cx) } } WorktreeStoreState::Local { fs } => { self.create_local_worktree(fs.clone(), abs_path.clone(), visible, cx) } }; self.loading_worktrees .insert(abs_path.clone(), task.shared()); } let task = self.loading_worktrees.get(&abs_path).unwrap().clone(); cx.spawn(async move |this, cx| {