worktree.rs

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