worktree.rs

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