worktree.rs

   1mod ignore;
   2mod worktree_settings;
   3#[cfg(test)]
   4mod worktree_tests;
   5
   6use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
   7use anyhow::{Context as _, Result, anyhow};
   8use clock::ReplicaId;
   9use collections::{HashMap, HashSet, VecDeque};
  10use fs::{Fs, MTime, PathEvent, RemoveOptions, Watcher, copy_recursive};
  11use futures::{
  12    FutureExt as _, Stream, StreamExt,
  13    channel::{
  14        mpsc::{self, UnboundedSender},
  15        oneshot,
  16    },
  17    select_biased,
  18    task::Poll,
  19};
  20use fuzzy::CharBag;
  21use git::{
  22    COMMIT_MESSAGE, DOT_GIT, FSMONITOR_DAEMON, GITIGNORE, INDEX_LOCK, LFS_DIR,
  23    repository::RepoPath, status::GitSummary,
  24};
  25use gpui::{
  26    App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task,
  27};
  28use ignore::IgnoreStack;
  29use language::DiskState;
  30
  31use parking_lot::Mutex;
  32use paths::{local_settings_folder_relative_path, local_vscode_folder_relative_path};
  33use postage::{
  34    barrier,
  35    prelude::{Sink as _, Stream as _},
  36    watch,
  37};
  38use rpc::{
  39    AnyProtoClient,
  40    proto::{self, FromProto, ToProto, split_worktree_update},
  41};
  42pub use settings::WorktreeId;
  43use settings::{Settings, SettingsLocation, SettingsStore};
  44use smallvec::{SmallVec, smallvec};
  45use smol::channel::{self, Sender};
  46use std::{
  47    any::Any,
  48    borrow::Borrow as _,
  49    cmp::Ordering,
  50    collections::hash_map,
  51    convert::TryFrom,
  52    ffi::OsStr,
  53    fmt,
  54    future::Future,
  55    mem::{self},
  56    ops::{Deref, DerefMut},
  57    path::{Component, Path, PathBuf},
  58    pin::Pin,
  59    sync::{
  60        Arc,
  61        atomic::{AtomicUsize, Ordering::SeqCst},
  62    },
  63    time::{Duration, Instant},
  64};
  65use sum_tree::{Bias, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet, Unit};
  66use text::{LineEnding, Rope};
  67use util::{
  68    ResultExt,
  69    paths::{PathMatcher, SanitizedPath, home_dir},
  70};
  71pub use worktree_settings::WorktreeSettings;
  72
  73pub const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
  74
  75/// A set of local or remote files that are being opened as part of a project.
  76/// Responsible for tracking related FS (for local)/collab (for remote) events and corresponding updates.
  77/// Stores git repositories data and the diagnostics for the file(s).
  78///
  79/// Has an absolute path, and may be set to be visible in Zed UI or not.
  80/// May correspond to a directory or a single file.
  81/// Possible examples:
  82/// * a drag and dropped file — may be added as an invisible, "ephemeral" entry to the current worktree
  83/// * a directory opened in Zed — may be added as a visible entry to the current worktree
  84///
  85/// Uses [`Entry`] to track the state of each file/directory, can look up absolute paths for entries.
  86pub enum Worktree {
  87    Local(LocalWorktree),
  88    Remote(RemoteWorktree),
  89}
  90
  91/// An entry, created in the worktree.
  92#[derive(Debug)]
  93pub enum CreatedEntry {
  94    /// Got created and indexed by the worktree, receiving a corresponding entry.
  95    Included(Entry),
  96    /// Got created, but not indexed due to falling under exclusion filters.
  97    Excluded { abs_path: PathBuf },
  98}
  99
 100pub struct LoadedFile {
 101    pub file: Arc<File>,
 102    pub text: String,
 103}
 104
 105pub struct LoadedBinaryFile {
 106    pub file: Arc<File>,
 107    pub content: Vec<u8>,
 108}
 109
 110pub struct LocalWorktree {
 111    snapshot: LocalSnapshot,
 112    scan_requests_tx: channel::Sender<ScanRequest>,
 113    path_prefixes_to_scan_tx: channel::Sender<PathPrefixScanRequest>,
 114    is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
 115    _background_scanner_tasks: Vec<Task<()>>,
 116    update_observer: Option<UpdateObservationState>,
 117    fs: Arc<dyn Fs>,
 118    fs_case_sensitive: bool,
 119    visible: bool,
 120    next_entry_id: Arc<AtomicUsize>,
 121    settings: WorktreeSettings,
 122    share_private_files: bool,
 123}
 124
 125pub struct PathPrefixScanRequest {
 126    path: Arc<Path>,
 127    done: SmallVec<[barrier::Sender; 1]>,
 128}
 129
 130struct ScanRequest {
 131    relative_paths: Vec<Arc<Path>>,
 132    done: SmallVec<[barrier::Sender; 1]>,
 133}
 134
 135pub struct RemoteWorktree {
 136    snapshot: Snapshot,
 137    background_snapshot: Arc<Mutex<(Snapshot, Vec<proto::UpdateWorktree>)>>,
 138    project_id: u64,
 139    client: AnyProtoClient,
 140    file_scan_inclusions: PathMatcher,
 141    updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
 142    update_observer: Option<mpsc::UnboundedSender<proto::UpdateWorktree>>,
 143    snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
 144    replica_id: ReplicaId,
 145    visible: bool,
 146    disconnected: bool,
 147}
 148
 149#[derive(Clone)]
 150pub struct Snapshot {
 151    id: WorktreeId,
 152    abs_path: SanitizedPath,
 153    root_name: String,
 154    root_char_bag: CharBag,
 155    entries_by_path: SumTree<Entry>,
 156    entries_by_id: SumTree<PathEntry>,
 157    always_included_entries: Vec<Arc<Path>>,
 158
 159    /// A number that increases every time the worktree begins scanning
 160    /// a set of paths from the filesystem. This scanning could be caused
 161    /// by some operation performed on the worktree, such as reading or
 162    /// writing a file, or by an event reported by the filesystem.
 163    scan_id: usize,
 164
 165    /// The latest scan id that has completed, and whose preceding scans
 166    /// have all completed. The current `scan_id` could be more than one
 167    /// greater than the `completed_scan_id` if operations are performed
 168    /// on the worktree while it is processing a file-system event.
 169    completed_scan_id: usize,
 170}
 171
 172/// This path corresponds to the 'content path' of a repository in relation
 173/// to Zed's project root.
 174/// In the majority of the cases, this is the folder that contains the .git folder.
 175/// But if a sub-folder of a git repository is opened, this corresponds to the
 176/// project root and the .git folder is located in a parent directory.
 177#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
 178pub enum WorkDirectory {
 179    InProject {
 180        relative_path: Arc<Path>,
 181    },
 182    AboveProject {
 183        absolute_path: Arc<Path>,
 184        location_in_repo: Arc<Path>,
 185    },
 186}
 187
 188impl WorkDirectory {
 189    #[cfg(test)]
 190    fn in_project(path: &str) -> Self {
 191        let path = Path::new(path);
 192        Self::InProject {
 193            relative_path: path.into(),
 194        }
 195    }
 196
 197    //#[cfg(test)]
 198    //fn canonicalize(&self) -> Self {
 199    //    match self {
 200    //        WorkDirectory::InProject { relative_path } => WorkDirectory::InProject {
 201    //            relative_path: relative_path.clone(),
 202    //        },
 203    //        WorkDirectory::AboveProject {
 204    //            absolute_path,
 205    //            location_in_repo,
 206    //        } => WorkDirectory::AboveProject {
 207    //            absolute_path: absolute_path.canonicalize().unwrap().into(),
 208    //            location_in_repo: location_in_repo.clone(),
 209    //        },
 210    //    }
 211    //}
 212
 213    fn path_key(&self) -> PathKey {
 214        match self {
 215            WorkDirectory::InProject { relative_path } => PathKey(relative_path.clone()),
 216            WorkDirectory::AboveProject { .. } => PathKey(Path::new("").into()),
 217        }
 218    }
 219
 220    /// Returns true if the given path is a child of the work directory.
 221    ///
 222    /// Note that the path may not be a member of this repository, if there
 223    /// is a repository in a directory between these two paths
 224    /// external .git folder in a parent folder of the project root.
 225    #[track_caller]
 226    pub fn directory_contains(&self, path: impl AsRef<Path>) -> bool {
 227        let path = path.as_ref();
 228        debug_assert!(path.is_relative());
 229        match self {
 230            WorkDirectory::InProject { relative_path } => path.starts_with(relative_path),
 231            WorkDirectory::AboveProject { .. } => true,
 232        }
 233    }
 234
 235    /// relativize returns the given project path relative to the root folder of the
 236    /// repository.
 237    /// If the root of the repository (and its .git folder) are located in a parent folder
 238    /// of the project root folder, then the returned RepoPath is relative to the root
 239    /// of the repository and not a valid path inside the project.
 240    pub fn relativize(&self, path: &Path) -> Result<RepoPath> {
 241        // path is assumed to be relative to worktree root.
 242        debug_assert!(path.is_relative());
 243        match self {
 244            WorkDirectory::InProject { relative_path } => Ok(path
 245                .strip_prefix(relative_path)
 246                .map_err(|_| {
 247                    anyhow!(
 248                        "could not relativize {:?} against {:?}",
 249                        path,
 250                        relative_path
 251                    )
 252                })?
 253                .into()),
 254            WorkDirectory::AboveProject {
 255                location_in_repo, ..
 256            } => {
 257                // Avoid joining a `/` to location_in_repo in the case of a single-file worktree.
 258                if path == Path::new("") {
 259                    Ok(RepoPath(location_in_repo.clone()))
 260                } else {
 261                    Ok(location_in_repo.join(path).into())
 262                }
 263            }
 264        }
 265    }
 266
 267    /// This is the opposite operation to `relativize` above
 268    pub fn try_unrelativize(&self, path: &RepoPath) -> Option<Arc<Path>> {
 269        match self {
 270            WorkDirectory::InProject { relative_path } => Some(relative_path.join(path).into()),
 271            WorkDirectory::AboveProject {
 272                location_in_repo, ..
 273            } => {
 274                // If we fail to strip the prefix, that means this status entry is
 275                // external to this worktree, and we definitely won't have an entry_id
 276                path.strip_prefix(location_in_repo).ok().map(Into::into)
 277            }
 278        }
 279    }
 280
 281    pub fn unrelativize(&self, path: &RepoPath) -> Arc<Path> {
 282        match self {
 283            WorkDirectory::InProject { relative_path } => relative_path.join(path).into(),
 284            WorkDirectory::AboveProject {
 285                location_in_repo, ..
 286            } => {
 287                if &path.0 == location_in_repo {
 288                    // Single-file worktree
 289                    return location_in_repo
 290                        .file_name()
 291                        .map(Path::new)
 292                        .unwrap_or(Path::new(""))
 293                        .into();
 294                }
 295                let mut location_in_repo = &**location_in_repo;
 296                let mut parents = PathBuf::new();
 297                loop {
 298                    if let Ok(segment) = path.strip_prefix(location_in_repo) {
 299                        return parents.join(segment).into();
 300                    }
 301                    location_in_repo = location_in_repo.parent().unwrap_or(Path::new(""));
 302                    parents.push(Component::ParentDir);
 303                }
 304            }
 305        }
 306    }
 307
 308    pub fn display_name(&self) -> String {
 309        match self {
 310            WorkDirectory::InProject { relative_path } => relative_path.display().to_string(),
 311            WorkDirectory::AboveProject {
 312                absolute_path,
 313                location_in_repo,
 314            } => {
 315                let num_of_dots = location_in_repo.components().count();
 316
 317                "../".repeat(num_of_dots)
 318                    + &absolute_path
 319                        .file_name()
 320                        .map(|s| s.to_string_lossy())
 321                        .unwrap_or_default()
 322                    + "/"
 323            }
 324        }
 325    }
 326}
 327
 328impl Default for WorkDirectory {
 329    fn default() -> Self {
 330        Self::InProject {
 331            relative_path: Arc::from(Path::new("")),
 332        }
 333    }
 334}
 335
 336#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
 337pub struct WorkDirectoryEntry(ProjectEntryId);
 338
 339impl Deref for WorkDirectoryEntry {
 340    type Target = ProjectEntryId;
 341
 342    fn deref(&self) -> &Self::Target {
 343        &self.0
 344    }
 345}
 346
 347impl From<ProjectEntryId> for WorkDirectoryEntry {
 348    fn from(value: ProjectEntryId) -> Self {
 349        WorkDirectoryEntry(value)
 350    }
 351}
 352
 353#[derive(Debug, Clone)]
 354pub struct LocalSnapshot {
 355    snapshot: Snapshot,
 356    /// All of the gitignore files in the worktree, indexed by their relative path.
 357    /// The boolean indicates whether the gitignore needs to be updated.
 358    ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, bool)>,
 359    /// All of the git repositories in the worktree, indexed by the project entry
 360    /// id of their parent directory.
 361    git_repositories: TreeMap<ProjectEntryId, LocalRepositoryEntry>,
 362    /// The file handle of the root dir
 363    /// (so we can find it after it's been moved)
 364    root_file_handle: Option<Arc<dyn fs::FileHandle>>,
 365}
 366
 367struct BackgroundScannerState {
 368    snapshot: LocalSnapshot,
 369    scanned_dirs: HashSet<ProjectEntryId>,
 370    path_prefixes_to_scan: HashSet<Arc<Path>>,
 371    paths_to_scan: HashSet<Arc<Path>>,
 372    /// The ids of all of the entries that were removed from the snapshot
 373    /// as part of the current update. These entry ids may be re-used
 374    /// if the same inode is discovered at a new path, or if the given
 375    /// path is re-created after being deleted.
 376    removed_entries: HashMap<u64, Entry>,
 377    changed_paths: Vec<Arc<Path>>,
 378    prev_snapshot: Snapshot,
 379}
 380
 381#[derive(Debug, Clone)]
 382struct LocalRepositoryEntry {
 383    work_directory_id: ProjectEntryId,
 384    work_directory: WorkDirectory,
 385    work_directory_abs_path: Arc<Path>,
 386    git_dir_scan_id: usize,
 387    /// Absolute path to the original .git entry that caused us to create this repository.
 388    ///
 389    /// This is normally a directory, but may be a "gitfile" that points to a directory elsewhere
 390    /// (whose path we then store in `repository_dir_abs_path`).
 391    dot_git_abs_path: Arc<Path>,
 392    /// Absolute path to the "commondir" for this repository.
 393    ///
 394    /// This is always a directory. For a normal repository, this is the same as dot_git_abs_path,
 395    /// but in the case of a submodule or a worktree it is the path to the "parent" .git directory
 396    /// from which the submodule/worktree was derived.
 397    common_dir_abs_path: Arc<Path>,
 398    /// Absolute path to the directory holding the repository's state.
 399    ///
 400    /// For a normal repository, this is a directory and coincides with `dot_git_abs_path` and
 401    /// `common_dir_abs_path`. For a submodule or worktree, this is some subdirectory of the
 402    /// commondir like `/project/.git/modules/foo`.
 403    repository_dir_abs_path: Arc<Path>,
 404}
 405
 406impl sum_tree::Item for LocalRepositoryEntry {
 407    type Summary = PathSummary<Unit>;
 408
 409    fn summary(&self, _: &<Self::Summary as Summary>::Context) -> Self::Summary {
 410        PathSummary {
 411            max_path: self.work_directory.path_key().0,
 412            item_summary: Unit,
 413        }
 414    }
 415}
 416
 417impl KeyedItem for LocalRepositoryEntry {
 418    type Key = PathKey;
 419
 420    fn key(&self) -> Self::Key {
 421        self.work_directory.path_key()
 422    }
 423}
 424
 425//impl LocalRepositoryEntry {
 426//    pub fn repo(&self) -> &Arc<dyn GitRepository> {
 427//        &self.repo_ptr
 428//    }
 429//}
 430
 431impl Deref for LocalRepositoryEntry {
 432    type Target = WorkDirectory;
 433
 434    fn deref(&self) -> &Self::Target {
 435        &self.work_directory
 436    }
 437}
 438
 439impl Deref for LocalSnapshot {
 440    type Target = Snapshot;
 441
 442    fn deref(&self) -> &Self::Target {
 443        &self.snapshot
 444    }
 445}
 446
 447impl DerefMut for LocalSnapshot {
 448    fn deref_mut(&mut self) -> &mut Self::Target {
 449        &mut self.snapshot
 450    }
 451}
 452
 453#[derive(Debug)]
 454enum ScanState {
 455    Started,
 456    Updated {
 457        snapshot: LocalSnapshot,
 458        changes: UpdatedEntriesSet,
 459        barrier: SmallVec<[barrier::Sender; 1]>,
 460        scanning: bool,
 461    },
 462    RootUpdated {
 463        new_path: Option<SanitizedPath>,
 464    },
 465}
 466
 467struct UpdateObservationState {
 468    snapshots_tx: mpsc::UnboundedSender<(LocalSnapshot, UpdatedEntriesSet)>,
 469    resume_updates: watch::Sender<()>,
 470    _maintain_remote_snapshot: Task<Option<()>>,
 471}
 472
 473#[derive(Clone)]
 474pub enum Event {
 475    UpdatedEntries(UpdatedEntriesSet),
 476    UpdatedGitRepositories(UpdatedGitRepositoriesSet),
 477    DeletedEntry(ProjectEntryId),
 478}
 479
 480const EMPTY_PATH: &str = "";
 481
 482impl EventEmitter<Event> for Worktree {}
 483
 484impl Worktree {
 485    pub async fn local(
 486        path: impl Into<Arc<Path>>,
 487        visible: bool,
 488        fs: Arc<dyn Fs>,
 489        next_entry_id: Arc<AtomicUsize>,
 490        cx: &mut AsyncApp,
 491    ) -> Result<Entity<Self>> {
 492        let abs_path = path.into();
 493        let metadata = fs
 494            .metadata(&abs_path)
 495            .await
 496            .context("failed to stat worktree path")?;
 497
 498        let fs_case_sensitive = fs.is_case_sensitive().await.unwrap_or_else(|e| {
 499            log::error!(
 500                "Failed to determine whether filesystem is case sensitive (falling back to true) due to error: {e:#}"
 501            );
 502            true
 503        });
 504
 505        let root_file_handle = fs.open_handle(&abs_path).await.log_err();
 506
 507        cx.new(move |cx: &mut Context<Worktree>| {
 508            let mut snapshot = LocalSnapshot {
 509                ignores_by_parent_abs_path: Default::default(),
 510                git_repositories: Default::default(),
 511                snapshot: Snapshot::new(
 512                    cx.entity_id().as_u64(),
 513                    abs_path
 514                        .file_name()
 515                        .map_or(String::new(), |f| f.to_string_lossy().to_string()),
 516                    abs_path.clone(),
 517                ),
 518                root_file_handle,
 519            };
 520
 521            let worktree_id = snapshot.id();
 522            let settings_location = Some(SettingsLocation {
 523                worktree_id,
 524                path: Path::new(EMPTY_PATH),
 525            });
 526
 527            let settings = WorktreeSettings::get(settings_location, cx).clone();
 528            cx.observe_global::<SettingsStore>(move |this, cx| {
 529                if let Self::Local(this) = this {
 530                    let settings = WorktreeSettings::get(settings_location, cx).clone();
 531                    if this.settings != settings {
 532                        this.settings = settings;
 533                        this.restart_background_scanners(cx);
 534                    }
 535                }
 536            })
 537            .detach();
 538
 539            let share_private_files = false;
 540            if let Some(metadata) = metadata {
 541                let mut entry = Entry::new(
 542                    Arc::from(Path::new("")),
 543                    &metadata,
 544                    &next_entry_id,
 545                    snapshot.root_char_bag,
 546                    None,
 547                );
 548                if !metadata.is_dir {
 549                    entry.is_private = !share_private_files
 550                        && settings.is_path_private(abs_path.file_name().unwrap().as_ref());
 551                }
 552                snapshot.insert_entry(entry, fs.as_ref());
 553            }
 554
 555            let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
 556            let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded();
 557            let mut worktree = LocalWorktree {
 558                share_private_files,
 559                next_entry_id,
 560                snapshot,
 561                is_scanning: watch::channel_with(true),
 562                update_observer: None,
 563                scan_requests_tx,
 564                path_prefixes_to_scan_tx,
 565                _background_scanner_tasks: Vec::new(),
 566                fs,
 567                fs_case_sensitive,
 568                visible,
 569                settings,
 570            };
 571            worktree.start_background_scanner(scan_requests_rx, path_prefixes_to_scan_rx, cx);
 572            Worktree::Local(worktree)
 573        })
 574    }
 575
 576    pub fn remote(
 577        project_id: u64,
 578        replica_id: ReplicaId,
 579        worktree: proto::WorktreeMetadata,
 580        client: AnyProtoClient,
 581        cx: &mut App,
 582    ) -> Entity<Self> {
 583        cx.new(|cx: &mut Context<Self>| {
 584            let snapshot = Snapshot::new(
 585                worktree.id,
 586                worktree.root_name,
 587                Arc::<Path>::from_proto(worktree.abs_path),
 588            );
 589
 590            let background_snapshot = Arc::new(Mutex::new((
 591                snapshot.clone(),
 592                Vec::<proto::UpdateWorktree>::new(),
 593            )));
 594            let (background_updates_tx, mut background_updates_rx) =
 595                mpsc::unbounded::<proto::UpdateWorktree>();
 596            let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
 597
 598            let worktree_id = snapshot.id();
 599            let settings_location = Some(SettingsLocation {
 600                worktree_id,
 601                path: Path::new(EMPTY_PATH),
 602            });
 603
 604            let settings = WorktreeSettings::get(settings_location, cx).clone();
 605            let worktree = RemoteWorktree {
 606                client,
 607                project_id,
 608                replica_id,
 609                snapshot,
 610                file_scan_inclusions: settings.file_scan_inclusions.clone(),
 611                background_snapshot: background_snapshot.clone(),
 612                updates_tx: Some(background_updates_tx),
 613                update_observer: None,
 614                snapshot_subscriptions: Default::default(),
 615                visible: worktree.visible,
 616                disconnected: false,
 617            };
 618
 619            // Apply updates to a separate snapshot in a background task, then
 620            // send them to a foreground task which updates the model.
 621            cx.background_spawn(async move {
 622                while let Some(update) = background_updates_rx.next().await {
 623                    {
 624                        let mut lock = background_snapshot.lock();
 625                        lock.0
 626                            .apply_remote_update(update.clone(), &settings.file_scan_inclusions)
 627                            .log_err();
 628                        lock.1.push(update);
 629                    }
 630                    snapshot_updated_tx.send(()).await.ok();
 631                }
 632            })
 633            .detach();
 634
 635            // On the foreground task, update to the latest snapshot and notify
 636            // any update observer of all updates that led to that snapshot.
 637            cx.spawn(async move |this, cx| {
 638                while (snapshot_updated_rx.recv().await).is_some() {
 639                    this.update(cx, |this, cx| {
 640                        let mut entries_changed = false;
 641                        let this = this.as_remote_mut().unwrap();
 642                        {
 643                            let mut lock = this.background_snapshot.lock();
 644                            this.snapshot = lock.0.clone();
 645                            for update in lock.1.drain(..) {
 646                                entries_changed |= !update.updated_entries.is_empty()
 647                                    || !update.removed_entries.is_empty();
 648                                if let Some(tx) = &this.update_observer {
 649                                    tx.unbounded_send(update).ok();
 650                                }
 651                            }
 652                        };
 653
 654                        if entries_changed {
 655                            cx.emit(Event::UpdatedEntries(Arc::default()));
 656                        }
 657                        cx.notify();
 658                        while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
 659                            if this.observed_snapshot(*scan_id) {
 660                                let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
 661                                let _ = tx.send(());
 662                            } else {
 663                                break;
 664                            }
 665                        }
 666                    })?;
 667                }
 668                anyhow::Ok(())
 669            })
 670            .detach();
 671
 672            Worktree::Remote(worktree)
 673        })
 674    }
 675
 676    pub fn as_local(&self) -> Option<&LocalWorktree> {
 677        if let Worktree::Local(worktree) = self {
 678            Some(worktree)
 679        } else {
 680            None
 681        }
 682    }
 683
 684    pub fn as_remote(&self) -> Option<&RemoteWorktree> {
 685        if let Worktree::Remote(worktree) = self {
 686            Some(worktree)
 687        } else {
 688            None
 689        }
 690    }
 691
 692    pub fn as_local_mut(&mut self) -> Option<&mut LocalWorktree> {
 693        if let Worktree::Local(worktree) = self {
 694            Some(worktree)
 695        } else {
 696            None
 697        }
 698    }
 699
 700    pub fn as_remote_mut(&mut self) -> Option<&mut RemoteWorktree> {
 701        if let Worktree::Remote(worktree) = self {
 702            Some(worktree)
 703        } else {
 704            None
 705        }
 706    }
 707
 708    pub fn is_local(&self) -> bool {
 709        matches!(self, Worktree::Local(_))
 710    }
 711
 712    pub fn is_remote(&self) -> bool {
 713        !self.is_local()
 714    }
 715
 716    pub fn settings_location(&self, _: &Context<Self>) -> SettingsLocation<'static> {
 717        SettingsLocation {
 718            worktree_id: self.id(),
 719            path: Path::new(EMPTY_PATH),
 720        }
 721    }
 722
 723    pub fn snapshot(&self) -> Snapshot {
 724        match self {
 725            Worktree::Local(worktree) => worktree.snapshot.snapshot.clone(),
 726            Worktree::Remote(worktree) => worktree.snapshot.clone(),
 727        }
 728    }
 729
 730    pub fn scan_id(&self) -> usize {
 731        match self {
 732            Worktree::Local(worktree) => worktree.snapshot.scan_id,
 733            Worktree::Remote(worktree) => worktree.snapshot.scan_id,
 734        }
 735    }
 736
 737    pub fn metadata_proto(&self) -> proto::WorktreeMetadata {
 738        proto::WorktreeMetadata {
 739            id: self.id().to_proto(),
 740            root_name: self.root_name().to_string(),
 741            visible: self.is_visible(),
 742            abs_path: self.abs_path().to_proto(),
 743        }
 744    }
 745
 746    pub fn completed_scan_id(&self) -> usize {
 747        match self {
 748            Worktree::Local(worktree) => worktree.snapshot.completed_scan_id,
 749            Worktree::Remote(worktree) => worktree.snapshot.completed_scan_id,
 750        }
 751    }
 752
 753    pub fn is_visible(&self) -> bool {
 754        match self {
 755            Worktree::Local(worktree) => worktree.visible,
 756            Worktree::Remote(worktree) => worktree.visible,
 757        }
 758    }
 759
 760    pub fn replica_id(&self) -> ReplicaId {
 761        match self {
 762            Worktree::Local(_) => 0,
 763            Worktree::Remote(worktree) => worktree.replica_id,
 764        }
 765    }
 766
 767    pub fn abs_path(&self) -> Arc<Path> {
 768        match self {
 769            Worktree::Local(worktree) => worktree.abs_path.clone().into(),
 770            Worktree::Remote(worktree) => worktree.abs_path.clone().into(),
 771        }
 772    }
 773
 774    pub fn root_file(&self, cx: &Context<Self>) -> Option<Arc<File>> {
 775        let entry = self.root_entry()?;
 776        Some(File::for_entry(entry.clone(), cx.entity()))
 777    }
 778
 779    pub fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &Context<Worktree>, callback: F)
 780    where
 781        F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
 782        Fut: 'static + Send + Future<Output = bool>,
 783    {
 784        match self {
 785            Worktree::Local(this) => this.observe_updates(project_id, cx, callback),
 786            Worktree::Remote(this) => this.observe_updates(project_id, cx, callback),
 787        }
 788    }
 789
 790    pub fn stop_observing_updates(&mut self) {
 791        match self {
 792            Worktree::Local(this) => {
 793                this.update_observer.take();
 794            }
 795            Worktree::Remote(this) => {
 796                this.update_observer.take();
 797            }
 798        }
 799    }
 800
 801    #[cfg(any(test, feature = "test-support"))]
 802    pub fn has_update_observer(&self) -> bool {
 803        match self {
 804            Worktree::Local(this) => this.update_observer.is_some(),
 805            Worktree::Remote(this) => this.update_observer.is_some(),
 806        }
 807    }
 808
 809    pub fn file_exists(&self, path: &Path, cx: &Context<Worktree>) -> Task<Result<bool>> {
 810        match self {
 811            Worktree::Local(this) => {
 812                let fs = this.fs.clone();
 813                let path = this.absolutize(path);
 814                cx.background_spawn(async move {
 815                    let path = path?;
 816                    let metadata = fs.metadata(&path).await?;
 817                    Ok(metadata.map_or(false, |metadata| !metadata.is_dir))
 818                })
 819            }
 820            Worktree::Remote(_) => Task::ready(Err(anyhow!(
 821                "remote worktrees can't yet check file existence"
 822            ))),
 823        }
 824    }
 825
 826    pub fn load_file(&self, path: &Path, cx: &Context<Worktree>) -> Task<Result<LoadedFile>> {
 827        match self {
 828            Worktree::Local(this) => this.load_file(path, cx),
 829            Worktree::Remote(_) => {
 830                Task::ready(Err(anyhow!("remote worktrees can't yet load files")))
 831            }
 832        }
 833    }
 834
 835    pub fn load_binary_file(
 836        &self,
 837        path: &Path,
 838        cx: &Context<Worktree>,
 839    ) -> Task<Result<LoadedBinaryFile>> {
 840        match self {
 841            Worktree::Local(this) => this.load_binary_file(path, cx),
 842            Worktree::Remote(_) => {
 843                Task::ready(Err(anyhow!("remote worktrees can't yet load binary files")))
 844            }
 845        }
 846    }
 847
 848    pub fn write_file(
 849        &self,
 850        path: &Path,
 851        text: Rope,
 852        line_ending: LineEnding,
 853        cx: &Context<Worktree>,
 854    ) -> Task<Result<Arc<File>>> {
 855        match self {
 856            Worktree::Local(this) => this.write_file(path, text, line_ending, cx),
 857            Worktree::Remote(_) => {
 858                Task::ready(Err(anyhow!("remote worktree can't yet write files")))
 859            }
 860        }
 861    }
 862
 863    pub fn create_entry(
 864        &mut self,
 865        path: impl Into<Arc<Path>>,
 866        is_directory: bool,
 867        cx: &Context<Worktree>,
 868    ) -> Task<Result<CreatedEntry>> {
 869        let path: Arc<Path> = path.into();
 870        let worktree_id = self.id();
 871        match self {
 872            Worktree::Local(this) => this.create_entry(path, is_directory, cx),
 873            Worktree::Remote(this) => {
 874                let project_id = this.project_id;
 875                let request = this.client.request(proto::CreateProjectEntry {
 876                    worktree_id: worktree_id.to_proto(),
 877                    project_id,
 878                    path: path.as_ref().to_proto(),
 879                    is_directory,
 880                });
 881                cx.spawn(async move |this, cx| {
 882                    let response = request.await?;
 883                    match response.entry {
 884                        Some(entry) => this
 885                            .update(cx, |worktree, cx| {
 886                                worktree.as_remote_mut().unwrap().insert_entry(
 887                                    entry,
 888                                    response.worktree_scan_id as usize,
 889                                    cx,
 890                                )
 891                            })?
 892                            .await
 893                            .map(CreatedEntry::Included),
 894                        None => {
 895                            let abs_path = this.update(cx, |worktree, _| {
 896                                worktree
 897                                    .absolutize(&path)
 898                                    .with_context(|| format!("absolutizing {path:?}"))
 899                            })??;
 900                            Ok(CreatedEntry::Excluded { abs_path })
 901                        }
 902                    }
 903                })
 904            }
 905        }
 906    }
 907
 908    pub fn delete_entry(
 909        &mut self,
 910        entry_id: ProjectEntryId,
 911        trash: bool,
 912        cx: &mut Context<Worktree>,
 913    ) -> Option<Task<Result<()>>> {
 914        let task = match self {
 915            Worktree::Local(this) => this.delete_entry(entry_id, trash, cx),
 916            Worktree::Remote(this) => this.delete_entry(entry_id, trash, cx),
 917        }?;
 918
 919        let entry = match &*self {
 920            Worktree::Local(this) => this.entry_for_id(entry_id),
 921            Worktree::Remote(this) => this.entry_for_id(entry_id),
 922        }?;
 923
 924        let mut ids = vec![entry_id];
 925        let path = &*entry.path;
 926
 927        self.get_children_ids_recursive(path, &mut ids);
 928
 929        for id in ids {
 930            cx.emit(Event::DeletedEntry(id));
 931        }
 932        Some(task)
 933    }
 934
 935    fn get_children_ids_recursive(&self, path: &Path, ids: &mut Vec<ProjectEntryId>) {
 936        let children_iter = self.child_entries(path);
 937        for child in children_iter {
 938            ids.push(child.id);
 939            self.get_children_ids_recursive(&child.path, ids);
 940        }
 941    }
 942
 943    pub fn rename_entry(
 944        &mut self,
 945        entry_id: ProjectEntryId,
 946        new_path: impl Into<Arc<Path>>,
 947        cx: &Context<Self>,
 948    ) -> Task<Result<CreatedEntry>> {
 949        let new_path = new_path.into();
 950        match self {
 951            Worktree::Local(this) => this.rename_entry(entry_id, new_path, cx),
 952            Worktree::Remote(this) => this.rename_entry(entry_id, new_path, cx),
 953        }
 954    }
 955
 956    pub fn copy_entry(
 957        &mut self,
 958        entry_id: ProjectEntryId,
 959        relative_worktree_source_path: Option<PathBuf>,
 960        new_path: impl Into<Arc<Path>>,
 961        cx: &Context<Self>,
 962    ) -> Task<Result<Option<Entry>>> {
 963        let new_path: Arc<Path> = new_path.into();
 964        match self {
 965            Worktree::Local(this) => {
 966                this.copy_entry(entry_id, relative_worktree_source_path, new_path, cx)
 967            }
 968            Worktree::Remote(this) => {
 969                let relative_worktree_source_path = relative_worktree_source_path
 970                    .map(|relative_worktree_source_path| relative_worktree_source_path.to_proto());
 971                let response = this.client.request(proto::CopyProjectEntry {
 972                    project_id: this.project_id,
 973                    entry_id: entry_id.to_proto(),
 974                    relative_worktree_source_path,
 975                    new_path: new_path.to_proto(),
 976                });
 977                cx.spawn(async move |this, cx| {
 978                    let response = response.await?;
 979                    match response.entry {
 980                        Some(entry) => this
 981                            .update(cx, |worktree, cx| {
 982                                worktree.as_remote_mut().unwrap().insert_entry(
 983                                    entry,
 984                                    response.worktree_scan_id as usize,
 985                                    cx,
 986                                )
 987                            })?
 988                            .await
 989                            .map(Some),
 990                        None => Ok(None),
 991                    }
 992                })
 993            }
 994        }
 995    }
 996
 997    pub fn copy_external_entries(
 998        &mut self,
 999        target_directory: PathBuf,
1000        paths: Vec<Arc<Path>>,
1001        overwrite_existing_files: bool,
1002        cx: &Context<Worktree>,
1003    ) -> Task<Result<Vec<ProjectEntryId>>> {
1004        match self {
1005            Worktree::Local(this) => {
1006                this.copy_external_entries(target_directory, paths, overwrite_existing_files, cx)
1007            }
1008            _ => Task::ready(Err(anyhow!(
1009                "Copying external entries is not supported for remote worktrees"
1010            ))),
1011        }
1012    }
1013
1014    pub fn expand_entry(
1015        &mut self,
1016        entry_id: ProjectEntryId,
1017        cx: &Context<Worktree>,
1018    ) -> Option<Task<Result<()>>> {
1019        match self {
1020            Worktree::Local(this) => this.expand_entry(entry_id, cx),
1021            Worktree::Remote(this) => {
1022                let response = this.client.request(proto::ExpandProjectEntry {
1023                    project_id: this.project_id,
1024                    entry_id: entry_id.to_proto(),
1025                });
1026                Some(cx.spawn(async move |this, cx| {
1027                    let response = response.await?;
1028                    this.update(cx, |this, _| {
1029                        this.as_remote_mut()
1030                            .unwrap()
1031                            .wait_for_snapshot(response.worktree_scan_id as usize)
1032                    })?
1033                    .await?;
1034                    Ok(())
1035                }))
1036            }
1037        }
1038    }
1039
1040    pub fn expand_all_for_entry(
1041        &mut self,
1042        entry_id: ProjectEntryId,
1043        cx: &Context<Worktree>,
1044    ) -> Option<Task<Result<()>>> {
1045        match self {
1046            Worktree::Local(this) => this.expand_all_for_entry(entry_id, cx),
1047            Worktree::Remote(this) => {
1048                let response = this.client.request(proto::ExpandAllForProjectEntry {
1049                    project_id: this.project_id,
1050                    entry_id: entry_id.to_proto(),
1051                });
1052                Some(cx.spawn(async move |this, cx| {
1053                    let response = response.await?;
1054                    this.update(cx, |this, _| {
1055                        this.as_remote_mut()
1056                            .unwrap()
1057                            .wait_for_snapshot(response.worktree_scan_id as usize)
1058                    })?
1059                    .await?;
1060                    Ok(())
1061                }))
1062            }
1063        }
1064    }
1065
1066    pub async fn handle_create_entry(
1067        this: Entity<Self>,
1068        request: proto::CreateProjectEntry,
1069        mut cx: AsyncApp,
1070    ) -> Result<proto::ProjectEntryResponse> {
1071        let (scan_id, entry) = this.update(&mut cx, |this, cx| {
1072            (
1073                this.scan_id(),
1074                this.create_entry(
1075                    Arc::<Path>::from_proto(request.path),
1076                    request.is_directory,
1077                    cx,
1078                ),
1079            )
1080        })?;
1081        Ok(proto::ProjectEntryResponse {
1082            entry: match &entry.await? {
1083                CreatedEntry::Included(entry) => Some(entry.into()),
1084                CreatedEntry::Excluded { .. } => None,
1085            },
1086            worktree_scan_id: scan_id as u64,
1087        })
1088    }
1089
1090    pub async fn handle_delete_entry(
1091        this: Entity<Self>,
1092        request: proto::DeleteProjectEntry,
1093        mut cx: AsyncApp,
1094    ) -> Result<proto::ProjectEntryResponse> {
1095        let (scan_id, task) = this.update(&mut cx, |this, cx| {
1096            (
1097                this.scan_id(),
1098                this.delete_entry(
1099                    ProjectEntryId::from_proto(request.entry_id),
1100                    request.use_trash,
1101                    cx,
1102                ),
1103            )
1104        })?;
1105        task.ok_or_else(|| anyhow!("invalid entry"))?.await?;
1106        Ok(proto::ProjectEntryResponse {
1107            entry: None,
1108            worktree_scan_id: scan_id as u64,
1109        })
1110    }
1111
1112    pub async fn handle_expand_entry(
1113        this: Entity<Self>,
1114        request: proto::ExpandProjectEntry,
1115        mut cx: AsyncApp,
1116    ) -> Result<proto::ExpandProjectEntryResponse> {
1117        let task = this.update(&mut cx, |this, cx| {
1118            this.expand_entry(ProjectEntryId::from_proto(request.entry_id), cx)
1119        })?;
1120        task.ok_or_else(|| anyhow!("no such entry"))?.await?;
1121        let scan_id = this.read_with(&cx, |this, _| this.scan_id())?;
1122        Ok(proto::ExpandProjectEntryResponse {
1123            worktree_scan_id: scan_id as u64,
1124        })
1125    }
1126
1127    pub async fn handle_expand_all_for_entry(
1128        this: Entity<Self>,
1129        request: proto::ExpandAllForProjectEntry,
1130        mut cx: AsyncApp,
1131    ) -> Result<proto::ExpandAllForProjectEntryResponse> {
1132        let task = this.update(&mut cx, |this, cx| {
1133            this.expand_all_for_entry(ProjectEntryId::from_proto(request.entry_id), cx)
1134        })?;
1135        task.ok_or_else(|| anyhow!("no such entry"))?.await?;
1136        let scan_id = this.read_with(&cx, |this, _| this.scan_id())?;
1137        Ok(proto::ExpandAllForProjectEntryResponse {
1138            worktree_scan_id: scan_id as u64,
1139        })
1140    }
1141
1142    pub async fn handle_rename_entry(
1143        this: Entity<Self>,
1144        request: proto::RenameProjectEntry,
1145        mut cx: AsyncApp,
1146    ) -> Result<proto::ProjectEntryResponse> {
1147        let (scan_id, task) = this.update(&mut cx, |this, cx| {
1148            (
1149                this.scan_id(),
1150                this.rename_entry(
1151                    ProjectEntryId::from_proto(request.entry_id),
1152                    Arc::<Path>::from_proto(request.new_path),
1153                    cx,
1154                ),
1155            )
1156        })?;
1157        Ok(proto::ProjectEntryResponse {
1158            entry: match &task.await? {
1159                CreatedEntry::Included(entry) => Some(entry.into()),
1160                CreatedEntry::Excluded { .. } => None,
1161            },
1162            worktree_scan_id: scan_id as u64,
1163        })
1164    }
1165
1166    pub async fn handle_copy_entry(
1167        this: Entity<Self>,
1168        request: proto::CopyProjectEntry,
1169        mut cx: AsyncApp,
1170    ) -> Result<proto::ProjectEntryResponse> {
1171        let (scan_id, task) = this.update(&mut cx, |this, cx| {
1172            let relative_worktree_source_path = request
1173                .relative_worktree_source_path
1174                .map(PathBuf::from_proto);
1175            (
1176                this.scan_id(),
1177                this.copy_entry(
1178                    ProjectEntryId::from_proto(request.entry_id),
1179                    relative_worktree_source_path,
1180                    PathBuf::from_proto(request.new_path),
1181                    cx,
1182                ),
1183            )
1184        })?;
1185        Ok(proto::ProjectEntryResponse {
1186            entry: task.await?.as_ref().map(|e| e.into()),
1187            worktree_scan_id: scan_id as u64,
1188        })
1189    }
1190
1191    pub fn dot_git_abs_path(&self, work_directory: &WorkDirectory) -> PathBuf {
1192        let mut path = match work_directory {
1193            WorkDirectory::InProject { relative_path } => self.abs_path().join(relative_path),
1194            WorkDirectory::AboveProject { absolute_path, .. } => absolute_path.as_ref().to_owned(),
1195        };
1196        path.push(".git");
1197        path
1198    }
1199
1200    pub fn is_single_file(&self) -> bool {
1201        self.root_dir().is_none()
1202    }
1203
1204    /// For visible worktrees, returns the path with the worktree name as the first component.
1205    /// Otherwise, returns an absolute path.
1206    pub fn full_path(&self, worktree_relative_path: &Path) -> PathBuf {
1207        let mut full_path = PathBuf::new();
1208
1209        if self.is_visible() {
1210            full_path.push(self.root_name());
1211        } else {
1212            let path = self.abs_path();
1213
1214            if self.is_local() && path.starts_with(home_dir().as_path()) {
1215                full_path.push("~");
1216                full_path.push(path.strip_prefix(home_dir().as_path()).unwrap());
1217            } else {
1218                full_path.push(path)
1219            }
1220        }
1221
1222        if worktree_relative_path.components().next().is_some() {
1223            full_path.push(&worktree_relative_path);
1224        }
1225
1226        full_path
1227    }
1228}
1229
1230impl LocalWorktree {
1231    pub fn fs(&self) -> &Arc<dyn Fs> {
1232        &self.fs
1233    }
1234
1235    pub fn is_path_private(&self, path: &Path) -> bool {
1236        !self.share_private_files && self.settings.is_path_private(path)
1237    }
1238
1239    fn restart_background_scanners(&mut self, cx: &Context<Worktree>) {
1240        let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
1241        let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded();
1242        self.scan_requests_tx = scan_requests_tx;
1243        self.path_prefixes_to_scan_tx = path_prefixes_to_scan_tx;
1244
1245        self.start_background_scanner(scan_requests_rx, path_prefixes_to_scan_rx, cx);
1246        let always_included_entries = mem::take(&mut self.snapshot.always_included_entries);
1247        log::debug!(
1248            "refreshing entries for the following always included paths: {:?}",
1249            always_included_entries
1250        );
1251
1252        // Cleans up old always included entries to ensure they get updated properly. Otherwise,
1253        // nested always included entries may not get updated and will result in out-of-date info.
1254        self.refresh_entries_for_paths(always_included_entries);
1255    }
1256
1257    fn start_background_scanner(
1258        &mut self,
1259        scan_requests_rx: channel::Receiver<ScanRequest>,
1260        path_prefixes_to_scan_rx: channel::Receiver<PathPrefixScanRequest>,
1261        cx: &Context<Worktree>,
1262    ) {
1263        let snapshot = self.snapshot();
1264        let share_private_files = self.share_private_files;
1265        let next_entry_id = self.next_entry_id.clone();
1266        let fs = self.fs.clone();
1267        let settings = self.settings.clone();
1268        let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
1269        let background_scanner = cx.background_spawn({
1270            let abs_path = snapshot.abs_path.as_path().to_path_buf();
1271            let background = cx.background_executor().clone();
1272            async move {
1273                let (events, watcher) = fs.watch(&abs_path, FS_WATCH_LATENCY).await;
1274                let fs_case_sensitive = fs.is_case_sensitive().await.unwrap_or_else(|e| {
1275                    log::error!("Failed to determine whether filesystem is case sensitive: {e:#}");
1276                    true
1277                });
1278
1279                let mut scanner = BackgroundScanner {
1280                    fs,
1281                    fs_case_sensitive,
1282                    status_updates_tx: scan_states_tx,
1283                    executor: background,
1284                    scan_requests_rx,
1285                    path_prefixes_to_scan_rx,
1286                    next_entry_id,
1287                    state: Mutex::new(BackgroundScannerState {
1288                        prev_snapshot: snapshot.snapshot.clone(),
1289                        snapshot,
1290                        scanned_dirs: Default::default(),
1291                        path_prefixes_to_scan: Default::default(),
1292                        paths_to_scan: Default::default(),
1293                        removed_entries: Default::default(),
1294                        changed_paths: Default::default(),
1295                    }),
1296                    phase: BackgroundScannerPhase::InitialScan,
1297                    share_private_files,
1298                    settings,
1299                    watcher,
1300                };
1301
1302                scanner
1303                    .run(Box::pin(events.map(|events| events.into_iter().collect())))
1304                    .await;
1305            }
1306        });
1307        let scan_state_updater = cx.spawn(async move |this, cx| {
1308            while let Some((state, this)) = scan_states_rx.next().await.zip(this.upgrade()) {
1309                this.update(cx, |this, cx| {
1310                    let this = this.as_local_mut().unwrap();
1311                    match state {
1312                        ScanState::Started => {
1313                            *this.is_scanning.0.borrow_mut() = true;
1314                        }
1315                        ScanState::Updated {
1316                            snapshot,
1317                            changes,
1318                            barrier,
1319                            scanning,
1320                        } => {
1321                            *this.is_scanning.0.borrow_mut() = scanning;
1322                            this.set_snapshot(snapshot, changes, cx);
1323                            drop(barrier);
1324                        }
1325                        ScanState::RootUpdated { new_path } => {
1326                            this.update_abs_path_and_refresh(new_path, cx);
1327                        }
1328                    }
1329                })
1330                .ok();
1331            }
1332        });
1333        self._background_scanner_tasks = vec![background_scanner, scan_state_updater];
1334        self.is_scanning = watch::channel_with(true);
1335    }
1336
1337    fn set_snapshot(
1338        &mut self,
1339        mut new_snapshot: LocalSnapshot,
1340        entry_changes: UpdatedEntriesSet,
1341        cx: &mut Context<Worktree>,
1342    ) {
1343        let repo_changes = self.changed_repos(&self.snapshot, &mut new_snapshot);
1344        self.snapshot = new_snapshot;
1345
1346        if let Some(share) = self.update_observer.as_mut() {
1347            share
1348                .snapshots_tx
1349                .unbounded_send((self.snapshot.clone(), entry_changes.clone()))
1350                .ok();
1351        }
1352
1353        if !entry_changes.is_empty() {
1354            cx.emit(Event::UpdatedEntries(entry_changes));
1355        }
1356        if !repo_changes.is_empty() {
1357            cx.emit(Event::UpdatedGitRepositories(repo_changes));
1358        }
1359    }
1360
1361    fn changed_repos(
1362        &self,
1363        old_snapshot: &LocalSnapshot,
1364        new_snapshot: &mut LocalSnapshot,
1365    ) -> UpdatedGitRepositoriesSet {
1366        let mut changes = Vec::new();
1367        let mut old_repos = old_snapshot.git_repositories.iter().peekable();
1368        let new_repos = new_snapshot.git_repositories.clone();
1369        let mut new_repos = new_repos.iter().peekable();
1370
1371        loop {
1372            match (new_repos.peek().map(clone), old_repos.peek().map(clone)) {
1373                (Some((new_entry_id, new_repo)), Some((old_entry_id, old_repo))) => {
1374                    match Ord::cmp(&new_entry_id, &old_entry_id) {
1375                        Ordering::Less => {
1376                            changes.push(UpdatedGitRepository {
1377                                work_directory_id: new_entry_id,
1378                                old_work_directory_abs_path: None,
1379                                new_work_directory_abs_path: Some(
1380                                    new_repo.work_directory_abs_path.clone(),
1381                                ),
1382                                dot_git_abs_path: Some(new_repo.dot_git_abs_path.clone()),
1383                                repository_dir_abs_path: Some(
1384                                    new_repo.repository_dir_abs_path.clone(),
1385                                ),
1386                                common_dir_abs_path: Some(new_repo.common_dir_abs_path.clone()),
1387                            });
1388                            new_repos.next();
1389                        }
1390                        Ordering::Equal => {
1391                            if new_repo.git_dir_scan_id != old_repo.git_dir_scan_id
1392                                || new_repo.work_directory_abs_path
1393                                    != old_repo.work_directory_abs_path
1394                            {
1395                                changes.push(UpdatedGitRepository {
1396                                    work_directory_id: new_entry_id,
1397                                    old_work_directory_abs_path: Some(
1398                                        old_repo.work_directory_abs_path.clone(),
1399                                    ),
1400                                    new_work_directory_abs_path: Some(
1401                                        new_repo.work_directory_abs_path.clone(),
1402                                    ),
1403                                    dot_git_abs_path: Some(new_repo.dot_git_abs_path.clone()),
1404                                    repository_dir_abs_path: Some(
1405                                        new_repo.repository_dir_abs_path.clone(),
1406                                    ),
1407                                    common_dir_abs_path: Some(new_repo.common_dir_abs_path.clone()),
1408                                });
1409                            }
1410                            new_repos.next();
1411                            old_repos.next();
1412                        }
1413                        Ordering::Greater => {
1414                            changes.push(UpdatedGitRepository {
1415                                work_directory_id: old_entry_id,
1416                                old_work_directory_abs_path: Some(
1417                                    old_repo.work_directory_abs_path.clone(),
1418                                ),
1419                                new_work_directory_abs_path: None,
1420                                dot_git_abs_path: None,
1421                                repository_dir_abs_path: None,
1422                                common_dir_abs_path: None,
1423                            });
1424                            old_repos.next();
1425                        }
1426                    }
1427                }
1428                (Some((entry_id, repo)), None) => {
1429                    changes.push(UpdatedGitRepository {
1430                        work_directory_id: entry_id,
1431                        old_work_directory_abs_path: None,
1432                        new_work_directory_abs_path: Some(repo.work_directory_abs_path.clone()),
1433                        dot_git_abs_path: Some(repo.dot_git_abs_path.clone()),
1434                        repository_dir_abs_path: Some(repo.repository_dir_abs_path.clone()),
1435                        common_dir_abs_path: Some(repo.common_dir_abs_path.clone()),
1436                    });
1437                    new_repos.next();
1438                }
1439                (None, Some((entry_id, repo))) => {
1440                    changes.push(UpdatedGitRepository {
1441                        work_directory_id: entry_id,
1442                        old_work_directory_abs_path: Some(repo.work_directory_abs_path.clone()),
1443                        new_work_directory_abs_path: None,
1444                        dot_git_abs_path: Some(repo.dot_git_abs_path.clone()),
1445                        repository_dir_abs_path: Some(repo.repository_dir_abs_path.clone()),
1446                        common_dir_abs_path: Some(repo.common_dir_abs_path.clone()),
1447                    });
1448                    old_repos.next();
1449                }
1450                (None, None) => break,
1451            }
1452        }
1453
1454        fn clone<T: Clone, U: Clone>(value: &(&T, &U)) -> (T, U) {
1455            (value.0.clone(), value.1.clone())
1456        }
1457
1458        changes.into()
1459    }
1460
1461    pub fn scan_complete(&self) -> impl Future<Output = ()> + use<> {
1462        let mut is_scanning_rx = self.is_scanning.1.clone();
1463        async move {
1464            let mut is_scanning = *is_scanning_rx.borrow();
1465            while is_scanning {
1466                if let Some(value) = is_scanning_rx.recv().await {
1467                    is_scanning = value;
1468                } else {
1469                    break;
1470                }
1471            }
1472        }
1473    }
1474
1475    pub fn snapshot(&self) -> LocalSnapshot {
1476        self.snapshot.clone()
1477    }
1478
1479    pub fn settings(&self) -> WorktreeSettings {
1480        self.settings.clone()
1481    }
1482
1483    fn load_binary_file(
1484        &self,
1485        path: &Path,
1486        cx: &Context<Worktree>,
1487    ) -> Task<Result<LoadedBinaryFile>> {
1488        let path = Arc::from(path);
1489        let abs_path = self.absolutize(&path);
1490        let fs = self.fs.clone();
1491        let entry = self.refresh_entry(path.clone(), None, cx);
1492        let is_private = self.is_path_private(path.as_ref());
1493
1494        let worktree = cx.weak_entity();
1495        cx.background_spawn(async move {
1496            let abs_path = abs_path?;
1497            let content = fs.load_bytes(&abs_path).await?;
1498
1499            let worktree = worktree
1500                .upgrade()
1501                .ok_or_else(|| anyhow!("worktree was dropped"))?;
1502            let file = match entry.await? {
1503                Some(entry) => File::for_entry(entry, worktree),
1504                None => {
1505                    let metadata = fs
1506                        .metadata(&abs_path)
1507                        .await
1508                        .with_context(|| {
1509                            format!("Loading metadata for excluded file {abs_path:?}")
1510                        })?
1511                        .with_context(|| {
1512                            format!("Excluded file {abs_path:?} got removed during loading")
1513                        })?;
1514                    Arc::new(File {
1515                        entry_id: None,
1516                        worktree,
1517                        path,
1518                        disk_state: DiskState::Present {
1519                            mtime: metadata.mtime,
1520                        },
1521                        is_local: true,
1522                        is_private,
1523                    })
1524                }
1525            };
1526
1527            Ok(LoadedBinaryFile { file, content })
1528        })
1529    }
1530
1531    fn load_file(&self, path: &Path, cx: &Context<Worktree>) -> Task<Result<LoadedFile>> {
1532        let path = Arc::from(path);
1533        let abs_path = self.absolutize(&path);
1534        let fs = self.fs.clone();
1535        let entry = self.refresh_entry(path.clone(), None, cx);
1536        let is_private = self.is_path_private(path.as_ref());
1537
1538        cx.spawn(async move |this, _cx| {
1539            let abs_path = abs_path?;
1540            // WARN: Temporary workaround for #27283.
1541            //       We are not efficient with our memory usage per file, and use in excess of 64GB for a 10GB file
1542            //       Therefore, as a temporary workaround to prevent system freezes, we just bail before opening a file
1543            //       if it is too large
1544            //       5GB seems to be more reasonable, peaking at ~16GB, while 6GB jumps up to >24GB which seems like a
1545            //       reasonable limit
1546            {
1547                const FILE_SIZE_MAX: u64 = 6 * 1024 * 1024 * 1024; // 6GB
1548                if let Ok(Some(metadata)) = fs.metadata(&abs_path).await {
1549                    if metadata.len >= FILE_SIZE_MAX {
1550                        anyhow::bail!("File is too large to load");
1551                    }
1552                }
1553            }
1554            let text = fs.load(&abs_path).await?;
1555
1556            let worktree = this
1557                .upgrade()
1558                .ok_or_else(|| anyhow!("worktree was dropped"))?;
1559            let file = match entry.await? {
1560                Some(entry) => File::for_entry(entry, worktree),
1561                None => {
1562                    let metadata = fs
1563                        .metadata(&abs_path)
1564                        .await
1565                        .with_context(|| {
1566                            format!("Loading metadata for excluded file {abs_path:?}")
1567                        })?
1568                        .with_context(|| {
1569                            format!("Excluded file {abs_path:?} got removed during loading")
1570                        })?;
1571                    Arc::new(File {
1572                        entry_id: None,
1573                        worktree,
1574                        path,
1575                        disk_state: DiskState::Present {
1576                            mtime: metadata.mtime,
1577                        },
1578                        is_local: true,
1579                        is_private,
1580                    })
1581                }
1582            };
1583
1584            Ok(LoadedFile { file, text })
1585        })
1586    }
1587
1588    /// Find the lowest path in the worktree's datastructures that is an ancestor
1589    fn lowest_ancestor(&self, path: &Path) -> PathBuf {
1590        let mut lowest_ancestor = None;
1591        for path in path.ancestors() {
1592            if self.entry_for_path(path).is_some() {
1593                lowest_ancestor = Some(path.to_path_buf());
1594                break;
1595            }
1596        }
1597
1598        lowest_ancestor.unwrap_or_else(|| PathBuf::from(""))
1599    }
1600
1601    fn create_entry(
1602        &self,
1603        path: impl Into<Arc<Path>>,
1604        is_dir: bool,
1605        cx: &Context<Worktree>,
1606    ) -> Task<Result<CreatedEntry>> {
1607        let path = path.into();
1608        let abs_path = match self.absolutize(&path) {
1609            Ok(path) => path,
1610            Err(e) => return Task::ready(Err(e.context(format!("absolutizing path {path:?}")))),
1611        };
1612        let path_excluded = self.settings.is_path_excluded(&abs_path);
1613        let fs = self.fs.clone();
1614        let task_abs_path = abs_path.clone();
1615        let write = cx.background_spawn(async move {
1616            if is_dir {
1617                fs.create_dir(&task_abs_path)
1618                    .await
1619                    .with_context(|| format!("creating directory {task_abs_path:?}"))
1620            } else {
1621                fs.save(&task_abs_path, &Rope::default(), LineEnding::default())
1622                    .await
1623                    .with_context(|| format!("creating file {task_abs_path:?}"))
1624            }
1625        });
1626
1627        let lowest_ancestor = self.lowest_ancestor(&path);
1628        cx.spawn(async move |this, cx| {
1629            write.await?;
1630            if path_excluded {
1631                return Ok(CreatedEntry::Excluded { abs_path });
1632            }
1633
1634            let (result, refreshes) = this.update(cx, |this, cx| {
1635                let mut refreshes = Vec::new();
1636                let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
1637                for refresh_path in refresh_paths.ancestors() {
1638                    if refresh_path == Path::new("") {
1639                        continue;
1640                    }
1641                    let refresh_full_path = lowest_ancestor.join(refresh_path);
1642
1643                    refreshes.push(this.as_local_mut().unwrap().refresh_entry(
1644                        refresh_full_path.into(),
1645                        None,
1646                        cx,
1647                    ));
1648                }
1649                (
1650                    this.as_local_mut().unwrap().refresh_entry(path, None, cx),
1651                    refreshes,
1652                )
1653            })?;
1654            for refresh in refreshes {
1655                refresh.await.log_err();
1656            }
1657
1658            Ok(result
1659                .await?
1660                .map(CreatedEntry::Included)
1661                .unwrap_or_else(|| CreatedEntry::Excluded { abs_path }))
1662        })
1663    }
1664
1665    fn write_file(
1666        &self,
1667        path: impl Into<Arc<Path>>,
1668        text: Rope,
1669        line_ending: LineEnding,
1670        cx: &Context<Worktree>,
1671    ) -> Task<Result<Arc<File>>> {
1672        let path = path.into();
1673        let fs = self.fs.clone();
1674        let is_private = self.is_path_private(&path);
1675        let Ok(abs_path) = self.absolutize(&path) else {
1676            return Task::ready(Err(anyhow!("invalid path {path:?}")));
1677        };
1678
1679        let write = cx.background_spawn({
1680            let fs = fs.clone();
1681            let abs_path = abs_path.clone();
1682            async move { fs.save(&abs_path, &text, line_ending).await }
1683        });
1684
1685        cx.spawn(async move |this, cx| {
1686            write.await?;
1687            let entry = this
1688                .update(cx, |this, cx| {
1689                    this.as_local_mut()
1690                        .unwrap()
1691                        .refresh_entry(path.clone(), None, cx)
1692                })?
1693                .await?;
1694            let worktree = this.upgrade().ok_or_else(|| anyhow!("worktree dropped"))?;
1695            if let Some(entry) = entry {
1696                Ok(File::for_entry(entry, worktree))
1697            } else {
1698                let metadata = fs
1699                    .metadata(&abs_path)
1700                    .await
1701                    .with_context(|| {
1702                        format!("Fetching metadata after saving the excluded buffer {abs_path:?}")
1703                    })?
1704                    .with_context(|| {
1705                        format!("Excluded buffer {path:?} got removed during saving")
1706                    })?;
1707                Ok(Arc::new(File {
1708                    worktree,
1709                    path,
1710                    disk_state: DiskState::Present {
1711                        mtime: metadata.mtime,
1712                    },
1713                    entry_id: None,
1714                    is_local: true,
1715                    is_private,
1716                }))
1717            }
1718        })
1719    }
1720
1721    fn delete_entry(
1722        &self,
1723        entry_id: ProjectEntryId,
1724        trash: bool,
1725        cx: &Context<Worktree>,
1726    ) -> Option<Task<Result<()>>> {
1727        let entry = self.entry_for_id(entry_id)?.clone();
1728        let abs_path = self.absolutize(&entry.path);
1729        let fs = self.fs.clone();
1730
1731        let delete = cx.background_spawn(async move {
1732            if entry.is_file() {
1733                if trash {
1734                    fs.trash_file(&abs_path?, Default::default()).await?;
1735                } else {
1736                    fs.remove_file(&abs_path?, Default::default()).await?;
1737                }
1738            } else if trash {
1739                fs.trash_dir(
1740                    &abs_path?,
1741                    RemoveOptions {
1742                        recursive: true,
1743                        ignore_if_not_exists: false,
1744                    },
1745                )
1746                .await?;
1747            } else {
1748                fs.remove_dir(
1749                    &abs_path?,
1750                    RemoveOptions {
1751                        recursive: true,
1752                        ignore_if_not_exists: false,
1753                    },
1754                )
1755                .await?;
1756            }
1757            anyhow::Ok(entry.path)
1758        });
1759
1760        Some(cx.spawn(async move |this, cx| {
1761            let path = delete.await?;
1762            this.update(cx, |this, _| {
1763                this.as_local_mut()
1764                    .unwrap()
1765                    .refresh_entries_for_paths(vec![path])
1766            })?
1767            .recv()
1768            .await;
1769            Ok(())
1770        }))
1771    }
1772
1773    /// Rename an entry.
1774    ///
1775    /// `new_path` is the new relative path to the worktree root.
1776    /// If the root entry is renamed then `new_path` is the new root name instead.
1777    fn rename_entry(
1778        &self,
1779        entry_id: ProjectEntryId,
1780        new_path: impl Into<Arc<Path>>,
1781        cx: &Context<Worktree>,
1782    ) -> Task<Result<CreatedEntry>> {
1783        let old_path = match self.entry_for_id(entry_id) {
1784            Some(entry) => entry.path.clone(),
1785            None => return Task::ready(Err(anyhow!("no entry to rename for id {entry_id:?}"))),
1786        };
1787        let new_path = new_path.into();
1788        let abs_old_path = self.absolutize(&old_path);
1789
1790        let is_root_entry = self.root_entry().is_some_and(|e| e.id == entry_id);
1791        let abs_new_path = if is_root_entry {
1792            let Some(root_parent_path) = self.abs_path().parent() else {
1793                return Task::ready(Err(anyhow!("no parent for path {:?}", self.abs_path)));
1794            };
1795            root_parent_path.join(&new_path)
1796        } else {
1797            let Ok(absolutize_path) = self.absolutize(&new_path) else {
1798                return Task::ready(Err(anyhow!("absolutizing path {new_path:?}")));
1799            };
1800            absolutize_path
1801        };
1802        let abs_path = abs_new_path.clone();
1803        let fs = self.fs.clone();
1804        let case_sensitive = self.fs_case_sensitive;
1805        let rename = cx.background_spawn(async move {
1806            let abs_old_path = abs_old_path?;
1807            let abs_new_path = abs_new_path;
1808
1809            let abs_old_path_lower = abs_old_path.to_str().map(|p| p.to_lowercase());
1810            let abs_new_path_lower = abs_new_path.to_str().map(|p| p.to_lowercase());
1811
1812            // If we're on a case-insensitive FS and we're doing a case-only rename (i.e. `foobar` to `FOOBAR`)
1813            // we want to overwrite, because otherwise we run into a file-already-exists error.
1814            let overwrite = !case_sensitive
1815                && abs_old_path != abs_new_path
1816                && abs_old_path_lower == abs_new_path_lower;
1817
1818            fs.rename(
1819                &abs_old_path,
1820                &abs_new_path,
1821                fs::RenameOptions {
1822                    overwrite,
1823                    ..Default::default()
1824                },
1825            )
1826            .await
1827            .with_context(|| format!("Renaming {abs_old_path:?} into {abs_new_path:?}"))
1828        });
1829
1830        cx.spawn(async move |this, cx| {
1831            rename.await?;
1832            Ok(this
1833                .update(cx, |this, cx| {
1834                    let local = this.as_local_mut().unwrap();
1835                    if is_root_entry {
1836                        // We eagerly update `abs_path` and refresh this worktree.
1837                        // Otherwise, the FS watcher would do it on the `RootUpdated` event,
1838                        // but with a noticeable delay, so we handle it proactively.
1839                        local.update_abs_path_and_refresh(
1840                            Some(SanitizedPath::from(abs_path.clone())),
1841                            cx,
1842                        );
1843                        Task::ready(Ok(this.root_entry().cloned()))
1844                    } else {
1845                        local.refresh_entry(new_path.clone(), Some(old_path), cx)
1846                    }
1847                })?
1848                .await?
1849                .map(CreatedEntry::Included)
1850                .unwrap_or_else(|| CreatedEntry::Excluded { abs_path }))
1851        })
1852    }
1853
1854    fn copy_entry(
1855        &self,
1856        entry_id: ProjectEntryId,
1857        relative_worktree_source_path: Option<PathBuf>,
1858        new_path: impl Into<Arc<Path>>,
1859        cx: &Context<Worktree>,
1860    ) -> Task<Result<Option<Entry>>> {
1861        let old_path = match self.entry_for_id(entry_id) {
1862            Some(entry) => entry.path.clone(),
1863            None => return Task::ready(Ok(None)),
1864        };
1865        let new_path = new_path.into();
1866        let abs_old_path =
1867            if let Some(relative_worktree_source_path) = relative_worktree_source_path {
1868                Ok(self.abs_path().join(relative_worktree_source_path))
1869            } else {
1870                self.absolutize(&old_path)
1871            };
1872        let abs_new_path = self.absolutize(&new_path);
1873        let fs = self.fs.clone();
1874        let copy = cx.background_spawn(async move {
1875            copy_recursive(
1876                fs.as_ref(),
1877                &abs_old_path?,
1878                &abs_new_path?,
1879                Default::default(),
1880            )
1881            .await
1882        });
1883
1884        cx.spawn(async move |this, cx| {
1885            copy.await?;
1886            this.update(cx, |this, cx| {
1887                this.as_local_mut()
1888                    .unwrap()
1889                    .refresh_entry(new_path.clone(), None, cx)
1890            })?
1891            .await
1892        })
1893    }
1894
1895    pub fn copy_external_entries(
1896        &self,
1897        target_directory: PathBuf,
1898        paths: Vec<Arc<Path>>,
1899        overwrite_existing_files: bool,
1900        cx: &Context<Worktree>,
1901    ) -> Task<Result<Vec<ProjectEntryId>>> {
1902        let worktree_path = self.abs_path().clone();
1903        let fs = self.fs.clone();
1904        let paths = paths
1905            .into_iter()
1906            .filter_map(|source| {
1907                let file_name = source.file_name()?;
1908                let mut target = target_directory.clone();
1909                target.push(file_name);
1910
1911                // Do not allow copying the same file to itself.
1912                if source.as_ref() != target.as_path() {
1913                    Some((source, target))
1914                } else {
1915                    None
1916                }
1917            })
1918            .collect::<Vec<_>>();
1919
1920        let paths_to_refresh = paths
1921            .iter()
1922            .filter_map(|(_, target)| Some(target.strip_prefix(&worktree_path).ok()?.into()))
1923            .collect::<Vec<_>>();
1924
1925        cx.spawn(async move |this, cx| {
1926            cx.background_spawn(async move {
1927                for (source, target) in paths {
1928                    copy_recursive(
1929                        fs.as_ref(),
1930                        &source,
1931                        &target,
1932                        fs::CopyOptions {
1933                            overwrite: overwrite_existing_files,
1934                            ..Default::default()
1935                        },
1936                    )
1937                    .await
1938                    .with_context(|| {
1939                        anyhow!("Failed to copy file from {source:?} to {target:?}")
1940                    })?;
1941                }
1942                Ok::<(), anyhow::Error>(())
1943            })
1944            .await
1945            .log_err();
1946            let mut refresh = cx.read_entity(
1947                &this.upgrade().with_context(|| "Dropped worktree")?,
1948                |this, _| {
1949                    Ok::<postage::barrier::Receiver, anyhow::Error>(
1950                        this.as_local()
1951                            .with_context(|| "Worktree is not local")?
1952                            .refresh_entries_for_paths(paths_to_refresh.clone()),
1953                    )
1954                },
1955            )??;
1956
1957            cx.background_spawn(async move {
1958                refresh.next().await;
1959                Ok::<(), anyhow::Error>(())
1960            })
1961            .await
1962            .log_err();
1963
1964            let this = this.upgrade().with_context(|| "Dropped worktree")?;
1965            cx.read_entity(&this, |this, _| {
1966                paths_to_refresh
1967                    .iter()
1968                    .filter_map(|path| Some(this.entry_for_path(path)?.id))
1969                    .collect()
1970            })
1971        })
1972    }
1973
1974    fn expand_entry(
1975        &self,
1976        entry_id: ProjectEntryId,
1977        cx: &Context<Worktree>,
1978    ) -> Option<Task<Result<()>>> {
1979        let path = self.entry_for_id(entry_id)?.path.clone();
1980        let mut refresh = self.refresh_entries_for_paths(vec![path]);
1981        Some(cx.background_spawn(async move {
1982            refresh.next().await;
1983            Ok(())
1984        }))
1985    }
1986
1987    fn expand_all_for_entry(
1988        &self,
1989        entry_id: ProjectEntryId,
1990        cx: &Context<Worktree>,
1991    ) -> Option<Task<Result<()>>> {
1992        let path = self.entry_for_id(entry_id).unwrap().path.clone();
1993        let mut rx = self.add_path_prefix_to_scan(path.clone());
1994        Some(cx.background_spawn(async move {
1995            rx.next().await;
1996            Ok(())
1997        }))
1998    }
1999
2000    fn refresh_entries_for_paths(&self, paths: Vec<Arc<Path>>) -> barrier::Receiver {
2001        let (tx, rx) = barrier::channel();
2002        self.scan_requests_tx
2003            .try_send(ScanRequest {
2004                relative_paths: paths,
2005                done: smallvec![tx],
2006            })
2007            .ok();
2008        rx
2009    }
2010
2011    #[cfg(feature = "test-support")]
2012    pub fn manually_refresh_entries_for_paths(&self, paths: Vec<Arc<Path>>) -> barrier::Receiver {
2013        self.refresh_entries_for_paths(paths)
2014    }
2015
2016    pub fn add_path_prefix_to_scan(&self, path_prefix: Arc<Path>) -> barrier::Receiver {
2017        let (tx, rx) = barrier::channel();
2018        self.path_prefixes_to_scan_tx
2019            .try_send(PathPrefixScanRequest {
2020                path: path_prefix,
2021                done: smallvec![tx],
2022            })
2023            .ok();
2024        rx
2025    }
2026
2027    fn refresh_entry(
2028        &self,
2029        path: Arc<Path>,
2030        old_path: Option<Arc<Path>>,
2031        cx: &Context<Worktree>,
2032    ) -> Task<Result<Option<Entry>>> {
2033        if self.settings.is_path_excluded(&path) {
2034            return Task::ready(Ok(None));
2035        }
2036        let paths = if let Some(old_path) = old_path.as_ref() {
2037            vec![old_path.clone(), path.clone()]
2038        } else {
2039            vec![path.clone()]
2040        };
2041        let t0 = Instant::now();
2042        let mut refresh = self.refresh_entries_for_paths(paths);
2043        cx.spawn(async move |this, cx| {
2044            refresh.recv().await;
2045            log::trace!("refreshed entry {path:?} in {:?}", t0.elapsed());
2046            let new_entry = this.update(cx, |this, _| {
2047                this.entry_for_path(path)
2048                    .cloned()
2049                    .ok_or_else(|| anyhow!("failed to read path after update"))
2050            })??;
2051            Ok(Some(new_entry))
2052        })
2053    }
2054
2055    fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &Context<Worktree>, callback: F)
2056    where
2057        F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
2058        Fut: 'static + Send + Future<Output = bool>,
2059    {
2060        if let Some(observer) = self.update_observer.as_mut() {
2061            *observer.resume_updates.borrow_mut() = ();
2062            return;
2063        }
2064
2065        let (resume_updates_tx, mut resume_updates_rx) = watch::channel::<()>();
2066        let (snapshots_tx, mut snapshots_rx) =
2067            mpsc::unbounded::<(LocalSnapshot, UpdatedEntriesSet)>();
2068        snapshots_tx
2069            .unbounded_send((self.snapshot(), Arc::default()))
2070            .ok();
2071
2072        let worktree_id = cx.entity_id().as_u64();
2073        let _maintain_remote_snapshot = cx.background_spawn(async move {
2074            let mut is_first = true;
2075            while let Some((snapshot, entry_changes)) = snapshots_rx.next().await {
2076                let update = if is_first {
2077                    is_first = false;
2078                    snapshot.build_initial_update(project_id, worktree_id)
2079                } else {
2080                    snapshot.build_update(project_id, worktree_id, entry_changes)
2081                };
2082
2083                for update in proto::split_worktree_update(update) {
2084                    let _ = resume_updates_rx.try_recv();
2085                    loop {
2086                        let result = callback(update.clone());
2087                        if result.await {
2088                            break;
2089                        } else {
2090                            log::info!("waiting to resume updates");
2091                            if resume_updates_rx.next().await.is_none() {
2092                                return Some(());
2093                            }
2094                        }
2095                    }
2096                }
2097            }
2098            Some(())
2099        });
2100
2101        self.update_observer = Some(UpdateObservationState {
2102            snapshots_tx,
2103            resume_updates: resume_updates_tx,
2104            _maintain_remote_snapshot,
2105        });
2106    }
2107
2108    pub fn share_private_files(&mut self, cx: &Context<Worktree>) {
2109        self.share_private_files = true;
2110        self.restart_background_scanners(cx);
2111    }
2112
2113    fn update_abs_path_and_refresh(
2114        &mut self,
2115        new_path: Option<SanitizedPath>,
2116        cx: &Context<Worktree>,
2117    ) {
2118        if let Some(new_path) = new_path {
2119            self.snapshot.git_repositories = Default::default();
2120            self.snapshot.ignores_by_parent_abs_path = Default::default();
2121            let root_name = new_path
2122                .as_path()
2123                .file_name()
2124                .map_or(String::new(), |f| f.to_string_lossy().to_string());
2125            self.snapshot.update_abs_path(new_path, root_name);
2126        }
2127        self.restart_background_scanners(cx);
2128    }
2129}
2130
2131impl RemoteWorktree {
2132    pub fn project_id(&self) -> u64 {
2133        self.project_id
2134    }
2135
2136    pub fn client(&self) -> AnyProtoClient {
2137        self.client.clone()
2138    }
2139
2140    pub fn disconnected_from_host(&mut self) {
2141        self.updates_tx.take();
2142        self.snapshot_subscriptions.clear();
2143        self.disconnected = true;
2144    }
2145
2146    pub fn update_from_remote(&self, update: proto::UpdateWorktree) {
2147        if let Some(updates_tx) = &self.updates_tx {
2148            updates_tx
2149                .unbounded_send(update)
2150                .expect("consumer runs to completion");
2151        }
2152    }
2153
2154    fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &Context<Worktree>, callback: F)
2155    where
2156        F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
2157        Fut: 'static + Send + Future<Output = bool>,
2158    {
2159        let (tx, mut rx) = mpsc::unbounded();
2160        let initial_update = self
2161            .snapshot
2162            .build_initial_update(project_id, self.id().to_proto());
2163        self.update_observer = Some(tx);
2164        cx.spawn(async move |this, cx| {
2165            let mut update = initial_update;
2166            'outer: loop {
2167                // SSH projects use a special project ID of 0, and we need to
2168                // remap it to the correct one here.
2169                update.project_id = project_id;
2170
2171                for chunk in split_worktree_update(update) {
2172                    if !callback(chunk).await {
2173                        break 'outer;
2174                    }
2175                }
2176
2177                if let Some(next_update) = rx.next().await {
2178                    update = next_update;
2179                } else {
2180                    break;
2181                }
2182            }
2183            this.update(cx, |this, _| {
2184                let this = this.as_remote_mut().unwrap();
2185                this.update_observer.take();
2186            })
2187        })
2188        .detach();
2189    }
2190
2191    fn observed_snapshot(&self, scan_id: usize) -> bool {
2192        self.completed_scan_id >= scan_id
2193    }
2194
2195    pub fn wait_for_snapshot(
2196        &mut self,
2197        scan_id: usize,
2198    ) -> impl Future<Output = Result<()>> + use<> {
2199        let (tx, rx) = oneshot::channel();
2200        if self.observed_snapshot(scan_id) {
2201            let _ = tx.send(());
2202        } else if self.disconnected {
2203            drop(tx);
2204        } else {
2205            match self
2206                .snapshot_subscriptions
2207                .binary_search_by_key(&scan_id, |probe| probe.0)
2208            {
2209                Ok(ix) | Err(ix) => self.snapshot_subscriptions.insert(ix, (scan_id, tx)),
2210            }
2211        }
2212
2213        async move {
2214            rx.await?;
2215            Ok(())
2216        }
2217    }
2218
2219    fn insert_entry(
2220        &mut self,
2221        entry: proto::Entry,
2222        scan_id: usize,
2223        cx: &Context<Worktree>,
2224    ) -> Task<Result<Entry>> {
2225        let wait_for_snapshot = self.wait_for_snapshot(scan_id);
2226        cx.spawn(async move |this, cx| {
2227            wait_for_snapshot.await?;
2228            this.update(cx, |worktree, _| {
2229                let worktree = worktree.as_remote_mut().unwrap();
2230                let snapshot = &mut worktree.background_snapshot.lock().0;
2231                let entry = snapshot.insert_entry(entry, &worktree.file_scan_inclusions);
2232                worktree.snapshot = snapshot.clone();
2233                entry
2234            })?
2235        })
2236    }
2237
2238    fn delete_entry(
2239        &self,
2240        entry_id: ProjectEntryId,
2241        trash: bool,
2242        cx: &Context<Worktree>,
2243    ) -> Option<Task<Result<()>>> {
2244        let response = self.client.request(proto::DeleteProjectEntry {
2245            project_id: self.project_id,
2246            entry_id: entry_id.to_proto(),
2247            use_trash: trash,
2248        });
2249        Some(cx.spawn(async move |this, cx| {
2250            let response = response.await?;
2251            let scan_id = response.worktree_scan_id as usize;
2252
2253            this.update(cx, move |this, _| {
2254                this.as_remote_mut().unwrap().wait_for_snapshot(scan_id)
2255            })?
2256            .await?;
2257
2258            this.update(cx, |this, _| {
2259                let this = this.as_remote_mut().unwrap();
2260                let snapshot = &mut this.background_snapshot.lock().0;
2261                snapshot.delete_entry(entry_id);
2262                this.snapshot = snapshot.clone();
2263            })
2264        }))
2265    }
2266
2267    fn rename_entry(
2268        &self,
2269        entry_id: ProjectEntryId,
2270        new_path: impl Into<Arc<Path>>,
2271        cx: &Context<Worktree>,
2272    ) -> Task<Result<CreatedEntry>> {
2273        let new_path: Arc<Path> = new_path.into();
2274        let response = self.client.request(proto::RenameProjectEntry {
2275            project_id: self.project_id,
2276            entry_id: entry_id.to_proto(),
2277            new_path: new_path.as_ref().to_proto(),
2278        });
2279        cx.spawn(async move |this, cx| {
2280            let response = response.await?;
2281            match response.entry {
2282                Some(entry) => this
2283                    .update(cx, |this, cx| {
2284                        this.as_remote_mut().unwrap().insert_entry(
2285                            entry,
2286                            response.worktree_scan_id as usize,
2287                            cx,
2288                        )
2289                    })?
2290                    .await
2291                    .map(CreatedEntry::Included),
2292                None => {
2293                    let abs_path = this.update(cx, |worktree, _| {
2294                        worktree
2295                            .absolutize(&new_path)
2296                            .with_context(|| format!("absolutizing {new_path:?}"))
2297                    })??;
2298                    Ok(CreatedEntry::Excluded { abs_path })
2299                }
2300            }
2301        })
2302    }
2303}
2304
2305impl Snapshot {
2306    pub fn new(id: u64, root_name: String, abs_path: Arc<Path>) -> Self {
2307        Snapshot {
2308            id: WorktreeId::from_usize(id as usize),
2309            abs_path: abs_path.into(),
2310            root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(),
2311            root_name,
2312            always_included_entries: Default::default(),
2313            entries_by_path: Default::default(),
2314            entries_by_id: Default::default(),
2315            scan_id: 1,
2316            completed_scan_id: 0,
2317        }
2318    }
2319
2320    pub fn id(&self) -> WorktreeId {
2321        self.id
2322    }
2323
2324    // TODO:
2325    // Consider the following:
2326    //
2327    // ```rust
2328    // let abs_path: Arc<Path> = snapshot.abs_path(); // e.g. "C:\Users\user\Desktop\project"
2329    // let some_non_trimmed_path = Path::new("\\\\?\\C:\\Users\\user\\Desktop\\project\\main.rs");
2330    // // The caller perform some actions here:
2331    // some_non_trimmed_path.strip_prefix(abs_path);  // This fails
2332    // some_non_trimmed_path.starts_with(abs_path);   // This fails too
2333    // ```
2334    //
2335    // This is definitely a bug, but it's not clear if we should handle it here or not.
2336    pub fn abs_path(&self) -> &Arc<Path> {
2337        self.abs_path.as_path()
2338    }
2339
2340    fn build_initial_update(&self, project_id: u64, worktree_id: u64) -> proto::UpdateWorktree {
2341        let mut updated_entries = self
2342            .entries_by_path
2343            .iter()
2344            .map(proto::Entry::from)
2345            .collect::<Vec<_>>();
2346        updated_entries.sort_unstable_by_key(|e| e.id);
2347
2348        proto::UpdateWorktree {
2349            project_id,
2350            worktree_id,
2351            abs_path: self.abs_path().to_proto(),
2352            root_name: self.root_name().to_string(),
2353            updated_entries,
2354            removed_entries: Vec::new(),
2355            scan_id: self.scan_id as u64,
2356            is_last_update: self.completed_scan_id == self.scan_id,
2357            // Sent in separate messages.
2358            updated_repositories: Vec::new(),
2359            removed_repositories: Vec::new(),
2360        }
2361    }
2362
2363    pub fn work_directory_abs_path(&self, work_directory: &WorkDirectory) -> Result<PathBuf> {
2364        match work_directory {
2365            WorkDirectory::InProject { relative_path } => self.absolutize(relative_path),
2366            WorkDirectory::AboveProject { absolute_path, .. } => {
2367                Ok(absolute_path.as_ref().to_owned())
2368            }
2369        }
2370    }
2371
2372    pub fn absolutize(&self, path: &Path) -> Result<PathBuf> {
2373        if path
2374            .components()
2375            .any(|component| !matches!(component, std::path::Component::Normal(_)))
2376        {
2377            return Err(anyhow!("invalid path"));
2378        }
2379        if path.file_name().is_some() {
2380            Ok(self.abs_path.as_path().join(path))
2381        } else {
2382            Ok(self.abs_path.as_path().to_path_buf())
2383        }
2384    }
2385
2386    pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool {
2387        self.entries_by_id.get(&entry_id, &()).is_some()
2388    }
2389
2390    fn insert_entry(
2391        &mut self,
2392        entry: proto::Entry,
2393        always_included_paths: &PathMatcher,
2394    ) -> Result<Entry> {
2395        let entry = Entry::try_from((&self.root_char_bag, always_included_paths, entry))?;
2396        let old_entry = self.entries_by_id.insert_or_replace(
2397            PathEntry {
2398                id: entry.id,
2399                path: entry.path.clone(),
2400                is_ignored: entry.is_ignored,
2401                scan_id: 0,
2402            },
2403            &(),
2404        );
2405        if let Some(old_entry) = old_entry {
2406            self.entries_by_path.remove(&PathKey(old_entry.path), &());
2407        }
2408        self.entries_by_path.insert_or_replace(entry.clone(), &());
2409        Ok(entry)
2410    }
2411
2412    fn delete_entry(&mut self, entry_id: ProjectEntryId) -> Option<Arc<Path>> {
2413        let removed_entry = self.entries_by_id.remove(&entry_id, &())?;
2414        self.entries_by_path = {
2415            let mut cursor = self.entries_by_path.cursor::<TraversalProgress>(&());
2416            let mut new_entries_by_path =
2417                cursor.slice(&TraversalTarget::path(&removed_entry.path), Bias::Left, &());
2418            while let Some(entry) = cursor.item() {
2419                if entry.path.starts_with(&removed_entry.path) {
2420                    self.entries_by_id.remove(&entry.id, &());
2421                    cursor.next(&());
2422                } else {
2423                    break;
2424                }
2425            }
2426            new_entries_by_path.append(cursor.suffix(&()), &());
2427            new_entries_by_path
2428        };
2429
2430        Some(removed_entry.path)
2431    }
2432
2433    fn update_abs_path(&mut self, abs_path: SanitizedPath, root_name: String) {
2434        self.abs_path = abs_path;
2435        if root_name != self.root_name {
2436            self.root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
2437            self.root_name = root_name;
2438        }
2439    }
2440
2441    fn apply_remote_update(
2442        &mut self,
2443        update: proto::UpdateWorktree,
2444        always_included_paths: &PathMatcher,
2445    ) -> Result<()> {
2446        log::debug!(
2447            "applying remote worktree update. {} entries updated, {} removed",
2448            update.updated_entries.len(),
2449            update.removed_entries.len()
2450        );
2451        self.update_abs_path(
2452            SanitizedPath::from(PathBuf::from_proto(update.abs_path)),
2453            update.root_name,
2454        );
2455
2456        let mut entries_by_path_edits = Vec::new();
2457        let mut entries_by_id_edits = Vec::new();
2458
2459        for entry_id in update.removed_entries {
2460            let entry_id = ProjectEntryId::from_proto(entry_id);
2461            entries_by_id_edits.push(Edit::Remove(entry_id));
2462            if let Some(entry) = self.entry_for_id(entry_id) {
2463                entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
2464            }
2465        }
2466
2467        for entry in update.updated_entries {
2468            let entry = Entry::try_from((&self.root_char_bag, always_included_paths, entry))?;
2469            if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) {
2470                entries_by_path_edits.push(Edit::Remove(PathKey(path.clone())));
2471            }
2472            if let Some(old_entry) = self.entries_by_path.get(&PathKey(entry.path.clone()), &()) {
2473                if old_entry.id != entry.id {
2474                    entries_by_id_edits.push(Edit::Remove(old_entry.id));
2475                }
2476            }
2477            entries_by_id_edits.push(Edit::Insert(PathEntry {
2478                id: entry.id,
2479                path: entry.path.clone(),
2480                is_ignored: entry.is_ignored,
2481                scan_id: 0,
2482            }));
2483            entries_by_path_edits.push(Edit::Insert(entry));
2484        }
2485
2486        self.entries_by_path.edit(entries_by_path_edits, &());
2487        self.entries_by_id.edit(entries_by_id_edits, &());
2488
2489        self.scan_id = update.scan_id as usize;
2490        if update.is_last_update {
2491            self.completed_scan_id = update.scan_id as usize;
2492        }
2493
2494        Ok(())
2495    }
2496
2497    pub fn entry_count(&self) -> usize {
2498        self.entries_by_path.summary().count
2499    }
2500
2501    pub fn visible_entry_count(&self) -> usize {
2502        self.entries_by_path.summary().non_ignored_count
2503    }
2504
2505    pub fn dir_count(&self) -> usize {
2506        let summary = self.entries_by_path.summary();
2507        summary.count - summary.file_count
2508    }
2509
2510    pub fn visible_dir_count(&self) -> usize {
2511        let summary = self.entries_by_path.summary();
2512        summary.non_ignored_count - summary.non_ignored_file_count
2513    }
2514
2515    pub fn file_count(&self) -> usize {
2516        self.entries_by_path.summary().file_count
2517    }
2518
2519    pub fn visible_file_count(&self) -> usize {
2520        self.entries_by_path.summary().non_ignored_file_count
2521    }
2522
2523    fn traverse_from_offset(
2524        &self,
2525        include_files: bool,
2526        include_dirs: bool,
2527        include_ignored: bool,
2528        start_offset: usize,
2529    ) -> Traversal {
2530        let mut cursor = self.entries_by_path.cursor(&());
2531        cursor.seek(
2532            &TraversalTarget::Count {
2533                count: start_offset,
2534                include_files,
2535                include_dirs,
2536                include_ignored,
2537            },
2538            Bias::Right,
2539            &(),
2540        );
2541        Traversal {
2542            snapshot: self,
2543            cursor,
2544            include_files,
2545            include_dirs,
2546            include_ignored,
2547        }
2548    }
2549
2550    pub fn traverse_from_path(
2551        &self,
2552        include_files: bool,
2553        include_dirs: bool,
2554        include_ignored: bool,
2555        path: &Path,
2556    ) -> Traversal {
2557        Traversal::new(self, include_files, include_dirs, include_ignored, path)
2558    }
2559
2560    pub fn files(&self, include_ignored: bool, start: usize) -> Traversal {
2561        self.traverse_from_offset(true, false, include_ignored, start)
2562    }
2563
2564    pub fn directories(&self, include_ignored: bool, start: usize) -> Traversal {
2565        self.traverse_from_offset(false, true, include_ignored, start)
2566    }
2567
2568    pub fn entries(&self, include_ignored: bool, start: usize) -> Traversal {
2569        self.traverse_from_offset(true, true, include_ignored, start)
2570    }
2571
2572    pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
2573        let empty_path = Path::new("");
2574        self.entries_by_path
2575            .cursor::<()>(&())
2576            .filter(move |entry| entry.path.as_ref() != empty_path)
2577            .map(|entry| &entry.path)
2578    }
2579
2580    pub fn child_entries<'a>(&'a self, parent_path: &'a Path) -> ChildEntriesIter<'a> {
2581        let options = ChildEntriesOptions {
2582            include_files: true,
2583            include_dirs: true,
2584            include_ignored: true,
2585        };
2586        self.child_entries_with_options(parent_path, options)
2587    }
2588
2589    pub fn child_entries_with_options<'a>(
2590        &'a self,
2591        parent_path: &'a Path,
2592        options: ChildEntriesOptions,
2593    ) -> ChildEntriesIter<'a> {
2594        let mut cursor = self.entries_by_path.cursor(&());
2595        cursor.seek(&TraversalTarget::path(parent_path), Bias::Right, &());
2596        let traversal = Traversal {
2597            snapshot: self,
2598            cursor,
2599            include_files: options.include_files,
2600            include_dirs: options.include_dirs,
2601            include_ignored: options.include_ignored,
2602        };
2603        ChildEntriesIter {
2604            traversal,
2605            parent_path,
2606        }
2607    }
2608
2609    pub fn root_entry(&self) -> Option<&Entry> {
2610        self.entry_for_path("")
2611    }
2612
2613    /// TODO: what's the difference between `root_dir` and `abs_path`?
2614    /// is there any? if so, document it.
2615    pub fn root_dir(&self) -> Option<Arc<Path>> {
2616        self.root_entry()
2617            .filter(|entry| entry.is_dir())
2618            .map(|_| self.abs_path().clone())
2619    }
2620
2621    pub fn root_name(&self) -> &str {
2622        &self.root_name
2623    }
2624
2625    pub fn scan_id(&self) -> usize {
2626        self.scan_id
2627    }
2628
2629    pub fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
2630        let path = path.as_ref();
2631        debug_assert!(path.is_relative());
2632        self.traverse_from_path(true, true, true, path)
2633            .entry()
2634            .and_then(|entry| {
2635                if entry.path.as_ref() == path {
2636                    Some(entry)
2637                } else {
2638                    None
2639                }
2640            })
2641    }
2642
2643    pub fn entry_for_id(&self, id: ProjectEntryId) -> Option<&Entry> {
2644        let entry = self.entries_by_id.get(&id, &())?;
2645        self.entry_for_path(&entry.path)
2646    }
2647
2648    pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
2649        self.entry_for_path(path.as_ref()).map(|e| e.inode)
2650    }
2651}
2652
2653impl LocalSnapshot {
2654    fn local_repo_for_work_directory_path(&self, path: &Path) -> Option<&LocalRepositoryEntry> {
2655        self.git_repositories
2656            .iter()
2657            .map(|(_, entry)| entry)
2658            .find(|entry| entry.work_directory.path_key() == PathKey(path.into()))
2659    }
2660
2661    fn build_update(
2662        &self,
2663        project_id: u64,
2664        worktree_id: u64,
2665        entry_changes: UpdatedEntriesSet,
2666    ) -> proto::UpdateWorktree {
2667        let mut updated_entries = Vec::new();
2668        let mut removed_entries = Vec::new();
2669
2670        for (_, entry_id, path_change) in entry_changes.iter() {
2671            if let PathChange::Removed = path_change {
2672                removed_entries.push(entry_id.0 as u64);
2673            } else if let Some(entry) = self.entry_for_id(*entry_id) {
2674                updated_entries.push(proto::Entry::from(entry));
2675            }
2676        }
2677
2678        removed_entries.sort_unstable();
2679        updated_entries.sort_unstable_by_key(|e| e.id);
2680
2681        // TODO - optimize, knowing that removed_entries are sorted.
2682        removed_entries.retain(|id| updated_entries.binary_search_by_key(id, |e| e.id).is_err());
2683
2684        proto::UpdateWorktree {
2685            project_id,
2686            worktree_id,
2687            abs_path: self.abs_path().to_proto(),
2688            root_name: self.root_name().to_string(),
2689            updated_entries,
2690            removed_entries,
2691            scan_id: self.scan_id as u64,
2692            is_last_update: self.completed_scan_id == self.scan_id,
2693            // Sent in separate messages.
2694            updated_repositories: Vec::new(),
2695            removed_repositories: Vec::new(),
2696        }
2697    }
2698
2699    fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
2700        if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
2701            let abs_path = self.abs_path.as_path().join(&entry.path);
2702            match smol::block_on(build_gitignore(&abs_path, fs)) {
2703                Ok(ignore) => {
2704                    self.ignores_by_parent_abs_path
2705                        .insert(abs_path.parent().unwrap().into(), (Arc::new(ignore), true));
2706                }
2707                Err(error) => {
2708                    log::error!(
2709                        "error loading .gitignore file {:?} - {:?}",
2710                        &entry.path,
2711                        error
2712                    );
2713                }
2714            }
2715        }
2716
2717        if entry.kind == EntryKind::PendingDir {
2718            if let Some(existing_entry) =
2719                self.entries_by_path.get(&PathKey(entry.path.clone()), &())
2720            {
2721                entry.kind = existing_entry.kind;
2722            }
2723        }
2724
2725        let scan_id = self.scan_id;
2726        let removed = self.entries_by_path.insert_or_replace(entry.clone(), &());
2727        if let Some(removed) = removed {
2728            if removed.id != entry.id {
2729                self.entries_by_id.remove(&removed.id, &());
2730            }
2731        }
2732        self.entries_by_id.insert_or_replace(
2733            PathEntry {
2734                id: entry.id,
2735                path: entry.path.clone(),
2736                is_ignored: entry.is_ignored,
2737                scan_id,
2738            },
2739            &(),
2740        );
2741
2742        entry
2743    }
2744
2745    fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet<u64> {
2746        let mut inodes = TreeSet::default();
2747        for ancestor in path.ancestors().skip(1) {
2748            if let Some(entry) = self.entry_for_path(ancestor) {
2749                inodes.insert(entry.inode);
2750            }
2751        }
2752        inodes
2753    }
2754
2755    fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
2756        let mut new_ignores = Vec::new();
2757        for (index, ancestor) in abs_path.ancestors().enumerate() {
2758            if index > 0 {
2759                if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
2760                    new_ignores.push((ancestor, Some(ignore.clone())));
2761                } else {
2762                    new_ignores.push((ancestor, None));
2763                }
2764            }
2765            if ancestor.join(*DOT_GIT).exists() {
2766                break;
2767            }
2768        }
2769
2770        let mut ignore_stack = IgnoreStack::none();
2771        for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
2772            if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
2773                ignore_stack = IgnoreStack::all();
2774                break;
2775            } else if let Some(ignore) = ignore {
2776                ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore);
2777            }
2778        }
2779
2780        if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
2781            ignore_stack = IgnoreStack::all();
2782        }
2783
2784        ignore_stack
2785    }
2786
2787    #[cfg(test)]
2788    fn expanded_entries(&self) -> impl Iterator<Item = &Entry> {
2789        self.entries_by_path
2790            .cursor::<()>(&())
2791            .filter(|entry| entry.kind == EntryKind::Dir && (entry.is_external || entry.is_ignored))
2792    }
2793
2794    #[cfg(test)]
2795    pub fn check_invariants(&self, git_state: bool) {
2796        use pretty_assertions::assert_eq;
2797
2798        assert_eq!(
2799            self.entries_by_path
2800                .cursor::<()>(&())
2801                .map(|e| (&e.path, e.id))
2802                .collect::<Vec<_>>(),
2803            self.entries_by_id
2804                .cursor::<()>(&())
2805                .map(|e| (&e.path, e.id))
2806                .collect::<collections::BTreeSet<_>>()
2807                .into_iter()
2808                .collect::<Vec<_>>(),
2809            "entries_by_path and entries_by_id are inconsistent"
2810        );
2811
2812        let mut files = self.files(true, 0);
2813        let mut visible_files = self.files(false, 0);
2814        for entry in self.entries_by_path.cursor::<()>(&()) {
2815            if entry.is_file() {
2816                assert_eq!(files.next().unwrap().inode, entry.inode);
2817                if (!entry.is_ignored && !entry.is_external) || entry.is_always_included {
2818                    assert_eq!(visible_files.next().unwrap().inode, entry.inode);
2819                }
2820            }
2821        }
2822
2823        assert!(files.next().is_none());
2824        assert!(visible_files.next().is_none());
2825
2826        let mut bfs_paths = Vec::new();
2827        let mut stack = self
2828            .root_entry()
2829            .map(|e| e.path.as_ref())
2830            .into_iter()
2831            .collect::<Vec<_>>();
2832        while let Some(path) = stack.pop() {
2833            bfs_paths.push(path);
2834            let ix = stack.len();
2835            for child_entry in self.child_entries(path) {
2836                stack.insert(ix, &child_entry.path);
2837            }
2838        }
2839
2840        let dfs_paths_via_iter = self
2841            .entries_by_path
2842            .cursor::<()>(&())
2843            .map(|e| e.path.as_ref())
2844            .collect::<Vec<_>>();
2845        assert_eq!(bfs_paths, dfs_paths_via_iter);
2846
2847        let dfs_paths_via_traversal = self
2848            .entries(true, 0)
2849            .map(|e| e.path.as_ref())
2850            .collect::<Vec<_>>();
2851        assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
2852
2853        if git_state {
2854            for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
2855                let ignore_parent_path = ignore_parent_abs_path
2856                    .strip_prefix(self.abs_path.as_path())
2857                    .unwrap();
2858                assert!(self.entry_for_path(ignore_parent_path).is_some());
2859                assert!(
2860                    self.entry_for_path(ignore_parent_path.join(*GITIGNORE))
2861                        .is_some()
2862                );
2863            }
2864        }
2865    }
2866
2867    #[cfg(test)]
2868    pub fn entries_without_ids(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> {
2869        let mut paths = Vec::new();
2870        for entry in self.entries_by_path.cursor::<()>(&()) {
2871            if include_ignored || !entry.is_ignored {
2872                paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
2873            }
2874        }
2875        paths.sort_by(|a, b| a.0.cmp(b.0));
2876        paths
2877    }
2878}
2879
2880impl BackgroundScannerState {
2881    fn should_scan_directory(&self, entry: &Entry) -> bool {
2882        (!entry.is_external && (!entry.is_ignored || entry.is_always_included))
2883            || entry.path.file_name() == Some(*DOT_GIT)
2884            || entry.path.file_name() == Some(local_settings_folder_relative_path().as_os_str())
2885            || entry.path.file_name() == Some(local_vscode_folder_relative_path().as_os_str())
2886            || self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning
2887            || self
2888                .paths_to_scan
2889                .iter()
2890                .any(|p| p.starts_with(&entry.path))
2891            || self
2892                .path_prefixes_to_scan
2893                .iter()
2894                .any(|p| entry.path.starts_with(p))
2895    }
2896
2897    fn enqueue_scan_dir(&self, abs_path: Arc<Path>, entry: &Entry, scan_job_tx: &Sender<ScanJob>) {
2898        let path = entry.path.clone();
2899        let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true);
2900        let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path);
2901
2902        if !ancestor_inodes.contains(&entry.inode) {
2903            ancestor_inodes.insert(entry.inode);
2904            scan_job_tx
2905                .try_send(ScanJob {
2906                    abs_path,
2907                    path,
2908                    ignore_stack,
2909                    scan_queue: scan_job_tx.clone(),
2910                    ancestor_inodes,
2911                    is_external: entry.is_external,
2912                })
2913                .unwrap();
2914        }
2915    }
2916
2917    fn reuse_entry_id(&mut self, entry: &mut Entry) {
2918        if let Some(mtime) = entry.mtime {
2919            // If an entry with the same inode was removed from the worktree during this scan,
2920            // then it *might* represent the same file or directory. But the OS might also have
2921            // re-used the inode for a completely different file or directory.
2922            //
2923            // Conditionally reuse the old entry's id:
2924            // * if the mtime is the same, the file was probably been renamed.
2925            // * if the path is the same, the file may just have been updated
2926            if let Some(removed_entry) = self.removed_entries.remove(&entry.inode) {
2927                if removed_entry.mtime == Some(mtime) || removed_entry.path == entry.path {
2928                    entry.id = removed_entry.id;
2929                }
2930            } else if let Some(existing_entry) = self.snapshot.entry_for_path(&entry.path) {
2931                entry.id = existing_entry.id;
2932            }
2933        }
2934    }
2935
2936    fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs, watcher: &dyn Watcher) -> Entry {
2937        self.reuse_entry_id(&mut entry);
2938        let entry = self.snapshot.insert_entry(entry, fs);
2939        if entry.path.file_name() == Some(&DOT_GIT) {
2940            self.insert_git_repository(entry.path.clone(), fs, watcher);
2941        }
2942
2943        #[cfg(test)]
2944        self.snapshot.check_invariants(false);
2945
2946        entry
2947    }
2948
2949    fn populate_dir(
2950        &mut self,
2951        parent_path: &Arc<Path>,
2952        entries: impl IntoIterator<Item = Entry>,
2953        ignore: Option<Arc<Gitignore>>,
2954    ) {
2955        let mut parent_entry = if let Some(parent_entry) = self
2956            .snapshot
2957            .entries_by_path
2958            .get(&PathKey(parent_path.clone()), &())
2959        {
2960            parent_entry.clone()
2961        } else {
2962            log::warn!(
2963                "populating a directory {:?} that has been removed",
2964                parent_path
2965            );
2966            return;
2967        };
2968
2969        match parent_entry.kind {
2970            EntryKind::PendingDir | EntryKind::UnloadedDir => parent_entry.kind = EntryKind::Dir,
2971            EntryKind::Dir => {}
2972            _ => return,
2973        }
2974
2975        if let Some(ignore) = ignore {
2976            let abs_parent_path = self.snapshot.abs_path.as_path().join(parent_path).into();
2977            self.snapshot
2978                .ignores_by_parent_abs_path
2979                .insert(abs_parent_path, (ignore, false));
2980        }
2981
2982        let parent_entry_id = parent_entry.id;
2983        self.scanned_dirs.insert(parent_entry_id);
2984        let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
2985        let mut entries_by_id_edits = Vec::new();
2986
2987        for entry in entries {
2988            entries_by_id_edits.push(Edit::Insert(PathEntry {
2989                id: entry.id,
2990                path: entry.path.clone(),
2991                is_ignored: entry.is_ignored,
2992                scan_id: self.snapshot.scan_id,
2993            }));
2994            entries_by_path_edits.push(Edit::Insert(entry));
2995        }
2996
2997        self.snapshot
2998            .entries_by_path
2999            .edit(entries_by_path_edits, &());
3000        self.snapshot.entries_by_id.edit(entries_by_id_edits, &());
3001
3002        if let Err(ix) = self.changed_paths.binary_search(parent_path) {
3003            self.changed_paths.insert(ix, parent_path.clone());
3004        }
3005
3006        #[cfg(test)]
3007        self.snapshot.check_invariants(false);
3008    }
3009
3010    fn remove_path(&mut self, path: &Path) {
3011        log::trace!("background scanner removing path {path:?}");
3012        let mut new_entries;
3013        let removed_entries;
3014        {
3015            let mut cursor = self
3016                .snapshot
3017                .entries_by_path
3018                .cursor::<TraversalProgress>(&());
3019            new_entries = cursor.slice(&TraversalTarget::path(path), Bias::Left, &());
3020            removed_entries = cursor.slice(&TraversalTarget::successor(path), Bias::Left, &());
3021            new_entries.append(cursor.suffix(&()), &());
3022        }
3023        self.snapshot.entries_by_path = new_entries;
3024
3025        let mut removed_ids = Vec::with_capacity(removed_entries.summary().count);
3026        for entry in removed_entries.cursor::<()>(&()) {
3027            match self.removed_entries.entry(entry.inode) {
3028                hash_map::Entry::Occupied(mut e) => {
3029                    let prev_removed_entry = e.get_mut();
3030                    if entry.id > prev_removed_entry.id {
3031                        *prev_removed_entry = entry.clone();
3032                    }
3033                }
3034                hash_map::Entry::Vacant(e) => {
3035                    e.insert(entry.clone());
3036                }
3037            }
3038
3039            if entry.path.file_name() == Some(&GITIGNORE) {
3040                let abs_parent_path = self
3041                    .snapshot
3042                    .abs_path
3043                    .as_path()
3044                    .join(entry.path.parent().unwrap());
3045                if let Some((_, needs_update)) = self
3046                    .snapshot
3047                    .ignores_by_parent_abs_path
3048                    .get_mut(abs_parent_path.as_path())
3049                {
3050                    *needs_update = true;
3051                }
3052            }
3053
3054            if let Err(ix) = removed_ids.binary_search(&entry.id) {
3055                removed_ids.insert(ix, entry.id);
3056            }
3057        }
3058
3059        self.snapshot.entries_by_id.edit(
3060            removed_ids.iter().map(|&id| Edit::Remove(id)).collect(),
3061            &(),
3062        );
3063        self.snapshot
3064            .git_repositories
3065            .retain(|id, _| removed_ids.binary_search(id).is_err());
3066
3067        #[cfg(test)]
3068        self.snapshot.check_invariants(false);
3069    }
3070
3071    fn insert_git_repository(
3072        &mut self,
3073        dot_git_path: Arc<Path>,
3074        fs: &dyn Fs,
3075        watcher: &dyn Watcher,
3076    ) {
3077        let work_dir_path: Arc<Path> = match dot_git_path.parent() {
3078            Some(parent_dir) => {
3079                // Guard against repositories inside the repository metadata
3080                if parent_dir.iter().any(|component| component == *DOT_GIT) {
3081                    log::debug!(
3082                        "not building git repository for nested `.git` directory, `.git` path in the worktree: {dot_git_path:?}"
3083                    );
3084                    return;
3085                };
3086
3087                parent_dir.into()
3088            }
3089            None => {
3090                // `dot_git_path.parent().is_none()` means `.git` directory is the opened worktree itself,
3091                // no files inside that directory are tracked by git, so no need to build the repo around it
3092                log::debug!(
3093                    "not building git repository for the worktree itself, `.git` path in the worktree: {dot_git_path:?}"
3094                );
3095                return;
3096            }
3097        };
3098
3099        self.insert_git_repository_for_path(
3100            WorkDirectory::InProject {
3101                relative_path: work_dir_path,
3102            },
3103            dot_git_path,
3104            fs,
3105            watcher,
3106        );
3107    }
3108
3109    fn insert_git_repository_for_path(
3110        &mut self,
3111        work_directory: WorkDirectory,
3112        dot_git_path: Arc<Path>,
3113        fs: &dyn Fs,
3114        watcher: &dyn Watcher,
3115    ) -> Option<LocalRepositoryEntry> {
3116        let work_dir_entry = self.snapshot.entry_for_path(work_directory.path_key().0)?;
3117        let work_directory_abs_path = self
3118            .snapshot
3119            .work_directory_abs_path(&work_directory)
3120            .log_err()?;
3121
3122        if self
3123            .snapshot
3124            .git_repositories
3125            .get(&work_dir_entry.id)
3126            .is_some()
3127        {
3128            log::trace!("existing git repository for {work_directory:?}");
3129            return None;
3130        }
3131
3132        let dot_git_abs_path: Arc<Path> = self
3133            .snapshot
3134            .abs_path
3135            .as_path()
3136            .join(&dot_git_path)
3137            .as_path()
3138            .into();
3139
3140        let (repository_dir_abs_path, common_dir_abs_path) =
3141            discover_git_paths(&dot_git_abs_path, fs);
3142        watcher.add(&common_dir_abs_path).log_err();
3143        if !repository_dir_abs_path.starts_with(&common_dir_abs_path) {
3144            watcher.add(&repository_dir_abs_path).log_err();
3145        }
3146
3147        let work_directory_id = work_dir_entry.id;
3148
3149        let local_repository = LocalRepositoryEntry {
3150            work_directory_id,
3151            work_directory,
3152            work_directory_abs_path: work_directory_abs_path.as_path().into(),
3153            git_dir_scan_id: 0,
3154            dot_git_abs_path,
3155            common_dir_abs_path,
3156            repository_dir_abs_path,
3157        };
3158
3159        self.snapshot
3160            .git_repositories
3161            .insert(work_directory_id, local_repository.clone());
3162
3163        log::trace!("inserting new local git repository");
3164        Some(local_repository)
3165    }
3166}
3167
3168async fn is_git_dir(path: &Path, fs: &dyn Fs) -> bool {
3169    if path.file_name() == Some(&*DOT_GIT) {
3170        return true;
3171    }
3172
3173    // If we're in a bare repository, we are not inside a `.git` folder. In a
3174    // bare repository, the root folder contains what would normally be in the
3175    // `.git` folder.
3176    let head_metadata = fs.metadata(&path.join("HEAD")).await;
3177    if !matches!(head_metadata, Ok(Some(_))) {
3178        return false;
3179    }
3180    let config_metadata = fs.metadata(&path.join("config")).await;
3181    matches!(config_metadata, Ok(Some(_)))
3182}
3183
3184async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
3185    let contents = fs.load(abs_path).await?;
3186    let parent = abs_path.parent().unwrap_or_else(|| Path::new("/"));
3187    let mut builder = GitignoreBuilder::new(parent);
3188    for line in contents.lines() {
3189        builder.add_line(Some(abs_path.into()), line)?;
3190    }
3191    Ok(builder.build()?)
3192}
3193
3194impl Deref for Worktree {
3195    type Target = Snapshot;
3196
3197    fn deref(&self) -> &Self::Target {
3198        match self {
3199            Worktree::Local(worktree) => &worktree.snapshot,
3200            Worktree::Remote(worktree) => &worktree.snapshot,
3201        }
3202    }
3203}
3204
3205impl Deref for LocalWorktree {
3206    type Target = LocalSnapshot;
3207
3208    fn deref(&self) -> &Self::Target {
3209        &self.snapshot
3210    }
3211}
3212
3213impl Deref for RemoteWorktree {
3214    type Target = Snapshot;
3215
3216    fn deref(&self) -> &Self::Target {
3217        &self.snapshot
3218    }
3219}
3220
3221impl fmt::Debug for LocalWorktree {
3222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3223        self.snapshot.fmt(f)
3224    }
3225}
3226
3227impl fmt::Debug for Snapshot {
3228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3229        struct EntriesById<'a>(&'a SumTree<PathEntry>);
3230        struct EntriesByPath<'a>(&'a SumTree<Entry>);
3231
3232        impl fmt::Debug for EntriesByPath<'_> {
3233            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3234                f.debug_map()
3235                    .entries(self.0.iter().map(|entry| (&entry.path, entry.id)))
3236                    .finish()
3237            }
3238        }
3239
3240        impl fmt::Debug for EntriesById<'_> {
3241            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3242                f.debug_list().entries(self.0.iter()).finish()
3243            }
3244        }
3245
3246        f.debug_struct("Snapshot")
3247            .field("id", &self.id)
3248            .field("root_name", &self.root_name)
3249            .field("entries_by_path", &EntriesByPath(&self.entries_by_path))
3250            .field("entries_by_id", &EntriesById(&self.entries_by_id))
3251            .finish()
3252    }
3253}
3254
3255#[derive(Clone, PartialEq)]
3256pub struct File {
3257    pub worktree: Entity<Worktree>,
3258    pub path: Arc<Path>,
3259    pub disk_state: DiskState,
3260    pub entry_id: Option<ProjectEntryId>,
3261    pub is_local: bool,
3262    pub is_private: bool,
3263}
3264
3265impl language::File for File {
3266    fn as_local(&self) -> Option<&dyn language::LocalFile> {
3267        if self.is_local { Some(self) } else { None }
3268    }
3269
3270    fn disk_state(&self) -> DiskState {
3271        self.disk_state
3272    }
3273
3274    fn path(&self) -> &Arc<Path> {
3275        &self.path
3276    }
3277
3278    fn full_path(&self, cx: &App) -> PathBuf {
3279        self.worktree.read(cx).full_path(&self.path)
3280    }
3281
3282    /// Returns the last component of this handle's absolute path. If this handle refers to the root
3283    /// of its worktree, then this method will return the name of the worktree itself.
3284    fn file_name<'a>(&'a self, cx: &'a App) -> &'a OsStr {
3285        self.path
3286            .file_name()
3287            .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
3288    }
3289
3290    fn worktree_id(&self, cx: &App) -> WorktreeId {
3291        self.worktree.read(cx).id()
3292    }
3293
3294    fn to_proto(&self, cx: &App) -> rpc::proto::File {
3295        rpc::proto::File {
3296            worktree_id: self.worktree.read(cx).id().to_proto(),
3297            entry_id: self.entry_id.map(|id| id.to_proto()),
3298            path: self.path.as_ref().to_proto(),
3299            mtime: self.disk_state.mtime().map(|time| time.into()),
3300            is_deleted: self.disk_state == DiskState::Deleted,
3301        }
3302    }
3303
3304    fn is_private(&self) -> bool {
3305        self.is_private
3306    }
3307}
3308
3309impl language::LocalFile for File {
3310    fn abs_path(&self, cx: &App) -> PathBuf {
3311        let worktree_path = &self.worktree.read(cx).abs_path();
3312        if self.path.as_ref() == Path::new("") {
3313            worktree_path.to_path_buf()
3314        } else {
3315            worktree_path.join(&self.path)
3316        }
3317    }
3318
3319    fn load(&self, cx: &App) -> Task<Result<String>> {
3320        let worktree = self.worktree.read(cx).as_local().unwrap();
3321        let abs_path = worktree.absolutize(&self.path);
3322        let fs = worktree.fs.clone();
3323        cx.background_spawn(async move { fs.load(&abs_path?).await })
3324    }
3325
3326    fn load_bytes(&self, cx: &App) -> Task<Result<Vec<u8>>> {
3327        let worktree = self.worktree.read(cx).as_local().unwrap();
3328        let abs_path = worktree.absolutize(&self.path);
3329        let fs = worktree.fs.clone();
3330        cx.background_spawn(async move { fs.load_bytes(&abs_path?).await })
3331    }
3332}
3333
3334impl File {
3335    pub fn for_entry(entry: Entry, worktree: Entity<Worktree>) -> Arc<Self> {
3336        Arc::new(Self {
3337            worktree,
3338            path: entry.path.clone(),
3339            disk_state: if let Some(mtime) = entry.mtime {
3340                DiskState::Present { mtime }
3341            } else {
3342                DiskState::New
3343            },
3344            entry_id: Some(entry.id),
3345            is_local: true,
3346            is_private: entry.is_private,
3347        })
3348    }
3349
3350    pub fn from_proto(
3351        proto: rpc::proto::File,
3352        worktree: Entity<Worktree>,
3353        cx: &App,
3354    ) -> Result<Self> {
3355        let worktree_id = worktree
3356            .read(cx)
3357            .as_remote()
3358            .ok_or_else(|| anyhow!("not remote"))?
3359            .id();
3360
3361        if worktree_id.to_proto() != proto.worktree_id {
3362            return Err(anyhow!("worktree id does not match file"));
3363        }
3364
3365        let disk_state = if proto.is_deleted {
3366            DiskState::Deleted
3367        } else {
3368            if let Some(mtime) = proto.mtime.map(&Into::into) {
3369                DiskState::Present { mtime }
3370            } else {
3371                DiskState::New
3372            }
3373        };
3374
3375        Ok(Self {
3376            worktree,
3377            path: Arc::<Path>::from_proto(proto.path),
3378            disk_state,
3379            entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
3380            is_local: false,
3381            is_private: false,
3382        })
3383    }
3384
3385    pub fn from_dyn(file: Option<&Arc<dyn language::File>>) -> Option<&Self> {
3386        file.and_then(|f| {
3387            let f: &dyn language::File = f.borrow();
3388            let f: &dyn Any = f;
3389            f.downcast_ref()
3390        })
3391    }
3392
3393    pub fn worktree_id(&self, cx: &App) -> WorktreeId {
3394        self.worktree.read(cx).id()
3395    }
3396
3397    pub fn project_entry_id(&self, _: &App) -> Option<ProjectEntryId> {
3398        match self.disk_state {
3399            DiskState::Deleted => None,
3400            _ => self.entry_id,
3401        }
3402    }
3403}
3404
3405#[derive(Clone, Debug, PartialEq, Eq)]
3406pub struct Entry {
3407    pub id: ProjectEntryId,
3408    pub kind: EntryKind,
3409    pub path: Arc<Path>,
3410    pub inode: u64,
3411    pub mtime: Option<MTime>,
3412
3413    pub canonical_path: Option<Arc<Path>>,
3414    /// Whether this entry is ignored by Git.
3415    ///
3416    /// We only scan ignored entries once the directory is expanded and
3417    /// exclude them from searches.
3418    pub is_ignored: bool,
3419
3420    /// Whether this entry is always included in searches.
3421    ///
3422    /// This is used for entries that are always included in searches, even
3423    /// if they are ignored by git. Overridden by file_scan_exclusions.
3424    pub is_always_included: bool,
3425
3426    /// Whether this entry's canonical path is outside of the worktree.
3427    /// This means the entry is only accessible from the worktree root via a
3428    /// symlink.
3429    ///
3430    /// We only scan entries outside of the worktree once the symlinked
3431    /// directory is expanded. External entries are treated like gitignored
3432    /// entries in that they are not included in searches.
3433    pub is_external: bool,
3434
3435    /// Whether this entry is considered to be a `.env` file.
3436    pub is_private: bool,
3437    /// The entry's size on disk, in bytes.
3438    pub size: u64,
3439    pub char_bag: CharBag,
3440    pub is_fifo: bool,
3441}
3442
3443#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3444pub enum EntryKind {
3445    UnloadedDir,
3446    PendingDir,
3447    Dir,
3448    File,
3449}
3450
3451#[derive(Clone, Copy, Debug, PartialEq)]
3452pub enum PathChange {
3453    /// A filesystem entry was was created.
3454    Added,
3455    /// A filesystem entry was removed.
3456    Removed,
3457    /// A filesystem entry was updated.
3458    Updated,
3459    /// A filesystem entry was either updated or added. We don't know
3460    /// whether or not it already existed, because the path had not
3461    /// been loaded before the event.
3462    AddedOrUpdated,
3463    /// A filesystem entry was found during the initial scan of the worktree.
3464    Loaded,
3465}
3466
3467#[derive(Clone, Debug, PartialEq, Eq)]
3468pub struct UpdatedGitRepository {
3469    /// ID of the repository's working directory.
3470    ///
3471    /// For a repo that's above the worktree root, this is the ID of the worktree root, and hence not unique.
3472    /// It's included here to aid the GitStore in detecting when a repository's working directory is renamed.
3473    pub work_directory_id: ProjectEntryId,
3474    pub old_work_directory_abs_path: Option<Arc<Path>>,
3475    pub new_work_directory_abs_path: Option<Arc<Path>>,
3476    /// For a normal git repository checkout, the absolute path to the .git directory.
3477    /// For a worktree, the absolute path to the worktree's subdirectory inside the .git directory.
3478    pub dot_git_abs_path: Option<Arc<Path>>,
3479    pub repository_dir_abs_path: Option<Arc<Path>>,
3480    pub common_dir_abs_path: Option<Arc<Path>>,
3481}
3482
3483pub type UpdatedEntriesSet = Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>;
3484pub type UpdatedGitRepositoriesSet = Arc<[UpdatedGitRepository]>;
3485
3486#[derive(Clone, Debug)]
3487pub struct PathProgress<'a> {
3488    pub max_path: &'a Path,
3489}
3490
3491#[derive(Clone, Debug)]
3492pub struct PathSummary<S> {
3493    pub max_path: Arc<Path>,
3494    pub item_summary: S,
3495}
3496
3497impl<S: Summary> Summary for PathSummary<S> {
3498    type Context = S::Context;
3499
3500    fn zero(cx: &Self::Context) -> Self {
3501        Self {
3502            max_path: Path::new("").into(),
3503            item_summary: S::zero(cx),
3504        }
3505    }
3506
3507    fn add_summary(&mut self, rhs: &Self, cx: &Self::Context) {
3508        self.max_path = rhs.max_path.clone();
3509        self.item_summary.add_summary(&rhs.item_summary, cx);
3510    }
3511}
3512
3513impl<'a, S: Summary> sum_tree::Dimension<'a, PathSummary<S>> for PathProgress<'a> {
3514    fn zero(_: &<PathSummary<S> as Summary>::Context) -> Self {
3515        Self {
3516            max_path: Path::new(""),
3517        }
3518    }
3519
3520    fn add_summary(
3521        &mut self,
3522        summary: &'a PathSummary<S>,
3523        _: &<PathSummary<S> as Summary>::Context,
3524    ) {
3525        self.max_path = summary.max_path.as_ref()
3526    }
3527}
3528
3529impl<'a> sum_tree::Dimension<'a, PathSummary<GitSummary>> for GitSummary {
3530    fn zero(_cx: &()) -> Self {
3531        Default::default()
3532    }
3533
3534    fn add_summary(&mut self, summary: &'a PathSummary<GitSummary>, _: &()) {
3535        *self += summary.item_summary
3536    }
3537}
3538
3539impl<'a> sum_tree::SeekTarget<'a, PathSummary<GitSummary>, (TraversalProgress<'a>, GitSummary)>
3540    for PathTarget<'_>
3541{
3542    fn cmp(&self, cursor_location: &(TraversalProgress<'a>, GitSummary), _: &()) -> Ordering {
3543        self.cmp_path(&cursor_location.0.max_path)
3544    }
3545}
3546
3547impl<'a, S: Summary> sum_tree::Dimension<'a, PathSummary<S>> for PathKey {
3548    fn zero(_: &S::Context) -> Self {
3549        Default::default()
3550    }
3551
3552    fn add_summary(&mut self, summary: &'a PathSummary<S>, _: &S::Context) {
3553        self.0 = summary.max_path.clone();
3554    }
3555}
3556
3557impl<'a, S: Summary> sum_tree::Dimension<'a, PathSummary<S>> for TraversalProgress<'a> {
3558    fn zero(_cx: &S::Context) -> Self {
3559        Default::default()
3560    }
3561
3562    fn add_summary(&mut self, summary: &'a PathSummary<S>, _: &S::Context) {
3563        self.max_path = summary.max_path.as_ref();
3564    }
3565}
3566
3567impl Entry {
3568    fn new(
3569        path: Arc<Path>,
3570        metadata: &fs::Metadata,
3571        next_entry_id: &AtomicUsize,
3572        root_char_bag: CharBag,
3573        canonical_path: Option<Arc<Path>>,
3574    ) -> Self {
3575        let char_bag = char_bag_for_path(root_char_bag, &path);
3576        Self {
3577            id: ProjectEntryId::new(next_entry_id),
3578            kind: if metadata.is_dir {
3579                EntryKind::PendingDir
3580            } else {
3581                EntryKind::File
3582            },
3583            path,
3584            inode: metadata.inode,
3585            mtime: Some(metadata.mtime),
3586            size: metadata.len,
3587            canonical_path,
3588            is_ignored: false,
3589            is_always_included: false,
3590            is_external: false,
3591            is_private: false,
3592            char_bag,
3593            is_fifo: metadata.is_fifo,
3594        }
3595    }
3596
3597    pub fn is_created(&self) -> bool {
3598        self.mtime.is_some()
3599    }
3600
3601    pub fn is_dir(&self) -> bool {
3602        self.kind.is_dir()
3603    }
3604
3605    pub fn is_file(&self) -> bool {
3606        self.kind.is_file()
3607    }
3608}
3609
3610impl EntryKind {
3611    pub fn is_dir(&self) -> bool {
3612        matches!(
3613            self,
3614            EntryKind::Dir | EntryKind::PendingDir | EntryKind::UnloadedDir
3615        )
3616    }
3617
3618    pub fn is_unloaded(&self) -> bool {
3619        matches!(self, EntryKind::UnloadedDir)
3620    }
3621
3622    pub fn is_file(&self) -> bool {
3623        matches!(self, EntryKind::File)
3624    }
3625}
3626
3627impl sum_tree::Item for Entry {
3628    type Summary = EntrySummary;
3629
3630    fn summary(&self, _cx: &()) -> Self::Summary {
3631        let non_ignored_count = if (self.is_ignored || self.is_external) && !self.is_always_included
3632        {
3633            0
3634        } else {
3635            1
3636        };
3637        let file_count;
3638        let non_ignored_file_count;
3639        if self.is_file() {
3640            file_count = 1;
3641            non_ignored_file_count = non_ignored_count;
3642        } else {
3643            file_count = 0;
3644            non_ignored_file_count = 0;
3645        }
3646
3647        EntrySummary {
3648            max_path: self.path.clone(),
3649            count: 1,
3650            non_ignored_count,
3651            file_count,
3652            non_ignored_file_count,
3653        }
3654    }
3655}
3656
3657impl sum_tree::KeyedItem for Entry {
3658    type Key = PathKey;
3659
3660    fn key(&self) -> Self::Key {
3661        PathKey(self.path.clone())
3662    }
3663}
3664
3665#[derive(Clone, Debug)]
3666pub struct EntrySummary {
3667    max_path: Arc<Path>,
3668    count: usize,
3669    non_ignored_count: usize,
3670    file_count: usize,
3671    non_ignored_file_count: usize,
3672}
3673
3674impl Default for EntrySummary {
3675    fn default() -> Self {
3676        Self {
3677            max_path: Arc::from(Path::new("")),
3678            count: 0,
3679            non_ignored_count: 0,
3680            file_count: 0,
3681            non_ignored_file_count: 0,
3682        }
3683    }
3684}
3685
3686impl sum_tree::Summary for EntrySummary {
3687    type Context = ();
3688
3689    fn zero(_cx: &()) -> Self {
3690        Default::default()
3691    }
3692
3693    fn add_summary(&mut self, rhs: &Self, _: &()) {
3694        self.max_path = rhs.max_path.clone();
3695        self.count += rhs.count;
3696        self.non_ignored_count += rhs.non_ignored_count;
3697        self.file_count += rhs.file_count;
3698        self.non_ignored_file_count += rhs.non_ignored_file_count;
3699    }
3700}
3701
3702#[derive(Clone, Debug)]
3703struct PathEntry {
3704    id: ProjectEntryId,
3705    path: Arc<Path>,
3706    is_ignored: bool,
3707    scan_id: usize,
3708}
3709
3710impl sum_tree::Item for PathEntry {
3711    type Summary = PathEntrySummary;
3712
3713    fn summary(&self, _cx: &()) -> Self::Summary {
3714        PathEntrySummary { max_id: self.id }
3715    }
3716}
3717
3718impl sum_tree::KeyedItem for PathEntry {
3719    type Key = ProjectEntryId;
3720
3721    fn key(&self) -> Self::Key {
3722        self.id
3723    }
3724}
3725
3726#[derive(Clone, Debug, Default)]
3727struct PathEntrySummary {
3728    max_id: ProjectEntryId,
3729}
3730
3731impl sum_tree::Summary for PathEntrySummary {
3732    type Context = ();
3733
3734    fn zero(_cx: &Self::Context) -> Self {
3735        Default::default()
3736    }
3737
3738    fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
3739        self.max_id = summary.max_id;
3740    }
3741}
3742
3743impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId {
3744    fn zero(_cx: &()) -> Self {
3745        Default::default()
3746    }
3747
3748    fn add_summary(&mut self, summary: &'a PathEntrySummary, _: &()) {
3749        *self = summary.max_id;
3750    }
3751}
3752
3753#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
3754pub struct PathKey(pub Arc<Path>);
3755
3756impl Default for PathKey {
3757    fn default() -> Self {
3758        Self(Path::new("").into())
3759    }
3760}
3761
3762impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
3763    fn zero(_cx: &()) -> Self {
3764        Default::default()
3765    }
3766
3767    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
3768        self.0 = summary.max_path.clone();
3769    }
3770}
3771
3772struct BackgroundScanner {
3773    state: Mutex<BackgroundScannerState>,
3774    fs: Arc<dyn Fs>,
3775    fs_case_sensitive: bool,
3776    status_updates_tx: UnboundedSender<ScanState>,
3777    executor: BackgroundExecutor,
3778    scan_requests_rx: channel::Receiver<ScanRequest>,
3779    path_prefixes_to_scan_rx: channel::Receiver<PathPrefixScanRequest>,
3780    next_entry_id: Arc<AtomicUsize>,
3781    phase: BackgroundScannerPhase,
3782    watcher: Arc<dyn Watcher>,
3783    settings: WorktreeSettings,
3784    share_private_files: bool,
3785}
3786
3787#[derive(Copy, Clone, PartialEq)]
3788enum BackgroundScannerPhase {
3789    InitialScan,
3790    EventsReceivedDuringInitialScan,
3791    Events,
3792}
3793
3794impl BackgroundScanner {
3795    async fn run(&mut self, mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>) {
3796        // If the worktree root does not contain a git repository, then find
3797        // the git repository in an ancestor directory. Find any gitignore files
3798        // in ancestor directories.
3799        let root_abs_path = self.state.lock().snapshot.abs_path.clone();
3800        let (ignores, repo) = discover_ancestor_git_repo(self.fs.clone(), &root_abs_path).await;
3801        self.state
3802            .lock()
3803            .snapshot
3804            .ignores_by_parent_abs_path
3805            .extend(ignores);
3806        let containing_git_repository = repo.and_then(|(ancestor_dot_git, work_directory)| {
3807            self.state.lock().insert_git_repository_for_path(
3808                work_directory,
3809                ancestor_dot_git.as_path().into(),
3810                self.fs.as_ref(),
3811                self.watcher.as_ref(),
3812            )?;
3813            Some(ancestor_dot_git)
3814        });
3815
3816        log::info!("containing git repository: {containing_git_repository:?}");
3817
3818        let (scan_job_tx, scan_job_rx) = channel::unbounded();
3819        {
3820            let mut state = self.state.lock();
3821            state.snapshot.scan_id += 1;
3822            if let Some(mut root_entry) = state.snapshot.root_entry().cloned() {
3823                let ignore_stack = state
3824                    .snapshot
3825                    .ignore_stack_for_abs_path(root_abs_path.as_path(), true);
3826                if ignore_stack.is_abs_path_ignored(root_abs_path.as_path(), true) {
3827                    root_entry.is_ignored = true;
3828                    state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
3829                }
3830                state.enqueue_scan_dir(root_abs_path.into(), &root_entry, &scan_job_tx);
3831            }
3832        };
3833
3834        // Perform an initial scan of the directory.
3835        drop(scan_job_tx);
3836        self.scan_dirs(true, scan_job_rx).await;
3837        {
3838            let mut state = self.state.lock();
3839            state.snapshot.completed_scan_id = state.snapshot.scan_id;
3840        }
3841
3842        self.send_status_update(false, SmallVec::new());
3843
3844        // Process any any FS events that occurred while performing the initial scan.
3845        // For these events, update events cannot be as precise, because we didn't
3846        // have the previous state loaded yet.
3847        self.phase = BackgroundScannerPhase::EventsReceivedDuringInitialScan;
3848        if let Poll::Ready(Some(mut paths)) = futures::poll!(fs_events_rx.next()) {
3849            while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) {
3850                paths.extend(more_paths);
3851            }
3852            self.process_events(paths.into_iter().map(Into::into).collect())
3853                .await;
3854        }
3855        if let Some(abs_path) = containing_git_repository {
3856            self.process_events(vec![abs_path]).await;
3857        }
3858
3859        // Continue processing events until the worktree is dropped.
3860        self.phase = BackgroundScannerPhase::Events;
3861
3862        loop {
3863            select_biased! {
3864                // Process any path refresh requests from the worktree. Prioritize
3865                // these before handling changes reported by the filesystem.
3866                request = self.next_scan_request().fuse() => {
3867                    let Ok(request) = request else { break };
3868                    if !self.process_scan_request(request, false).await {
3869                        return;
3870                    }
3871                }
3872
3873                path_prefix_request = self.path_prefixes_to_scan_rx.recv().fuse() => {
3874                    let Ok(request) = path_prefix_request else { break };
3875                    log::trace!("adding path prefix {:?}", request.path);
3876
3877                    let did_scan = self.forcibly_load_paths(&[request.path.clone()]).await;
3878                    if did_scan {
3879                        let abs_path =
3880                        {
3881                            let mut state = self.state.lock();
3882                            state.path_prefixes_to_scan.insert(request.path.clone());
3883                            state.snapshot.abs_path.as_path().join(&request.path)
3884                        };
3885
3886                        if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() {
3887                            self.process_events(vec![abs_path]).await;
3888                        }
3889                    }
3890                    self.send_status_update(false, request.done);
3891                }
3892
3893                paths = fs_events_rx.next().fuse() => {
3894                    let Some(mut paths) = paths else { break };
3895                    while let Poll::Ready(Some(more_paths)) = futures::poll!(fs_events_rx.next()) {
3896                        paths.extend(more_paths);
3897                    }
3898                    self.process_events(paths.into_iter().map(Into::into).collect()).await;
3899                }
3900            }
3901        }
3902    }
3903
3904    async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool {
3905        log::debug!("rescanning paths {:?}", request.relative_paths);
3906
3907        request.relative_paths.sort_unstable();
3908        self.forcibly_load_paths(&request.relative_paths).await;
3909
3910        let root_path = self.state.lock().snapshot.abs_path.clone();
3911        let root_canonical_path = match self.fs.canonicalize(root_path.as_path()).await {
3912            Ok(path) => SanitizedPath::from(path),
3913            Err(err) => {
3914                log::error!("failed to canonicalize root path: {}", err);
3915                return true;
3916            }
3917        };
3918        let abs_paths = request
3919            .relative_paths
3920            .iter()
3921            .map(|path| {
3922                if path.file_name().is_some() {
3923                    root_canonical_path.as_path().join(path).to_path_buf()
3924                } else {
3925                    root_canonical_path.as_path().to_path_buf()
3926                }
3927            })
3928            .collect::<Vec<_>>();
3929
3930        {
3931            let mut state = self.state.lock();
3932            let is_idle = state.snapshot.completed_scan_id == state.snapshot.scan_id;
3933            state.snapshot.scan_id += 1;
3934            if is_idle {
3935                state.snapshot.completed_scan_id = state.snapshot.scan_id;
3936            }
3937        }
3938
3939        self.reload_entries_for_paths(
3940            root_path,
3941            root_canonical_path,
3942            &request.relative_paths,
3943            abs_paths,
3944            None,
3945        )
3946        .await;
3947
3948        self.send_status_update(scanning, request.done)
3949    }
3950
3951    async fn process_events(&self, mut abs_paths: Vec<PathBuf>) {
3952        let root_path = self.state.lock().snapshot.abs_path.clone();
3953        let root_canonical_path = match self.fs.canonicalize(root_path.as_path()).await {
3954            Ok(path) => SanitizedPath::from(path),
3955            Err(err) => {
3956                let new_path = self
3957                    .state
3958                    .lock()
3959                    .snapshot
3960                    .root_file_handle
3961                    .clone()
3962                    .and_then(|handle| handle.current_path(&self.fs).log_err())
3963                    .map(SanitizedPath::from)
3964                    .filter(|new_path| *new_path != root_path);
3965
3966                if let Some(new_path) = new_path.as_ref() {
3967                    log::info!(
3968                        "root renamed from {} to {}",
3969                        root_path.as_path().display(),
3970                        new_path.as_path().display()
3971                    )
3972                } else {
3973                    log::warn!("root path could not be canonicalized: {}", err);
3974                }
3975                self.status_updates_tx
3976                    .unbounded_send(ScanState::RootUpdated { new_path })
3977                    .ok();
3978                return;
3979            }
3980        };
3981
3982        // Certain directories may have FS changes, but do not lead to git data changes that Zed cares about.
3983        // Ignore these, to avoid Zed unnecessarily rescanning git metadata.
3984        let skipped_files_in_dot_git = HashSet::from_iter([*COMMIT_MESSAGE, *INDEX_LOCK]);
3985        let skipped_dirs_in_dot_git = [*FSMONITOR_DAEMON, *LFS_DIR];
3986
3987        let mut relative_paths = Vec::with_capacity(abs_paths.len());
3988        let mut dot_git_abs_paths = Vec::new();
3989        abs_paths.sort_unstable();
3990        abs_paths.dedup_by(|a, b| a.starts_with(b));
3991        abs_paths.retain(|abs_path| {
3992            let abs_path = SanitizedPath::from(abs_path);
3993
3994            let snapshot = &self.state.lock().snapshot;
3995            {
3996                let mut is_git_related = false;
3997
3998                let dot_git_paths = abs_path.as_path().ancestors().find_map(|ancestor| {
3999                    if smol::block_on(is_git_dir(ancestor, self.fs.as_ref())) {
4000                        let path_in_git_dir = abs_path.as_path().strip_prefix(ancestor).expect("stripping off the ancestor");
4001                        Some((ancestor.to_owned(), path_in_git_dir.to_owned()))
4002                    } else {
4003                        None
4004                    }
4005                });
4006
4007                if let Some((dot_git_abs_path, path_in_git_dir)) = dot_git_paths {
4008                    if skipped_files_in_dot_git.contains(path_in_git_dir.as_os_str()) || skipped_dirs_in_dot_git.iter().any(|skipped_git_subdir| path_in_git_dir.starts_with(skipped_git_subdir)) {
4009                        log::debug!("ignoring event {abs_path:?} as it's in the .git directory among skipped files or directories");
4010                        return false;
4011                    }
4012
4013                    is_git_related = true;
4014                    if !dot_git_abs_paths.contains(&dot_git_abs_path) {
4015                        dot_git_abs_paths.push(dot_git_abs_path);
4016                    }
4017                }
4018
4019                let relative_path: Arc<Path> =
4020                    if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
4021                        path.into()
4022                    } else {
4023                        if is_git_related {
4024                            log::debug!(
4025                              "ignoring event {abs_path:?}, since it's in git dir outside of root path {root_canonical_path:?}",
4026                            );
4027                        } else {
4028                            log::error!(
4029                              "ignoring event {abs_path:?} outside of root path {root_canonical_path:?}",
4030                            );
4031                        }
4032                        return false;
4033                    };
4034
4035                if abs_path.0.file_name() == Some(*GITIGNORE) {
4036                    for (_, repo) in snapshot.git_repositories.iter().filter(|(_, repo)| repo.directory_contains(&relative_path)) {
4037                        if !dot_git_abs_paths.iter().any(|dot_git_abs_path| dot_git_abs_path == repo.common_dir_abs_path.as_ref()) {
4038                            dot_git_abs_paths.push(repo.common_dir_abs_path.to_path_buf());
4039                        }
4040                    }
4041                }
4042
4043                let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| {
4044                    snapshot
4045                        .entry_for_path(parent)
4046                        .map_or(false, |entry| entry.kind == EntryKind::Dir)
4047                });
4048                if !parent_dir_is_loaded {
4049                    log::debug!("ignoring event {relative_path:?} within unloaded directory");
4050                    return false;
4051                }
4052
4053                if self.settings.is_path_excluded(&relative_path) {
4054                    if !is_git_related {
4055                        log::debug!("ignoring FS event for excluded path {relative_path:?}");
4056                    }
4057                    return false;
4058                }
4059
4060                relative_paths.push(relative_path);
4061                true
4062            }
4063        });
4064
4065        if relative_paths.is_empty() && dot_git_abs_paths.is_empty() {
4066            return;
4067        }
4068
4069        self.state.lock().snapshot.scan_id += 1;
4070
4071        let (scan_job_tx, scan_job_rx) = channel::unbounded();
4072        log::debug!("received fs events {:?}", relative_paths);
4073        self.reload_entries_for_paths(
4074            root_path,
4075            root_canonical_path,
4076            &relative_paths,
4077            abs_paths,
4078            Some(scan_job_tx.clone()),
4079        )
4080        .await;
4081
4082        self.update_ignore_statuses(scan_job_tx).await;
4083        self.scan_dirs(false, scan_job_rx).await;
4084
4085        if !dot_git_abs_paths.is_empty() {
4086            self.update_git_repositories(dot_git_abs_paths);
4087        }
4088
4089        {
4090            let mut state = self.state.lock();
4091            state.snapshot.completed_scan_id = state.snapshot.scan_id;
4092            for (_, entry) in mem::take(&mut state.removed_entries) {
4093                state.scanned_dirs.remove(&entry.id);
4094            }
4095        }
4096        self.send_status_update(false, SmallVec::new());
4097    }
4098
4099    async fn forcibly_load_paths(&self, paths: &[Arc<Path>]) -> bool {
4100        let (scan_job_tx, scan_job_rx) = channel::unbounded();
4101        {
4102            let mut state = self.state.lock();
4103            let root_path = state.snapshot.abs_path.clone();
4104            for path in paths {
4105                for ancestor in path.ancestors() {
4106                    if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
4107                        if entry.kind == EntryKind::UnloadedDir {
4108                            let abs_path = root_path.as_path().join(ancestor);
4109                            state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx);
4110                            state.paths_to_scan.insert(path.clone());
4111                            break;
4112                        }
4113                    }
4114                }
4115            }
4116            drop(scan_job_tx);
4117        }
4118        while let Ok(job) = scan_job_rx.recv().await {
4119            self.scan_dir(&job).await.log_err();
4120        }
4121
4122        !mem::take(&mut self.state.lock().paths_to_scan).is_empty()
4123    }
4124
4125    async fn scan_dirs(
4126        &self,
4127        enable_progress_updates: bool,
4128        scan_jobs_rx: channel::Receiver<ScanJob>,
4129    ) {
4130        if self
4131            .status_updates_tx
4132            .unbounded_send(ScanState::Started)
4133            .is_err()
4134        {
4135            return;
4136        }
4137
4138        let progress_update_count = AtomicUsize::new(0);
4139        self.executor
4140            .scoped(|scope| {
4141                for _ in 0..self.executor.num_cpus() {
4142                    scope.spawn(async {
4143                        let mut last_progress_update_count = 0;
4144                        let progress_update_timer = self.progress_timer(enable_progress_updates).fuse();
4145                        futures::pin_mut!(progress_update_timer);
4146
4147                        loop {
4148                            select_biased! {
4149                                // Process any path refresh requests before moving on to process
4150                                // the scan queue, so that user operations are prioritized.
4151                                request = self.next_scan_request().fuse() => {
4152                                    let Ok(request) = request else { break };
4153                                    if !self.process_scan_request(request, true).await {
4154                                        return;
4155                                    }
4156                                }
4157
4158                                // Send periodic progress updates to the worktree. Use an atomic counter
4159                                // to ensure that only one of the workers sends a progress update after
4160                                // the update interval elapses.
4161                                _ = progress_update_timer => {
4162                                    match progress_update_count.compare_exchange(
4163                                        last_progress_update_count,
4164                                        last_progress_update_count + 1,
4165                                        SeqCst,
4166                                        SeqCst
4167                                    ) {
4168                                        Ok(_) => {
4169                                            last_progress_update_count += 1;
4170                                            self.send_status_update(true, SmallVec::new());
4171                                        }
4172                                        Err(count) => {
4173                                            last_progress_update_count = count;
4174                                        }
4175                                    }
4176                                    progress_update_timer.set(self.progress_timer(enable_progress_updates).fuse());
4177                                }
4178
4179                                // Recursively load directories from the file system.
4180                                job = scan_jobs_rx.recv().fuse() => {
4181                                    let Ok(job) = job else { break };
4182                                    if let Err(err) = self.scan_dir(&job).await {
4183                                        if job.path.as_ref() != Path::new("") {
4184                                            log::error!("error scanning directory {:?}: {}", job.abs_path, err);
4185                                        }
4186                                    }
4187                                }
4188                            }
4189                        }
4190                    });
4191                }
4192            })
4193            .await;
4194    }
4195
4196    fn send_status_update(&self, scanning: bool, barrier: SmallVec<[barrier::Sender; 1]>) -> bool {
4197        let mut state = self.state.lock();
4198        if state.changed_paths.is_empty() && scanning {
4199            return true;
4200        }
4201
4202        let new_snapshot = state.snapshot.clone();
4203        let old_snapshot = mem::replace(&mut state.prev_snapshot, new_snapshot.snapshot.clone());
4204        let changes = build_diff(
4205            self.phase,
4206            &old_snapshot,
4207            &new_snapshot,
4208            &state.changed_paths,
4209        );
4210        state.changed_paths.clear();
4211
4212        self.status_updates_tx
4213            .unbounded_send(ScanState::Updated {
4214                snapshot: new_snapshot,
4215                changes,
4216                scanning,
4217                barrier,
4218            })
4219            .is_ok()
4220    }
4221
4222    async fn scan_dir(&self, job: &ScanJob) -> Result<()> {
4223        let root_abs_path;
4224        let root_char_bag;
4225        {
4226            let snapshot = &self.state.lock().snapshot;
4227            if self.settings.is_path_excluded(&job.path) {
4228                log::error!("skipping excluded directory {:?}", job.path);
4229                return Ok(());
4230            }
4231            log::trace!("scanning directory {:?}", job.path);
4232            root_abs_path = snapshot.abs_path().clone();
4233            root_char_bag = snapshot.root_char_bag;
4234        }
4235
4236        let next_entry_id = self.next_entry_id.clone();
4237        let mut ignore_stack = job.ignore_stack.clone();
4238        let mut new_ignore = None;
4239        let mut root_canonical_path = None;
4240        let mut new_entries: Vec<Entry> = Vec::new();
4241        let mut new_jobs: Vec<Option<ScanJob>> = Vec::new();
4242        let mut child_paths = self
4243            .fs
4244            .read_dir(&job.abs_path)
4245            .await?
4246            .filter_map(|entry| async {
4247                match entry {
4248                    Ok(entry) => Some(entry),
4249                    Err(error) => {
4250                        log::error!("error processing entry {:?}", error);
4251                        None
4252                    }
4253                }
4254            })
4255            .collect::<Vec<_>>()
4256            .await;
4257
4258        // Ensure that .git and .gitignore are processed first.
4259        swap_to_front(&mut child_paths, *GITIGNORE);
4260        swap_to_front(&mut child_paths, *DOT_GIT);
4261
4262        for child_abs_path in child_paths {
4263            let child_abs_path: Arc<Path> = child_abs_path.into();
4264            let child_name = child_abs_path.file_name().unwrap();
4265            let child_path: Arc<Path> = job.path.join(child_name).into();
4266
4267            if child_name == *DOT_GIT {
4268                let mut state = self.state.lock();
4269                state.insert_git_repository(
4270                    child_path.clone(),
4271                    self.fs.as_ref(),
4272                    self.watcher.as_ref(),
4273                );
4274            } else if child_name == *GITIGNORE {
4275                match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
4276                    Ok(ignore) => {
4277                        let ignore = Arc::new(ignore);
4278                        ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
4279                        new_ignore = Some(ignore);
4280                    }
4281                    Err(error) => {
4282                        log::error!(
4283                            "error loading .gitignore file {:?} - {:?}",
4284                            child_name,
4285                            error
4286                        );
4287                    }
4288                }
4289            }
4290
4291            if self.settings.is_path_excluded(&child_path) {
4292                log::debug!("skipping excluded child entry {child_path:?}");
4293                self.state.lock().remove_path(&child_path);
4294                continue;
4295            }
4296
4297            let child_metadata = match self.fs.metadata(&child_abs_path).await {
4298                Ok(Some(metadata)) => metadata,
4299                Ok(None) => continue,
4300                Err(err) => {
4301                    log::error!("error processing {child_abs_path:?}: {err:?}");
4302                    continue;
4303                }
4304            };
4305
4306            let mut child_entry = Entry::new(
4307                child_path.clone(),
4308                &child_metadata,
4309                &next_entry_id,
4310                root_char_bag,
4311                None,
4312            );
4313
4314            if job.is_external {
4315                child_entry.is_external = true;
4316            } else if child_metadata.is_symlink {
4317                let canonical_path = match self.fs.canonicalize(&child_abs_path).await {
4318                    Ok(path) => path,
4319                    Err(err) => {
4320                        log::error!(
4321                            "error reading target of symlink {:?}: {:?}",
4322                            child_abs_path,
4323                            err
4324                        );
4325                        continue;
4326                    }
4327                };
4328
4329                // lazily canonicalize the root path in order to determine if
4330                // symlinks point outside of the worktree.
4331                let root_canonical_path = match &root_canonical_path {
4332                    Some(path) => path,
4333                    None => match self.fs.canonicalize(&root_abs_path).await {
4334                        Ok(path) => root_canonical_path.insert(path),
4335                        Err(err) => {
4336                            log::error!("error canonicalizing root {:?}: {:?}", root_abs_path, err);
4337                            continue;
4338                        }
4339                    },
4340                };
4341
4342                if !canonical_path.starts_with(root_canonical_path) {
4343                    child_entry.is_external = true;
4344                }
4345
4346                child_entry.canonical_path = Some(canonical_path.into());
4347            }
4348
4349            if child_entry.is_dir() {
4350                child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true);
4351                child_entry.is_always_included = self.settings.is_path_always_included(&child_path);
4352
4353                // Avoid recursing until crash in the case of a recursive symlink
4354                if job.ancestor_inodes.contains(&child_entry.inode) {
4355                    new_jobs.push(None);
4356                } else {
4357                    let mut ancestor_inodes = job.ancestor_inodes.clone();
4358                    ancestor_inodes.insert(child_entry.inode);
4359
4360                    new_jobs.push(Some(ScanJob {
4361                        abs_path: child_abs_path.clone(),
4362                        path: child_path,
4363                        is_external: child_entry.is_external,
4364                        ignore_stack: if child_entry.is_ignored {
4365                            IgnoreStack::all()
4366                        } else {
4367                            ignore_stack.clone()
4368                        },
4369                        ancestor_inodes,
4370                        scan_queue: job.scan_queue.clone(),
4371                    }));
4372                }
4373            } else {
4374                child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
4375                child_entry.is_always_included = self.settings.is_path_always_included(&child_path);
4376            }
4377
4378            {
4379                let relative_path = job.path.join(child_name);
4380                if self.is_path_private(&relative_path) {
4381                    log::debug!("detected private file: {relative_path:?}");
4382                    child_entry.is_private = true;
4383                }
4384            }
4385
4386            new_entries.push(child_entry);
4387        }
4388
4389        let mut state = self.state.lock();
4390
4391        // Identify any subdirectories that should not be scanned.
4392        let mut job_ix = 0;
4393        for entry in &mut new_entries {
4394            state.reuse_entry_id(entry);
4395            if entry.is_dir() {
4396                if state.should_scan_directory(entry) {
4397                    job_ix += 1;
4398                } else {
4399                    log::debug!("defer scanning directory {:?}", entry.path);
4400                    entry.kind = EntryKind::UnloadedDir;
4401                    new_jobs.remove(job_ix);
4402                }
4403            }
4404            if entry.is_always_included {
4405                state
4406                    .snapshot
4407                    .always_included_entries
4408                    .push(entry.path.clone());
4409            }
4410        }
4411
4412        state.populate_dir(&job.path, new_entries, new_ignore);
4413        self.watcher.add(job.abs_path.as_ref()).log_err();
4414
4415        for new_job in new_jobs.into_iter().flatten() {
4416            job.scan_queue
4417                .try_send(new_job)
4418                .expect("channel is unbounded");
4419        }
4420
4421        Ok(())
4422    }
4423
4424    /// All list arguments should be sorted before calling this function
4425    async fn reload_entries_for_paths(
4426        &self,
4427        root_abs_path: SanitizedPath,
4428        root_canonical_path: SanitizedPath,
4429        relative_paths: &[Arc<Path>],
4430        abs_paths: Vec<PathBuf>,
4431        scan_queue_tx: Option<Sender<ScanJob>>,
4432    ) {
4433        // grab metadata for all requested paths
4434        let metadata = futures::future::join_all(
4435            abs_paths
4436                .iter()
4437                .map(|abs_path| async move {
4438                    let metadata = self.fs.metadata(abs_path).await?;
4439                    if let Some(metadata) = metadata {
4440                        let canonical_path = self.fs.canonicalize(abs_path).await?;
4441
4442                        // If we're on a case-insensitive filesystem (default on macOS), we want
4443                        // to only ignore metadata for non-symlink files if their absolute-path matches
4444                        // the canonical-path.
4445                        // Because if not, this might be a case-only-renaming (`mv test.txt TEST.TXT`)
4446                        // and we want to ignore the metadata for the old path (`test.txt`) so it's
4447                        // treated as removed.
4448                        if !self.fs_case_sensitive && !metadata.is_symlink {
4449                            let canonical_file_name = canonical_path.file_name();
4450                            let file_name = abs_path.file_name();
4451                            if canonical_file_name != file_name {
4452                                return Ok(None);
4453                            }
4454                        }
4455
4456                        anyhow::Ok(Some((metadata, SanitizedPath::from(canonical_path))))
4457                    } else {
4458                        Ok(None)
4459                    }
4460                })
4461                .collect::<Vec<_>>(),
4462        )
4463        .await;
4464
4465        let mut new_ancestor_repo = if relative_paths
4466            .iter()
4467            .any(|path| path.as_ref() == Path::new(""))
4468        {
4469            Some(discover_ancestor_git_repo(self.fs.clone(), &root_abs_path).await)
4470        } else {
4471            None
4472        };
4473
4474        let mut state = self.state.lock();
4475        let doing_recursive_update = scan_queue_tx.is_some();
4476
4477        // Remove any entries for paths that no longer exist or are being recursively
4478        // refreshed. Do this before adding any new entries, so that renames can be
4479        // detected regardless of the order of the paths.
4480        for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
4481            if matches!(metadata, Ok(None)) || doing_recursive_update {
4482                log::trace!("remove path {:?}", path);
4483                state.remove_path(path);
4484            }
4485        }
4486
4487        for (path, metadata) in relative_paths.iter().zip(metadata.into_iter()) {
4488            let abs_path: Arc<Path> = root_abs_path.as_path().join(path).into();
4489            match metadata {
4490                Ok(Some((metadata, canonical_path))) => {
4491                    let ignore_stack = state
4492                        .snapshot
4493                        .ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
4494                    let is_external = !canonical_path.starts_with(&root_canonical_path);
4495                    let mut fs_entry = Entry::new(
4496                        path.clone(),
4497                        &metadata,
4498                        self.next_entry_id.as_ref(),
4499                        state.snapshot.root_char_bag,
4500                        if metadata.is_symlink {
4501                            Some(canonical_path.as_path().to_path_buf().into())
4502                        } else {
4503                            None
4504                        },
4505                    );
4506
4507                    let is_dir = fs_entry.is_dir();
4508                    fs_entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, is_dir);
4509                    fs_entry.is_external = is_external;
4510                    fs_entry.is_private = self.is_path_private(path);
4511                    fs_entry.is_always_included = self.settings.is_path_always_included(path);
4512
4513                    if let (Some(scan_queue_tx), true) = (&scan_queue_tx, is_dir) {
4514                        if state.should_scan_directory(&fs_entry)
4515                            || (fs_entry.path.as_os_str().is_empty()
4516                                && abs_path.file_name() == Some(*DOT_GIT))
4517                        {
4518                            state.enqueue_scan_dir(abs_path, &fs_entry, scan_queue_tx);
4519                        } else {
4520                            fs_entry.kind = EntryKind::UnloadedDir;
4521                        }
4522                    }
4523
4524                    state.insert_entry(fs_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
4525
4526                    if path.as_ref() == Path::new("") {
4527                        if let Some((ignores, repo)) = new_ancestor_repo.take() {
4528                            log::trace!("updating ancestor git repository");
4529                            state.snapshot.ignores_by_parent_abs_path.extend(ignores);
4530                            if let Some((ancestor_dot_git, work_directory)) = repo {
4531                                state.insert_git_repository_for_path(
4532                                    work_directory,
4533                                    ancestor_dot_git.as_path().into(),
4534                                    self.fs.as_ref(),
4535                                    self.watcher.as_ref(),
4536                                );
4537                            }
4538                        }
4539                    }
4540                }
4541                Ok(None) => {
4542                    self.remove_repo_path(path, &mut state.snapshot);
4543                }
4544                Err(err) => {
4545                    log::error!("error reading file {abs_path:?} on event: {err:#}");
4546                }
4547            }
4548        }
4549
4550        util::extend_sorted(
4551            &mut state.changed_paths,
4552            relative_paths.iter().cloned(),
4553            usize::MAX,
4554            Ord::cmp,
4555        );
4556    }
4557
4558    fn remove_repo_path(&self, path: &Arc<Path>, snapshot: &mut LocalSnapshot) -> Option<()> {
4559        if !path
4560            .components()
4561            .any(|component| component.as_os_str() == *DOT_GIT)
4562        {
4563            if let Some(local_repo) = snapshot.local_repo_for_work_directory_path(path) {
4564                let id = local_repo.work_directory_id;
4565                log::debug!("remove repo path: {:?}", path);
4566                snapshot.git_repositories.remove(&id);
4567                return Some(());
4568            }
4569        }
4570
4571        Some(())
4572    }
4573
4574    async fn update_ignore_statuses(&self, scan_job_tx: Sender<ScanJob>) {
4575        let mut ignores_to_update = Vec::new();
4576        let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
4577        let prev_snapshot;
4578        {
4579            let snapshot = &mut self.state.lock().snapshot;
4580            let abs_path = snapshot.abs_path.clone();
4581            snapshot
4582                .ignores_by_parent_abs_path
4583                .retain(|parent_abs_path, (_, needs_update)| {
4584                    if let Ok(parent_path) = parent_abs_path.strip_prefix(abs_path.as_path()) {
4585                        if *needs_update {
4586                            *needs_update = false;
4587                            if snapshot.snapshot.entry_for_path(parent_path).is_some() {
4588                                ignores_to_update.push(parent_abs_path.clone());
4589                            }
4590                        }
4591
4592                        let ignore_path = parent_path.join(*GITIGNORE);
4593                        if snapshot.snapshot.entry_for_path(ignore_path).is_none() {
4594                            return false;
4595                        }
4596                    }
4597                    true
4598                });
4599
4600            ignores_to_update.sort_unstable();
4601            let mut ignores_to_update = ignores_to_update.into_iter().peekable();
4602            while let Some(parent_abs_path) = ignores_to_update.next() {
4603                while ignores_to_update
4604                    .peek()
4605                    .map_or(false, |p| p.starts_with(&parent_abs_path))
4606                {
4607                    ignores_to_update.next().unwrap();
4608                }
4609
4610                let ignore_stack = snapshot.ignore_stack_for_abs_path(&parent_abs_path, true);
4611                ignore_queue_tx
4612                    .send_blocking(UpdateIgnoreStatusJob {
4613                        abs_path: parent_abs_path,
4614                        ignore_stack,
4615                        ignore_queue: ignore_queue_tx.clone(),
4616                        scan_queue: scan_job_tx.clone(),
4617                    })
4618                    .unwrap();
4619            }
4620
4621            prev_snapshot = snapshot.clone();
4622        }
4623        drop(ignore_queue_tx);
4624
4625        self.executor
4626            .scoped(|scope| {
4627                for _ in 0..self.executor.num_cpus() {
4628                    scope.spawn(async {
4629                        loop {
4630                            select_biased! {
4631                                // Process any path refresh requests before moving on to process
4632                                // the queue of ignore statuses.
4633                                request = self.next_scan_request().fuse() => {
4634                                    let Ok(request) = request else { break };
4635                                    if !self.process_scan_request(request, true).await {
4636                                        return;
4637                                    }
4638                                }
4639
4640                                // Recursively process directories whose ignores have changed.
4641                                job = ignore_queue_rx.recv().fuse() => {
4642                                    let Ok(job) = job else { break };
4643                                    self.update_ignore_status(job, &prev_snapshot).await;
4644                                }
4645                            }
4646                        }
4647                    });
4648                }
4649            })
4650            .await;
4651    }
4652
4653    async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
4654        log::trace!("update ignore status {:?}", job.abs_path);
4655
4656        let mut ignore_stack = job.ignore_stack;
4657        if let Some((ignore, _)) = snapshot.ignores_by_parent_abs_path.get(&job.abs_path) {
4658            ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
4659        }
4660
4661        let mut entries_by_id_edits = Vec::new();
4662        let mut entries_by_path_edits = Vec::new();
4663        let path = job
4664            .abs_path
4665            .strip_prefix(snapshot.abs_path.as_path())
4666            .unwrap();
4667
4668        for mut entry in snapshot.child_entries(path).cloned() {
4669            let was_ignored = entry.is_ignored;
4670            let abs_path: Arc<Path> = snapshot.abs_path().join(&entry.path).into();
4671            entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
4672
4673            if entry.is_dir() {
4674                let child_ignore_stack = if entry.is_ignored {
4675                    IgnoreStack::all()
4676                } else {
4677                    ignore_stack.clone()
4678                };
4679
4680                // Scan any directories that were previously ignored and weren't previously scanned.
4681                if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
4682                    let state = self.state.lock();
4683                    if state.should_scan_directory(&entry) {
4684                        state.enqueue_scan_dir(abs_path.clone(), &entry, &job.scan_queue);
4685                    }
4686                }
4687
4688                job.ignore_queue
4689                    .send(UpdateIgnoreStatusJob {
4690                        abs_path: abs_path.clone(),
4691                        ignore_stack: child_ignore_stack,
4692                        ignore_queue: job.ignore_queue.clone(),
4693                        scan_queue: job.scan_queue.clone(),
4694                    })
4695                    .await
4696                    .unwrap();
4697            }
4698
4699            if entry.is_ignored != was_ignored {
4700                let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
4701                path_entry.scan_id = snapshot.scan_id;
4702                path_entry.is_ignored = entry.is_ignored;
4703                entries_by_id_edits.push(Edit::Insert(path_entry));
4704                entries_by_path_edits.push(Edit::Insert(entry));
4705            }
4706        }
4707
4708        let state = &mut self.state.lock();
4709        for edit in &entries_by_path_edits {
4710            if let Edit::Insert(entry) = edit {
4711                if let Err(ix) = state.changed_paths.binary_search(&entry.path) {
4712                    state.changed_paths.insert(ix, entry.path.clone());
4713                }
4714            }
4715        }
4716
4717        state
4718            .snapshot
4719            .entries_by_path
4720            .edit(entries_by_path_edits, &());
4721        state.snapshot.entries_by_id.edit(entries_by_id_edits, &());
4722    }
4723
4724    fn update_git_repositories(&self, dot_git_paths: Vec<PathBuf>) {
4725        log::trace!("reloading repositories: {dot_git_paths:?}");
4726        let mut state = self.state.lock();
4727        let scan_id = state.snapshot.scan_id;
4728        for dot_git_dir in dot_git_paths {
4729            let existing_repository_entry =
4730                state
4731                    .snapshot
4732                    .git_repositories
4733                    .iter()
4734                    .find_map(|(_, repo)| {
4735                        if repo.common_dir_abs_path.as_ref() == &dot_git_dir
4736                            || repo.repository_dir_abs_path.as_ref() == &dot_git_dir
4737                        {
4738                            Some(repo.clone())
4739                        } else {
4740                            None
4741                        }
4742                    });
4743
4744            match existing_repository_entry {
4745                None => {
4746                    let Ok(relative) = dot_git_dir.strip_prefix(state.snapshot.abs_path()) else {
4747                        return;
4748                    };
4749                    state.insert_git_repository(
4750                        relative.into(),
4751                        self.fs.as_ref(),
4752                        self.watcher.as_ref(),
4753                    );
4754                }
4755                Some(local_repository) => {
4756                    state.snapshot.git_repositories.update(
4757                        &local_repository.work_directory_id,
4758                        |entry| {
4759                            entry.git_dir_scan_id = scan_id;
4760                        },
4761                    );
4762                }
4763            };
4764        }
4765
4766        // Remove any git repositories whose .git entry no longer exists.
4767        let snapshot = &mut state.snapshot;
4768        let mut ids_to_preserve = HashSet::default();
4769        for (&work_directory_id, entry) in snapshot.git_repositories.iter() {
4770            let exists_in_snapshot = snapshot
4771                .entry_for_id(work_directory_id)
4772                .map_or(false, |entry| {
4773                    snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some()
4774                });
4775
4776            if exists_in_snapshot
4777                || matches!(
4778                    smol::block_on(self.fs.metadata(&entry.common_dir_abs_path)),
4779                    Ok(Some(_))
4780                )
4781            {
4782                ids_to_preserve.insert(work_directory_id);
4783            }
4784        }
4785
4786        snapshot
4787            .git_repositories
4788            .retain(|work_directory_id, _| ids_to_preserve.contains(work_directory_id));
4789    }
4790
4791    async fn progress_timer(&self, running: bool) {
4792        if !running {
4793            return futures::future::pending().await;
4794        }
4795
4796        #[cfg(any(test, feature = "test-support"))]
4797        if self.fs.is_fake() {
4798            return self.executor.simulate_random_delay().await;
4799        }
4800
4801        smol::Timer::after(FS_WATCH_LATENCY).await;
4802    }
4803
4804    fn is_path_private(&self, path: &Path) -> bool {
4805        !self.share_private_files && self.settings.is_path_private(path)
4806    }
4807
4808    async fn next_scan_request(&self) -> Result<ScanRequest> {
4809        let mut request = self.scan_requests_rx.recv().await?;
4810        while let Ok(next_request) = self.scan_requests_rx.try_recv() {
4811            request.relative_paths.extend(next_request.relative_paths);
4812            request.done.extend(next_request.done);
4813        }
4814        Ok(request)
4815    }
4816}
4817
4818async fn discover_ancestor_git_repo(
4819    fs: Arc<dyn Fs>,
4820    root_abs_path: &SanitizedPath,
4821) -> (
4822    HashMap<Arc<Path>, (Arc<Gitignore>, bool)>,
4823    Option<(PathBuf, WorkDirectory)>,
4824) {
4825    let mut ignores = HashMap::default();
4826    for (index, ancestor) in root_abs_path.as_path().ancestors().enumerate() {
4827        if index != 0 {
4828            if Some(ancestor) == fs.home_dir().as_deref() {
4829                // Unless $HOME is itself the worktree root, don't consider it as a
4830                // containing git repository---expensive and likely unwanted.
4831                break;
4832            } else if let Ok(ignore) =
4833                build_gitignore(&ancestor.join(*GITIGNORE), fs.as_ref()).await
4834            {
4835                ignores.insert(ancestor.into(), (ignore.into(), false));
4836            }
4837        }
4838
4839        let ancestor_dot_git = ancestor.join(*DOT_GIT);
4840        log::trace!("considering ancestor: {ancestor_dot_git:?}");
4841        // Check whether the directory or file called `.git` exists (in the
4842        // case of worktrees it's a file.)
4843        if fs
4844            .metadata(&ancestor_dot_git)
4845            .await
4846            .is_ok_and(|metadata| metadata.is_some())
4847        {
4848            if index != 0 {
4849                // We canonicalize, since the FS events use the canonicalized path.
4850                if let Some(ancestor_dot_git) = fs.canonicalize(&ancestor_dot_git).await.log_err() {
4851                    let location_in_repo = root_abs_path
4852                        .as_path()
4853                        .strip_prefix(ancestor)
4854                        .unwrap()
4855                        .into();
4856                    log::info!("inserting parent git repo for this worktree: {location_in_repo:?}");
4857                    // We associate the external git repo with our root folder and
4858                    // also mark where in the git repo the root folder is located.
4859                    return (
4860                        ignores,
4861                        Some((
4862                            ancestor_dot_git,
4863                            WorkDirectory::AboveProject {
4864                                absolute_path: ancestor.into(),
4865                                location_in_repo,
4866                            },
4867                        )),
4868                    );
4869                };
4870            }
4871
4872            // Reached root of git repository.
4873            break;
4874        }
4875    }
4876
4877    (ignores, None)
4878}
4879
4880fn build_diff(
4881    phase: BackgroundScannerPhase,
4882    old_snapshot: &Snapshot,
4883    new_snapshot: &Snapshot,
4884    event_paths: &[Arc<Path>],
4885) -> UpdatedEntriesSet {
4886    use BackgroundScannerPhase::*;
4887    use PathChange::{Added, AddedOrUpdated, Loaded, Removed, Updated};
4888
4889    // Identify which paths have changed. Use the known set of changed
4890    // parent paths to optimize the search.
4891    let mut changes = Vec::new();
4892    let mut old_paths = old_snapshot.entries_by_path.cursor::<PathKey>(&());
4893    let mut new_paths = new_snapshot.entries_by_path.cursor::<PathKey>(&());
4894    let mut last_newly_loaded_dir_path = None;
4895    old_paths.next(&());
4896    new_paths.next(&());
4897    for path in event_paths {
4898        let path = PathKey(path.clone());
4899        if old_paths.item().map_or(false, |e| e.path < path.0) {
4900            old_paths.seek_forward(&path, Bias::Left, &());
4901        }
4902        if new_paths.item().map_or(false, |e| e.path < path.0) {
4903            new_paths.seek_forward(&path, Bias::Left, &());
4904        }
4905        loop {
4906            match (old_paths.item(), new_paths.item()) {
4907                (Some(old_entry), Some(new_entry)) => {
4908                    if old_entry.path > path.0
4909                        && new_entry.path > path.0
4910                        && !old_entry.path.starts_with(&path.0)
4911                        && !new_entry.path.starts_with(&path.0)
4912                    {
4913                        break;
4914                    }
4915
4916                    match Ord::cmp(&old_entry.path, &new_entry.path) {
4917                        Ordering::Less => {
4918                            changes.push((old_entry.path.clone(), old_entry.id, Removed));
4919                            old_paths.next(&());
4920                        }
4921                        Ordering::Equal => {
4922                            if phase == EventsReceivedDuringInitialScan {
4923                                if old_entry.id != new_entry.id {
4924                                    changes.push((old_entry.path.clone(), old_entry.id, Removed));
4925                                }
4926                                // If the worktree was not fully initialized when this event was generated,
4927                                // we can't know whether this entry was added during the scan or whether
4928                                // it was merely updated.
4929                                changes.push((
4930                                    new_entry.path.clone(),
4931                                    new_entry.id,
4932                                    AddedOrUpdated,
4933                                ));
4934                            } else if old_entry.id != new_entry.id {
4935                                changes.push((old_entry.path.clone(), old_entry.id, Removed));
4936                                changes.push((new_entry.path.clone(), new_entry.id, Added));
4937                            } else if old_entry != new_entry {
4938                                if old_entry.kind.is_unloaded() {
4939                                    last_newly_loaded_dir_path = Some(&new_entry.path);
4940                                    changes.push((new_entry.path.clone(), new_entry.id, Loaded));
4941                                } else {
4942                                    changes.push((new_entry.path.clone(), new_entry.id, Updated));
4943                                }
4944                            }
4945                            old_paths.next(&());
4946                            new_paths.next(&());
4947                        }
4948                        Ordering::Greater => {
4949                            let is_newly_loaded = phase == InitialScan
4950                                || last_newly_loaded_dir_path
4951                                    .as_ref()
4952                                    .map_or(false, |dir| new_entry.path.starts_with(dir));
4953                            changes.push((
4954                                new_entry.path.clone(),
4955                                new_entry.id,
4956                                if is_newly_loaded { Loaded } else { Added },
4957                            ));
4958                            new_paths.next(&());
4959                        }
4960                    }
4961                }
4962                (Some(old_entry), None) => {
4963                    changes.push((old_entry.path.clone(), old_entry.id, Removed));
4964                    old_paths.next(&());
4965                }
4966                (None, Some(new_entry)) => {
4967                    let is_newly_loaded = phase == InitialScan
4968                        || last_newly_loaded_dir_path
4969                            .as_ref()
4970                            .map_or(false, |dir| new_entry.path.starts_with(dir));
4971                    changes.push((
4972                        new_entry.path.clone(),
4973                        new_entry.id,
4974                        if is_newly_loaded { Loaded } else { Added },
4975                    ));
4976                    new_paths.next(&());
4977                }
4978                (None, None) => break,
4979            }
4980        }
4981    }
4982
4983    changes.into()
4984}
4985
4986fn swap_to_front(child_paths: &mut Vec<PathBuf>, file: &OsStr) {
4987    let position = child_paths
4988        .iter()
4989        .position(|path| path.file_name().unwrap() == file);
4990    if let Some(position) = position {
4991        let temp = child_paths.remove(position);
4992        child_paths.insert(0, temp);
4993    }
4994}
4995
4996fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
4997    let mut result = root_char_bag;
4998    result.extend(
4999        path.to_string_lossy()
5000            .chars()
5001            .map(|c| c.to_ascii_lowercase()),
5002    );
5003    result
5004}
5005
5006#[derive(Debug)]
5007struct ScanJob {
5008    abs_path: Arc<Path>,
5009    path: Arc<Path>,
5010    ignore_stack: Arc<IgnoreStack>,
5011    scan_queue: Sender<ScanJob>,
5012    ancestor_inodes: TreeSet<u64>,
5013    is_external: bool,
5014}
5015
5016struct UpdateIgnoreStatusJob {
5017    abs_path: Arc<Path>,
5018    ignore_stack: Arc<IgnoreStack>,
5019    ignore_queue: Sender<UpdateIgnoreStatusJob>,
5020    scan_queue: Sender<ScanJob>,
5021}
5022
5023pub trait WorktreeModelHandle {
5024    #[cfg(any(test, feature = "test-support"))]
5025    fn flush_fs_events<'a>(
5026        &self,
5027        cx: &'a mut gpui::TestAppContext,
5028    ) -> futures::future::LocalBoxFuture<'a, ()>;
5029
5030    #[cfg(any(test, feature = "test-support"))]
5031    fn flush_fs_events_in_root_git_repository<'a>(
5032        &self,
5033        cx: &'a mut gpui::TestAppContext,
5034    ) -> futures::future::LocalBoxFuture<'a, ()>;
5035}
5036
5037impl WorktreeModelHandle for Entity<Worktree> {
5038    // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
5039    // occurred before the worktree was constructed. These events can cause the worktree to perform
5040    // extra directory scans, and emit extra scan-state notifications.
5041    //
5042    // This function mutates the worktree's directory and waits for those mutations to be picked up,
5043    // to ensure that all redundant FS events have already been processed.
5044    #[cfg(any(test, feature = "test-support"))]
5045    fn flush_fs_events<'a>(
5046        &self,
5047        cx: &'a mut gpui::TestAppContext,
5048    ) -> futures::future::LocalBoxFuture<'a, ()> {
5049        let file_name = "fs-event-sentinel";
5050
5051        let tree = self.clone();
5052        let (fs, root_path) = self.update(cx, |tree, _| {
5053            let tree = tree.as_local().unwrap();
5054            (tree.fs.clone(), tree.abs_path().clone())
5055        });
5056
5057        async move {
5058            fs.create_file(&root_path.join(file_name), Default::default())
5059                .await
5060                .unwrap();
5061
5062            let mut events = cx.events(&tree);
5063            while events.next().await.is_some() {
5064                if tree.update(cx, |tree, _| tree.entry_for_path(file_name).is_some()) {
5065                    break;
5066                }
5067            }
5068
5069            fs.remove_file(&root_path.join(file_name), Default::default())
5070                .await
5071                .unwrap();
5072            while events.next().await.is_some() {
5073                if tree.update(cx, |tree, _| tree.entry_for_path(file_name).is_none()) {
5074                    break;
5075                }
5076            }
5077
5078            cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
5079                .await;
5080        }
5081        .boxed_local()
5082    }
5083
5084    // This function is similar to flush_fs_events, except that it waits for events to be flushed in
5085    // the .git folder of the root repository.
5086    // The reason for its existence is that a repository's .git folder might live *outside* of the
5087    // worktree and thus its FS events might go through a different path.
5088    // In order to flush those, we need to create artificial events in the .git folder and wait
5089    // for the repository to be reloaded.
5090    #[cfg(any(test, feature = "test-support"))]
5091    fn flush_fs_events_in_root_git_repository<'a>(
5092        &self,
5093        cx: &'a mut gpui::TestAppContext,
5094    ) -> futures::future::LocalBoxFuture<'a, ()> {
5095        let file_name = "fs-event-sentinel";
5096
5097        let tree = self.clone();
5098        let (fs, root_path, mut git_dir_scan_id) = self.update(cx, |tree, _| {
5099            let tree = tree.as_local().unwrap();
5100            let local_repo_entry = tree
5101                .git_repositories
5102                .values()
5103                .min_by_key(|local_repo_entry| local_repo_entry.work_directory.clone())
5104                .unwrap();
5105            (
5106                tree.fs.clone(),
5107                local_repo_entry.common_dir_abs_path.clone(),
5108                local_repo_entry.git_dir_scan_id,
5109            )
5110        });
5111
5112        let scan_id_increased = |tree: &mut Worktree, git_dir_scan_id: &mut usize| {
5113            let tree = tree.as_local().unwrap();
5114            // let repository = tree.repositories.first().unwrap();
5115            let local_repo_entry = tree
5116                .git_repositories
5117                .values()
5118                .min_by_key(|local_repo_entry| local_repo_entry.work_directory.clone())
5119                .unwrap();
5120
5121            if local_repo_entry.git_dir_scan_id > *git_dir_scan_id {
5122                *git_dir_scan_id = local_repo_entry.git_dir_scan_id;
5123                true
5124            } else {
5125                false
5126            }
5127        };
5128
5129        async move {
5130            fs.create_file(&root_path.join(file_name), Default::default())
5131                .await
5132                .unwrap();
5133
5134            let mut events = cx.events(&tree);
5135            while events.next().await.is_some() {
5136                if tree.update(cx, |tree, _| scan_id_increased(tree, &mut git_dir_scan_id)) {
5137                    break;
5138                }
5139            }
5140
5141            fs.remove_file(&root_path.join(file_name), Default::default())
5142                .await
5143                .unwrap();
5144
5145            while events.next().await.is_some() {
5146                if tree.update(cx, |tree, _| scan_id_increased(tree, &mut git_dir_scan_id)) {
5147                    break;
5148                }
5149            }
5150
5151            cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
5152                .await;
5153        }
5154        .boxed_local()
5155    }
5156}
5157
5158#[derive(Clone, Debug)]
5159struct TraversalProgress<'a> {
5160    max_path: &'a Path,
5161    count: usize,
5162    non_ignored_count: usize,
5163    file_count: usize,
5164    non_ignored_file_count: usize,
5165}
5166
5167impl TraversalProgress<'_> {
5168    fn count(&self, include_files: bool, include_dirs: bool, include_ignored: bool) -> usize {
5169        match (include_files, include_dirs, include_ignored) {
5170            (true, true, true) => self.count,
5171            (true, true, false) => self.non_ignored_count,
5172            (true, false, true) => self.file_count,
5173            (true, false, false) => self.non_ignored_file_count,
5174            (false, true, true) => self.count - self.file_count,
5175            (false, true, false) => self.non_ignored_count - self.non_ignored_file_count,
5176            (false, false, _) => 0,
5177        }
5178    }
5179}
5180
5181impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> {
5182    fn zero(_cx: &()) -> Self {
5183        Default::default()
5184    }
5185
5186    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
5187        self.max_path = summary.max_path.as_ref();
5188        self.count += summary.count;
5189        self.non_ignored_count += summary.non_ignored_count;
5190        self.file_count += summary.file_count;
5191        self.non_ignored_file_count += summary.non_ignored_file_count;
5192    }
5193}
5194
5195impl Default for TraversalProgress<'_> {
5196    fn default() -> Self {
5197        Self {
5198            max_path: Path::new(""),
5199            count: 0,
5200            non_ignored_count: 0,
5201            file_count: 0,
5202            non_ignored_file_count: 0,
5203        }
5204    }
5205}
5206
5207#[derive(Debug)]
5208pub struct Traversal<'a> {
5209    snapshot: &'a Snapshot,
5210    cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>,
5211    include_ignored: bool,
5212    include_files: bool,
5213    include_dirs: bool,
5214}
5215
5216impl<'a> Traversal<'a> {
5217    fn new(
5218        snapshot: &'a Snapshot,
5219        include_files: bool,
5220        include_dirs: bool,
5221        include_ignored: bool,
5222        start_path: &Path,
5223    ) -> Self {
5224        let mut cursor = snapshot.entries_by_path.cursor(&());
5225        cursor.seek(&TraversalTarget::path(start_path), Bias::Left, &());
5226        let mut traversal = Self {
5227            snapshot,
5228            cursor,
5229            include_files,
5230            include_dirs,
5231            include_ignored,
5232        };
5233        if traversal.end_offset() == traversal.start_offset() {
5234            traversal.next();
5235        }
5236        traversal
5237    }
5238
5239    pub fn advance(&mut self) -> bool {
5240        self.advance_by(1)
5241    }
5242
5243    pub fn advance_by(&mut self, count: usize) -> bool {
5244        self.cursor.seek_forward(
5245            &TraversalTarget::Count {
5246                count: self.end_offset() + count,
5247                include_dirs: self.include_dirs,
5248                include_files: self.include_files,
5249                include_ignored: self.include_ignored,
5250            },
5251            Bias::Left,
5252            &(),
5253        )
5254    }
5255
5256    pub fn advance_to_sibling(&mut self) -> bool {
5257        while let Some(entry) = self.cursor.item() {
5258            self.cursor
5259                .seek_forward(&TraversalTarget::successor(&entry.path), Bias::Left, &());
5260            if let Some(entry) = self.cursor.item() {
5261                if (self.include_files || !entry.is_file())
5262                    && (self.include_dirs || !entry.is_dir())
5263                    && (self.include_ignored || !entry.is_ignored || entry.is_always_included)
5264                {
5265                    return true;
5266                }
5267            }
5268        }
5269        false
5270    }
5271
5272    pub fn back_to_parent(&mut self) -> bool {
5273        let Some(parent_path) = self.cursor.item().and_then(|entry| entry.path.parent()) else {
5274            return false;
5275        };
5276        self.cursor
5277            .seek(&TraversalTarget::path(parent_path), Bias::Left, &())
5278    }
5279
5280    pub fn entry(&self) -> Option<&'a Entry> {
5281        self.cursor.item()
5282    }
5283
5284    pub fn snapshot(&self) -> &'a Snapshot {
5285        self.snapshot
5286    }
5287
5288    pub fn start_offset(&self) -> usize {
5289        self.cursor
5290            .start()
5291            .count(self.include_files, self.include_dirs, self.include_ignored)
5292    }
5293
5294    pub fn end_offset(&self) -> usize {
5295        self.cursor
5296            .end(&())
5297            .count(self.include_files, self.include_dirs, self.include_ignored)
5298    }
5299}
5300
5301impl<'a> Iterator for Traversal<'a> {
5302    type Item = &'a Entry;
5303
5304    fn next(&mut self) -> Option<Self::Item> {
5305        if let Some(item) = self.entry() {
5306            self.advance();
5307            Some(item)
5308        } else {
5309            None
5310        }
5311    }
5312}
5313
5314#[derive(Debug, Clone, Copy)]
5315pub enum PathTarget<'a> {
5316    Path(&'a Path),
5317    Successor(&'a Path),
5318}
5319
5320impl PathTarget<'_> {
5321    fn cmp_path(&self, other: &Path) -> Ordering {
5322        match self {
5323            PathTarget::Path(path) => path.cmp(&other),
5324            PathTarget::Successor(path) => {
5325                if other.starts_with(path) {
5326                    Ordering::Greater
5327                } else {
5328                    Ordering::Equal
5329                }
5330            }
5331        }
5332    }
5333}
5334
5335impl<'a, S: Summary> SeekTarget<'a, PathSummary<S>, PathProgress<'a>> for PathTarget<'_> {
5336    fn cmp(&self, cursor_location: &PathProgress<'a>, _: &S::Context) -> Ordering {
5337        self.cmp_path(&cursor_location.max_path)
5338    }
5339}
5340
5341impl<'a, S: Summary> SeekTarget<'a, PathSummary<S>, TraversalProgress<'a>> for PathTarget<'_> {
5342    fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &S::Context) -> Ordering {
5343        self.cmp_path(&cursor_location.max_path)
5344    }
5345}
5346
5347#[derive(Debug)]
5348enum TraversalTarget<'a> {
5349    Path(PathTarget<'a>),
5350    Count {
5351        count: usize,
5352        include_files: bool,
5353        include_ignored: bool,
5354        include_dirs: bool,
5355    },
5356}
5357
5358impl<'a> TraversalTarget<'a> {
5359    fn path(path: &'a Path) -> Self {
5360        Self::Path(PathTarget::Path(path))
5361    }
5362
5363    fn successor(path: &'a Path) -> Self {
5364        Self::Path(PathTarget::Successor(path))
5365    }
5366
5367    fn cmp_progress(&self, progress: &TraversalProgress) -> Ordering {
5368        match self {
5369            TraversalTarget::Path(path) => path.cmp_path(&progress.max_path),
5370            TraversalTarget::Count {
5371                count,
5372                include_files,
5373                include_dirs,
5374                include_ignored,
5375            } => Ord::cmp(
5376                count,
5377                &progress.count(*include_files, *include_dirs, *include_ignored),
5378            ),
5379        }
5380    }
5381}
5382
5383impl<'a> SeekTarget<'a, EntrySummary, TraversalProgress<'a>> for TraversalTarget<'_> {
5384    fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering {
5385        self.cmp_progress(cursor_location)
5386    }
5387}
5388
5389impl<'a> SeekTarget<'a, PathSummary<Unit>, TraversalProgress<'a>> for TraversalTarget<'_> {
5390    fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering {
5391        self.cmp_progress(cursor_location)
5392    }
5393}
5394
5395pub struct ChildEntriesOptions {
5396    pub include_files: bool,
5397    pub include_dirs: bool,
5398    pub include_ignored: bool,
5399}
5400
5401pub struct ChildEntriesIter<'a> {
5402    parent_path: &'a Path,
5403    traversal: Traversal<'a>,
5404}
5405
5406impl<'a> Iterator for ChildEntriesIter<'a> {
5407    type Item = &'a Entry;
5408
5409    fn next(&mut self) -> Option<Self::Item> {
5410        if let Some(item) = self.traversal.entry() {
5411            if item.path.starts_with(self.parent_path) {
5412                self.traversal.advance_to_sibling();
5413                return Some(item);
5414            }
5415        }
5416        None
5417    }
5418}
5419
5420impl<'a> From<&'a Entry> for proto::Entry {
5421    fn from(entry: &'a Entry) -> Self {
5422        Self {
5423            id: entry.id.to_proto(),
5424            is_dir: entry.is_dir(),
5425            path: entry.path.as_ref().to_proto(),
5426            inode: entry.inode,
5427            mtime: entry.mtime.map(|time| time.into()),
5428            is_ignored: entry.is_ignored,
5429            is_external: entry.is_external,
5430            is_fifo: entry.is_fifo,
5431            size: Some(entry.size),
5432            canonical_path: entry
5433                .canonical_path
5434                .as_ref()
5435                .map(|path| path.as_ref().to_proto()),
5436        }
5437    }
5438}
5439
5440impl<'a> TryFrom<(&'a CharBag, &PathMatcher, proto::Entry)> for Entry {
5441    type Error = anyhow::Error;
5442
5443    fn try_from(
5444        (root_char_bag, always_included, entry): (&'a CharBag, &PathMatcher, proto::Entry),
5445    ) -> Result<Self> {
5446        let kind = if entry.is_dir {
5447            EntryKind::Dir
5448        } else {
5449            EntryKind::File
5450        };
5451
5452        let path = Arc::<Path>::from_proto(entry.path);
5453        let char_bag = char_bag_for_path(*root_char_bag, &path);
5454        let is_always_included = always_included.is_match(path.as_ref());
5455        Ok(Entry {
5456            id: ProjectEntryId::from_proto(entry.id),
5457            kind,
5458            path,
5459            inode: entry.inode,
5460            mtime: entry.mtime.map(|time| time.into()),
5461            size: entry.size.unwrap_or(0),
5462            canonical_path: entry
5463                .canonical_path
5464                .map(|path_string| Arc::from(PathBuf::from_proto(path_string))),
5465            is_ignored: entry.is_ignored,
5466            is_always_included,
5467            is_external: entry.is_external,
5468            is_private: false,
5469            char_bag,
5470            is_fifo: entry.is_fifo,
5471        })
5472    }
5473}
5474
5475#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
5476pub struct ProjectEntryId(usize);
5477
5478impl ProjectEntryId {
5479    pub const MAX: Self = Self(usize::MAX);
5480    pub const MIN: Self = Self(usize::MIN);
5481
5482    pub fn new(counter: &AtomicUsize) -> Self {
5483        Self(counter.fetch_add(1, SeqCst))
5484    }
5485
5486    pub fn from_proto(id: u64) -> Self {
5487        Self(id as usize)
5488    }
5489
5490    pub fn to_proto(&self) -> u64 {
5491        self.0 as u64
5492    }
5493
5494    pub fn to_usize(&self) -> usize {
5495        self.0
5496    }
5497}
5498
5499#[cfg(any(test, feature = "test-support"))]
5500impl CreatedEntry {
5501    pub fn to_included(self) -> Option<Entry> {
5502        match self {
5503            CreatedEntry::Included(entry) => Some(entry),
5504            CreatedEntry::Excluded { .. } => None,
5505        }
5506    }
5507}
5508
5509fn parse_gitfile(content: &str) -> anyhow::Result<&Path> {
5510    let path = content
5511        .strip_prefix("gitdir:")
5512        .ok_or_else(|| anyhow!("failed to parse gitfile content {content:?}"))?;
5513    Ok(Path::new(path.trim()))
5514}
5515
5516fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>, Arc<Path>) {
5517    let mut repository_dir_abs_path = dot_git_abs_path.clone();
5518    let mut common_dir_abs_path = dot_git_abs_path.clone();
5519
5520    if let Some(path) = smol::block_on(fs.load(&dot_git_abs_path))
5521        .ok()
5522        .as_ref()
5523        .and_then(|contents| parse_gitfile(contents).log_err())
5524    {
5525        let path = dot_git_abs_path
5526            .parent()
5527            .unwrap_or(Path::new(""))
5528            .join(path);
5529        if let Some(path) = smol::block_on(fs.canonicalize(&path)).log_err() {
5530            repository_dir_abs_path = Path::new(&path).into();
5531            common_dir_abs_path = repository_dir_abs_path.clone();
5532            if let Some(commondir_contents) = smol::block_on(fs.load(&path.join("commondir"))).ok()
5533            {
5534                if let Some(commondir_path) =
5535                    smol::block_on(fs.canonicalize(&path.join(commondir_contents.trim()))).log_err()
5536                {
5537                    common_dir_abs_path = commondir_path.as_path().into();
5538                }
5539            }
5540        }
5541    };
5542
5543    (repository_dir_abs_path, common_dir_abs_path)
5544}