worktree.rs

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