worktree.rs

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