mod ignore;mod worktree_settings;#[cfg(test)]mod worktree_tests; use ::ignore::gitignore::{Gitignore, GitignoreBuilder};use anyhow::{Context as _, Result, anyhow};use clock::ReplicaId;use collections::{HashMap, HashSet, VecDeque};use fs::{Fs, MTime, PathEvent, RemoveOptions, Watcher, copy_recursive, read_dir_items};use futures::{ FutureExt as _, Stream, StreamExt, channel::{ mpsc::{self, UnboundedSender}, oneshot, }, select_biased, stream, task::Poll,};use fuzzy::CharBag;use git::{ COMMIT_MESSAGE, DOT_GIT, FSMONITOR_DAEMON, GITIGNORE, INDEX_LOCK, LFS_DIR, status::GitSummary,};use gpui::{ App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Priority, Task,};use ignore::IgnoreStack;use language::DiskState; use parking_lot::Mutex;use paths::{local_settings_folder_name, local_vscode_folder_name};use postage::{ barrier, prelude::{Sink as _, Stream as _}, watch,};use rpc::{ AnyProtoClient, proto::{self, split_worktree_update},};pub use settings::WorktreeId;use settings::{Settings, SettingsLocation, SettingsStore};use smallvec::{SmallVec, smallvec};use smol::channel::{self, Sender};use std::{ any::Any, borrow::Borrow as _, cmp::Ordering, collections::hash_map, convert::TryFrom, ffi::OsStr, fmt, future::Future, mem::{self}, ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, pin::Pin, sync::{ Arc, atomic::{AtomicUsize, Ordering::SeqCst}, }, time::{Duration, Instant},};use sum_tree::{Bias, Dimensions, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet};use text::{LineEnding, Rope};use util::{ ResultExt, debug_panic, maybe, paths::{PathMatcher, PathStyle, SanitizedPath, home_dir}, rel_path::RelPath,};pub use worktree_settings::WorktreeSettings; pub const FS_WATCH_LATENCY: Duration = Duration::from_millis(100); /// A set of local or remote files that are being opened as part of a project./// Responsible for tracking related FS (for local)/collab (for remote) events and corresponding updates./// Stores git repositories data and the diagnostics for the file(s).////// Has an absolute path, and may be set to be visible in Zed UI or not./// May correspond to a directory or a single file./// Possible examples:/// * a drag and dropped file — may be added as an invisible, "ephemeral" entry to the current worktree/// * a directory opened in Zed — may be added as a visible entry to the current worktree////// Uses [`Entry`] to track the state of each file/directory, can look up absolute paths for entries.pub enum Worktree { Local(LocalWorktree), Remote(RemoteWorktree),} /// An entry, created in the worktree.#[derive(Debug)]pub enum CreatedEntry { /// Got created and indexed by the worktree, receiving a corresponding entry. Included(Entry), /// Got created, but not indexed due to falling under exclusion filters. Excluded { abs_path: PathBuf },} #[derive(Debug)]pub struct LoadedFile { pub file: Arc<File>, pub text: String,} pub struct LoadedBinaryFile { pub file: Arc<File>, pub content: Vec<u8>,} impl fmt::Debug for LoadedBinaryFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("LoadedBinaryFile") .field("file", &self.file) .field("content_bytes", &self.content.len()) .finish() }} pub struct LocalWorktree { snapshot: LocalSnapshot, scan_requests_tx: channel::Sender<ScanRequest>, path_prefixes_to_scan_tx: channel::Sender<PathPrefixScanRequest>, is_scanning: (watch::Sender<bool>, watch::Receiver<bool>), _background_scanner_tasks: Vec<Task<()>>, update_observer: Option<UpdateObservationState>, fs: Arc<dyn Fs>, fs_case_sensitive: bool, visible: bool, next_entry_id: Arc<AtomicUsize>, settings: WorktreeSettings, share_private_files: bool, scanning_enabled: bool,} pub struct PathPrefixScanRequest { path: Arc<RelPath>, done: SmallVec<[barrier::Sender; 1]>,} struct ScanRequest { relative_paths: Vec<Arc<RelPath>>, done: SmallVec<[barrier::Sender; 1]>,} pub struct RemoteWorktree { snapshot: Snapshot, background_snapshot: Arc<Mutex<(Snapshot, Vec<proto::UpdateWorktree>)>>, project_id: u64, client: AnyProtoClient, file_scan_inclusions: PathMatcher, updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>, update_observer: Option<mpsc::UnboundedSender<proto::UpdateWorktree>>, snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>, replica_id: ReplicaId, visible: bool, disconnected: bool,} #[derive(Clone)]pub struct Snapshot { id: WorktreeId, /// The absolute path of the worktree root. abs_path: Arc<SanitizedPath>, path_style: PathStyle, root_name: Arc<RelPath>, root_char_bag: CharBag, entries_by_path: SumTree<Entry>, entries_by_id: SumTree<PathEntry>, always_included_entries: Vec<Arc<RelPath>>, /// A number that increases every time the worktree begins scanning /// a set of paths from the filesystem. This scanning could be caused /// by some operation performed on the worktree, such as reading or /// writing a file, or by an event reported by the filesystem. scan_id: usize, /// The latest scan id that has completed, and whose preceding scans /// have all completed. The current `scan_id` could be more than one /// greater than the `completed_scan_id` if operations are performed /// on the worktree while it is processing a file-system event. completed_scan_id: usize,} /// This path corresponds to the 'content path' of a repository in relation/// to Zed's project root./// In the majority of the cases, this is the folder that contains the .git folder./// But if a sub-folder of a git repository is opened, this corresponds to the/// project root and the .git folder is located in a parent directory.#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]pub enum WorkDirectory { InProject { relative_path: Arc<RelPath>, }, AboveProject { absolute_path: Arc<Path>, location_in_repo: Arc<Path>, },} impl WorkDirectory { fn path_key(&self) -> PathKey { match self { WorkDirectory::InProject { relative_path } => PathKey(relative_path.clone()), WorkDirectory::AboveProject { .. } => PathKey(RelPath::empty().into()), } } /// Returns true if the given path is a child of the work directory. /// /// Note that the path may not be a member of this repository, if there /// is a repository in a directory between these two paths /// external .git folder in a parent folder of the project root. #[track_caller] pub fn directory_contains(&self, path: &RelPath) -> bool { match self { WorkDirectory::InProject { relative_path } => path.starts_with(relative_path), WorkDirectory::AboveProject { .. } => true, } }} impl Default for WorkDirectory { fn default() -> Self { Self::InProject { relative_path: Arc::from(RelPath::empty()), } }} #[derive(Clone)]pub struct LocalSnapshot { snapshot: Snapshot, global_gitignore: Option<Arc<Gitignore>>, /// All of the gitignore files in the worktree, indexed by their absolute path. /// The boolean indicates whether the gitignore needs to be updated. ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, bool)>, /// All of the git repositories in the worktree, indexed by the project entry /// id of their parent directory. git_repositories: TreeMap<ProjectEntryId, LocalRepositoryEntry>, /// The file handle of the worktree root /// (so we can find it after it's been moved) root_file_handle: Option<Arc<dyn fs::FileHandle>>, executor: BackgroundExecutor,} struct BackgroundScannerState { snapshot: LocalSnapshot, scanned_dirs: HashSet<ProjectEntryId>, path_prefixes_to_scan: HashSet<Arc<RelPath>>, paths_to_scan: HashSet<Arc<RelPath>>, /// The ids of all of the entries that were removed from the snapshot /// as part of the current update. These entry ids may be re-used /// if the same inode is discovered at a new path, or if the given /// path is re-created after being deleted. removed_entries: HashMap<u64, Entry>, changed_paths: Vec<Arc<RelPath>>, prev_snapshot: Snapshot,} #[derive(Debug, Clone)]struct LocalRepositoryEntry { work_directory_id: ProjectEntryId, work_directory: WorkDirectory, work_directory_abs_path: Arc<Path>, git_dir_scan_id: usize, /// Absolute path to the original .git entry that caused us to create this repository. /// /// This is normally a directory, but may be a "gitfile" that points to a directory elsewhere /// (whose path we then store in `repository_dir_abs_path`). dot_git_abs_path: Arc<Path>, /// Absolute path to the "commondir" for this repository. /// /// This is always a directory. For a normal repository, this is the same as dot_git_abs_path, /// but in the case of a submodule or a worktree it is the path to the "parent" .git directory /// from which the submodule/worktree was derived. common_dir_abs_path: Arc<Path>, /// Absolute path to the directory holding the repository's state. /// /// For a normal repository, this is a directory and coincides with `dot_git_abs_path` and /// `common_dir_abs_path`. For a submodule or worktree, this is some subdirectory of the /// commondir like `/project/.git/modules/foo`. repository_dir_abs_path: Arc<Path>,} impl sum_tree::Item for LocalRepositoryEntry { type Summary = PathSummary<sum_tree::NoSummary>; fn summary(&self, _: <Self::Summary as Summary>::Context<'_>) -> Self::Summary { PathSummary { max_path: self.work_directory.path_key().0, item_summary: sum_tree::NoSummary, } }} impl KeyedItem for LocalRepositoryEntry { type Key = PathKey; fn key(&self) -> Self::Key { self.work_directory.path_key() }} impl Deref for LocalRepositoryEntry { type Target = WorkDirectory; fn deref(&self) -> &Self::Target { &self.work_directory }} impl Deref for LocalSnapshot { type Target = Snapshot; fn deref(&self) -> &Self::Target { &self.snapshot }} impl DerefMut for LocalSnapshot { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.snapshot }} enum ScanState { Started, Updated { snapshot: LocalSnapshot, changes: UpdatedEntriesSet, barrier: SmallVec<[barrier::Sender; 1]>, scanning: bool, }, RootUpdated { new_path: Arc<SanitizedPath>, },} struct UpdateObservationState { snapshots_tx: mpsc::UnboundedSender<(LocalSnapshot, UpdatedEntriesSet)>, resume_updates: watch::Sender<()>, _maintain_remote_snapshot: Task<Option<()>>,} #[derive(Clone)]pub enum Event { UpdatedEntries(UpdatedEntriesSet), UpdatedGitRepositories(UpdatedGitRepositoriesSet), DeletedEntry(ProjectEntryId),} impl EventEmitter<Event> for Worktree {} impl Worktree { pub async fn local( path: impl Into<Arc<Path>>, visible: bool, fs: Arc<dyn Fs>, next_entry_id: Arc<AtomicUsize>, scanning_enabled: bool, cx: &mut AsyncApp, ) -> Result<Entity<Self>> { let abs_path = path.into(); let metadata = fs .metadata(&abs_path) .await .context("failed to stat worktree path")?; let fs_case_sensitive = fs.is_case_sensitive().await.unwrap_or_else(|e| { log::error!( "Failed to determine whether filesystem is case sensitive (falling back to true) due to error: {e:#}" ); true }); let root_file_handle = if metadata.as_ref().is_some() { fs.open_handle(&abs_path) .await .with_context(|| { format!( "failed to open local worktree root at {}", abs_path.display() ) }) .log_err() } else { None }; cx.new(move |cx: &mut Context<Worktree>| { let mut snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), global_gitignore: Default::default(), git_repositories: Default::default(), snapshot: Snapshot::new( cx.entity_id().as_u64(), abs_path .file_name() .and_then(|f| f.to_str()) .map_or(RelPath::empty().into(), |f| { RelPath::unix(f).unwrap().into() }), abs_path.clone(), PathStyle::local(), ), root_file_handle, executor: cx.background_executor().clone(), }; let worktree_id = snapshot.id(); let settings_location = Some(SettingsLocation { worktree_id, path: RelPath::empty(), }); let settings = WorktreeSettings::get(settings_location, cx).clone(); cx.observe_global::<SettingsStore>(move |this, cx| { if let Self::Local(this) = this { let settings = WorktreeSettings::get(settings_location, cx).clone(); if this.settings != settings { this.settings = settings; this.restart_background_scanners(cx); } } }) .detach(); let share_private_files = false; if let Some(metadata) = metadata { let mut entry = Entry::new( RelPath::empty().into(), &metadata, ProjectEntryId::new(&next_entry_id), snapshot.root_char_bag, None, ); if !metadata.is_dir { if let Some(file_name) = abs_path.file_name() && let Some(file_name) = file_name.to_str() && let Ok(path) = RelPath::unix(file_name) { entry.is_private = !share_private_files && settings.is_path_private(path); entry.is_hidden = settings.is_path_hidden(path); } } snapshot.insert_entry(entry, fs.as_ref()); } let (scan_requests_tx, scan_requests_rx) = channel::unbounded(); let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded(); let mut worktree = LocalWorktree { share_private_files, next_entry_id, snapshot, is_scanning: watch::channel_with(true), update_observer: None, scan_requests_tx, path_prefixes_to_scan_tx, _background_scanner_tasks: Vec::new(), fs, fs_case_sensitive, visible, settings, scanning_enabled, }; worktree.start_background_scanner(scan_requests_rx, path_prefixes_to_scan_rx, cx); Worktree::Local(worktree) }) } pub fn remote( project_id: u64, replica_id: ReplicaId, worktree: proto::WorktreeMetadata, client: AnyProtoClient, path_style: PathStyle, cx: &mut App, ) -> Entity<Self> { cx.new(|cx: &mut Context<Self>| { let snapshot = Snapshot::new( worktree.id, RelPath::from_proto(&worktree.root_name) .unwrap_or_else(|_| RelPath::empty().into()), Path::new(&worktree.abs_path).into(), path_style, ); let background_snapshot = Arc::new(Mutex::new(( snapshot.clone(), Vec::<proto::UpdateWorktree>::new(), ))); let (background_updates_tx, mut background_updates_rx) = mpsc::unbounded::<proto::UpdateWorktree>(); let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel(); let worktree_id = snapshot.id(); let settings_location = Some(SettingsLocation { worktree_id,Lines 1–500 of 5677. Load more
mod ignore; mod worktree_settings; #[cfg(test)] mod worktree_tests; use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{Context as _, Result, anyhow}; use clock::ReplicaId; use collections::{HashMap, HashSet, VecDeque}; use fs::{Fs, MTime, PathEvent, RemoveOptions, Watcher, copy_recursive, read_dir_items}; use futures::{ FutureExt as _, Stream, StreamExt, channel::{ mpsc::{self, UnboundedSender}, oneshot, }, select_biased, stream, task::Poll, }; use fuzzy::CharBag; use git::{ COMMIT_MESSAGE, DOT_GIT, FSMONITOR_DAEMON, GITIGNORE, INDEX_LOCK, LFS_DIR, status::GitSummary, }; use gpui::{ App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Priority, Task, }; use ignore::IgnoreStack; use language::DiskState; use parking_lot::Mutex; use paths::{local_settings_folder_name, local_vscode_folder_name}; use postage::{ barrier, prelude::{Sink as _, Stream as _}, watch, }; use rpc::{ AnyProtoClient, proto::{self, split_worktree_update}, }; pub use settings::WorktreeId; use settings::{Settings, SettingsLocation, SettingsStore}; use smallvec::{SmallVec, smallvec}; use smol::channel::{self, Sender}; use std::{ any::Any, borrow::Borrow as _, cmp::Ordering, collections::hash_map, convert::TryFrom, ffi::OsStr, fmt, future::Future, mem::{self}, ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, pin::Pin, sync::{ Arc, atomic::{AtomicUsize, Ordering::SeqCst}, }, time::{Duration, Instant}, }; use sum_tree::{Bias, Dimensions, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet}; use text::{LineEnding, Rope}; use util::{ ResultExt, debug_panic, maybe, paths::{PathMatcher, PathStyle, SanitizedPath, home_dir}, rel_path::RelPath, }; pub use worktree_settings::WorktreeSettings; pub const FS_WATCH_LATENCY: Duration = Duration::from_millis(100); /// A set of local or remote files that are being opened as part of a project. /// Responsible for tracking related FS (for local)/collab (for remote) events and corresponding updates. /// Stores git repositories data and the diagnostics for the file(s). /// /// Has an absolute path, and may be set to be visible in Zed UI or not. /// May correspond to a directory or a single file. /// Possible examples: /// * a drag and dropped file — may be added as an invisible, "ephemeral" entry to the current worktree /// * a directory opened in Zed — may be added as a visible entry to the current worktree /// /// Uses [`Entry`] to track the state of each file/directory, can look up absolute paths for entries. pub enum Worktree { Local(LocalWorktree), Remote(RemoteWorktree), } /// An entry, created in the worktree. #[derive(Debug)] pub enum CreatedEntry { /// Got created and indexed by the worktree, receiving a corresponding entry. Included(Entry), /// Got created, but not indexed due to falling under exclusion filters. Excluded { abs_path: PathBuf }, } #[derive(Debug)] pub struct LoadedFile { pub file: Arc<File>, pub text: String, } pub struct LoadedBinaryFile { pub file: Arc<File>, pub content: Vec<u8>, } impl fmt::Debug for LoadedBinaryFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("LoadedBinaryFile") .field("file", &self.file) .field("content_bytes", &self.content.len()) .finish() } } pub struct LocalWorktree { snapshot: LocalSnapshot, scan_requests_tx: channel::Sender<ScanRequest>, path_prefixes_to_scan_tx: channel::Sender<PathPrefixScanRequest>, is_scanning: (watch::Sender<bool>, watch::Receiver<bool>), _background_scanner_tasks: Vec<Task<()>>, update_observer: Option<UpdateObservationState>, fs: Arc<dyn Fs>, fs_case_sensitive: bool, visible: bool, next_entry_id: Arc<AtomicUsize>, settings: WorktreeSettings, share_private_files: bool, scanning_enabled: bool, } pub struct PathPrefixScanRequest { path: Arc<RelPath>, done: SmallVec<[barrier::Sender; 1]>, } struct ScanRequest { relative_paths: Vec<Arc<RelPath>>, done: SmallVec<[barrier::Sender; 1]>, } pub struct RemoteWorktree { snapshot: Snapshot, background_snapshot: Arc<Mutex<(Snapshot, Vec<proto::UpdateWorktree>)>>, project_id: u64, client: AnyProtoClient, file_scan_inclusions: PathMatcher, updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>, update_observer: Option<mpsc::UnboundedSender<proto::UpdateWorktree>>, snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>, replica_id: ReplicaId, visible: bool, disconnected: bool, } #[derive(Clone)] pub struct Snapshot { id: WorktreeId, /// The absolute path of the worktree root. abs_path: Arc<SanitizedPath>, path_style: PathStyle, root_name: Arc<RelPath>, root_char_bag: CharBag, entries_by_path: SumTree<Entry>, entries_by_id: SumTree<PathEntry>, always_included_entries: Vec<Arc<RelPath>>, /// A number that increases every time the worktree begins scanning /// a set of paths from the filesystem. This scanning could be caused /// by some operation performed on the worktree, such as reading or /// writing a file, or by an event reported by the filesystem. scan_id: usize, /// The latest scan id that has completed, and whose preceding scans /// have all completed. The current `scan_id` could be more than one /// greater than the `completed_scan_id` if operations are performed /// on the worktree while it is processing a file-system event. completed_scan_id: usize, } /// This path corresponds to the 'content path' of a repository in relation /// to Zed's project root. /// In the majority of the cases, this is the folder that contains the .git folder. /// But if a sub-folder of a git repository is opened, this corresponds to the /// project root and the .git folder is located in a parent directory. #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum WorkDirectory { InProject { relative_path: Arc<RelPath>, }, AboveProject { absolute_path: Arc<Path>, location_in_repo: Arc<Path>, }, } impl WorkDirectory { fn path_key(&self) -> PathKey { match self { WorkDirectory::InProject { relative_path } => PathKey(relative_path.clone()), WorkDirectory::AboveProject { .. } => PathKey(RelPath::empty().into()), } } /// Returns true if the given path is a child of the work directory. /// /// Note that the path may not be a member of this repository, if there /// is a repository in a directory between these two paths /// external .git folder in a parent folder of the project root. #[track_caller] pub fn directory_contains(&self, path: &RelPath) -> bool { match self { WorkDirectory::InProject { relative_path } => path.starts_with(relative_path), WorkDirectory::AboveProject { .. } => true, } } } impl Default for WorkDirectory { fn default() -> Self { Self::InProject { relative_path: Arc::from(RelPath::empty()), } } } #[derive(Clone)] pub struct LocalSnapshot { snapshot: Snapshot, global_gitignore: Option<Arc<Gitignore>>, /// All of the gitignore files in the worktree, indexed by their absolute path. /// The boolean indicates whether the gitignore needs to be updated. ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, bool)>, /// All of the git repositories in the worktree, indexed by the project entry /// id of their parent directory. git_repositories: TreeMap<ProjectEntryId, LocalRepositoryEntry>, /// The file handle of the worktree root /// (so we can find it after it's been moved) root_file_handle: Option<Arc<dyn fs::FileHandle>>, executor: BackgroundExecutor, } struct BackgroundScannerState { snapshot: LocalSnapshot, scanned_dirs: HashSet<ProjectEntryId>, path_prefixes_to_scan: HashSet<Arc<RelPath>>, paths_to_scan: HashSet<Arc<RelPath>>, /// The ids of all of the entries that were removed from the snapshot /// as part of the current update. These entry ids may be re-used /// if the same inode is discovered at a new path, or if the given /// path is re-created after being deleted. removed_entries: HashMap<u64, Entry>, changed_paths: Vec<Arc<RelPath>>, prev_snapshot: Snapshot, } #[derive(Debug, Clone)] struct LocalRepositoryEntry { work_directory_id: ProjectEntryId, work_directory: WorkDirectory, work_directory_abs_path: Arc<Path>, git_dir_scan_id: usize, /// Absolute path to the original .git entry that caused us to create this repository. /// /// This is normally a directory, but may be a "gitfile" that points to a directory elsewhere /// (whose path we then store in `repository_dir_abs_path`). dot_git_abs_path: Arc<Path>, /// Absolute path to the "commondir" for this repository. /// /// This is always a directory. For a normal repository, this is the same as dot_git_abs_path, /// but in the case of a submodule or a worktree it is the path to the "parent" .git directory /// from which the submodule/worktree was derived. common_dir_abs_path: Arc<Path>, /// Absolute path to the directory holding the repository's state. /// /// For a normal repository, this is a directory and coincides with `dot_git_abs_path` and /// `common_dir_abs_path`. For a submodule or worktree, this is some subdirectory of the /// commondir like `/project/.git/modules/foo`. repository_dir_abs_path: Arc<Path>, } impl sum_tree::Item for LocalRepositoryEntry { type Summary = PathSummary<sum_tree::NoSummary>; fn summary(&self, _: <Self::Summary as Summary>::Context<'_>) -> Self::Summary { PathSummary { max_path: self.work_directory.path_key().0, item_summary: sum_tree::NoSummary, } } } impl KeyedItem for LocalRepositoryEntry { type Key = PathKey; fn key(&self) -> Self::Key { self.work_directory.path_key() } } impl Deref for LocalRepositoryEntry { type Target = WorkDirectory; fn deref(&self) -> &Self::Target { &self.work_directory } } impl Deref for LocalSnapshot { type Target = Snapshot; fn deref(&self) -> &Self::Target { &self.snapshot } } impl DerefMut for LocalSnapshot { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.snapshot } } enum ScanState { Started, Updated { snapshot: LocalSnapshot, changes: UpdatedEntriesSet, barrier: SmallVec<[barrier::Sender; 1]>, scanning: bool, }, RootUpdated { new_path: Arc<SanitizedPath>, }, } struct UpdateObservationState { snapshots_tx: mpsc::UnboundedSender<(LocalSnapshot, UpdatedEntriesSet)>, resume_updates: watch::Sender<()>, _maintain_remote_snapshot: Task<Option<()>>, } #[derive(Clone)] pub enum Event { UpdatedEntries(UpdatedEntriesSet), UpdatedGitRepositories(UpdatedGitRepositoriesSet), DeletedEntry(ProjectEntryId), } impl EventEmitter<Event> for Worktree {} impl Worktree { pub async fn local( path: impl Into<Arc<Path>>, visible: bool, fs: Arc<dyn Fs>, next_entry_id: Arc<AtomicUsize>, scanning_enabled: bool, cx: &mut AsyncApp, ) -> Result<Entity<Self>> { let abs_path = path.into(); let metadata = fs .metadata(&abs_path) .await .context("failed to stat worktree path")?; let fs_case_sensitive = fs.is_case_sensitive().await.unwrap_or_else(|e| { log::error!( "Failed to determine whether filesystem is case sensitive (falling back to true) due to error: {e:#}" ); true }); let root_file_handle = if metadata.as_ref().is_some() { fs.open_handle(&abs_path) .await .with_context(|| { format!( "failed to open local worktree root at {}", abs_path.display() ) }) .log_err() } else { None }; cx.new(move |cx: &mut Context<Worktree>| { let mut snapshot = LocalSnapshot { ignores_by_parent_abs_path: Default::default(), global_gitignore: Default::default(), git_repositories: Default::default(), snapshot: Snapshot::new( cx.entity_id().as_u64(), abs_path .file_name() .and_then(|f| f.to_str()) .map_or(RelPath::empty().into(), |f| { RelPath::unix(f).unwrap().into() }), abs_path.clone(), PathStyle::local(), ), root_file_handle, executor: cx.background_executor().clone(), }; let worktree_id = snapshot.id(); let settings_location = Some(SettingsLocation { worktree_id, path: RelPath::empty(), }); let settings = WorktreeSettings::get(settings_location, cx).clone(); cx.observe_global::<SettingsStore>(move |this, cx| { if let Self::Local(this) = this { let settings = WorktreeSettings::get(settings_location, cx).clone(); if this.settings != settings { this.settings = settings; this.restart_background_scanners(cx); } } }) .detach(); let share_private_files = false; if let Some(metadata) = metadata { let mut entry = Entry::new( RelPath::empty().into(), &metadata, ProjectEntryId::new(&next_entry_id), snapshot.root_char_bag, None, ); if !metadata.is_dir { if let Some(file_name) = abs_path.file_name() && let Some(file_name) = file_name.to_str() && let Ok(path) = RelPath::unix(file_name) { entry.is_private = !share_private_files && settings.is_path_private(path); entry.is_hidden = settings.is_path_hidden(path); } } snapshot.insert_entry(entry, fs.as_ref()); } let (scan_requests_tx, scan_requests_rx) = channel::unbounded(); let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded(); let mut worktree = LocalWorktree { share_private_files, next_entry_id, snapshot, is_scanning: watch::channel_with(true), update_observer: None, scan_requests_tx, path_prefixes_to_scan_tx, _background_scanner_tasks: Vec::new(), fs, fs_case_sensitive, visible, settings, scanning_enabled, }; worktree.start_background_scanner(scan_requests_rx, path_prefixes_to_scan_rx, cx); Worktree::Local(worktree) }) } pub fn remote( project_id: u64, replica_id: ReplicaId, worktree: proto::WorktreeMetadata, client: AnyProtoClient, path_style: PathStyle, cx: &mut App, ) -> Entity<Self> { cx.new(|cx: &mut Context<Self>| { let snapshot = Snapshot::new( worktree.id, RelPath::from_proto(&worktree.root_name) .unwrap_or_else(|_| RelPath::empty().into()), Path::new(&worktree.abs_path).into(), path_style, ); let background_snapshot = Arc::new(Mutex::new(( snapshot.clone(), Vec::<proto::UpdateWorktree>::new(), ))); let (background_updates_tx, mut background_updates_rx) = mpsc::unbounded::<proto::UpdateWorktree>(); let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel(); let worktree_id = snapshot.id(); let settings_location = Some(SettingsLocation { worktree_id,