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