worktree.rs

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