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