worktree.rs

   1use super::{ignore::IgnoreStack, DiagnosticSummary};
   2use crate::{copy_recursive, ProjectEntryId, RemoveOptions};
   3use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
   4use anyhow::{anyhow, Context, Result};
   5use client::{proto, Client};
   6use clock::ReplicaId;
   7use collections::{HashMap, VecDeque};
   8use fs::{repository::GitRepository, Fs};
   9use fs::{HomeDir, LineEnding};
  10use futures::{
  11    channel::{
  12        mpsc::{self, UnboundedSender},
  13        oneshot,
  14    },
  15    Stream, StreamExt,
  16};
  17use fuzzy::CharBag;
  18use git::{DOT_GIT, GITIGNORE};
  19use gpui::{
  20    executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
  21    Task,
  22};
  23use language::Unclipped;
  24use language::{
  25    proto::{deserialize_version, serialize_line_ending, serialize_version},
  26    Buffer, DiagnosticEntry, PointUtf16, Rope,
  27};
  28use parking_lot::Mutex;
  29use postage::{
  30    prelude::{Sink as _, Stream as _},
  31    watch,
  32};
  33
  34use smol::channel::{self, Sender};
  35use std::{
  36    any::Any,
  37    cmp::{self, Ordering},
  38    convert::TryFrom,
  39    ffi::{OsStr, OsString},
  40    fmt,
  41    future::Future,
  42    mem,
  43    ops::{Deref, DerefMut},
  44    path::{Path, PathBuf},
  45    sync::{atomic::AtomicUsize, Arc},
  46    task::Poll,
  47    time::{Duration, SystemTime},
  48};
  49use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
  50use util::{ResultExt, TryFutureExt};
  51
  52#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
  53pub struct WorktreeId(usize);
  54
  55#[allow(clippy::large_enum_variant)]
  56pub enum Worktree {
  57    Local(LocalWorktree),
  58    Remote(RemoteWorktree),
  59}
  60
  61pub struct LocalWorktree {
  62    snapshot: LocalSnapshot,
  63    background_snapshot: Arc<Mutex<LocalSnapshot>>,
  64    last_scan_state_rx: watch::Receiver<ScanState>,
  65    _background_scanner_task: Option<Task<()>>,
  66    poll_task: Option<Task<()>>,
  67    share: Option<ShareState>,
  68    diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<Unclipped<PointUtf16>>>>,
  69    diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
  70    client: Arc<Client>,
  71    fs: Arc<dyn Fs>,
  72    visible: bool,
  73}
  74
  75pub struct RemoteWorktree {
  76    pub snapshot: Snapshot,
  77    pub(crate) background_snapshot: Arc<Mutex<Snapshot>>,
  78    project_id: u64,
  79    client: Arc<Client>,
  80    updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
  81    snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
  82    replica_id: ReplicaId,
  83    diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
  84    visible: bool,
  85    disconnected: bool,
  86}
  87
  88#[derive(Clone)]
  89pub struct Snapshot {
  90    id: WorktreeId,
  91    abs_path: Arc<Path>,
  92    root_name: String,
  93    root_char_bag: CharBag,
  94    entries_by_path: SumTree<Entry>,
  95    entries_by_id: SumTree<PathEntry>,
  96    scan_id: usize,
  97    is_complete: bool,
  98}
  99
 100#[derive(Clone)]
 101pub struct GitRepositoryEntry {
 102    pub(crate) repo: Arc<Mutex<dyn GitRepository>>,
 103
 104    pub(crate) scan_id: usize,
 105    // Path to folder containing the .git file or directory
 106    pub(crate) content_path: Arc<Path>,
 107    // Path to the actual .git folder.
 108    // Note: if .git is a file, this points to the folder indicated by the .git file
 109    pub(crate) git_dir_path: Arc<Path>,
 110}
 111
 112impl std::fmt::Debug for GitRepositoryEntry {
 113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 114        f.debug_struct("GitRepositoryEntry")
 115            .field("content_path", &self.content_path)
 116            .field("git_dir_path", &self.git_dir_path)
 117            .field("libgit_repository", &"LibGitRepository")
 118            .finish()
 119    }
 120}
 121
 122pub struct LocalSnapshot {
 123    ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
 124    git_repositories: Vec<GitRepositoryEntry>,
 125    removed_entry_ids: HashMap<u64, ProjectEntryId>,
 126    next_entry_id: Arc<AtomicUsize>,
 127    snapshot: Snapshot,
 128}
 129
 130impl Clone for LocalSnapshot {
 131    fn clone(&self) -> Self {
 132        Self {
 133            ignores_by_parent_abs_path: self.ignores_by_parent_abs_path.clone(),
 134            git_repositories: self.git_repositories.iter().cloned().collect(),
 135            removed_entry_ids: self.removed_entry_ids.clone(),
 136            next_entry_id: self.next_entry_id.clone(),
 137            snapshot: self.snapshot.clone(),
 138        }
 139    }
 140}
 141
 142impl Deref for LocalSnapshot {
 143    type Target = Snapshot;
 144
 145    fn deref(&self) -> &Self::Target {
 146        &self.snapshot
 147    }
 148}
 149
 150impl DerefMut for LocalSnapshot {
 151    fn deref_mut(&mut self) -> &mut Self::Target {
 152        &mut self.snapshot
 153    }
 154}
 155
 156#[derive(Clone, Debug)]
 157enum ScanState {
 158    Idle,
 159    /// The worktree is performing its initial scan of the filesystem.
 160    Initializing,
 161    /// The worktree is updating in response to filesystem events.
 162    Updating,
 163    Err(Arc<anyhow::Error>),
 164}
 165
 166struct ShareState {
 167    project_id: u64,
 168    snapshots_tx: watch::Sender<LocalSnapshot>,
 169    _maintain_remote_snapshot: Task<Option<()>>,
 170}
 171
 172pub enum Event {
 173    UpdatedEntries,
 174    UpdatedGitRepositories(Vec<GitRepositoryEntry>),
 175}
 176
 177impl Entity for Worktree {
 178    type Event = Event;
 179}
 180
 181impl Worktree {
 182    pub async fn local(
 183        client: Arc<Client>,
 184        path: impl Into<Arc<Path>>,
 185        visible: bool,
 186        fs: Arc<dyn Fs>,
 187        next_entry_id: Arc<AtomicUsize>,
 188        cx: &mut AsyncAppContext,
 189    ) -> Result<ModelHandle<Self>> {
 190        let (tree, scan_states_tx) =
 191            LocalWorktree::create(client, path, visible, fs.clone(), next_entry_id, cx).await?;
 192        tree.update(cx, |tree, cx| {
 193            let tree = tree.as_local_mut().unwrap();
 194            let abs_path = tree.abs_path().clone();
 195            let background_snapshot = tree.background_snapshot.clone();
 196            let background = cx.background().clone();
 197            tree._background_scanner_task = Some(cx.background().spawn(async move {
 198                let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
 199                let scanner =
 200                    BackgroundScanner::new(background_snapshot, scan_states_tx, fs, background);
 201                scanner.run(events).await;
 202            }));
 203        });
 204        Ok(tree)
 205    }
 206
 207    pub fn remote(
 208        project_remote_id: u64,
 209        replica_id: ReplicaId,
 210        worktree: proto::WorktreeMetadata,
 211        client: Arc<Client>,
 212        cx: &mut MutableAppContext,
 213    ) -> ModelHandle<Self> {
 214        let remote_id = worktree.id;
 215        let root_char_bag: CharBag = worktree
 216            .root_name
 217            .chars()
 218            .map(|c| c.to_ascii_lowercase())
 219            .collect();
 220        let root_name = worktree.root_name.clone();
 221        let visible = worktree.visible;
 222
 223        let abs_path = PathBuf::from(worktree.abs_path);
 224        let snapshot = Snapshot {
 225            id: WorktreeId(remote_id as usize),
 226            abs_path: Arc::from(abs_path.deref()),
 227            root_name,
 228            root_char_bag,
 229            entries_by_path: Default::default(),
 230            entries_by_id: Default::default(),
 231            scan_id: 0,
 232            is_complete: false,
 233        };
 234
 235        let (updates_tx, mut updates_rx) = mpsc::unbounded();
 236        let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
 237        let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
 238        let worktree_handle = cx.add_model(|_: &mut ModelContext<Worktree>| {
 239            Worktree::Remote(RemoteWorktree {
 240                project_id: project_remote_id,
 241                replica_id,
 242                snapshot: snapshot.clone(),
 243                background_snapshot: background_snapshot.clone(),
 244                updates_tx: Some(updates_tx),
 245                snapshot_subscriptions: Default::default(),
 246                client: client.clone(),
 247                diagnostic_summaries: Default::default(),
 248                visible,
 249                disconnected: false,
 250            })
 251        });
 252
 253        cx.background()
 254            .spawn(async move {
 255                while let Some(update) = updates_rx.next().await {
 256                    if let Err(error) = background_snapshot.lock().apply_remote_update(update) {
 257                        log::error!("error applying worktree update: {}", error);
 258                    }
 259                    snapshot_updated_tx.send(()).await.ok();
 260                }
 261            })
 262            .detach();
 263
 264        cx.spawn(|mut cx| {
 265            let this = worktree_handle.downgrade();
 266            async move {
 267                while (snapshot_updated_rx.recv().await).is_some() {
 268                    if let Some(this) = this.upgrade(&cx) {
 269                        this.update(&mut cx, |this, cx| {
 270                            this.poll_snapshot(cx);
 271                            let this = this.as_remote_mut().unwrap();
 272                            while let Some((scan_id, _)) = this.snapshot_subscriptions.front() {
 273                                if this.observed_snapshot(*scan_id) {
 274                                    let (_, tx) = this.snapshot_subscriptions.pop_front().unwrap();
 275                                    let _ = tx.send(());
 276                                } else {
 277                                    break;
 278                                }
 279                            }
 280                        });
 281                    } else {
 282                        break;
 283                    }
 284                }
 285            }
 286        })
 287        .detach();
 288
 289        worktree_handle
 290    }
 291
 292    pub fn as_local(&self) -> Option<&LocalWorktree> {
 293        if let Worktree::Local(worktree) = self {
 294            Some(worktree)
 295        } else {
 296            None
 297        }
 298    }
 299
 300    pub fn as_remote(&self) -> Option<&RemoteWorktree> {
 301        if let Worktree::Remote(worktree) = self {
 302            Some(worktree)
 303        } else {
 304            None
 305        }
 306    }
 307
 308    pub fn as_local_mut(&mut self) -> Option<&mut LocalWorktree> {
 309        if let Worktree::Local(worktree) = self {
 310            Some(worktree)
 311        } else {
 312            None
 313        }
 314    }
 315
 316    pub fn as_remote_mut(&mut self) -> Option<&mut RemoteWorktree> {
 317        if let Worktree::Remote(worktree) = self {
 318            Some(worktree)
 319        } else {
 320            None
 321        }
 322    }
 323
 324    pub fn is_local(&self) -> bool {
 325        matches!(self, Worktree::Local(_))
 326    }
 327
 328    pub fn is_remote(&self) -> bool {
 329        !self.is_local()
 330    }
 331
 332    pub fn snapshot(&self) -> Snapshot {
 333        match self {
 334            Worktree::Local(worktree) => worktree.snapshot().snapshot,
 335            Worktree::Remote(worktree) => worktree.snapshot(),
 336        }
 337    }
 338
 339    pub fn scan_id(&self) -> usize {
 340        match self {
 341            Worktree::Local(worktree) => worktree.snapshot.scan_id,
 342            Worktree::Remote(worktree) => worktree.snapshot.scan_id,
 343        }
 344    }
 345
 346    pub fn is_visible(&self) -> bool {
 347        match self {
 348            Worktree::Local(worktree) => worktree.visible,
 349            Worktree::Remote(worktree) => worktree.visible,
 350        }
 351    }
 352
 353    pub fn replica_id(&self) -> ReplicaId {
 354        match self {
 355            Worktree::Local(_) => 0,
 356            Worktree::Remote(worktree) => worktree.replica_id,
 357        }
 358    }
 359
 360    pub fn diagnostic_summaries(
 361        &self,
 362    ) -> impl Iterator<Item = (Arc<Path>, DiagnosticSummary)> + '_ {
 363        match self {
 364            Worktree::Local(worktree) => &worktree.diagnostic_summaries,
 365            Worktree::Remote(worktree) => &worktree.diagnostic_summaries,
 366        }
 367        .iter()
 368        .map(|(path, summary)| (path.0.clone(), *summary))
 369    }
 370
 371    fn poll_snapshot(&mut self, cx: &mut ModelContext<Self>) {
 372        match self {
 373            Self::Local(worktree) => worktree.poll_snapshot(false, cx),
 374            Self::Remote(worktree) => worktree.poll_snapshot(cx),
 375        };
 376    }
 377
 378    pub fn abs_path(&self) -> Arc<Path> {
 379        match self {
 380            Worktree::Local(worktree) => worktree.abs_path.clone(),
 381            Worktree::Remote(worktree) => worktree.abs_path.clone(),
 382        }
 383    }
 384}
 385
 386impl LocalWorktree {
 387    async fn create(
 388        client: Arc<Client>,
 389        path: impl Into<Arc<Path>>,
 390        visible: bool,
 391        fs: Arc<dyn Fs>,
 392        next_entry_id: Arc<AtomicUsize>,
 393        cx: &mut AsyncAppContext,
 394    ) -> Result<(ModelHandle<Worktree>, UnboundedSender<ScanState>)> {
 395        let abs_path = path.into();
 396        let path: Arc<Path> = Arc::from(Path::new(""));
 397
 398        // After determining whether the root entry is a file or a directory, populate the
 399        // snapshot's "root name", which will be used for the purpose of fuzzy matching.
 400        let root_name = abs_path
 401            .file_name()
 402            .map_or(String::new(), |f| f.to_string_lossy().to_string());
 403        let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
 404        let metadata = fs
 405            .metadata(&abs_path)
 406            .await
 407            .context("failed to stat worktree path")?;
 408
 409        let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded();
 410        let (mut last_scan_state_tx, last_scan_state_rx) =
 411            watch::channel_with(ScanState::Initializing);
 412        let tree = cx.add_model(move |cx: &mut ModelContext<Worktree>| {
 413            let mut snapshot = LocalSnapshot {
 414                ignores_by_parent_abs_path: Default::default(),
 415                git_repositories: Default::default(),
 416                removed_entry_ids: Default::default(),
 417                next_entry_id,
 418                snapshot: Snapshot {
 419                    id: WorktreeId::from_usize(cx.model_id()),
 420                    abs_path,
 421                    root_name: root_name.clone(),
 422                    root_char_bag,
 423                    entries_by_path: Default::default(),
 424                    entries_by_id: Default::default(),
 425                    scan_id: 0,
 426                    is_complete: true,
 427                },
 428            };
 429            if let Some(metadata) = metadata {
 430                let entry = Entry::new(
 431                    path,
 432                    &metadata,
 433                    &snapshot.next_entry_id,
 434                    snapshot.root_char_bag,
 435                );
 436                snapshot.insert_entry(entry, fs.as_ref());
 437            }
 438
 439            let tree = Self {
 440                snapshot: snapshot.clone(),
 441                background_snapshot: Arc::new(Mutex::new(snapshot)),
 442                last_scan_state_rx,
 443                _background_scanner_task: None,
 444                share: None,
 445                poll_task: None,
 446                diagnostics: Default::default(),
 447                diagnostic_summaries: Default::default(),
 448                client,
 449                fs,
 450                visible,
 451            };
 452
 453            cx.spawn_weak(|this, mut cx| async move {
 454                while let Some(scan_state) = scan_states_rx.next().await {
 455                    if let Some(this) = this.upgrade(&cx) {
 456                        last_scan_state_tx.blocking_send(scan_state).ok();
 457                        this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
 458                    } else {
 459                        break;
 460                    }
 461                }
 462            })
 463            .detach();
 464
 465            Worktree::Local(tree)
 466        });
 467
 468        Ok((tree, scan_states_tx))
 469    }
 470
 471    pub fn contains_abs_path(&self, path: &Path) -> bool {
 472        path.starts_with(&self.abs_path)
 473    }
 474
 475    fn absolutize(&self, path: &Path) -> PathBuf {
 476        if path.file_name().is_some() {
 477            self.abs_path.join(path)
 478        } else {
 479            self.abs_path.to_path_buf()
 480        }
 481    }
 482
 483    pub(crate) fn load_buffer(
 484        &mut self,
 485        path: &Path,
 486        cx: &mut ModelContext<Worktree>,
 487    ) -> Task<Result<ModelHandle<Buffer>>> {
 488        let path = Arc::from(path);
 489        cx.spawn(move |this, mut cx| async move {
 490            let (file, contents, diff_base) = this
 491                .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))
 492                .await?;
 493            Ok(cx.add_model(|cx| {
 494                let mut buffer = Buffer::from_file(0, contents, diff_base, Arc::new(file), cx);
 495                buffer.git_diff_recalc(cx);
 496                buffer
 497            }))
 498        })
 499    }
 500
 501    pub fn diagnostics_for_path(
 502        &self,
 503        path: &Path,
 504    ) -> Option<Vec<DiagnosticEntry<Unclipped<PointUtf16>>>> {
 505        self.diagnostics.get(path).cloned()
 506    }
 507
 508    pub fn update_diagnostics(
 509        &mut self,
 510        language_server_id: usize,
 511        worktree_path: Arc<Path>,
 512        diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
 513        _: &mut ModelContext<Worktree>,
 514    ) -> Result<bool> {
 515        self.diagnostics.remove(&worktree_path);
 516        let old_summary = self
 517            .diagnostic_summaries
 518            .remove(&PathKey(worktree_path.clone()))
 519            .unwrap_or_default();
 520        let new_summary = DiagnosticSummary::new(language_server_id, &diagnostics);
 521        if !new_summary.is_empty() {
 522            self.diagnostic_summaries
 523                .insert(PathKey(worktree_path.clone()), new_summary);
 524            self.diagnostics.insert(worktree_path.clone(), diagnostics);
 525        }
 526
 527        let updated = !old_summary.is_empty() || !new_summary.is_empty();
 528        if updated {
 529            if let Some(share) = self.share.as_ref() {
 530                self.client
 531                    .send(proto::UpdateDiagnosticSummary {
 532                        project_id: share.project_id,
 533                        worktree_id: self.id().to_proto(),
 534                        summary: Some(proto::DiagnosticSummary {
 535                            path: worktree_path.to_string_lossy().to_string(),
 536                            language_server_id: language_server_id as u64,
 537                            error_count: new_summary.error_count as u32,
 538                            warning_count: new_summary.warning_count as u32,
 539                        }),
 540                    })
 541                    .log_err();
 542            }
 543        }
 544
 545        Ok(updated)
 546    }
 547
 548    fn poll_snapshot(&mut self, force: bool, cx: &mut ModelContext<Worktree>) {
 549        self.poll_task.take();
 550
 551        match self.scan_state() {
 552            ScanState::Idle => {
 553                let new_snapshot = self.background_snapshot.lock().clone();
 554                let updated_repos = Self::changed_repos(
 555                    &self.snapshot.git_repositories,
 556                    &new_snapshot.git_repositories,
 557                );
 558                self.snapshot = new_snapshot;
 559
 560                if let Some(share) = self.share.as_mut() {
 561                    *share.snapshots_tx.borrow_mut() = self.snapshot.clone();
 562                }
 563
 564                cx.emit(Event::UpdatedEntries);
 565
 566                if !updated_repos.is_empty() {
 567                    cx.emit(Event::UpdatedGitRepositories(updated_repos));
 568                }
 569            }
 570
 571            ScanState::Initializing => {
 572                let is_fake_fs = self.fs.is_fake();
 573
 574                let new_snapshot = self.background_snapshot.lock().clone();
 575                let updated_repos = Self::changed_repos(
 576                    &self.snapshot.git_repositories,
 577                    &new_snapshot.git_repositories,
 578                );
 579                self.snapshot = new_snapshot;
 580
 581                self.poll_task = Some(cx.spawn_weak(|this, mut cx| async move {
 582                    if is_fake_fs {
 583                        #[cfg(any(test, feature = "test-support"))]
 584                        cx.background().simulate_random_delay().await;
 585                    } else {
 586                        smol::Timer::after(Duration::from_millis(100)).await;
 587                    }
 588                    if let Some(this) = this.upgrade(&cx) {
 589                        this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
 590                    }
 591                }));
 592
 593                cx.emit(Event::UpdatedEntries);
 594
 595                if !updated_repos.is_empty() {
 596                    cx.emit(Event::UpdatedGitRepositories(updated_repos));
 597                }
 598            }
 599
 600            _ => {
 601                if force {
 602                    self.snapshot = self.background_snapshot.lock().clone();
 603                }
 604            }
 605        }
 606
 607        cx.notify();
 608    }
 609
 610    fn changed_repos(
 611        old_repos: &[GitRepositoryEntry],
 612        new_repos: &[GitRepositoryEntry],
 613    ) -> Vec<GitRepositoryEntry> {
 614        fn diff<'a>(
 615            a: &'a [GitRepositoryEntry],
 616            b: &'a [GitRepositoryEntry],
 617            updated: &mut HashMap<&'a Path, GitRepositoryEntry>,
 618        ) {
 619            for a_repo in a {
 620                let matched = b.iter().find(|b_repo| {
 621                    a_repo.git_dir_path == b_repo.git_dir_path && a_repo.scan_id == b_repo.scan_id
 622                });
 623
 624                if matched.is_none() {
 625                    updated.insert(a_repo.git_dir_path.as_ref(), a_repo.clone());
 626                }
 627            }
 628        }
 629
 630        let mut updated = HashMap::<&Path, GitRepositoryEntry>::default();
 631
 632        diff(old_repos, new_repos, &mut updated);
 633        diff(new_repos, old_repos, &mut updated);
 634
 635        updated.into_values().collect()
 636    }
 637
 638    pub fn scan_complete(&self) -> impl Future<Output = ()> {
 639        let mut scan_state_rx = self.last_scan_state_rx.clone();
 640        async move {
 641            let mut scan_state = Some(scan_state_rx.borrow().clone());
 642            while let Some(ScanState::Initializing | ScanState::Updating) = scan_state {
 643                scan_state = scan_state_rx.recv().await;
 644            }
 645        }
 646    }
 647
 648    fn scan_state(&self) -> ScanState {
 649        self.last_scan_state_rx.borrow().clone()
 650    }
 651
 652    pub fn snapshot(&self) -> LocalSnapshot {
 653        self.snapshot.clone()
 654    }
 655
 656    pub fn metadata_proto(&self) -> proto::WorktreeMetadata {
 657        proto::WorktreeMetadata {
 658            id: self.id().to_proto(),
 659            root_name: self.root_name().to_string(),
 660            visible: self.visible,
 661            abs_path: self.abs_path().as_os_str().to_string_lossy().into(),
 662        }
 663    }
 664
 665    fn load(
 666        &self,
 667        path: &Path,
 668        cx: &mut ModelContext<Worktree>,
 669    ) -> Task<Result<(File, String, Option<String>)>> {
 670        let handle = cx.handle();
 671        let path = Arc::from(path);
 672        let abs_path = self.absolutize(&path);
 673        let fs = self.fs.clone();
 674        let snapshot = self.snapshot();
 675
 676        cx.spawn(|this, mut cx| async move {
 677            let text = fs.load(&abs_path).await?;
 678
 679            let diff_base = if let Some(repo) = snapshot.repo_for(&path) {
 680                if let Ok(repo_relative) = path.strip_prefix(repo.content_path) {
 681                    let repo_relative = repo_relative.to_owned();
 682                    cx.background()
 683                        .spawn(async move { repo.repo.lock().load_index_text(&repo_relative) })
 684                        .await
 685                } else {
 686                    None
 687                }
 688            } else {
 689                None
 690            };
 691
 692            // Eagerly populate the snapshot with an updated entry for the loaded file
 693            let entry = this
 694                .update(&mut cx, |this, cx| {
 695                    this.as_local()
 696                        .unwrap()
 697                        .refresh_entry(path, abs_path, None, cx)
 698                })
 699                .await?;
 700
 701            Ok((
 702                File {
 703                    entry_id: entry.id,
 704                    worktree: handle,
 705                    path: entry.path,
 706                    mtime: entry.mtime,
 707                    is_local: true,
 708                    is_deleted: false,
 709                },
 710                text,
 711                diff_base,
 712            ))
 713        })
 714    }
 715
 716    pub fn save_buffer_as(
 717        &self,
 718        buffer_handle: ModelHandle<Buffer>,
 719        path: impl Into<Arc<Path>>,
 720        cx: &mut ModelContext<Worktree>,
 721    ) -> Task<Result<()>> {
 722        let buffer = buffer_handle.read(cx);
 723        let text = buffer.as_rope().clone();
 724        let fingerprint = text.fingerprint();
 725        let version = buffer.version();
 726        let save = self.write_file(path, text, buffer.line_ending(), cx);
 727        let handle = cx.handle();
 728        cx.as_mut().spawn(|mut cx| async move {
 729            let entry = save.await?;
 730            let file = File {
 731                entry_id: entry.id,
 732                worktree: handle,
 733                path: entry.path,
 734                mtime: entry.mtime,
 735                is_local: true,
 736                is_deleted: false,
 737            };
 738
 739            buffer_handle.update(&mut cx, |buffer, cx| {
 740                buffer.did_save(version, fingerprint, file.mtime, Some(Arc::new(file)), cx);
 741            });
 742
 743            Ok(())
 744        })
 745    }
 746
 747    pub fn create_entry(
 748        &self,
 749        path: impl Into<Arc<Path>>,
 750        is_dir: bool,
 751        cx: &mut ModelContext<Worktree>,
 752    ) -> Task<Result<Entry>> {
 753        self.write_entry_internal(
 754            path,
 755            if is_dir {
 756                None
 757            } else {
 758                Some(Default::default())
 759            },
 760            cx,
 761        )
 762    }
 763
 764    pub fn write_file(
 765        &self,
 766        path: impl Into<Arc<Path>>,
 767        text: Rope,
 768        line_ending: LineEnding,
 769        cx: &mut ModelContext<Worktree>,
 770    ) -> Task<Result<Entry>> {
 771        self.write_entry_internal(path, Some((text, line_ending)), cx)
 772    }
 773
 774    pub fn delete_entry(
 775        &self,
 776        entry_id: ProjectEntryId,
 777        cx: &mut ModelContext<Worktree>,
 778    ) -> Option<Task<Result<()>>> {
 779        let entry = self.entry_for_id(entry_id)?.clone();
 780        let abs_path = self.absolutize(&entry.path);
 781        let delete = cx.background().spawn({
 782            let fs = self.fs.clone();
 783            let abs_path = abs_path;
 784            async move {
 785                if entry.is_file() {
 786                    fs.remove_file(&abs_path, Default::default()).await
 787                } else {
 788                    fs.remove_dir(
 789                        &abs_path,
 790                        RemoveOptions {
 791                            recursive: true,
 792                            ignore_if_not_exists: false,
 793                        },
 794                    )
 795                    .await
 796                }
 797            }
 798        });
 799
 800        Some(cx.spawn(|this, mut cx| async move {
 801            delete.await?;
 802            this.update(&mut cx, |this, cx| {
 803                let this = this.as_local_mut().unwrap();
 804                {
 805                    let mut snapshot = this.background_snapshot.lock();
 806                    snapshot.delete_entry(entry_id);
 807                }
 808                this.poll_snapshot(true, cx);
 809            });
 810            Ok(())
 811        }))
 812    }
 813
 814    pub fn rename_entry(
 815        &self,
 816        entry_id: ProjectEntryId,
 817        new_path: impl Into<Arc<Path>>,
 818        cx: &mut ModelContext<Worktree>,
 819    ) -> Option<Task<Result<Entry>>> {
 820        let old_path = self.entry_for_id(entry_id)?.path.clone();
 821        let new_path = new_path.into();
 822        let abs_old_path = self.absolutize(&old_path);
 823        let abs_new_path = self.absolutize(&new_path);
 824        let rename = cx.background().spawn({
 825            let fs = self.fs.clone();
 826            let abs_new_path = abs_new_path.clone();
 827            async move {
 828                fs.rename(&abs_old_path, &abs_new_path, Default::default())
 829                    .await
 830            }
 831        });
 832
 833        Some(cx.spawn(|this, mut cx| async move {
 834            rename.await?;
 835            let entry = this
 836                .update(&mut cx, |this, cx| {
 837                    this.as_local_mut().unwrap().refresh_entry(
 838                        new_path.clone(),
 839                        abs_new_path,
 840                        Some(old_path),
 841                        cx,
 842                    )
 843                })
 844                .await?;
 845            Ok(entry)
 846        }))
 847    }
 848
 849    pub fn copy_entry(
 850        &self,
 851        entry_id: ProjectEntryId,
 852        new_path: impl Into<Arc<Path>>,
 853        cx: &mut ModelContext<Worktree>,
 854    ) -> Option<Task<Result<Entry>>> {
 855        let old_path = self.entry_for_id(entry_id)?.path.clone();
 856        let new_path = new_path.into();
 857        let abs_old_path = self.absolutize(&old_path);
 858        let abs_new_path = self.absolutize(&new_path);
 859        let copy = cx.background().spawn({
 860            let fs = self.fs.clone();
 861            let abs_new_path = abs_new_path.clone();
 862            async move {
 863                copy_recursive(
 864                    fs.as_ref(),
 865                    &abs_old_path,
 866                    &abs_new_path,
 867                    Default::default(),
 868                )
 869                .await
 870            }
 871        });
 872
 873        Some(cx.spawn(|this, mut cx| async move {
 874            copy.await?;
 875            let entry = this
 876                .update(&mut cx, |this, cx| {
 877                    this.as_local_mut().unwrap().refresh_entry(
 878                        new_path.clone(),
 879                        abs_new_path,
 880                        None,
 881                        cx,
 882                    )
 883                })
 884                .await?;
 885            Ok(entry)
 886        }))
 887    }
 888
 889    fn write_entry_internal(
 890        &self,
 891        path: impl Into<Arc<Path>>,
 892        text_if_file: Option<(Rope, LineEnding)>,
 893        cx: &mut ModelContext<Worktree>,
 894    ) -> Task<Result<Entry>> {
 895        let path = path.into();
 896        let abs_path = self.absolutize(&path);
 897        let write = cx.background().spawn({
 898            let fs = self.fs.clone();
 899            let abs_path = abs_path.clone();
 900            async move {
 901                if let Some((text, line_ending)) = text_if_file {
 902                    fs.save(&abs_path, &text, line_ending).await
 903                } else {
 904                    fs.create_dir(&abs_path).await
 905                }
 906            }
 907        });
 908
 909        cx.spawn(|this, mut cx| async move {
 910            write.await?;
 911            let entry = this
 912                .update(&mut cx, |this, cx| {
 913                    this.as_local_mut()
 914                        .unwrap()
 915                        .refresh_entry(path, abs_path, None, cx)
 916                })
 917                .await?;
 918            Ok(entry)
 919        })
 920    }
 921
 922    fn refresh_entry(
 923        &self,
 924        path: Arc<Path>,
 925        abs_path: PathBuf,
 926        old_path: Option<Arc<Path>>,
 927        cx: &mut ModelContext<Worktree>,
 928    ) -> Task<Result<Entry>> {
 929        let fs = self.fs.clone();
 930        let root_char_bag;
 931        let next_entry_id;
 932        {
 933            let snapshot = self.background_snapshot.lock();
 934            root_char_bag = snapshot.root_char_bag;
 935            next_entry_id = snapshot.next_entry_id.clone();
 936        }
 937        cx.spawn_weak(|this, mut cx| async move {
 938            let metadata = fs
 939                .metadata(&abs_path)
 940                .await?
 941                .ok_or_else(|| anyhow!("could not read saved file metadata"))?;
 942            let this = this
 943                .upgrade(&cx)
 944                .ok_or_else(|| anyhow!("worktree was dropped"))?;
 945            this.update(&mut cx, |this, cx| {
 946                let this = this.as_local_mut().unwrap();
 947                let inserted_entry;
 948                {
 949                    let mut snapshot = this.background_snapshot.lock();
 950                    let mut entry = Entry::new(path, &metadata, &next_entry_id, root_char_bag);
 951                    entry.is_ignored = snapshot
 952                        .ignore_stack_for_abs_path(&abs_path, entry.is_dir())
 953                        .is_abs_path_ignored(&abs_path, entry.is_dir());
 954                    if let Some(old_path) = old_path {
 955                        snapshot.remove_path(&old_path);
 956                    }
 957                    inserted_entry = snapshot.insert_entry(entry, fs.as_ref());
 958                    snapshot.scan_id += 1;
 959                }
 960                this.poll_snapshot(true, cx);
 961                Ok(inserted_entry)
 962            })
 963        })
 964    }
 965
 966    pub fn share(&mut self, project_id: u64, cx: &mut ModelContext<Worktree>) -> Task<Result<()>> {
 967        let (share_tx, share_rx) = oneshot::channel();
 968
 969        if self.share.is_some() {
 970            let _ = share_tx.send(Ok(()));
 971        } else {
 972            let (snapshots_tx, mut snapshots_rx) = watch::channel_with(self.snapshot());
 973            let worktree_id = cx.model_id() as u64;
 974
 975            for (path, summary) in self.diagnostic_summaries.iter() {
 976                if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
 977                    project_id,
 978                    worktree_id,
 979                    summary: Some(summary.to_proto(&path.0)),
 980                }) {
 981                    return Task::ready(Err(e));
 982                }
 983            }
 984
 985            let maintain_remote_snapshot = cx.background().spawn({
 986                let rpc = self.client.clone();
 987                async move {
 988                    let mut prev_snapshot = match snapshots_rx.recv().await {
 989                        Some(snapshot) => {
 990                            let update = proto::UpdateWorktree {
 991                                project_id,
 992                                worktree_id,
 993                                abs_path: snapshot.abs_path().to_string_lossy().into(),
 994                                root_name: snapshot.root_name().to_string(),
 995                                updated_entries: snapshot
 996                                    .entries_by_path
 997                                    .iter()
 998                                    .map(Into::into)
 999                                    .collect(),
1000                                removed_entries: Default::default(),
1001                                scan_id: snapshot.scan_id as u64,
1002                                is_last_update: true,
1003                            };
1004                            if let Err(error) = send_worktree_update(&rpc, update).await {
1005                                let _ = share_tx.send(Err(error));
1006                                return Err(anyhow!("failed to send initial update worktree"));
1007                            } else {
1008                                let _ = share_tx.send(Ok(()));
1009                                snapshot
1010                            }
1011                        }
1012                        None => {
1013                            share_tx
1014                                .send(Err(anyhow!("worktree dropped before share completed")))
1015                                .ok();
1016                            return Err(anyhow!("failed to send initial update worktree"));
1017                        }
1018                    };
1019
1020                    while let Some(snapshot) = snapshots_rx.recv().await {
1021                        send_worktree_update(
1022                            &rpc,
1023                            snapshot.build_update(&prev_snapshot, project_id, worktree_id, true),
1024                        )
1025                        .await?;
1026                        prev_snapshot = snapshot;
1027                    }
1028
1029                    Ok::<_, anyhow::Error>(())
1030                }
1031                .log_err()
1032            });
1033
1034            self.share = Some(ShareState {
1035                project_id,
1036                snapshots_tx,
1037                _maintain_remote_snapshot: maintain_remote_snapshot,
1038            });
1039        }
1040
1041        cx.foreground().spawn(async move {
1042            share_rx
1043                .await
1044                .unwrap_or_else(|_| Err(anyhow!("share ended")))
1045        })
1046    }
1047
1048    pub fn unshare(&mut self) {
1049        self.share.take();
1050    }
1051
1052    pub fn is_shared(&self) -> bool {
1053        self.share.is_some()
1054    }
1055}
1056
1057impl RemoteWorktree {
1058    fn snapshot(&self) -> Snapshot {
1059        self.snapshot.clone()
1060    }
1061
1062    fn poll_snapshot(&mut self, cx: &mut ModelContext<Worktree>) {
1063        self.snapshot = self.background_snapshot.lock().clone();
1064        cx.emit(Event::UpdatedEntries);
1065        cx.notify();
1066    }
1067
1068    pub fn disconnected_from_host(&mut self) {
1069        self.updates_tx.take();
1070        self.snapshot_subscriptions.clear();
1071        self.disconnected = true;
1072    }
1073
1074    pub fn update_from_remote(&mut self, update: proto::UpdateWorktree) {
1075        if let Some(updates_tx) = &self.updates_tx {
1076            updates_tx
1077                .unbounded_send(update)
1078                .expect("consumer runs to completion");
1079        }
1080    }
1081
1082    fn observed_snapshot(&self, scan_id: usize) -> bool {
1083        self.scan_id > scan_id || (self.scan_id == scan_id && self.is_complete)
1084    }
1085
1086    fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
1087        let (tx, rx) = oneshot::channel();
1088        if self.observed_snapshot(scan_id) {
1089            let _ = tx.send(());
1090        } else if self.disconnected {
1091            drop(tx);
1092        } else {
1093            match self
1094                .snapshot_subscriptions
1095                .binary_search_by_key(&scan_id, |probe| probe.0)
1096            {
1097                Ok(ix) | Err(ix) => self.snapshot_subscriptions.insert(ix, (scan_id, tx)),
1098            }
1099        }
1100
1101        async move {
1102            rx.await?;
1103            Ok(())
1104        }
1105    }
1106
1107    pub fn update_diagnostic_summary(
1108        &mut self,
1109        path: Arc<Path>,
1110        summary: &proto::DiagnosticSummary,
1111    ) {
1112        let summary = DiagnosticSummary {
1113            language_server_id: summary.language_server_id as usize,
1114            error_count: summary.error_count as usize,
1115            warning_count: summary.warning_count as usize,
1116        };
1117        if summary.is_empty() {
1118            self.diagnostic_summaries.remove(&PathKey(path));
1119        } else {
1120            self.diagnostic_summaries.insert(PathKey(path), summary);
1121        }
1122    }
1123
1124    pub fn insert_entry(
1125        &mut self,
1126        entry: proto::Entry,
1127        scan_id: usize,
1128        cx: &mut ModelContext<Worktree>,
1129    ) -> Task<Result<Entry>> {
1130        let wait_for_snapshot = self.wait_for_snapshot(scan_id);
1131        cx.spawn(|this, mut cx| async move {
1132            wait_for_snapshot.await?;
1133            this.update(&mut cx, |worktree, _| {
1134                let worktree = worktree.as_remote_mut().unwrap();
1135                let mut snapshot = worktree.background_snapshot.lock();
1136                let entry = snapshot.insert_entry(entry);
1137                worktree.snapshot = snapshot.clone();
1138                entry
1139            })
1140        })
1141    }
1142
1143    pub(crate) fn delete_entry(
1144        &mut self,
1145        id: ProjectEntryId,
1146        scan_id: usize,
1147        cx: &mut ModelContext<Worktree>,
1148    ) -> Task<Result<()>> {
1149        let wait_for_snapshot = self.wait_for_snapshot(scan_id);
1150        cx.spawn(|this, mut cx| async move {
1151            wait_for_snapshot.await?;
1152            this.update(&mut cx, |worktree, _| {
1153                let worktree = worktree.as_remote_mut().unwrap();
1154                let mut snapshot = worktree.background_snapshot.lock();
1155                snapshot.delete_entry(id);
1156                worktree.snapshot = snapshot.clone();
1157            });
1158            Ok(())
1159        })
1160    }
1161}
1162
1163impl Snapshot {
1164    pub fn id(&self) -> WorktreeId {
1165        self.id
1166    }
1167
1168    pub fn abs_path(&self) -> &Arc<Path> {
1169        &self.abs_path
1170    }
1171
1172    pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool {
1173        self.entries_by_id.get(&entry_id, &()).is_some()
1174    }
1175
1176    pub(crate) fn insert_entry(&mut self, entry: proto::Entry) -> Result<Entry> {
1177        let entry = Entry::try_from((&self.root_char_bag, entry))?;
1178        let old_entry = self.entries_by_id.insert_or_replace(
1179            PathEntry {
1180                id: entry.id,
1181                path: entry.path.clone(),
1182                is_ignored: entry.is_ignored,
1183                scan_id: 0,
1184            },
1185            &(),
1186        );
1187        if let Some(old_entry) = old_entry {
1188            self.entries_by_path.remove(&PathKey(old_entry.path), &());
1189        }
1190        self.entries_by_path.insert_or_replace(entry.clone(), &());
1191        Ok(entry)
1192    }
1193
1194    fn delete_entry(&mut self, entry_id: ProjectEntryId) -> bool {
1195        if let Some(removed_entry) = self.entries_by_id.remove(&entry_id, &()) {
1196            self.entries_by_path = {
1197                let mut cursor = self.entries_by_path.cursor();
1198                let mut new_entries_by_path =
1199                    cursor.slice(&TraversalTarget::Path(&removed_entry.path), Bias::Left, &());
1200                while let Some(entry) = cursor.item() {
1201                    if entry.path.starts_with(&removed_entry.path) {
1202                        self.entries_by_id.remove(&entry.id, &());
1203                        cursor.next(&());
1204                    } else {
1205                        break;
1206                    }
1207                }
1208                new_entries_by_path.push_tree(cursor.suffix(&()), &());
1209                new_entries_by_path
1210            };
1211
1212            true
1213        } else {
1214            false
1215        }
1216    }
1217
1218    pub(crate) fn apply_remote_update(&mut self, update: proto::UpdateWorktree) -> Result<()> {
1219        let mut entries_by_path_edits = Vec::new();
1220        let mut entries_by_id_edits = Vec::new();
1221        for entry_id in update.removed_entries {
1222            let entry = self
1223                .entry_for_id(ProjectEntryId::from_proto(entry_id))
1224                .ok_or_else(|| anyhow!("unknown entry {}", entry_id))?;
1225            entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
1226            entries_by_id_edits.push(Edit::Remove(entry.id));
1227        }
1228
1229        for entry in update.updated_entries {
1230            let entry = Entry::try_from((&self.root_char_bag, entry))?;
1231            if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) {
1232                entries_by_path_edits.push(Edit::Remove(PathKey(path.clone())));
1233            }
1234            entries_by_id_edits.push(Edit::Insert(PathEntry {
1235                id: entry.id,
1236                path: entry.path.clone(),
1237                is_ignored: entry.is_ignored,
1238                scan_id: 0,
1239            }));
1240            entries_by_path_edits.push(Edit::Insert(entry));
1241        }
1242
1243        self.entries_by_path.edit(entries_by_path_edits, &());
1244        self.entries_by_id.edit(entries_by_id_edits, &());
1245        self.scan_id = update.scan_id as usize;
1246        self.is_complete = update.is_last_update;
1247
1248        Ok(())
1249    }
1250
1251    pub fn file_count(&self) -> usize {
1252        self.entries_by_path.summary().file_count
1253    }
1254
1255    pub fn visible_file_count(&self) -> usize {
1256        self.entries_by_path.summary().visible_file_count
1257    }
1258
1259    fn traverse_from_offset(
1260        &self,
1261        include_dirs: bool,
1262        include_ignored: bool,
1263        start_offset: usize,
1264    ) -> Traversal {
1265        let mut cursor = self.entries_by_path.cursor();
1266        cursor.seek(
1267            &TraversalTarget::Count {
1268                count: start_offset,
1269                include_dirs,
1270                include_ignored,
1271            },
1272            Bias::Right,
1273            &(),
1274        );
1275        Traversal {
1276            cursor,
1277            include_dirs,
1278            include_ignored,
1279        }
1280    }
1281
1282    fn traverse_from_path(
1283        &self,
1284        include_dirs: bool,
1285        include_ignored: bool,
1286        path: &Path,
1287    ) -> Traversal {
1288        let mut cursor = self.entries_by_path.cursor();
1289        cursor.seek(&TraversalTarget::Path(path), Bias::Left, &());
1290        Traversal {
1291            cursor,
1292            include_dirs,
1293            include_ignored,
1294        }
1295    }
1296
1297    pub fn files(&self, include_ignored: bool, start: usize) -> Traversal {
1298        self.traverse_from_offset(false, include_ignored, start)
1299    }
1300
1301    pub fn entries(&self, include_ignored: bool) -> Traversal {
1302        self.traverse_from_offset(true, include_ignored, 0)
1303    }
1304
1305    pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
1306        let empty_path = Path::new("");
1307        self.entries_by_path
1308            .cursor::<()>()
1309            .filter(move |entry| entry.path.as_ref() != empty_path)
1310            .map(|entry| &entry.path)
1311    }
1312
1313    fn child_entries<'a>(&'a self, parent_path: &'a Path) -> ChildEntriesIter<'a> {
1314        let mut cursor = self.entries_by_path.cursor();
1315        cursor.seek(&TraversalTarget::Path(parent_path), Bias::Right, &());
1316        let traversal = Traversal {
1317            cursor,
1318            include_dirs: true,
1319            include_ignored: true,
1320        };
1321        ChildEntriesIter {
1322            traversal,
1323            parent_path,
1324        }
1325    }
1326
1327    pub fn root_entry(&self) -> Option<&Entry> {
1328        self.entry_for_path("")
1329    }
1330
1331    pub fn root_name(&self) -> &str {
1332        &self.root_name
1333    }
1334
1335    pub fn scan_id(&self) -> usize {
1336        self.scan_id
1337    }
1338
1339    pub fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
1340        let path = path.as_ref();
1341        self.traverse_from_path(true, true, path)
1342            .entry()
1343            .and_then(|entry| {
1344                if entry.path.as_ref() == path {
1345                    Some(entry)
1346                } else {
1347                    None
1348                }
1349            })
1350    }
1351
1352    pub fn entry_for_id(&self, id: ProjectEntryId) -> Option<&Entry> {
1353        let entry = self.entries_by_id.get(&id, &())?;
1354        self.entry_for_path(&entry.path)
1355    }
1356
1357    pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
1358        self.entry_for_path(path.as_ref()).map(|e| e.inode)
1359    }
1360}
1361
1362impl LocalSnapshot {
1363    // Gives the most specific git repository for a given path
1364    pub(crate) fn repo_for(&self, path: &Path) -> Option<GitRepositoryEntry> {
1365        self.git_repositories
1366            .iter()
1367            .rev() //git_repository is ordered lexicographically
1368            .find(|repo| repo.manages(path))
1369            .cloned()
1370    }
1371
1372    pub(crate) fn in_dot_git(&mut self, path: &Path) -> Option<&mut GitRepositoryEntry> {
1373        // Git repositories cannot be nested, so we don't need to reverse the order
1374        self.git_repositories
1375            .iter_mut()
1376            .find(|repo| repo.in_dot_git(path))
1377    }
1378
1379    #[cfg(test)]
1380    pub(crate) fn build_initial_update(&self, project_id: u64) -> proto::UpdateWorktree {
1381        let root_name = self.root_name.clone();
1382        proto::UpdateWorktree {
1383            project_id,
1384            worktree_id: self.id().to_proto(),
1385            abs_path: self.abs_path().to_string_lossy().into(),
1386            root_name,
1387            updated_entries: self.entries_by_path.iter().map(Into::into).collect(),
1388            removed_entries: Default::default(),
1389            scan_id: self.scan_id as u64,
1390            is_last_update: true,
1391        }
1392    }
1393
1394    pub(crate) fn build_update(
1395        &self,
1396        other: &Self,
1397        project_id: u64,
1398        worktree_id: u64,
1399        include_ignored: bool,
1400    ) -> proto::UpdateWorktree {
1401        let mut updated_entries = Vec::new();
1402        let mut removed_entries = Vec::new();
1403        let mut self_entries = self
1404            .entries_by_id
1405            .cursor::<()>()
1406            .filter(|e| include_ignored || !e.is_ignored)
1407            .peekable();
1408        let mut other_entries = other
1409            .entries_by_id
1410            .cursor::<()>()
1411            .filter(|e| include_ignored || !e.is_ignored)
1412            .peekable();
1413        loop {
1414            match (self_entries.peek(), other_entries.peek()) {
1415                (Some(self_entry), Some(other_entry)) => {
1416                    match Ord::cmp(&self_entry.id, &other_entry.id) {
1417                        Ordering::Less => {
1418                            let entry = self.entry_for_id(self_entry.id).unwrap().into();
1419                            updated_entries.push(entry);
1420                            self_entries.next();
1421                        }
1422                        Ordering::Equal => {
1423                            if self_entry.scan_id != other_entry.scan_id {
1424                                let entry = self.entry_for_id(self_entry.id).unwrap().into();
1425                                updated_entries.push(entry);
1426                            }
1427
1428                            self_entries.next();
1429                            other_entries.next();
1430                        }
1431                        Ordering::Greater => {
1432                            removed_entries.push(other_entry.id.to_proto());
1433                            other_entries.next();
1434                        }
1435                    }
1436                }
1437                (Some(self_entry), None) => {
1438                    let entry = self.entry_for_id(self_entry.id).unwrap().into();
1439                    updated_entries.push(entry);
1440                    self_entries.next();
1441                }
1442                (None, Some(other_entry)) => {
1443                    removed_entries.push(other_entry.id.to_proto());
1444                    other_entries.next();
1445                }
1446                (None, None) => break,
1447            }
1448        }
1449
1450        proto::UpdateWorktree {
1451            project_id,
1452            worktree_id,
1453            abs_path: self.abs_path().to_string_lossy().into(),
1454            root_name: self.root_name().to_string(),
1455            updated_entries,
1456            removed_entries,
1457            scan_id: self.scan_id as u64,
1458            is_last_update: true,
1459        }
1460    }
1461
1462    fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
1463        if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
1464            let abs_path = self.abs_path.join(&entry.path);
1465            match smol::block_on(build_gitignore(&abs_path, fs)) {
1466                Ok(ignore) => {
1467                    self.ignores_by_parent_abs_path.insert(
1468                        abs_path.parent().unwrap().into(),
1469                        (Arc::new(ignore), self.scan_id),
1470                    );
1471                }
1472                Err(error) => {
1473                    log::error!(
1474                        "error loading .gitignore file {:?} - {:?}",
1475                        &entry.path,
1476                        error
1477                    );
1478                }
1479            }
1480        }
1481
1482        self.reuse_entry_id(&mut entry);
1483
1484        if entry.kind == EntryKind::PendingDir {
1485            if let Some(existing_entry) =
1486                self.entries_by_path.get(&PathKey(entry.path.clone()), &())
1487            {
1488                entry.kind = existing_entry.kind;
1489            }
1490        }
1491
1492        self.entries_by_path.insert_or_replace(entry.clone(), &());
1493        let scan_id = self.scan_id;
1494        let removed_entry = self.entries_by_id.insert_or_replace(
1495            PathEntry {
1496                id: entry.id,
1497                path: entry.path.clone(),
1498                is_ignored: entry.is_ignored,
1499                scan_id,
1500            },
1501            &(),
1502        );
1503
1504        entry
1505    }
1506
1507    fn populate_dir(
1508        &mut self,
1509        parent_path: Arc<Path>,
1510        entries: impl IntoIterator<Item = Entry>,
1511        ignore: Option<Arc<Gitignore>>,
1512        fs: &dyn Fs,
1513    ) {
1514        let mut parent_entry = if let Some(parent_entry) =
1515            self.entries_by_path.get(&PathKey(parent_path.clone()), &())
1516        {
1517            parent_entry.clone()
1518        } else {
1519            log::warn!(
1520                "populating a directory {:?} that has been removed",
1521                parent_path
1522            );
1523            return;
1524        };
1525
1526        if let Some(ignore) = ignore {
1527            self.ignores_by_parent_abs_path.insert(
1528                self.abs_path.join(&parent_path).into(),
1529                (ignore, self.scan_id),
1530            );
1531        }
1532        if matches!(parent_entry.kind, EntryKind::PendingDir) {
1533            parent_entry.kind = EntryKind::Dir;
1534        } else {
1535            unreachable!();
1536        }
1537
1538        if parent_path.file_name() == Some(&DOT_GIT) {
1539            let abs_path = self.abs_path.join(&parent_path);
1540            let content_path: Arc<Path> = parent_path.parent().unwrap().into();
1541            if let Err(ix) = self
1542                .git_repositories
1543                .binary_search_by_key(&&content_path, |repo| &repo.content_path)
1544            {
1545                if let Some(repo) = fs.open_repo(abs_path.as_path()) {
1546                    self.git_repositories.insert(
1547                        ix,
1548                        GitRepositoryEntry {
1549                            repo,
1550                            scan_id: 0,
1551                            content_path,
1552                            git_dir_path: parent_path,
1553                        },
1554                    );
1555                }
1556            }
1557        }
1558
1559        let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
1560        let mut entries_by_id_edits = Vec::new();
1561
1562        for mut entry in entries {
1563            self.reuse_entry_id(&mut entry);
1564            entries_by_id_edits.push(Edit::Insert(PathEntry {
1565                id: entry.id,
1566                path: entry.path.clone(),
1567                is_ignored: entry.is_ignored,
1568                scan_id: self.scan_id,
1569            }));
1570            entries_by_path_edits.push(Edit::Insert(entry));
1571        }
1572
1573        self.entries_by_path.edit(entries_by_path_edits, &());
1574        self.entries_by_id.edit(entries_by_id_edits, &());
1575    }
1576
1577    fn reuse_entry_id(&mut self, entry: &mut Entry) {
1578        if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
1579            entry.id = removed_entry_id;
1580        } else if let Some(existing_entry) = self.entry_for_path(&entry.path) {
1581            entry.id = existing_entry.id;
1582        }
1583    }
1584
1585    fn remove_path(&mut self, path: &Path) {
1586        let mut new_entries;
1587        let removed_entries;
1588        {
1589            let mut cursor = self.entries_by_path.cursor::<TraversalProgress>();
1590            new_entries = cursor.slice(&TraversalTarget::Path(path), Bias::Left, &());
1591            removed_entries = cursor.slice(&TraversalTarget::PathSuccessor(path), Bias::Left, &());
1592            new_entries.push_tree(cursor.suffix(&()), &());
1593        }
1594        self.entries_by_path = new_entries;
1595
1596        let mut entries_by_id_edits = Vec::new();
1597        for entry in removed_entries.cursor::<()>() {
1598            let removed_entry_id = self
1599                .removed_entry_ids
1600                .entry(entry.inode)
1601                .or_insert(entry.id);
1602            *removed_entry_id = cmp::max(*removed_entry_id, entry.id);
1603            entries_by_id_edits.push(Edit::Remove(entry.id));
1604        }
1605        self.entries_by_id.edit(entries_by_id_edits, &());
1606
1607        if path.file_name() == Some(&GITIGNORE) {
1608            let abs_parent_path = self.abs_path.join(path.parent().unwrap());
1609            if let Some((_, scan_id)) = self
1610                .ignores_by_parent_abs_path
1611                .get_mut(abs_parent_path.as_path())
1612            {
1613                *scan_id = self.snapshot.scan_id;
1614            }
1615        } else if path.file_name() == Some(&DOT_GIT) {
1616            let parent_path = path.parent().unwrap();
1617            if let Ok(ix) = self
1618                .git_repositories
1619                .binary_search_by_key(&parent_path, |repo| repo.git_dir_path.as_ref())
1620            {
1621                self.git_repositories[ix].scan_id = self.snapshot.scan_id;
1622            }
1623        }
1624    }
1625
1626    fn ancestor_inodes_for_path(&self, path: &Path) -> TreeSet<u64> {
1627        let mut inodes = TreeSet::default();
1628        for ancestor in path.ancestors().skip(1) {
1629            if let Some(entry) = self.entry_for_path(ancestor) {
1630                inodes.insert(entry.inode);
1631            }
1632        }
1633        inodes
1634    }
1635
1636    fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
1637        let mut new_ignores = Vec::new();
1638        for ancestor in abs_path.ancestors().skip(1) {
1639            if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
1640                new_ignores.push((ancestor, Some(ignore.clone())));
1641            } else {
1642                new_ignores.push((ancestor, None));
1643            }
1644        }
1645
1646        let mut ignore_stack = IgnoreStack::none();
1647        for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
1648            if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
1649                ignore_stack = IgnoreStack::all();
1650                break;
1651            } else if let Some(ignore) = ignore {
1652                ignore_stack = ignore_stack.append(parent_abs_path.into(), ignore);
1653            }
1654        }
1655
1656        if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
1657            ignore_stack = IgnoreStack::all();
1658        }
1659
1660        ignore_stack
1661    }
1662
1663    pub fn git_repo_entries(&self) -> &[GitRepositoryEntry] {
1664        &self.git_repositories
1665    }
1666}
1667
1668impl GitRepositoryEntry {
1669    // Note that these paths should be relative to the worktree root.
1670    pub(crate) fn manages(&self, path: &Path) -> bool {
1671        path.starts_with(self.content_path.as_ref())
1672    }
1673
1674    // Note that theis path should be relative to the worktree root.
1675    pub(crate) fn in_dot_git(&self, path: &Path) -> bool {
1676        path.starts_with(self.git_dir_path.as_ref())
1677    }
1678}
1679
1680async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
1681    let contents = fs.load(abs_path).await?;
1682    let parent = abs_path.parent().unwrap_or_else(|| Path::new("/"));
1683    let mut builder = GitignoreBuilder::new(parent);
1684    for line in contents.lines() {
1685        builder.add_line(Some(abs_path.into()), line)?;
1686    }
1687    Ok(builder.build()?)
1688}
1689
1690impl WorktreeId {
1691    pub fn from_usize(handle_id: usize) -> Self {
1692        Self(handle_id)
1693    }
1694
1695    pub(crate) fn from_proto(id: u64) -> Self {
1696        Self(id as usize)
1697    }
1698
1699    pub fn to_proto(&self) -> u64 {
1700        self.0 as u64
1701    }
1702
1703    pub fn to_usize(&self) -> usize {
1704        self.0
1705    }
1706}
1707
1708impl fmt::Display for WorktreeId {
1709    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1710        self.0.fmt(f)
1711    }
1712}
1713
1714impl Deref for Worktree {
1715    type Target = Snapshot;
1716
1717    fn deref(&self) -> &Self::Target {
1718        match self {
1719            Worktree::Local(worktree) => &worktree.snapshot,
1720            Worktree::Remote(worktree) => &worktree.snapshot,
1721        }
1722    }
1723}
1724
1725impl Deref for LocalWorktree {
1726    type Target = LocalSnapshot;
1727
1728    fn deref(&self) -> &Self::Target {
1729        &self.snapshot
1730    }
1731}
1732
1733impl Deref for RemoteWorktree {
1734    type Target = Snapshot;
1735
1736    fn deref(&self) -> &Self::Target {
1737        &self.snapshot
1738    }
1739}
1740
1741impl fmt::Debug for LocalWorktree {
1742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1743        self.snapshot.fmt(f)
1744    }
1745}
1746
1747impl fmt::Debug for Snapshot {
1748    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1749        struct EntriesById<'a>(&'a SumTree<PathEntry>);
1750        struct EntriesByPath<'a>(&'a SumTree<Entry>);
1751
1752        impl<'a> fmt::Debug for EntriesByPath<'a> {
1753            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1754                f.debug_map()
1755                    .entries(self.0.iter().map(|entry| (&entry.path, entry.id)))
1756                    .finish()
1757            }
1758        }
1759
1760        impl<'a> fmt::Debug for EntriesById<'a> {
1761            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1762                f.debug_list().entries(self.0.iter()).finish()
1763            }
1764        }
1765
1766        f.debug_struct("Snapshot")
1767            .field("id", &self.id)
1768            .field("root_name", &self.root_name)
1769            .field("entries_by_path", &EntriesByPath(&self.entries_by_path))
1770            .field("entries_by_id", &EntriesById(&self.entries_by_id))
1771            .finish()
1772    }
1773}
1774
1775#[derive(Clone, PartialEq)]
1776pub struct File {
1777    pub worktree: ModelHandle<Worktree>,
1778    pub path: Arc<Path>,
1779    pub mtime: SystemTime,
1780    pub(crate) entry_id: ProjectEntryId,
1781    pub(crate) is_local: bool,
1782    pub(crate) is_deleted: bool,
1783}
1784
1785impl language::File for File {
1786    fn as_local(&self) -> Option<&dyn language::LocalFile> {
1787        if self.is_local {
1788            Some(self)
1789        } else {
1790            None
1791        }
1792    }
1793
1794    fn mtime(&self) -> SystemTime {
1795        self.mtime
1796    }
1797
1798    fn path(&self) -> &Arc<Path> {
1799        &self.path
1800    }
1801
1802    fn full_path(&self, cx: &AppContext) -> PathBuf {
1803        let mut full_path = PathBuf::new();
1804        let worktree = self.worktree.read(cx);
1805
1806        if worktree.is_visible() {
1807            full_path.push(worktree.root_name());
1808        } else {
1809            let path = worktree.abs_path();
1810
1811            if worktree.is_local() && path.starts_with(cx.global::<HomeDir>().as_path()) {
1812                full_path.push("~");
1813                full_path.push(path.strip_prefix(cx.global::<HomeDir>().as_path()).unwrap());
1814            } else {
1815                full_path.push(path)
1816            }
1817        }
1818
1819        if self.path.components().next().is_some() {
1820            full_path.push(&self.path);
1821        }
1822
1823        full_path
1824    }
1825
1826    /// Returns the last component of this handle's absolute path. If this handle refers to the root
1827    /// of its worktree, then this method will return the name of the worktree itself.
1828    fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr {
1829        self.path
1830            .file_name()
1831            .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
1832    }
1833
1834    fn is_deleted(&self) -> bool {
1835        self.is_deleted
1836    }
1837
1838    fn save(
1839        &self,
1840        buffer_id: u64,
1841        text: Rope,
1842        version: clock::Global,
1843        line_ending: LineEnding,
1844        cx: &mut MutableAppContext,
1845    ) -> Task<Result<(clock::Global, String, SystemTime)>> {
1846        self.worktree.update(cx, |worktree, cx| match worktree {
1847            Worktree::Local(worktree) => {
1848                let rpc = worktree.client.clone();
1849                let project_id = worktree.share.as_ref().map(|share| share.project_id);
1850                let fingerprint = text.fingerprint();
1851                let save = worktree.write_file(self.path.clone(), text, line_ending, cx);
1852                cx.background().spawn(async move {
1853                    let entry = save.await?;
1854                    if let Some(project_id) = project_id {
1855                        rpc.send(proto::BufferSaved {
1856                            project_id,
1857                            buffer_id,
1858                            version: serialize_version(&version),
1859                            mtime: Some(entry.mtime.into()),
1860                            fingerprint: fingerprint.clone(),
1861                        })?;
1862                    }
1863                    Ok((version, fingerprint, entry.mtime))
1864                })
1865            }
1866            Worktree::Remote(worktree) => {
1867                let rpc = worktree.client.clone();
1868                let project_id = worktree.project_id;
1869                cx.foreground().spawn(async move {
1870                    let response = rpc
1871                        .request(proto::SaveBuffer {
1872                            project_id,
1873                            buffer_id,
1874                            version: serialize_version(&version),
1875                        })
1876                        .await?;
1877                    let version = deserialize_version(response.version);
1878                    let mtime = response
1879                        .mtime
1880                        .ok_or_else(|| anyhow!("missing mtime"))?
1881                        .into();
1882                    Ok((version, response.fingerprint, mtime))
1883                })
1884            }
1885        })
1886    }
1887
1888    fn as_any(&self) -> &dyn Any {
1889        self
1890    }
1891
1892    fn to_proto(&self) -> rpc::proto::File {
1893        rpc::proto::File {
1894            worktree_id: self.worktree.id() as u64,
1895            entry_id: self.entry_id.to_proto(),
1896            path: self.path.to_string_lossy().into(),
1897            mtime: Some(self.mtime.into()),
1898            is_deleted: self.is_deleted,
1899        }
1900    }
1901}
1902
1903impl language::LocalFile for File {
1904    fn abs_path(&self, cx: &AppContext) -> PathBuf {
1905        self.worktree
1906            .read(cx)
1907            .as_local()
1908            .unwrap()
1909            .abs_path
1910            .join(&self.path)
1911    }
1912
1913    fn load(&self, cx: &AppContext) -> Task<Result<String>> {
1914        let worktree = self.worktree.read(cx).as_local().unwrap();
1915        let abs_path = worktree.absolutize(&self.path);
1916        let fs = worktree.fs.clone();
1917        cx.background()
1918            .spawn(async move { fs.load(&abs_path).await })
1919    }
1920
1921    fn buffer_reloaded(
1922        &self,
1923        buffer_id: u64,
1924        version: &clock::Global,
1925        fingerprint: String,
1926        line_ending: LineEnding,
1927        mtime: SystemTime,
1928        cx: &mut MutableAppContext,
1929    ) {
1930        let worktree = self.worktree.read(cx).as_local().unwrap();
1931        if let Some(project_id) = worktree.share.as_ref().map(|share| share.project_id) {
1932            worktree
1933                .client
1934                .send(proto::BufferReloaded {
1935                    project_id,
1936                    buffer_id,
1937                    version: serialize_version(version),
1938                    mtime: Some(mtime.into()),
1939                    fingerprint,
1940                    line_ending: serialize_line_ending(line_ending) as i32,
1941                })
1942                .log_err();
1943        }
1944    }
1945}
1946
1947impl File {
1948    pub fn from_proto(
1949        proto: rpc::proto::File,
1950        worktree: ModelHandle<Worktree>,
1951        cx: &AppContext,
1952    ) -> Result<Self> {
1953        let worktree_id = worktree
1954            .read(cx)
1955            .as_remote()
1956            .ok_or_else(|| anyhow!("not remote"))?
1957            .id();
1958
1959        if worktree_id.to_proto() != proto.worktree_id {
1960            return Err(anyhow!("worktree id does not match file"));
1961        }
1962
1963        Ok(Self {
1964            worktree,
1965            path: Path::new(&proto.path).into(),
1966            mtime: proto.mtime.ok_or_else(|| anyhow!("no timestamp"))?.into(),
1967            entry_id: ProjectEntryId::from_proto(proto.entry_id),
1968            is_local: false,
1969            is_deleted: proto.is_deleted,
1970        })
1971    }
1972
1973    pub fn from_dyn(file: Option<&dyn language::File>) -> Option<&Self> {
1974        file.and_then(|f| f.as_any().downcast_ref())
1975    }
1976
1977    pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId {
1978        self.worktree.read(cx).id()
1979    }
1980
1981    pub fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
1982        if self.is_deleted {
1983            None
1984        } else {
1985            Some(self.entry_id)
1986        }
1987    }
1988}
1989
1990#[derive(Clone, Debug, PartialEq, Eq)]
1991pub struct Entry {
1992    pub id: ProjectEntryId,
1993    pub kind: EntryKind,
1994    pub path: Arc<Path>,
1995    pub inode: u64,
1996    pub mtime: SystemTime,
1997    pub is_symlink: bool,
1998    pub is_ignored: bool,
1999}
2000
2001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2002pub enum EntryKind {
2003    PendingDir,
2004    Dir,
2005    File(CharBag),
2006}
2007
2008impl Entry {
2009    fn new(
2010        path: Arc<Path>,
2011        metadata: &fs::Metadata,
2012        next_entry_id: &AtomicUsize,
2013        root_char_bag: CharBag,
2014    ) -> Self {
2015        Self {
2016            id: ProjectEntryId::new(next_entry_id),
2017            kind: if metadata.is_dir {
2018                EntryKind::PendingDir
2019            } else {
2020                EntryKind::File(char_bag_for_path(root_char_bag, &path))
2021            },
2022            path,
2023            inode: metadata.inode,
2024            mtime: metadata.mtime,
2025            is_symlink: metadata.is_symlink,
2026            is_ignored: false,
2027        }
2028    }
2029
2030    pub fn is_dir(&self) -> bool {
2031        matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
2032    }
2033
2034    pub fn is_file(&self) -> bool {
2035        matches!(self.kind, EntryKind::File(_))
2036    }
2037}
2038
2039impl sum_tree::Item for Entry {
2040    type Summary = EntrySummary;
2041
2042    fn summary(&self) -> Self::Summary {
2043        let visible_count = if self.is_ignored { 0 } else { 1 };
2044        let file_count;
2045        let visible_file_count;
2046        if self.is_file() {
2047            file_count = 1;
2048            visible_file_count = visible_count;
2049        } else {
2050            file_count = 0;
2051            visible_file_count = 0;
2052        }
2053
2054        EntrySummary {
2055            max_path: self.path.clone(),
2056            count: 1,
2057            visible_count,
2058            file_count,
2059            visible_file_count,
2060        }
2061    }
2062}
2063
2064impl sum_tree::KeyedItem for Entry {
2065    type Key = PathKey;
2066
2067    fn key(&self) -> Self::Key {
2068        PathKey(self.path.clone())
2069    }
2070}
2071
2072#[derive(Clone, Debug)]
2073pub struct EntrySummary {
2074    max_path: Arc<Path>,
2075    count: usize,
2076    visible_count: usize,
2077    file_count: usize,
2078    visible_file_count: usize,
2079}
2080
2081impl Default for EntrySummary {
2082    fn default() -> Self {
2083        Self {
2084            max_path: Arc::from(Path::new("")),
2085            count: 0,
2086            visible_count: 0,
2087            file_count: 0,
2088            visible_file_count: 0,
2089        }
2090    }
2091}
2092
2093impl sum_tree::Summary for EntrySummary {
2094    type Context = ();
2095
2096    fn add_summary(&mut self, rhs: &Self, _: &()) {
2097        self.max_path = rhs.max_path.clone();
2098        self.count += rhs.count;
2099        self.visible_count += rhs.visible_count;
2100        self.file_count += rhs.file_count;
2101        self.visible_file_count += rhs.visible_file_count;
2102    }
2103}
2104
2105#[derive(Clone, Debug)]
2106struct PathEntry {
2107    id: ProjectEntryId,
2108    path: Arc<Path>,
2109    is_ignored: bool,
2110    scan_id: usize,
2111}
2112
2113impl sum_tree::Item for PathEntry {
2114    type Summary = PathEntrySummary;
2115
2116    fn summary(&self) -> Self::Summary {
2117        PathEntrySummary { max_id: self.id }
2118    }
2119}
2120
2121impl sum_tree::KeyedItem for PathEntry {
2122    type Key = ProjectEntryId;
2123
2124    fn key(&self) -> Self::Key {
2125        self.id
2126    }
2127}
2128
2129#[derive(Clone, Debug, Default)]
2130struct PathEntrySummary {
2131    max_id: ProjectEntryId,
2132}
2133
2134impl sum_tree::Summary for PathEntrySummary {
2135    type Context = ();
2136
2137    fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
2138        self.max_id = summary.max_id;
2139    }
2140}
2141
2142impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId {
2143    fn add_summary(&mut self, summary: &'a PathEntrySummary, _: &()) {
2144        *self = summary.max_id;
2145    }
2146}
2147
2148#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
2149pub struct PathKey(Arc<Path>);
2150
2151impl Default for PathKey {
2152    fn default() -> Self {
2153        Self(Path::new("").into())
2154    }
2155}
2156
2157impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
2158    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
2159        self.0 = summary.max_path.clone();
2160    }
2161}
2162
2163struct BackgroundScanner {
2164    fs: Arc<dyn Fs>,
2165    snapshot: Arc<Mutex<LocalSnapshot>>,
2166    notify: UnboundedSender<ScanState>,
2167    executor: Arc<executor::Background>,
2168}
2169
2170impl BackgroundScanner {
2171    fn new(
2172        snapshot: Arc<Mutex<LocalSnapshot>>,
2173        notify: UnboundedSender<ScanState>,
2174        fs: Arc<dyn Fs>,
2175        executor: Arc<executor::Background>,
2176    ) -> Self {
2177        Self {
2178            fs,
2179            snapshot,
2180            notify,
2181            executor,
2182        }
2183    }
2184
2185    fn abs_path(&self) -> Arc<Path> {
2186        self.snapshot.lock().abs_path.clone()
2187    }
2188
2189    fn snapshot(&self) -> LocalSnapshot {
2190        self.snapshot.lock().clone()
2191    }
2192
2193    async fn run(mut self, events_rx: impl Stream<Item = Vec<fsevent::Event>>) {
2194        if self.notify.unbounded_send(ScanState::Initializing).is_err() {
2195            return;
2196        }
2197
2198        if let Err(err) = self.scan_dirs().await {
2199            if self
2200                .notify
2201                .unbounded_send(ScanState::Err(Arc::new(err)))
2202                .is_err()
2203            {
2204                return;
2205            }
2206        }
2207
2208        if self.notify.unbounded_send(ScanState::Idle).is_err() {
2209            return;
2210        }
2211
2212        futures::pin_mut!(events_rx);
2213
2214        while let Some(mut events) = events_rx.next().await {
2215            while let Poll::Ready(Some(additional_events)) = futures::poll!(events_rx.next()) {
2216                events.extend(additional_events);
2217            }
2218
2219            if self.notify.unbounded_send(ScanState::Updating).is_err() {
2220                break;
2221            }
2222
2223            if !self.process_events(events).await {
2224                break;
2225            }
2226
2227            if self.notify.unbounded_send(ScanState::Idle).is_err() {
2228                break;
2229            }
2230        }
2231    }
2232
2233    async fn scan_dirs(&mut self) -> Result<()> {
2234        let root_char_bag;
2235        let root_abs_path;
2236        let root_inode;
2237        let is_dir;
2238        let next_entry_id;
2239        {
2240            let snapshot = self.snapshot.lock();
2241            root_char_bag = snapshot.root_char_bag;
2242            root_abs_path = snapshot.abs_path.clone();
2243            root_inode = snapshot.root_entry().map(|e| e.inode);
2244            is_dir = snapshot.root_entry().map_or(false, |e| e.is_dir());
2245            next_entry_id = snapshot.next_entry_id.clone();
2246        };
2247
2248        // Populate ignores above the root.
2249        for ancestor in root_abs_path.ancestors().skip(1) {
2250            if let Ok(ignore) = build_gitignore(&ancestor.join(&*GITIGNORE), self.fs.as_ref()).await
2251            {
2252                self.snapshot
2253                    .lock()
2254                    .ignores_by_parent_abs_path
2255                    .insert(ancestor.into(), (ignore.into(), 0));
2256            }
2257        }
2258
2259        let ignore_stack = {
2260            let mut snapshot = self.snapshot.lock();
2261            let ignore_stack = snapshot.ignore_stack_for_abs_path(&root_abs_path, true);
2262            if ignore_stack.is_all() {
2263                if let Some(mut root_entry) = snapshot.root_entry().cloned() {
2264                    root_entry.is_ignored = true;
2265                    snapshot.insert_entry(root_entry, self.fs.as_ref());
2266                }
2267            }
2268            ignore_stack
2269        };
2270
2271        if is_dir {
2272            let path: Arc<Path> = Arc::from(Path::new(""));
2273            let mut ancestor_inodes = TreeSet::default();
2274            if let Some(root_inode) = root_inode {
2275                ancestor_inodes.insert(root_inode);
2276            }
2277
2278            let (tx, rx) = channel::unbounded();
2279            self.executor
2280                .block(tx.send(ScanJob {
2281                    abs_path: root_abs_path.to_path_buf(),
2282                    path,
2283                    ignore_stack,
2284                    ancestor_inodes,
2285                    scan_queue: tx.clone(),
2286                }))
2287                .unwrap();
2288            drop(tx);
2289
2290            self.executor
2291                .scoped(|scope| {
2292                    for _ in 0..self.executor.num_cpus() {
2293                        scope.spawn(async {
2294                            while let Ok(job) = rx.recv().await {
2295                                if let Err(err) = self
2296                                    .scan_dir(root_char_bag, next_entry_id.clone(), &job)
2297                                    .await
2298                                {
2299                                    log::error!("error scanning {:?}: {}", job.abs_path, err);
2300                                }
2301                            }
2302                        });
2303                    }
2304                })
2305                .await;
2306        }
2307
2308        Ok(())
2309    }
2310
2311    async fn scan_dir(
2312        &self,
2313        root_char_bag: CharBag,
2314        next_entry_id: Arc<AtomicUsize>,
2315        job: &ScanJob,
2316    ) -> Result<()> {
2317        let mut new_entries: Vec<Entry> = Vec::new();
2318        let mut new_jobs: Vec<ScanJob> = Vec::new();
2319        let mut ignore_stack = job.ignore_stack.clone();
2320        let mut new_ignore = None;
2321
2322        let mut child_paths = self.fs.read_dir(&job.abs_path).await?;
2323        while let Some(child_abs_path) = child_paths.next().await {
2324            let child_abs_path = match child_abs_path {
2325                Ok(child_abs_path) => child_abs_path,
2326                Err(error) => {
2327                    log::error!("error processing entry {:?}", error);
2328                    continue;
2329                }
2330            };
2331            let child_name = child_abs_path.file_name().unwrap();
2332            let child_path: Arc<Path> = job.path.join(child_name).into();
2333            let child_metadata = match self.fs.metadata(&child_abs_path).await {
2334                Ok(Some(metadata)) => metadata,
2335                Ok(None) => continue,
2336                Err(err) => {
2337                    log::error!("error processing {:?}: {:?}", child_abs_path, err);
2338                    continue;
2339                }
2340            };
2341
2342            // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
2343            if child_name == *GITIGNORE {
2344                match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
2345                    Ok(ignore) => {
2346                        let ignore = Arc::new(ignore);
2347                        ignore_stack =
2348                            ignore_stack.append(job.abs_path.as_path().into(), ignore.clone());
2349                        new_ignore = Some(ignore);
2350                    }
2351                    Err(error) => {
2352                        log::error!(
2353                            "error loading .gitignore file {:?} - {:?}",
2354                            child_name,
2355                            error
2356                        );
2357                    }
2358                }
2359
2360                // Update ignore status of any child entries we've already processed to reflect the
2361                // ignore file in the current directory. Because `.gitignore` starts with a `.`,
2362                // there should rarely be too numerous. Update the ignore stack associated with any
2363                // new jobs as well.
2364                let mut new_jobs = new_jobs.iter_mut();
2365                for entry in &mut new_entries {
2366                    let entry_abs_path = self.abs_path().join(&entry.path);
2367                    entry.is_ignored =
2368                        ignore_stack.is_abs_path_ignored(&entry_abs_path, entry.is_dir());
2369                    if entry.is_dir() {
2370                        new_jobs.next().unwrap().ignore_stack = if entry.is_ignored {
2371                            IgnoreStack::all()
2372                        } else {
2373                            ignore_stack.clone()
2374                        };
2375                    }
2376                }
2377            }
2378
2379            let mut child_entry = Entry::new(
2380                child_path.clone(),
2381                &child_metadata,
2382                &next_entry_id,
2383                root_char_bag,
2384            );
2385
2386            if child_entry.is_dir() {
2387                let is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true);
2388                child_entry.is_ignored = is_ignored;
2389
2390                if !job.ancestor_inodes.contains(&child_entry.inode) {
2391                    let mut ancestor_inodes = job.ancestor_inodes.clone();
2392                    ancestor_inodes.insert(child_entry.inode);
2393                    new_jobs.push(ScanJob {
2394                        abs_path: child_abs_path,
2395                        path: child_path,
2396                        ignore_stack: if is_ignored {
2397                            IgnoreStack::all()
2398                        } else {
2399                            ignore_stack.clone()
2400                        },
2401                        ancestor_inodes,
2402                        scan_queue: job.scan_queue.clone(),
2403                    });
2404                }
2405            } else {
2406                child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false);
2407            }
2408
2409            new_entries.push(child_entry);
2410        }
2411
2412        self.snapshot.lock().populate_dir(
2413            job.path.clone(),
2414            new_entries,
2415            new_ignore,
2416            self.fs.as_ref(),
2417        );
2418        for new_job in new_jobs {
2419            job.scan_queue.send(new_job).await.unwrap();
2420        }
2421
2422        Ok(())
2423    }
2424
2425    async fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
2426        events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
2427        events.dedup_by(|a, b| a.path.starts_with(&b.path));
2428
2429        let root_char_bag;
2430        let root_abs_path;
2431        let next_entry_id;
2432        {
2433            let snapshot = self.snapshot.lock();
2434            root_char_bag = snapshot.root_char_bag;
2435            root_abs_path = snapshot.abs_path.clone();
2436            next_entry_id = snapshot.next_entry_id.clone();
2437        }
2438
2439        let root_canonical_path = if let Ok(path) = self.fs.canonicalize(&root_abs_path).await {
2440            path
2441        } else {
2442            return false;
2443        };
2444        let metadata = futures::future::join_all(
2445            events
2446                .iter()
2447                .map(|event| self.fs.metadata(&event.path))
2448                .collect::<Vec<_>>(),
2449        )
2450        .await;
2451
2452        // Hold the snapshot lock while clearing and re-inserting the root entries
2453        // for each event. This way, the snapshot is not observable to the foreground
2454        // thread while this operation is in-progress.
2455        let (scan_queue_tx, scan_queue_rx) = channel::unbounded();
2456        {
2457            let mut snapshot = self.snapshot.lock();
2458            snapshot.scan_id += 1;
2459            for event in &events {
2460                if let Ok(path) = event.path.strip_prefix(&root_canonical_path) {
2461                    snapshot.remove_path(path);
2462                }
2463            }
2464
2465            for (event, metadata) in events.into_iter().zip(metadata.into_iter()) {
2466                let path: Arc<Path> = match event.path.strip_prefix(&root_canonical_path) {
2467                    Ok(path) => Arc::from(path.to_path_buf()),
2468                    Err(_) => {
2469                        log::error!(
2470                            "unexpected event {:?} for root path {:?}",
2471                            event.path,
2472                            root_canonical_path
2473                        );
2474                        continue;
2475                    }
2476                };
2477                let abs_path = root_abs_path.join(&path);
2478
2479                match metadata {
2480                    Ok(Some(metadata)) => {
2481                        let ignore_stack =
2482                            snapshot.ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
2483                        let mut fs_entry = Entry::new(
2484                            path.clone(),
2485                            &metadata,
2486                            snapshot.next_entry_id.as_ref(),
2487                            snapshot.root_char_bag,
2488                        );
2489                        fs_entry.is_ignored = ignore_stack.is_all();
2490                        snapshot.insert_entry(fs_entry, self.fs.as_ref());
2491
2492                        let scan_id = snapshot.scan_id;
2493                        if let Some(repo) = snapshot.in_dot_git(&path) {
2494                            repo.repo.lock().reload_index();
2495                            repo.scan_id = scan_id;
2496                        }
2497
2498                        let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path);
2499                        if metadata.is_dir && !ancestor_inodes.contains(&metadata.inode) {
2500                            ancestor_inodes.insert(metadata.inode);
2501                            self.executor
2502                                .block(scan_queue_tx.send(ScanJob {
2503                                    abs_path,
2504                                    path,
2505                                    ignore_stack,
2506                                    ancestor_inodes,
2507                                    scan_queue: scan_queue_tx.clone(),
2508                                }))
2509                                .unwrap();
2510                        }
2511                    }
2512                    Ok(None) => {}
2513                    Err(err) => {
2514                        // TODO - create a special 'error' entry in the entries tree to mark this
2515                        log::error!("error reading file on event {:?}", err);
2516                    }
2517                }
2518            }
2519            drop(scan_queue_tx);
2520        }
2521
2522        // Scan any directories that were created as part of this event batch.
2523        self.executor
2524            .scoped(|scope| {
2525                for _ in 0..self.executor.num_cpus() {
2526                    scope.spawn(async {
2527                        while let Ok(job) = scan_queue_rx.recv().await {
2528                            if let Err(err) = self
2529                                .scan_dir(root_char_bag, next_entry_id.clone(), &job)
2530                                .await
2531                            {
2532                                log::error!("error scanning {:?}: {}", job.abs_path, err);
2533                            }
2534                        }
2535                    });
2536                }
2537            })
2538            .await;
2539
2540        // Attempt to detect renames only over a single batch of file-system events.
2541        self.snapshot.lock().removed_entry_ids.clear();
2542
2543        self.update_ignore_statuses().await;
2544        self.update_git_repositories();
2545        true
2546    }
2547
2548    async fn update_ignore_statuses(&self) {
2549        let mut snapshot = self.snapshot();
2550
2551        let mut ignores_to_update = Vec::new();
2552        let mut ignores_to_delete = Vec::new();
2553        for (parent_abs_path, (_, scan_id)) in &snapshot.ignores_by_parent_abs_path {
2554            if let Ok(parent_path) = parent_abs_path.strip_prefix(&snapshot.abs_path) {
2555                if *scan_id == snapshot.scan_id && snapshot.entry_for_path(parent_path).is_some() {
2556                    ignores_to_update.push(parent_abs_path.clone());
2557                }
2558
2559                let ignore_path = parent_path.join(&*GITIGNORE);
2560                if snapshot.entry_for_path(ignore_path).is_none() {
2561                    ignores_to_delete.push(parent_abs_path.clone());
2562                }
2563            }
2564        }
2565
2566        for parent_abs_path in ignores_to_delete {
2567            snapshot.ignores_by_parent_abs_path.remove(&parent_abs_path);
2568            self.snapshot
2569                .lock()
2570                .ignores_by_parent_abs_path
2571                .remove(&parent_abs_path);
2572        }
2573
2574        let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
2575        ignores_to_update.sort_unstable();
2576        let mut ignores_to_update = ignores_to_update.into_iter().peekable();
2577        while let Some(parent_abs_path) = ignores_to_update.next() {
2578            while ignores_to_update
2579                .peek()
2580                .map_or(false, |p| p.starts_with(&parent_abs_path))
2581            {
2582                ignores_to_update.next().unwrap();
2583            }
2584
2585            let ignore_stack = snapshot.ignore_stack_for_abs_path(&parent_abs_path, true);
2586            ignore_queue_tx
2587                .send(UpdateIgnoreStatusJob {
2588                    abs_path: parent_abs_path,
2589                    ignore_stack,
2590                    ignore_queue: ignore_queue_tx.clone(),
2591                })
2592                .await
2593                .unwrap();
2594        }
2595        drop(ignore_queue_tx);
2596
2597        self.executor
2598            .scoped(|scope| {
2599                for _ in 0..self.executor.num_cpus() {
2600                    scope.spawn(async {
2601                        while let Ok(job) = ignore_queue_rx.recv().await {
2602                            self.update_ignore_status(job, &snapshot).await;
2603                        }
2604                    });
2605                }
2606            })
2607            .await;
2608    }
2609
2610    fn update_git_repositories(&self) {
2611        let mut snapshot = self.snapshot.lock();
2612        let mut git_repositories = mem::take(&mut snapshot.git_repositories);
2613        git_repositories.retain(|repo| snapshot.entry_for_path(&repo.git_dir_path).is_some());
2614        snapshot.git_repositories = git_repositories;
2615    }
2616
2617    async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
2618        let mut ignore_stack = job.ignore_stack;
2619        if let Some((ignore, _)) = snapshot.ignores_by_parent_abs_path.get(&job.abs_path) {
2620            ignore_stack = ignore_stack.append(job.abs_path.clone(), ignore.clone());
2621        }
2622
2623        let mut entries_by_id_edits = Vec::new();
2624        let mut entries_by_path_edits = Vec::new();
2625        let path = job.abs_path.strip_prefix(&snapshot.abs_path).unwrap();
2626        for mut entry in snapshot.child_entries(path).cloned() {
2627            let was_ignored = entry.is_ignored;
2628            let abs_path = self.abs_path().join(&entry.path);
2629            entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
2630            if entry.is_dir() {
2631                let child_ignore_stack = if entry.is_ignored {
2632                    IgnoreStack::all()
2633                } else {
2634                    ignore_stack.clone()
2635                };
2636                job.ignore_queue
2637                    .send(UpdateIgnoreStatusJob {
2638                        abs_path: abs_path.into(),
2639                        ignore_stack: child_ignore_stack,
2640                        ignore_queue: job.ignore_queue.clone(),
2641                    })
2642                    .await
2643                    .unwrap();
2644            }
2645
2646            if entry.is_ignored != was_ignored {
2647                let mut path_entry = snapshot.entries_by_id.get(&entry.id, &()).unwrap().clone();
2648                path_entry.scan_id = snapshot.scan_id;
2649                path_entry.is_ignored = entry.is_ignored;
2650                entries_by_id_edits.push(Edit::Insert(path_entry));
2651                entries_by_path_edits.push(Edit::Insert(entry));
2652            }
2653        }
2654
2655        let mut snapshot = self.snapshot.lock();
2656        snapshot.entries_by_path.edit(entries_by_path_edits, &());
2657        snapshot.entries_by_id.edit(entries_by_id_edits, &());
2658    }
2659}
2660
2661fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
2662    let mut result = root_char_bag;
2663    result.extend(
2664        path.to_string_lossy()
2665            .chars()
2666            .map(|c| c.to_ascii_lowercase()),
2667    );
2668    result
2669}
2670
2671struct ScanJob {
2672    abs_path: PathBuf,
2673    path: Arc<Path>,
2674    ignore_stack: Arc<IgnoreStack>,
2675    scan_queue: Sender<ScanJob>,
2676    ancestor_inodes: TreeSet<u64>,
2677}
2678
2679struct UpdateIgnoreStatusJob {
2680    abs_path: Arc<Path>,
2681    ignore_stack: Arc<IgnoreStack>,
2682    ignore_queue: Sender<UpdateIgnoreStatusJob>,
2683}
2684
2685pub trait WorktreeHandle {
2686    #[cfg(any(test, feature = "test-support"))]
2687    fn flush_fs_events<'a>(
2688        &self,
2689        cx: &'a gpui::TestAppContext,
2690    ) -> futures::future::LocalBoxFuture<'a, ()>;
2691}
2692
2693impl WorktreeHandle for ModelHandle<Worktree> {
2694    // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
2695    // occurred before the worktree was constructed. These events can cause the worktree to perfrom
2696    // extra directory scans, and emit extra scan-state notifications.
2697    //
2698    // This function mutates the worktree's directory and waits for those mutations to be picked up,
2699    // to ensure that all redundant FS events have already been processed.
2700    #[cfg(any(test, feature = "test-support"))]
2701    fn flush_fs_events<'a>(
2702        &self,
2703        cx: &'a gpui::TestAppContext,
2704    ) -> futures::future::LocalBoxFuture<'a, ()> {
2705        use smol::future::FutureExt;
2706
2707        let filename = "fs-event-sentinel";
2708        let tree = self.clone();
2709        let (fs, root_path) = self.read_with(cx, |tree, _| {
2710            let tree = tree.as_local().unwrap();
2711            (tree.fs.clone(), tree.abs_path().clone())
2712        });
2713
2714        async move {
2715            fs.create_file(&root_path.join(filename), Default::default())
2716                .await
2717                .unwrap();
2718            tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_some())
2719                .await;
2720
2721            fs.remove_file(&root_path.join(filename), Default::default())
2722                .await
2723                .unwrap();
2724            tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_none())
2725                .await;
2726
2727            cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2728                .await;
2729        }
2730        .boxed_local()
2731    }
2732}
2733
2734#[derive(Clone, Debug)]
2735struct TraversalProgress<'a> {
2736    max_path: &'a Path,
2737    count: usize,
2738    visible_count: usize,
2739    file_count: usize,
2740    visible_file_count: usize,
2741}
2742
2743impl<'a> TraversalProgress<'a> {
2744    fn count(&self, include_dirs: bool, include_ignored: bool) -> usize {
2745        match (include_ignored, include_dirs) {
2746            (true, true) => self.count,
2747            (true, false) => self.file_count,
2748            (false, true) => self.visible_count,
2749            (false, false) => self.visible_file_count,
2750        }
2751    }
2752}
2753
2754impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> {
2755    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
2756        self.max_path = summary.max_path.as_ref();
2757        self.count += summary.count;
2758        self.visible_count += summary.visible_count;
2759        self.file_count += summary.file_count;
2760        self.visible_file_count += summary.visible_file_count;
2761    }
2762}
2763
2764impl<'a> Default for TraversalProgress<'a> {
2765    fn default() -> Self {
2766        Self {
2767            max_path: Path::new(""),
2768            count: 0,
2769            visible_count: 0,
2770            file_count: 0,
2771            visible_file_count: 0,
2772        }
2773    }
2774}
2775
2776pub struct Traversal<'a> {
2777    cursor: sum_tree::Cursor<'a, Entry, TraversalProgress<'a>>,
2778    include_ignored: bool,
2779    include_dirs: bool,
2780}
2781
2782impl<'a> Traversal<'a> {
2783    pub fn advance(&mut self) -> bool {
2784        self.advance_to_offset(self.offset() + 1)
2785    }
2786
2787    pub fn advance_to_offset(&mut self, offset: usize) -> bool {
2788        self.cursor.seek_forward(
2789            &TraversalTarget::Count {
2790                count: offset,
2791                include_dirs: self.include_dirs,
2792                include_ignored: self.include_ignored,
2793            },
2794            Bias::Right,
2795            &(),
2796        )
2797    }
2798
2799    pub fn advance_to_sibling(&mut self) -> bool {
2800        while let Some(entry) = self.cursor.item() {
2801            self.cursor.seek_forward(
2802                &TraversalTarget::PathSuccessor(&entry.path),
2803                Bias::Left,
2804                &(),
2805            );
2806            if let Some(entry) = self.cursor.item() {
2807                if (self.include_dirs || !entry.is_dir())
2808                    && (self.include_ignored || !entry.is_ignored)
2809                {
2810                    return true;
2811                }
2812            }
2813        }
2814        false
2815    }
2816
2817    pub fn entry(&self) -> Option<&'a Entry> {
2818        self.cursor.item()
2819    }
2820
2821    pub fn offset(&self) -> usize {
2822        self.cursor
2823            .start()
2824            .count(self.include_dirs, self.include_ignored)
2825    }
2826}
2827
2828impl<'a> Iterator for Traversal<'a> {
2829    type Item = &'a Entry;
2830
2831    fn next(&mut self) -> Option<Self::Item> {
2832        if let Some(item) = self.entry() {
2833            self.advance();
2834            Some(item)
2835        } else {
2836            None
2837        }
2838    }
2839}
2840
2841#[derive(Debug)]
2842enum TraversalTarget<'a> {
2843    Path(&'a Path),
2844    PathSuccessor(&'a Path),
2845    Count {
2846        count: usize,
2847        include_ignored: bool,
2848        include_dirs: bool,
2849    },
2850}
2851
2852impl<'a, 'b> SeekTarget<'a, EntrySummary, TraversalProgress<'a>> for TraversalTarget<'b> {
2853    fn cmp(&self, cursor_location: &TraversalProgress<'a>, _: &()) -> Ordering {
2854        match self {
2855            TraversalTarget::Path(path) => path.cmp(&cursor_location.max_path),
2856            TraversalTarget::PathSuccessor(path) => {
2857                if !cursor_location.max_path.starts_with(path) {
2858                    Ordering::Equal
2859                } else {
2860                    Ordering::Greater
2861                }
2862            }
2863            TraversalTarget::Count {
2864                count,
2865                include_dirs,
2866                include_ignored,
2867            } => Ord::cmp(
2868                count,
2869                &cursor_location.count(*include_dirs, *include_ignored),
2870            ),
2871        }
2872    }
2873}
2874
2875struct ChildEntriesIter<'a> {
2876    parent_path: &'a Path,
2877    traversal: Traversal<'a>,
2878}
2879
2880impl<'a> Iterator for ChildEntriesIter<'a> {
2881    type Item = &'a Entry;
2882
2883    fn next(&mut self) -> Option<Self::Item> {
2884        if let Some(item) = self.traversal.entry() {
2885            if item.path.starts_with(&self.parent_path) {
2886                self.traversal.advance_to_sibling();
2887                return Some(item);
2888            }
2889        }
2890        None
2891    }
2892}
2893
2894impl<'a> From<&'a Entry> for proto::Entry {
2895    fn from(entry: &'a Entry) -> Self {
2896        Self {
2897            id: entry.id.to_proto(),
2898            is_dir: entry.is_dir(),
2899            path: entry.path.to_string_lossy().into(),
2900            inode: entry.inode,
2901            mtime: Some(entry.mtime.into()),
2902            is_symlink: entry.is_symlink,
2903            is_ignored: entry.is_ignored,
2904        }
2905    }
2906}
2907
2908impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
2909    type Error = anyhow::Error;
2910
2911    fn try_from((root_char_bag, entry): (&'a CharBag, proto::Entry)) -> Result<Self> {
2912        if let Some(mtime) = entry.mtime {
2913            let kind = if entry.is_dir {
2914                EntryKind::Dir
2915            } else {
2916                let mut char_bag = *root_char_bag;
2917                char_bag.extend(entry.path.chars().map(|c| c.to_ascii_lowercase()));
2918                EntryKind::File(char_bag)
2919            };
2920            let path: Arc<Path> = PathBuf::from(entry.path).into();
2921            Ok(Entry {
2922                id: ProjectEntryId::from_proto(entry.id),
2923                kind,
2924                path,
2925                inode: entry.inode,
2926                mtime: mtime.into(),
2927                is_symlink: entry.is_symlink,
2928                is_ignored: entry.is_ignored,
2929            })
2930        } else {
2931            Err(anyhow!(
2932                "missing mtime in remote worktree entry {:?}",
2933                entry.path
2934            ))
2935        }
2936    }
2937}
2938
2939async fn send_worktree_update(client: &Arc<Client>, update: proto::UpdateWorktree) -> Result<()> {
2940    #[cfg(any(test, feature = "test-support"))]
2941    const MAX_CHUNK_SIZE: usize = 2;
2942    #[cfg(not(any(test, feature = "test-support")))]
2943    const MAX_CHUNK_SIZE: usize = 256;
2944
2945    for update in proto::split_worktree_update(update, MAX_CHUNK_SIZE) {
2946        client.request(update).await?;
2947    }
2948
2949    Ok(())
2950}
2951
2952#[cfg(test)]
2953mod tests {
2954    use super::*;
2955    use anyhow::Result;
2956    use client::test::FakeHttpClient;
2957    use fs::repository::FakeGitRepository;
2958    use fs::{FakeFs, RealFs};
2959    use gpui::{executor::Deterministic, TestAppContext};
2960    use rand::prelude::*;
2961    use serde_json::json;
2962    use std::{
2963        env,
2964        fmt::Write,
2965        time::{SystemTime, UNIX_EPOCH},
2966    };
2967
2968    use util::test::temp_tree;
2969
2970    #[gpui::test]
2971    async fn test_traversal(cx: &mut TestAppContext) {
2972        let fs = FakeFs::new(cx.background());
2973        fs.insert_tree(
2974            "/root",
2975            json!({
2976               ".gitignore": "a/b\n",
2977               "a": {
2978                   "b": "",
2979                   "c": "",
2980               }
2981            }),
2982        )
2983        .await;
2984
2985        let http_client = FakeHttpClient::with_404_response();
2986        let client = cx.read(|cx| Client::new(http_client, cx));
2987
2988        let tree = Worktree::local(
2989            client,
2990            Arc::from(Path::new("/root")),
2991            true,
2992            fs,
2993            Default::default(),
2994            &mut cx.to_async(),
2995        )
2996        .await
2997        .unwrap();
2998        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2999            .await;
3000
3001        tree.read_with(cx, |tree, _| {
3002            assert_eq!(
3003                tree.entries(false)
3004                    .map(|entry| entry.path.as_ref())
3005                    .collect::<Vec<_>>(),
3006                vec![
3007                    Path::new(""),
3008                    Path::new(".gitignore"),
3009                    Path::new("a"),
3010                    Path::new("a/c"),
3011                ]
3012            );
3013            assert_eq!(
3014                tree.entries(true)
3015                    .map(|entry| entry.path.as_ref())
3016                    .collect::<Vec<_>>(),
3017                vec![
3018                    Path::new(""),
3019                    Path::new(".gitignore"),
3020                    Path::new("a"),
3021                    Path::new("a/b"),
3022                    Path::new("a/c"),
3023                ]
3024            );
3025        })
3026    }
3027
3028    #[gpui::test(iterations = 10)]
3029    async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
3030        let fs = FakeFs::new(cx.background());
3031        fs.insert_tree(
3032            "/root",
3033            json!({
3034                "lib": {
3035                    "a": {
3036                        "a.txt": ""
3037                    },
3038                    "b": {
3039                        "b.txt": ""
3040                    }
3041                }
3042            }),
3043        )
3044        .await;
3045        fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
3046        fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
3047
3048        let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3049        let tree = Worktree::local(
3050            client,
3051            Arc::from(Path::new("/root")),
3052            true,
3053            fs.clone(),
3054            Default::default(),
3055            &mut cx.to_async(),
3056        )
3057        .await
3058        .unwrap();
3059
3060        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3061            .await;
3062
3063        tree.read_with(cx, |tree, _| {
3064            assert_eq!(
3065                tree.entries(false)
3066                    .map(|entry| entry.path.as_ref())
3067                    .collect::<Vec<_>>(),
3068                vec![
3069                    Path::new(""),
3070                    Path::new("lib"),
3071                    Path::new("lib/a"),
3072                    Path::new("lib/a/a.txt"),
3073                    Path::new("lib/a/lib"),
3074                    Path::new("lib/b"),
3075                    Path::new("lib/b/b.txt"),
3076                    Path::new("lib/b/lib"),
3077                ]
3078            );
3079        });
3080
3081        fs.rename(
3082            Path::new("/root/lib/a/lib"),
3083            Path::new("/root/lib/a/lib-2"),
3084            Default::default(),
3085        )
3086        .await
3087        .unwrap();
3088        executor.run_until_parked();
3089        tree.read_with(cx, |tree, _| {
3090            assert_eq!(
3091                tree.entries(false)
3092                    .map(|entry| entry.path.as_ref())
3093                    .collect::<Vec<_>>(),
3094                vec![
3095                    Path::new(""),
3096                    Path::new("lib"),
3097                    Path::new("lib/a"),
3098                    Path::new("lib/a/a.txt"),
3099                    Path::new("lib/a/lib-2"),
3100                    Path::new("lib/b"),
3101                    Path::new("lib/b/b.txt"),
3102                    Path::new("lib/b/lib"),
3103                ]
3104            );
3105        });
3106    }
3107
3108    #[gpui::test]
3109    async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
3110        let parent_dir = temp_tree(json!({
3111            ".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
3112            "tree": {
3113                ".git": {},
3114                ".gitignore": "ignored-dir\n",
3115                "tracked-dir": {
3116                    "tracked-file1": "",
3117                    "ancestor-ignored-file1": "",
3118                },
3119                "ignored-dir": {
3120                    "ignored-file1": ""
3121                }
3122            }
3123        }));
3124        let dir = parent_dir.path().join("tree");
3125
3126        let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3127
3128        let tree = Worktree::local(
3129            client,
3130            dir.as_path(),
3131            true,
3132            Arc::new(RealFs),
3133            Default::default(),
3134            &mut cx.to_async(),
3135        )
3136        .await
3137        .unwrap();
3138        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3139            .await;
3140        tree.flush_fs_events(cx).await;
3141        cx.read(|cx| {
3142            let tree = tree.read(cx);
3143            assert!(
3144                !tree
3145                    .entry_for_path("tracked-dir/tracked-file1")
3146                    .unwrap()
3147                    .is_ignored
3148            );
3149            assert!(
3150                tree.entry_for_path("tracked-dir/ancestor-ignored-file1")
3151                    .unwrap()
3152                    .is_ignored
3153            );
3154            assert!(
3155                tree.entry_for_path("ignored-dir/ignored-file1")
3156                    .unwrap()
3157                    .is_ignored
3158            );
3159        });
3160
3161        std::fs::write(dir.join("tracked-dir/tracked-file2"), "").unwrap();
3162        std::fs::write(dir.join("tracked-dir/ancestor-ignored-file2"), "").unwrap();
3163        std::fs::write(dir.join("ignored-dir/ignored-file2"), "").unwrap();
3164        tree.flush_fs_events(cx).await;
3165        cx.read(|cx| {
3166            let tree = tree.read(cx);
3167            assert!(
3168                !tree
3169                    .entry_for_path("tracked-dir/tracked-file2")
3170                    .unwrap()
3171                    .is_ignored
3172            );
3173            assert!(
3174                tree.entry_for_path("tracked-dir/ancestor-ignored-file2")
3175                    .unwrap()
3176                    .is_ignored
3177            );
3178            assert!(
3179                tree.entry_for_path("ignored-dir/ignored-file2")
3180                    .unwrap()
3181                    .is_ignored
3182            );
3183            assert!(tree.entry_for_path(".git").unwrap().is_ignored);
3184        });
3185    }
3186
3187    #[gpui::test]
3188    async fn test_git_repository_for_path(cx: &mut TestAppContext) {
3189        let root = temp_tree(json!({
3190            "dir1": {
3191                ".git": {},
3192                "deps": {
3193                    "dep1": {
3194                        ".git": {},
3195                        "src": {
3196                            "a.txt": ""
3197                        }
3198                    }
3199                },
3200                "src": {
3201                    "b.txt": ""
3202                }
3203            },
3204            "c.txt": "",
3205        }));
3206
3207        let http_client = FakeHttpClient::with_404_response();
3208        let client = cx.read(|cx| Client::new(http_client, cx));
3209        let tree = Worktree::local(
3210            client,
3211            root.path(),
3212            true,
3213            Arc::new(RealFs),
3214            Default::default(),
3215            &mut cx.to_async(),
3216        )
3217        .await
3218        .unwrap();
3219
3220        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3221            .await;
3222        tree.flush_fs_events(cx).await;
3223
3224        tree.read_with(cx, |tree, _cx| {
3225            let tree = tree.as_local().unwrap();
3226
3227            assert!(tree.repo_for("c.txt".as_ref()).is_none());
3228
3229            let repo = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap();
3230            assert_eq!(repo.content_path.as_ref(), Path::new("dir1"));
3231            assert_eq!(repo.git_dir_path.as_ref(), Path::new("dir1/.git"));
3232
3233            let repo = tree.repo_for("dir1/deps/dep1/src/a.txt".as_ref()).unwrap();
3234            assert_eq!(repo.content_path.as_ref(), Path::new("dir1/deps/dep1"));
3235            assert_eq!(repo.git_dir_path.as_ref(), Path::new("dir1/deps/dep1/.git"),);
3236        });
3237
3238        let original_scan_id = tree.read_with(cx, |tree, _cx| {
3239            let tree = tree.as_local().unwrap();
3240            tree.repo_for("dir1/src/b.txt".as_ref()).unwrap().scan_id
3241        });
3242
3243        std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
3244        tree.flush_fs_events(cx).await;
3245
3246        tree.read_with(cx, |tree, _cx| {
3247            let tree = tree.as_local().unwrap();
3248            let new_scan_id = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap().scan_id;
3249            assert_ne!(
3250                original_scan_id, new_scan_id,
3251                "original {original_scan_id}, new {new_scan_id}"
3252            );
3253        });
3254
3255        std::fs::remove_dir_all(root.path().join("dir1/.git")).unwrap();
3256        tree.flush_fs_events(cx).await;
3257
3258        tree.read_with(cx, |tree, _cx| {
3259            let tree = tree.as_local().unwrap();
3260
3261            assert!(tree.repo_for("dir1/src/b.txt".as_ref()).is_none());
3262        });
3263    }
3264
3265    #[test]
3266    fn test_changed_repos() {
3267        fn fake_entry(git_dir_path: impl AsRef<Path>, scan_id: usize) -> GitRepositoryEntry {
3268            GitRepositoryEntry {
3269                repo: Arc::new(Mutex::new(FakeGitRepository::default())),
3270                scan_id,
3271                content_path: git_dir_path.as_ref().parent().unwrap().into(),
3272                git_dir_path: git_dir_path.as_ref().into(),
3273            }
3274        }
3275
3276        let prev_repos: Vec<GitRepositoryEntry> = vec![
3277            fake_entry("/.git", 0),
3278            fake_entry("/a/.git", 0),
3279            fake_entry("/a/b/.git", 0),
3280        ];
3281
3282        let new_repos: Vec<GitRepositoryEntry> = vec![
3283            fake_entry("/a/.git", 1),
3284            fake_entry("/a/b/.git", 0),
3285            fake_entry("/a/c/.git", 0),
3286        ];
3287
3288        let res = LocalWorktree::changed_repos(&prev_repos, &new_repos);
3289
3290        // Deletion retained
3291        assert!(res
3292            .iter()
3293            .find(|repo| repo.git_dir_path.as_ref() == Path::new("/.git") && repo.scan_id == 0)
3294            .is_some());
3295
3296        // Update retained
3297        assert!(res
3298            .iter()
3299            .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/.git") && repo.scan_id == 1)
3300            .is_some());
3301
3302        // Addition retained
3303        assert!(res
3304            .iter()
3305            .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/c/.git") && repo.scan_id == 0)
3306            .is_some());
3307
3308        // Nochange, not retained
3309        assert!(res
3310            .iter()
3311            .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/b/.git") && repo.scan_id == 0)
3312            .is_none());
3313    }
3314
3315    #[gpui::test]
3316    async fn test_write_file(cx: &mut TestAppContext) {
3317        let dir = temp_tree(json!({
3318            ".git": {},
3319            ".gitignore": "ignored-dir\n",
3320            "tracked-dir": {},
3321            "ignored-dir": {}
3322        }));
3323
3324        let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3325
3326        let tree = Worktree::local(
3327            client,
3328            dir.path(),
3329            true,
3330            Arc::new(RealFs),
3331            Default::default(),
3332            &mut cx.to_async(),
3333        )
3334        .await
3335        .unwrap();
3336        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
3337            .await;
3338        tree.flush_fs_events(cx).await;
3339
3340        tree.update(cx, |tree, cx| {
3341            tree.as_local().unwrap().write_file(
3342                Path::new("tracked-dir/file.txt"),
3343                "hello".into(),
3344                Default::default(),
3345                cx,
3346            )
3347        })
3348        .await
3349        .unwrap();
3350        tree.update(cx, |tree, cx| {
3351            tree.as_local().unwrap().write_file(
3352                Path::new("ignored-dir/file.txt"),
3353                "world".into(),
3354                Default::default(),
3355                cx,
3356            )
3357        })
3358        .await
3359        .unwrap();
3360
3361        tree.read_with(cx, |tree, _| {
3362            let tracked = tree.entry_for_path("tracked-dir/file.txt").unwrap();
3363            let ignored = tree.entry_for_path("ignored-dir/file.txt").unwrap();
3364            assert!(!tracked.is_ignored);
3365            assert!(ignored.is_ignored);
3366        });
3367    }
3368
3369    #[gpui::test(iterations = 30)]
3370    async fn test_create_directory(cx: &mut TestAppContext) {
3371        let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
3372
3373        let fs = FakeFs::new(cx.background());
3374        fs.insert_tree(
3375            "/a",
3376            json!({
3377                "b": {},
3378                "c": {},
3379                "d": {},
3380            }),
3381        )
3382        .await;
3383
3384        let tree = Worktree::local(
3385            client,
3386            "/a".as_ref(),
3387            true,
3388            fs,
3389            Default::default(),
3390            &mut cx.to_async(),
3391        )
3392        .await
3393        .unwrap();
3394
3395        let entry = tree
3396            .update(cx, |tree, cx| {
3397                tree.as_local_mut()
3398                    .unwrap()
3399                    .create_entry("a/e".as_ref(), true, cx)
3400            })
3401            .await
3402            .unwrap();
3403        assert!(entry.is_dir());
3404
3405        cx.foreground().run_until_parked();
3406        tree.read_with(cx, |tree, _| {
3407            assert_eq!(tree.entry_for_path("a/e").unwrap().kind, EntryKind::Dir);
3408        });
3409    }
3410
3411    #[gpui::test(iterations = 100)]
3412    fn test_random(mut rng: StdRng) {
3413        let operations = env::var("OPERATIONS")
3414            .map(|o| o.parse().unwrap())
3415            .unwrap_or(40);
3416        let initial_entries = env::var("INITIAL_ENTRIES")
3417            .map(|o| o.parse().unwrap())
3418            .unwrap_or(20);
3419
3420        let root_dir = tempdir::TempDir::new("worktree-test").unwrap();
3421        for _ in 0..initial_entries {
3422            randomly_mutate_tree(root_dir.path(), 1.0, &mut rng).unwrap();
3423        }
3424        log::info!("Generated initial tree");
3425
3426        let (notify_tx, _notify_rx) = mpsc::unbounded();
3427        let fs = Arc::new(RealFs);
3428        let next_entry_id = Arc::new(AtomicUsize::new(0));
3429        let mut initial_snapshot = LocalSnapshot {
3430            removed_entry_ids: Default::default(),
3431            ignores_by_parent_abs_path: Default::default(),
3432            git_repositories: Default::default(),
3433            next_entry_id: next_entry_id.clone(),
3434            snapshot: Snapshot {
3435                id: WorktreeId::from_usize(0),
3436                entries_by_path: Default::default(),
3437                entries_by_id: Default::default(),
3438                abs_path: root_dir.path().into(),
3439                root_name: Default::default(),
3440                root_char_bag: Default::default(),
3441                scan_id: 0,
3442                is_complete: true,
3443            },
3444        };
3445        initial_snapshot.insert_entry(
3446            Entry::new(
3447                Path::new("").into(),
3448                &smol::block_on(fs.metadata(root_dir.path()))
3449                    .unwrap()
3450                    .unwrap(),
3451                &next_entry_id,
3452                Default::default(),
3453            ),
3454            fs.as_ref(),
3455        );
3456        let mut scanner = BackgroundScanner::new(
3457            Arc::new(Mutex::new(initial_snapshot.clone())),
3458            notify_tx,
3459            fs.clone(),
3460            Arc::new(gpui::executor::Background::new()),
3461        );
3462        smol::block_on(scanner.scan_dirs()).unwrap();
3463        scanner.snapshot().check_invariants();
3464
3465        let mut events = Vec::new();
3466        let mut snapshots = Vec::new();
3467        let mut mutations_len = operations;
3468        while mutations_len > 1 {
3469            if !events.is_empty() && rng.gen_bool(0.4) {
3470                let len = rng.gen_range(0..=events.len());
3471                let to_deliver = events.drain(0..len).collect::<Vec<_>>();
3472                log::info!("Delivering events: {:#?}", to_deliver);
3473                smol::block_on(scanner.process_events(to_deliver));
3474                scanner.snapshot().check_invariants();
3475            } else {
3476                events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
3477                mutations_len -= 1;
3478            }
3479
3480            if rng.gen_bool(0.2) {
3481                snapshots.push(scanner.snapshot());
3482            }
3483        }
3484        log::info!("Quiescing: {:#?}", events);
3485        smol::block_on(scanner.process_events(events));
3486        scanner.snapshot().check_invariants();
3487
3488        let (notify_tx, _notify_rx) = mpsc::unbounded();
3489        let mut new_scanner = BackgroundScanner::new(
3490            Arc::new(Mutex::new(initial_snapshot)),
3491            notify_tx,
3492            scanner.fs.clone(),
3493            scanner.executor.clone(),
3494        );
3495        smol::block_on(new_scanner.scan_dirs()).unwrap();
3496        assert_eq!(
3497            scanner.snapshot().to_vec(true),
3498            new_scanner.snapshot().to_vec(true)
3499        );
3500
3501        for mut prev_snapshot in snapshots {
3502            let include_ignored = rng.gen::<bool>();
3503            if !include_ignored {
3504                let mut entries_by_path_edits = Vec::new();
3505                let mut entries_by_id_edits = Vec::new();
3506                for entry in prev_snapshot
3507                    .entries_by_id
3508                    .cursor::<()>()
3509                    .filter(|e| e.is_ignored)
3510                {
3511                    entries_by_path_edits.push(Edit::Remove(PathKey(entry.path.clone())));
3512                    entries_by_id_edits.push(Edit::Remove(entry.id));
3513                }
3514
3515                prev_snapshot
3516                    .entries_by_path
3517                    .edit(entries_by_path_edits, &());
3518                prev_snapshot.entries_by_id.edit(entries_by_id_edits, &());
3519            }
3520
3521            let update = scanner
3522                .snapshot()
3523                .build_update(&prev_snapshot, 0, 0, include_ignored);
3524            prev_snapshot.apply_remote_update(update).unwrap();
3525            assert_eq!(
3526                prev_snapshot.to_vec(true),
3527                scanner.snapshot().to_vec(include_ignored)
3528            );
3529        }
3530    }
3531
3532    fn randomly_mutate_tree(
3533        root_path: &Path,
3534        insertion_probability: f64,
3535        rng: &mut impl Rng,
3536    ) -> Result<Vec<fsevent::Event>> {
3537        let root_path = root_path.canonicalize().unwrap();
3538        let (dirs, files) = read_dir_recursive(root_path.clone());
3539
3540        let mut events = Vec::new();
3541        let mut record_event = |path: PathBuf| {
3542            events.push(fsevent::Event {
3543                event_id: SystemTime::now()
3544                    .duration_since(UNIX_EPOCH)
3545                    .unwrap()
3546                    .as_secs(),
3547                flags: fsevent::StreamFlags::empty(),
3548                path,
3549            });
3550        };
3551
3552        if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
3553            let path = dirs.choose(rng).unwrap();
3554            let new_path = path.join(gen_name(rng));
3555
3556            if rng.gen() {
3557                log::info!("Creating dir {:?}", new_path.strip_prefix(root_path)?);
3558                std::fs::create_dir(&new_path)?;
3559            } else {
3560                log::info!("Creating file {:?}", new_path.strip_prefix(root_path)?);
3561                std::fs::write(&new_path, "")?;
3562            }
3563            record_event(new_path);
3564        } else if rng.gen_bool(0.05) {
3565            let ignore_dir_path = dirs.choose(rng).unwrap();
3566            let ignore_path = ignore_dir_path.join(&*GITIGNORE);
3567
3568            let (subdirs, subfiles) = read_dir_recursive(ignore_dir_path.clone());
3569            let files_to_ignore = {
3570                let len = rng.gen_range(0..=subfiles.len());
3571                subfiles.choose_multiple(rng, len)
3572            };
3573            let dirs_to_ignore = {
3574                let len = rng.gen_range(0..subdirs.len());
3575                subdirs.choose_multiple(rng, len)
3576            };
3577
3578            let mut ignore_contents = String::new();
3579            for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
3580                writeln!(
3581                    ignore_contents,
3582                    "{}",
3583                    path_to_ignore
3584                        .strip_prefix(&ignore_dir_path)?
3585                        .to_str()
3586                        .unwrap()
3587                )
3588                .unwrap();
3589            }
3590            log::info!(
3591                "Creating {:?} with contents:\n{}",
3592                ignore_path.strip_prefix(&root_path)?,
3593                ignore_contents
3594            );
3595            std::fs::write(&ignore_path, ignore_contents).unwrap();
3596            record_event(ignore_path);
3597        } else {
3598            let old_path = {
3599                let file_path = files.choose(rng);
3600                let dir_path = dirs[1..].choose(rng);
3601                file_path.into_iter().chain(dir_path).choose(rng).unwrap()
3602            };
3603
3604            let is_rename = rng.gen();
3605            if is_rename {
3606                let new_path_parent = dirs
3607                    .iter()
3608                    .filter(|d| !d.starts_with(old_path))
3609                    .choose(rng)
3610                    .unwrap();
3611
3612                let overwrite_existing_dir =
3613                    !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
3614                let new_path = if overwrite_existing_dir {
3615                    std::fs::remove_dir_all(&new_path_parent).ok();
3616                    new_path_parent.to_path_buf()
3617                } else {
3618                    new_path_parent.join(gen_name(rng))
3619                };
3620
3621                log::info!(
3622                    "Renaming {:?} to {}{:?}",
3623                    old_path.strip_prefix(&root_path)?,
3624                    if overwrite_existing_dir {
3625                        "overwrite "
3626                    } else {
3627                        ""
3628                    },
3629                    new_path.strip_prefix(&root_path)?
3630                );
3631                std::fs::rename(&old_path, &new_path)?;
3632                record_event(old_path.clone());
3633                record_event(new_path);
3634            } else if old_path.is_dir() {
3635                let (dirs, files) = read_dir_recursive(old_path.clone());
3636
3637                log::info!("Deleting dir {:?}", old_path.strip_prefix(&root_path)?);
3638                std::fs::remove_dir_all(&old_path).unwrap();
3639                for file in files {
3640                    record_event(file);
3641                }
3642                for dir in dirs {
3643                    record_event(dir);
3644                }
3645            } else {
3646                log::info!("Deleting file {:?}", old_path.strip_prefix(&root_path)?);
3647                std::fs::remove_file(old_path).unwrap();
3648                record_event(old_path.clone());
3649            }
3650        }
3651
3652        Ok(events)
3653    }
3654
3655    fn read_dir_recursive(path: PathBuf) -> (Vec<PathBuf>, Vec<PathBuf>) {
3656        let child_entries = std::fs::read_dir(&path).unwrap();
3657        let mut dirs = vec![path];
3658        let mut files = Vec::new();
3659        for child_entry in child_entries {
3660            let child_path = child_entry.unwrap().path();
3661            if child_path.is_dir() {
3662                let (child_dirs, child_files) = read_dir_recursive(child_path);
3663                dirs.extend(child_dirs);
3664                files.extend(child_files);
3665            } else {
3666                files.push(child_path);
3667            }
3668        }
3669        (dirs, files)
3670    }
3671
3672    fn gen_name(rng: &mut impl Rng) -> String {
3673        (0..6)
3674            .map(|_| rng.sample(rand::distributions::Alphanumeric))
3675            .map(char::from)
3676            .collect()
3677    }
3678
3679    impl LocalSnapshot {
3680        fn check_invariants(&self) {
3681            let mut files = self.files(true, 0);
3682            let mut visible_files = self.files(false, 0);
3683            for entry in self.entries_by_path.cursor::<()>() {
3684                if entry.is_file() {
3685                    assert_eq!(files.next().unwrap().inode, entry.inode);
3686                    if !entry.is_ignored {
3687                        assert_eq!(visible_files.next().unwrap().inode, entry.inode);
3688                    }
3689                }
3690            }
3691            assert!(files.next().is_none());
3692            assert!(visible_files.next().is_none());
3693
3694            let mut bfs_paths = Vec::new();
3695            let mut stack = vec![Path::new("")];
3696            while let Some(path) = stack.pop() {
3697                bfs_paths.push(path);
3698                let ix = stack.len();
3699                for child_entry in self.child_entries(path) {
3700                    stack.insert(ix, &child_entry.path);
3701                }
3702            }
3703
3704            let dfs_paths_via_iter = self
3705                .entries_by_path
3706                .cursor::<()>()
3707                .map(|e| e.path.as_ref())
3708                .collect::<Vec<_>>();
3709            assert_eq!(bfs_paths, dfs_paths_via_iter);
3710
3711            let dfs_paths_via_traversal = self
3712                .entries(true)
3713                .map(|e| e.path.as_ref())
3714                .collect::<Vec<_>>();
3715            assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter);
3716
3717            for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() {
3718                let ignore_parent_path =
3719                    ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap();
3720                assert!(self.entry_for_path(&ignore_parent_path).is_some());
3721                assert!(self
3722                    .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
3723                    .is_some());
3724            }
3725        }
3726
3727        fn to_vec(&self, include_ignored: bool) -> Vec<(&Path, u64, bool)> {
3728            let mut paths = Vec::new();
3729            for entry in self.entries_by_path.cursor::<()>() {
3730                if include_ignored || !entry.is_ignored {
3731                    paths.push((entry.path.as_ref(), entry.inode, entry.is_ignored));
3732                }
3733            }
3734            paths.sort_by(|a, b| a.0.cmp(b.0));
3735            paths
3736        }
3737    }
3738}