worktree.rs

   1mod char_bag;
   2mod fuzzy;
   3mod ignore;
   4
   5use crate::{
   6    editor::{History, Snapshot as BufferSnapshot},
   7    sum_tree::{self, Cursor, Edit, SeekBias, SumTree},
   8};
   9use ::ignore::gitignore::Gitignore;
  10use anyhow::{anyhow, Context, Result};
  11pub use fuzzy::{match_paths, PathMatch};
  12use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
  13use lazy_static::lazy_static;
  14use parking_lot::Mutex;
  15use postage::{
  16    prelude::{Sink, Stream},
  17    watch,
  18};
  19use smol::{channel::Sender, Timer};
  20use std::{
  21    cmp,
  22    collections::{BTreeMap, HashSet},
  23    ffi::{CStr, OsStr},
  24    fmt, fs,
  25    future::Future,
  26    io::{self, Read, Write},
  27    ops::{AddAssign, Deref},
  28    os::unix::{ffi::OsStrExt, fs::MetadataExt},
  29    path::{Path, PathBuf},
  30    sync::Arc,
  31    time::Duration,
  32};
  33
  34use self::{char_bag::CharBag, ignore::IgnoreStack};
  35
  36lazy_static! {
  37    static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
  38}
  39
  40#[derive(Clone, Debug)]
  41enum ScanState {
  42    Idle,
  43    Scanning,
  44    Err(Arc<io::Error>),
  45}
  46
  47pub struct Worktree {
  48    snapshot: Snapshot,
  49    background_snapshot: Arc<Mutex<Snapshot>>,
  50    scan_state: (watch::Sender<ScanState>, watch::Receiver<ScanState>),
  51    _event_stream_handle: fsevent::Handle,
  52    poll_scheduled: bool,
  53}
  54
  55#[derive(Clone)]
  56pub struct FileHandle {
  57    worktree: ModelHandle<Worktree>,
  58    path: Arc<Path>,
  59}
  60
  61impl Worktree {
  62    pub fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
  63        let abs_path = path.into();
  64        let root_name_chars = abs_path.file_name().map_or(Vec::new(), |n| {
  65            n.to_string_lossy().chars().chain(Some('/')).collect()
  66        });
  67        let (scan_state_tx, scan_state_rx) = smol::channel::unbounded();
  68        let id = ctx.model_id();
  69        let snapshot = Snapshot {
  70            id,
  71            scan_id: 0,
  72            abs_path,
  73            root_name_chars,
  74            ignores: Default::default(),
  75            entries: Default::default(),
  76        };
  77        let (event_stream, event_stream_handle) =
  78            fsevent::EventStream::new(&[snapshot.abs_path.as_ref()], Duration::from_millis(100));
  79
  80        let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
  81
  82        let tree = Self {
  83            snapshot,
  84            background_snapshot: background_snapshot.clone(),
  85            scan_state: watch::channel_with(ScanState::Scanning),
  86            _event_stream_handle: event_stream_handle,
  87            poll_scheduled: false,
  88        };
  89
  90        std::thread::spawn(move || {
  91            let scanner = BackgroundScanner::new(background_snapshot, scan_state_tx, id);
  92            scanner.run(event_stream)
  93        });
  94
  95        ctx.spawn_stream(scan_state_rx, Self::observe_scan_state, |_, _| {})
  96            .detach();
  97
  98        tree
  99    }
 100
 101    pub fn scan_complete(&self) -> impl Future<Output = ()> {
 102        let mut scan_state_rx = self.scan_state.1.clone();
 103        async move {
 104            let mut scan_state = Some(scan_state_rx.borrow().clone());
 105            while let Some(ScanState::Scanning) = scan_state {
 106                scan_state = scan_state_rx.recv().await;
 107            }
 108        }
 109    }
 110
 111    pub fn next_scan_complete(&self) -> impl Future<Output = ()> {
 112        let mut scan_state_rx = self.scan_state.1.clone();
 113        let mut did_scan = matches!(*scan_state_rx.borrow(), ScanState::Scanning);
 114        async move {
 115            loop {
 116                if let ScanState::Scanning = *scan_state_rx.borrow() {
 117                    did_scan = true;
 118                } else if did_scan {
 119                    break;
 120                }
 121                scan_state_rx.recv().await;
 122            }
 123        }
 124    }
 125
 126    fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
 127        let _ = self.scan_state.0.blocking_send(scan_state);
 128        self.poll_entries(ctx);
 129    }
 130
 131    fn poll_entries(&mut self, ctx: &mut ModelContext<Self>) {
 132        self.snapshot = self.background_snapshot.lock().clone();
 133        ctx.notify();
 134
 135        if self.is_scanning() && !self.poll_scheduled {
 136            ctx.spawn(Timer::after(Duration::from_millis(100)), |this, _, ctx| {
 137                this.poll_scheduled = false;
 138                this.poll_entries(ctx);
 139            })
 140            .detach();
 141            self.poll_scheduled = true;
 142        }
 143    }
 144
 145    fn is_scanning(&self) -> bool {
 146        if let ScanState::Scanning = *self.scan_state.1.borrow() {
 147            true
 148        } else {
 149            false
 150        }
 151    }
 152
 153    pub fn snapshot(&self) -> Snapshot {
 154        self.snapshot.clone()
 155    }
 156
 157    pub fn contains_abs_path(&self, path: &Path) -> bool {
 158        path.starts_with(&self.snapshot.abs_path)
 159    }
 160
 161    pub fn load_history(
 162        &self,
 163        path: &Path,
 164        ctx: &AppContext,
 165    ) -> impl Future<Output = Result<History>> {
 166        let abs_path = self.snapshot.abs_path.join(path);
 167        ctx.background_executor().spawn(async move {
 168            let mut file = std::fs::File::open(&abs_path)?;
 169            let mut base_text = String::new();
 170            file.read_to_string(&mut base_text)?;
 171            Ok(History::new(Arc::from(base_text)))
 172        })
 173    }
 174
 175    pub fn save<'a>(
 176        &self,
 177        path: &Path,
 178        content: BufferSnapshot,
 179        ctx: &AppContext,
 180    ) -> Task<Result<()>> {
 181        let abs_path = self.snapshot.abs_path.join(path);
 182        ctx.background_executor().spawn(async move {
 183            let buffer_size = content.text_summary().bytes.min(10 * 1024);
 184            let file = std::fs::File::create(&abs_path)?;
 185            let mut writer = std::io::BufWriter::with_capacity(buffer_size, file);
 186            for chunk in content.fragments() {
 187                writer.write(chunk.as_bytes())?;
 188            }
 189            writer.flush()?;
 190            Ok(())
 191        })
 192    }
 193}
 194
 195impl Entity for Worktree {
 196    type Event = ();
 197}
 198
 199impl Deref for Worktree {
 200    type Target = Snapshot;
 201
 202    fn deref(&self) -> &Self::Target {
 203        &self.snapshot
 204    }
 205}
 206
 207impl fmt::Debug for Worktree {
 208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 209        self.snapshot.fmt(f)
 210    }
 211}
 212
 213#[derive(Clone)]
 214pub struct Snapshot {
 215    id: usize,
 216    scan_id: usize,
 217    abs_path: Arc<Path>,
 218    root_name_chars: Vec<char>,
 219    ignores: BTreeMap<u64, Arc<Gitignore>>,
 220    entries: SumTree<Entry>,
 221}
 222
 223impl Snapshot {
 224    pub fn file_count(&self) -> usize {
 225        self.entries.summary().file_count
 226    }
 227
 228    pub fn visible_file_count(&self) -> usize {
 229        self.entries.summary().visible_file_count
 230    }
 231
 232    pub fn files(&self, start: usize) -> FileIter {
 233        FileIter::all(self, start)
 234    }
 235
 236    #[cfg(test)]
 237    pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
 238        let mut cursor = self.entries.cursor::<(), ()>();
 239        cursor.next();
 240        cursor.map(|entry| entry.path())
 241    }
 242
 243    pub fn visible_files(&self, start: usize) -> FileIter {
 244        FileIter::visible(self, start)
 245    }
 246
 247    pub fn root_entry(&self) -> &Entry {
 248        self.entry_for_path("").unwrap()
 249    }
 250
 251    pub fn root_name(&self) -> Option<&OsStr> {
 252        self.abs_path.file_name()
 253    }
 254
 255    pub fn root_name_chars(&self) -> &[char] {
 256        &self.root_name_chars
 257    }
 258
 259    fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
 260        let mut cursor = self.entries.cursor::<_, ()>();
 261        if cursor.seek(&PathSearch::Exact(path.as_ref()), SeekBias::Left) {
 262            cursor.item()
 263        } else {
 264            None
 265        }
 266    }
 267
 268    pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
 269        self.entry_for_path(path.as_ref()).map(|e| e.inode())
 270    }
 271
 272    fn is_path_ignored(&self, path: &Path) -> Result<bool> {
 273        todo!();
 274        Ok(false)
 275        // let mut entry = self
 276        //     .entry_for_path(path)
 277        //     .ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
 278
 279        // if path.starts_with(".git") {
 280        //     Ok(true)
 281        // } else {
 282        //     while let Some(parent_entry) =
 283        //         entry.path().parent().and_then(|p| self.entry_for_path(p))
 284        //     {
 285        //         let parent_path = parent_entry.path();
 286        //         if let Some((ignore, _)) = self.ignores.get(parent_path) {
 287        //             let relative_path = path.strip_prefix(parent_path).unwrap();
 288        //             match ignore.matched_path_or_any_parents(relative_path, entry.is_dir()) {
 289        //                 ::ignore::Match::Whitelist(_) => return Ok(false),
 290        //                 ::ignore::Match::Ignore(_) => return Ok(true),
 291        //                 ::ignore::Match::None => {}
 292        //             }
 293        //         }
 294        //         entry = parent_entry;
 295        //     }
 296        //     Ok(false)
 297        // }
 298    }
 299
 300    fn insert_entry(&mut self, entry: Entry) {
 301        if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
 302            self.insert_ignore_file(entry.path());
 303        }
 304        self.entries.insert(entry);
 305    }
 306
 307    fn populate_dir(
 308        &mut self,
 309        parent_path: Arc<Path>,
 310        entries: impl IntoIterator<Item = Entry>,
 311        ignore: Option<Arc<Gitignore>>,
 312    ) {
 313        let mut edits = Vec::new();
 314
 315        let mut parent_entry = self.entries.get(&PathKey(parent_path)).unwrap().clone();
 316        if let Some(ignore) = ignore {
 317            self.ignores.insert(parent_entry.inode, ignore);
 318        }
 319        if matches!(parent_entry.kind, EntryKind::PendingDir) {
 320            parent_entry.kind = EntryKind::Dir;
 321        } else {
 322            unreachable!();
 323        }
 324        edits.push(Edit::Insert(parent_entry));
 325
 326        for entry in entries {
 327            if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
 328                self.insert_ignore_file(entry.path());
 329            }
 330            edits.push(Edit::Insert(entry));
 331        }
 332        self.entries.edit(edits);
 333    }
 334
 335    fn remove_path(&mut self, path: &Path) {
 336        let new_entries = {
 337            let mut cursor = self.entries.cursor::<_, ()>();
 338            let mut new_entries = cursor.slice(&PathSearch::Exact(path), SeekBias::Left);
 339            cursor.seek_forward(&PathSearch::Successor(path), SeekBias::Left);
 340            new_entries.push_tree(cursor.suffix());
 341            new_entries
 342        };
 343        self.entries = new_entries;
 344
 345        // if path.file_name() == Some(&GITIGNORE) {
 346        //     if let Some((_, scan_id)) = self.ignores.get_mut(path.parent().unwrap()) {
 347        //         *scan_id = self.scan_id;
 348        //     }
 349        // }
 350    }
 351
 352    fn insert_ignore_file(&mut self, path: &Path) -> Arc<Gitignore> {
 353        let (ignore, err) = Gitignore::new(self.abs_path.join(path));
 354        if let Some(err) = err {
 355            log::error!("error in ignore file {:?} - {:?}", path, err);
 356        }
 357        let ignore = Arc::new(ignore);
 358        let ignore_parent_inode = self.entry_for_path(path.parent().unwrap()).unwrap().inode;
 359        self.ignores.insert(ignore_parent_inode, ignore.clone());
 360        ignore
 361    }
 362}
 363
 364impl fmt::Debug for Snapshot {
 365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 366        for entry in self.entries.cursor::<(), ()>() {
 367            for _ in entry.path().ancestors().skip(1) {
 368                write!(f, " ")?;
 369            }
 370            writeln!(f, "{:?} (inode: {})", entry.path(), entry.inode())?;
 371        }
 372        Ok(())
 373    }
 374}
 375
 376impl FileHandle {
 377    pub fn path(&self) -> &Arc<Path> {
 378        &self.path
 379    }
 380
 381    pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
 382        self.worktree.read(ctx).load_history(&self.path, ctx)
 383    }
 384
 385    pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
 386        let worktree = self.worktree.read(ctx);
 387        worktree.save(&self.path, content, ctx)
 388    }
 389
 390    pub fn entry_id(&self) -> (usize, Arc<Path>) {
 391        (self.worktree.id(), self.path.clone())
 392    }
 393}
 394
 395#[derive(Clone, Debug)]
 396pub struct Entry {
 397    kind: EntryKind,
 398    path: Arc<Path>,
 399    inode: u64,
 400    is_symlink: bool,
 401    is_ignored: bool,
 402}
 403
 404#[derive(Clone, Debug)]
 405pub enum EntryKind {
 406    PendingDir,
 407    Dir,
 408    File(CharBag),
 409}
 410
 411impl Entry {
 412    pub fn path(&self) -> &Arc<Path> {
 413        &self.path
 414    }
 415
 416    pub fn inode(&self) -> u64 {
 417        self.inode
 418    }
 419
 420    fn is_ignored(&self) -> bool {
 421        self.is_ignored
 422    }
 423
 424    fn set_ignored(&mut self, ignored: bool) {
 425        self.is_ignored = ignored;
 426    }
 427
 428    fn is_dir(&self) -> bool {
 429        matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
 430    }
 431}
 432
 433impl sum_tree::Item for Entry {
 434    type Summary = EntrySummary;
 435
 436    fn summary(&self) -> Self::Summary {
 437        let file_count;
 438        let visible_file_count;
 439        if matches!(self.kind, EntryKind::File(_)) {
 440            file_count = 1;
 441            if self.is_ignored {
 442                visible_file_count = 0;
 443            } else {
 444                visible_file_count = 1;
 445            }
 446        } else {
 447            file_count = 0;
 448            visible_file_count = 0;
 449        }
 450
 451        EntrySummary {
 452            max_path: self.path().clone(),
 453            file_count,
 454            visible_file_count,
 455        }
 456    }
 457}
 458
 459impl sum_tree::KeyedItem for Entry {
 460    type Key = PathKey;
 461
 462    fn key(&self) -> Self::Key {
 463        PathKey(self.path().clone())
 464    }
 465}
 466
 467#[derive(Clone, Debug)]
 468pub struct EntrySummary {
 469    max_path: Arc<Path>,
 470    file_count: usize,
 471    visible_file_count: usize,
 472}
 473
 474impl Default for EntrySummary {
 475    fn default() -> Self {
 476        Self {
 477            max_path: Arc::from(Path::new("")),
 478            file_count: 0,
 479            visible_file_count: 0,
 480        }
 481    }
 482}
 483
 484impl<'a> AddAssign<&'a EntrySummary> for EntrySummary {
 485    fn add_assign(&mut self, rhs: &'a EntrySummary) {
 486        self.max_path = rhs.max_path.clone();
 487        self.file_count += rhs.file_count;
 488        self.visible_file_count += rhs.visible_file_count;
 489    }
 490}
 491
 492#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
 493pub struct PathKey(Arc<Path>);
 494
 495impl Default for PathKey {
 496    fn default() -> Self {
 497        Self(Path::new("").into())
 498    }
 499}
 500
 501impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
 502    fn add_summary(&mut self, summary: &'a EntrySummary) {
 503        self.0 = summary.max_path.clone();
 504    }
 505}
 506
 507#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 508enum PathSearch<'a> {
 509    Exact(&'a Path),
 510    Successor(&'a Path),
 511}
 512
 513impl<'a> Ord for PathSearch<'a> {
 514    fn cmp(&self, other: &Self) -> cmp::Ordering {
 515        match (self, other) {
 516            (Self::Exact(a), Self::Exact(b)) => a.cmp(b),
 517            (Self::Successor(a), Self::Exact(b)) => {
 518                if b.starts_with(a) {
 519                    cmp::Ordering::Greater
 520                } else {
 521                    a.cmp(b)
 522                }
 523            }
 524            _ => todo!("not sure we need the other two cases"),
 525        }
 526    }
 527}
 528
 529impl<'a> PartialOrd for PathSearch<'a> {
 530    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
 531        Some(self.cmp(other))
 532    }
 533}
 534
 535impl<'a> Default for PathSearch<'a> {
 536    fn default() -> Self {
 537        Self::Exact(Path::new("").into())
 538    }
 539}
 540
 541impl<'a: 'b, 'b> sum_tree::Dimension<'a, EntrySummary> for PathSearch<'b> {
 542    fn add_summary(&mut self, summary: &'a EntrySummary) {
 543        *self = Self::Exact(summary.max_path.as_ref());
 544    }
 545}
 546
 547#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
 548pub struct FileCount(usize);
 549
 550impl<'a> sum_tree::Dimension<'a, EntrySummary> for FileCount {
 551    fn add_summary(&mut self, summary: &'a EntrySummary) {
 552        self.0 += summary.file_count;
 553    }
 554}
 555
 556#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
 557pub struct VisibleFileCount(usize);
 558
 559impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
 560    fn add_summary(&mut self, summary: &'a EntrySummary) {
 561        self.0 += summary.visible_file_count;
 562    }
 563}
 564
 565struct BackgroundScanner {
 566    snapshot: Arc<Mutex<Snapshot>>,
 567    notify: Sender<ScanState>,
 568    other_mount_paths: HashSet<PathBuf>,
 569    thread_pool: scoped_pool::Pool,
 570    root_char_bag: CharBag,
 571}
 572
 573impl BackgroundScanner {
 574    fn new(snapshot: Arc<Mutex<Snapshot>>, notify: Sender<ScanState>, worktree_id: usize) -> Self {
 575        let root_char_bag = CharBag::from(snapshot.lock().root_name_chars.as_slice());
 576        let mut scanner = Self {
 577            root_char_bag,
 578            snapshot,
 579            notify,
 580            other_mount_paths: Default::default(),
 581            thread_pool: scoped_pool::Pool::new(16, format!("worktree-{}-scanner", worktree_id)),
 582        };
 583        scanner.update_other_mount_paths();
 584        scanner
 585    }
 586
 587    fn update_other_mount_paths(&mut self) {
 588        let path = self.snapshot.lock().abs_path.clone();
 589        self.other_mount_paths.clear();
 590        self.other_mount_paths.extend(
 591            mounted_volume_paths()
 592                .into_iter()
 593                .filter(|mount_path| !path.starts_with(mount_path)),
 594        );
 595    }
 596
 597    fn abs_path(&self) -> Arc<Path> {
 598        self.snapshot.lock().abs_path.clone()
 599    }
 600
 601    fn snapshot(&self) -> Snapshot {
 602        self.snapshot.lock().clone()
 603    }
 604
 605    fn run(mut self, event_stream: fsevent::EventStream) {
 606        if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
 607            return;
 608        }
 609
 610        if let Err(err) = self.scan_dirs() {
 611            if smol::block_on(self.notify.send(ScanState::Err(Arc::new(err)))).is_err() {
 612                return;
 613            }
 614        }
 615
 616        if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
 617            return;
 618        }
 619
 620        return;
 621
 622        event_stream.run(move |events| {
 623            if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
 624                return false;
 625            }
 626
 627            if !self.process_events(events) {
 628                return false;
 629            }
 630
 631            if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
 632                return false;
 633            }
 634
 635            true
 636        });
 637    }
 638
 639    fn scan_dirs(&self) -> io::Result<()> {
 640        self.snapshot.lock().scan_id += 1;
 641
 642        let path: Arc<Path> = Arc::from(Path::new(""));
 643        let abs_path = self.abs_path();
 644        let metadata = fs::metadata(&abs_path)?;
 645        let inode = metadata.ino();
 646        let is_symlink = fs::symlink_metadata(&abs_path)?.file_type().is_symlink();
 647
 648        if metadata.file_type().is_dir() {
 649            let dir_entry = Entry {
 650                kind: EntryKind::PendingDir,
 651                path: path.clone(),
 652                inode,
 653                is_symlink,
 654                is_ignored: false,
 655            };
 656            self.snapshot.lock().insert_entry(dir_entry);
 657
 658            let (tx, rx) = crossbeam_channel::unbounded();
 659
 660            tx.send(ScanJob {
 661                abs_path: abs_path.to_path_buf(),
 662                path,
 663                ignore_stack: IgnoreStack::none(),
 664                scan_queue: tx.clone(),
 665            })
 666            .unwrap();
 667            drop(tx);
 668
 669            self.thread_pool.scoped(|pool| {
 670                for _ in 0..self.thread_pool.thread_count() {
 671                    pool.execute(|| {
 672                        while let Ok(job) = rx.recv() {
 673                            if let Err(err) = self.scan_dir(&job) {
 674                                log::error!("error scanning {:?}: {}", job.abs_path, err);
 675                            }
 676                        }
 677                    });
 678                }
 679            });
 680        } else {
 681            self.snapshot.lock().insert_entry(Entry {
 682                kind: EntryKind::File(self.char_bag(&path)),
 683                path,
 684                inode,
 685                is_symlink,
 686                is_ignored: false,
 687            });
 688        }
 689
 690        self.recompute_ignore_statuses();
 691
 692        Ok(())
 693    }
 694
 695    fn scan_dir(&self, job: &ScanJob) -> io::Result<()> {
 696        let mut new_entries: Vec<Entry> = Vec::new();
 697        let mut new_jobs: Vec<ScanJob> = Vec::new();
 698        let mut ignore_stack = job.ignore_stack.clone();
 699        let mut new_ignore = None;
 700
 701        for child_entry in fs::read_dir(&job.abs_path)? {
 702            let child_entry = child_entry?;
 703            let child_name = child_entry.file_name();
 704            let child_abs_path = job.abs_path.join(&child_name);
 705            let child_path: Arc<Path> = job.path.join(&child_name).into();
 706            let child_metadata = child_entry.metadata()?;
 707            let child_inode = child_metadata.ino();
 708            let child_is_symlink = child_metadata.file_type().is_symlink();
 709
 710            // Disallow mount points outside the file system containing the root of this worktree
 711            if self.other_mount_paths.contains(&child_abs_path) {
 712                continue;
 713            }
 714
 715            // If we find a .gitignore, add it to the stack of ignores used to determine which paths are ignored
 716            if child_name == *GITIGNORE {
 717                let (ignore, err) = Gitignore::new(&child_abs_path);
 718                if let Some(err) = err {
 719                    log::error!("error in ignore file {:?} - {:?}", child_path, err);
 720                }
 721                let ignore = Arc::new(ignore);
 722                ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
 723                new_ignore = Some(ignore);
 724
 725                // Update ignore status of any child entries we've already processed to reflect the
 726                // ignore file in the current directory. Because `.gitignore` starts with a `.`,
 727                // there should rarely be too numerous. Update the ignore stack associated with any
 728                // new jobs as well.
 729                let mut new_jobs = new_jobs.iter_mut();
 730                for entry in &mut new_entries {
 731                    entry.is_ignored = ignore_stack.is_path_ignored(&entry.path, entry.is_dir());
 732                    if entry.is_dir() {
 733                        new_jobs.next().unwrap().ignore_stack = if entry.is_ignored {
 734                            IgnoreStack::all()
 735                        } else {
 736                            ignore_stack.clone()
 737                        };
 738                    }
 739                }
 740            }
 741
 742            if child_metadata.is_dir() {
 743                let is_ignored = ignore_stack.is_path_ignored(&child_path, true);
 744                new_entries.push(Entry {
 745                    kind: EntryKind::PendingDir,
 746                    path: child_path.clone(),
 747                    inode: child_inode,
 748                    is_symlink: child_is_symlink,
 749                    is_ignored,
 750                });
 751                new_jobs.push(ScanJob {
 752                    abs_path: child_abs_path,
 753                    path: child_path,
 754                    ignore_stack: if is_ignored {
 755                        IgnoreStack::all()
 756                    } else {
 757                        ignore_stack.clone()
 758                    },
 759                    scan_queue: job.scan_queue.clone(),
 760                });
 761            } else {
 762                let is_ignored = ignore_stack.is_path_ignored(&child_path, false);
 763                new_entries.push(Entry {
 764                    kind: EntryKind::File(self.char_bag(&child_path)),
 765                    path: child_path,
 766                    inode: child_inode,
 767                    is_symlink: child_is_symlink,
 768                    is_ignored,
 769                });
 770            };
 771        }
 772
 773        self.snapshot
 774            .lock()
 775            .populate_dir(job.path.clone(), new_entries, new_ignore);
 776        for new_job in new_jobs {
 777            job.scan_queue.send(new_job).unwrap();
 778        }
 779
 780        Ok(())
 781    }
 782
 783    fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
 784        self.update_other_mount_paths();
 785
 786        let mut snapshot = self.snapshot();
 787        snapshot.scan_id += 1;
 788
 789        let root_abs_path = if let Ok(abs_path) = snapshot.abs_path.canonicalize() {
 790            abs_path
 791        } else {
 792            return false;
 793        };
 794
 795        events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
 796        let mut abs_paths = events.into_iter().map(|e| e.path).peekable();
 797        let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
 798
 799        while let Some(abs_path) = abs_paths.next() {
 800            let path = match abs_path.strip_prefix(&root_abs_path) {
 801                Ok(path) => Arc::from(path.to_path_buf()),
 802                Err(_) => {
 803                    log::error!(
 804                        "unexpected event {:?} for root path {:?}",
 805                        abs_path,
 806                        root_abs_path
 807                    );
 808                    continue;
 809                }
 810            };
 811
 812            while abs_paths.peek().map_or(false, |p| p.starts_with(&abs_path)) {
 813                abs_paths.next();
 814            }
 815
 816            snapshot.remove_path(&path);
 817
 818            match self.fs_entry_for_path(path.clone(), &abs_path) {
 819                Ok(Some(fs_entry)) => {
 820                    let is_dir = fs_entry.is_dir();
 821                    snapshot.insert_entry(fs_entry);
 822                    if is_dir {
 823                        scan_queue_tx
 824                            .send(ScanJob {
 825                                abs_path,
 826                                path,
 827                                ignore_stack: todo!(),
 828                                scan_queue: scan_queue_tx.clone(),
 829                            })
 830                            .unwrap();
 831                    }
 832                }
 833                Ok(None) => {}
 834                Err(err) => {
 835                    // TODO - create a special 'error' entry in the entries tree to mark this
 836                    log::error!("error reading file on event {:?}", err);
 837                }
 838            }
 839        }
 840
 841        *self.snapshot.lock() = snapshot;
 842
 843        // Scan any directories that were created as part of this event batch.
 844        drop(scan_queue_tx);
 845        self.thread_pool.scoped(|pool| {
 846            for _ in 0..self.thread_pool.thread_count() {
 847                pool.execute(|| {
 848                    while let Ok(job) = scan_queue_rx.recv() {
 849                        if let Err(err) = self.scan_dir(&job) {
 850                            log::error!("error scanning {:?}: {}", job.abs_path, err);
 851                        }
 852                    }
 853                });
 854            }
 855        });
 856
 857        self.recompute_ignore_statuses();
 858
 859        true
 860    }
 861
 862    fn recompute_ignore_statuses(&self) {
 863        self.compute_ignore_status_for_new_ignores();
 864        self.compute_ignore_status_for_new_entries();
 865    }
 866
 867    fn compute_ignore_status_for_new_ignores(&self) {
 868        // let mut snapshot = self.snapshot();
 869
 870        // let mut ignores_to_delete = Vec::new();
 871        // let mut changed_ignore_parents = Vec::new();
 872        // for (parent_path, (_, scan_id)) in &snapshot.ignores {
 873        //     let prev_ignore_parent = changed_ignore_parents.last();
 874        //     if *scan_id == snapshot.scan_id
 875        //         && prev_ignore_parent.map_or(true, |l| !parent_path.starts_with(l))
 876        //     {
 877        //         changed_ignore_parents.push(parent_path.clone());
 878        //     }
 879
 880        //     let ignore_parent_exists = snapshot.entry_for_path(parent_path).is_some();
 881        //     let ignore_exists = snapshot
 882        //         .entry_for_path(parent_path.join(&*GITIGNORE))
 883        //         .is_some();
 884        //     if !ignore_parent_exists || !ignore_exists {
 885        //         ignores_to_delete.push(parent_path.clone());
 886        //     }
 887        // }
 888
 889        // for parent_path in ignores_to_delete {
 890        //     snapshot.ignores.remove(&parent_path);
 891        //     self.snapshot.lock().ignores.remove(&parent_path);
 892        // }
 893
 894        // let (entries_tx, entries_rx) = crossbeam_channel::unbounded();
 895        // self.thread_pool.scoped(|scope| {
 896        //     let (edits_tx, edits_rx) = crossbeam_channel::unbounded();
 897        //     scope.execute(move || {
 898        //         let mut edits = Vec::new();
 899        //         while let Ok(edit) = edits_rx.recv() {
 900        //             edits.push(edit);
 901        //             while let Ok(edit) = edits_rx.try_recv() {
 902        //                 edits.push(edit);
 903        //             }
 904        //             self.snapshot.lock().entries.edit(mem::take(&mut edits));
 905        //         }
 906        //     });
 907
 908        //     scope.execute(|| {
 909        //         let entries_tx = entries_tx;
 910        //         let mut cursor = snapshot.entries.cursor::<_, ()>();
 911        //         for ignore_parent_path in &changed_ignore_parents {
 912        //             cursor.seek(&PathSearch::Exact(ignore_parent_path), SeekBias::Right);
 913        //             while let Some(entry) = cursor.item() {
 914        //                 if entry.path().starts_with(ignore_parent_path) {
 915        //                     entries_tx.send(entry.clone()).unwrap();
 916        //                     cursor.next();
 917        //                 } else {
 918        //                     break;
 919        //                 }
 920        //             }
 921        //         }
 922        //     });
 923
 924        //     for _ in 0..self.thread_pool.thread_count() - 2 {
 925        //         let edits_tx = edits_tx.clone();
 926        //         scope.execute(|| {
 927        //             let edits_tx = edits_tx;
 928        //             while let Ok(mut entry) = entries_rx.recv() {
 929        //                 entry.set_ignored(snapshot.is_path_ignored(entry.path()).unwrap());
 930        //                 edits_tx.send(Edit::Insert(entry)).unwrap();
 931        //             }
 932        //         });
 933        //     }
 934        // });
 935    }
 936
 937    fn compute_ignore_status_for_new_entries(&self) {
 938        // let snapshot = self.snapshot.lock().clone();
 939
 940        // let (entries_tx, entries_rx) = crossbeam_channel::unbounded();
 941        // self.thread_pool.scoped(|scope| {
 942        //     let (edits_tx, edits_rx) = crossbeam_channel::unbounded();
 943        //     scope.execute(move || {
 944        //         let mut edits = Vec::new();
 945        //         while let Ok(edit) = edits_rx.recv() {
 946        //             edits.push(edit);
 947        //             while let Ok(edit) = edits_rx.try_recv() {
 948        //                 edits.push(edit);
 949        //             }
 950        //             self.snapshot.lock().entries.edit(mem::take(&mut edits));
 951        //         }
 952        //     });
 953
 954        //     scope.execute(|| {
 955        //         let entries_tx = entries_tx;
 956        //         for entry in snapshot
 957        //             .entries
 958        //             .filter::<_, ()>(|e| e.recompute_ignore_status)
 959        //         {
 960        //             entries_tx.send(entry.clone()).unwrap();
 961        //         }
 962        //     });
 963
 964        //     for _ in 0..self.thread_pool.thread_count() - 2 {
 965        //         let edits_tx = edits_tx.clone();
 966        //         scope.execute(|| {
 967        //             let edits_tx = edits_tx;
 968        //             while let Ok(mut entry) = entries_rx.recv() {
 969        //                 entry.set_ignored(snapshot.is_path_ignored(entry.path()).unwrap());
 970        //                 edits_tx.send(Edit::Insert(entry)).unwrap();
 971        //             }
 972        //         });
 973        //     }
 974        // });
 975    }
 976
 977    fn fs_entry_for_path(&self, path: Arc<Path>, abs_path: &Path) -> Result<Option<Entry>> {
 978        let metadata = match fs::metadata(&abs_path) {
 979            Err(err) => {
 980                return match (err.kind(), err.raw_os_error()) {
 981                    (io::ErrorKind::NotFound, _) => Ok(None),
 982                    (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
 983                    _ => Err(anyhow::Error::new(err)),
 984                }
 985            }
 986            Ok(metadata) => metadata,
 987        };
 988        let inode = metadata.ino();
 989        let is_symlink = fs::symlink_metadata(&abs_path)
 990            .context("failed to read symlink metadata")?
 991            .file_type()
 992            .is_symlink();
 993
 994        let entry = Entry {
 995            kind: if metadata.file_type().is_dir() {
 996                EntryKind::PendingDir
 997            } else {
 998                EntryKind::File(self.char_bag(&path))
 999            },
1000            path,
1001            inode,
1002            is_symlink,
1003            is_ignored: false,
1004        };
1005
1006        Ok(Some(entry))
1007    }
1008
1009    fn char_bag(&self, path: &Path) -> CharBag {
1010        let mut result = self.root_char_bag;
1011        result.extend(path.to_string_lossy().chars());
1012        result
1013    }
1014}
1015
1016struct ScanJob {
1017    abs_path: PathBuf,
1018    path: Arc<Path>,
1019    ignore_stack: Arc<IgnoreStack>,
1020    scan_queue: crossbeam_channel::Sender<ScanJob>,
1021}
1022
1023pub trait WorktreeHandle {
1024    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle>;
1025}
1026
1027impl WorktreeHandle for ModelHandle<Worktree> {
1028    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle> {
1029        self.read(app)
1030            .entry_for_path(&path)
1031            .map(|entry| FileHandle {
1032                worktree: self.clone(),
1033                path: entry.path().clone(),
1034            })
1035            .ok_or_else(|| anyhow!("path does not exist in tree"))
1036    }
1037}
1038
1039pub enum FileIter<'a> {
1040    All(Cursor<'a, Entry, FileCount, FileCount>),
1041    Visible(Cursor<'a, Entry, VisibleFileCount, VisibleFileCount>),
1042}
1043
1044impl<'a> FileIter<'a> {
1045    fn all(snapshot: &'a Snapshot, start: usize) -> Self {
1046        let mut cursor = snapshot.entries.cursor();
1047        cursor.seek(&FileCount(start), SeekBias::Right);
1048        Self::All(cursor)
1049    }
1050
1051    fn visible(snapshot: &'a Snapshot, start: usize) -> Self {
1052        let mut cursor = snapshot.entries.cursor();
1053        cursor.seek(&VisibleFileCount(start), SeekBias::Right);
1054        Self::Visible(cursor)
1055    }
1056
1057    fn next_internal(&mut self) {
1058        match self {
1059            Self::All(cursor) => {
1060                let ix = *cursor.start();
1061                cursor.seek_forward(&FileCount(ix.0 + 1), SeekBias::Right);
1062            }
1063            Self::Visible(cursor) => {
1064                let ix = *cursor.start();
1065                cursor.seek_forward(&VisibleFileCount(ix.0 + 1), SeekBias::Right);
1066            }
1067        }
1068    }
1069
1070    fn item(&self) -> Option<&'a Entry> {
1071        match self {
1072            Self::All(cursor) => cursor.item(),
1073            Self::Visible(cursor) => cursor.item(),
1074        }
1075    }
1076}
1077
1078impl<'a> Iterator for FileIter<'a> {
1079    type Item = &'a Entry;
1080
1081    fn next(&mut self) -> Option<Self::Item> {
1082        if let Some(entry) = self.item() {
1083            self.next_internal();
1084            Some(entry)
1085        } else {
1086            None
1087        }
1088    }
1089}
1090
1091fn mounted_volume_paths() -> Vec<PathBuf> {
1092    unsafe {
1093        let mut stat_ptr: *mut libc::statfs = std::ptr::null_mut();
1094        let count = libc::getmntinfo(&mut stat_ptr as *mut _, libc::MNT_WAIT);
1095        if count >= 0 {
1096            std::slice::from_raw_parts(stat_ptr, count as usize)
1097                .iter()
1098                .map(|stat| {
1099                    PathBuf::from(OsStr::from_bytes(
1100                        CStr::from_ptr(&stat.f_mntonname[0]).to_bytes(),
1101                    ))
1102                })
1103                .collect()
1104        } else {
1105            panic!("failed to run getmntinfo");
1106        }
1107    }
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112    use super::*;
1113    use crate::editor::Buffer;
1114    use crate::test::*;
1115    use anyhow::Result;
1116    use gpui::App;
1117    use rand::prelude::*;
1118    use serde_json::json;
1119    use std::env;
1120    use std::fmt::Write;
1121    use std::os::unix;
1122    use std::time::{SystemTime, UNIX_EPOCH};
1123
1124    #[test]
1125    fn test_populate_and_search() {
1126        App::test_async((), |mut app| async move {
1127            let dir = temp_tree(json!({
1128                "root": {
1129                    "apple": "",
1130                    "banana": {
1131                        "carrot": {
1132                            "date": "",
1133                            "endive": "",
1134                        }
1135                    },
1136                    "fennel": {
1137                        "grape": "",
1138                    }
1139                }
1140            }));
1141
1142            let root_link_path = dir.path().join("root_link");
1143            unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1144
1145            let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
1146
1147            app.read(|ctx| tree.read(ctx).scan_complete()).await;
1148            app.read(|ctx| {
1149                let tree = tree.read(ctx);
1150                assert_eq!(tree.file_count(), 4);
1151                let results = match_paths(
1152                    Some(tree.snapshot()).iter(),
1153                    "bna",
1154                    false,
1155                    false,
1156                    false,
1157                    10,
1158                    ctx.thread_pool().clone(),
1159                )
1160                .into_iter()
1161                .map(|result| result.path)
1162                .collect::<Vec<Arc<Path>>>();
1163                assert_eq!(
1164                    results,
1165                    vec![
1166                        PathBuf::from("banana/carrot/date").into(),
1167                        PathBuf::from("banana/carrot/endive").into(),
1168                    ]
1169                );
1170            })
1171        });
1172    }
1173
1174    #[test]
1175    fn test_save_file() {
1176        App::test_async((), |mut app| async move {
1177            let dir = temp_tree(json!({
1178                "file1": "the old contents",
1179            }));
1180
1181            let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1182            app.read(|ctx| tree.read(ctx).scan_complete()).await;
1183            app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
1184
1185            let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
1186
1187            let path = tree.update(&mut app, |tree, ctx| {
1188                let path = tree.files(0).next().unwrap().path().clone();
1189                assert_eq!(path.file_name().unwrap(), "file1");
1190                smol::block_on(tree.save(&path, buffer.snapshot(), ctx.as_ref())).unwrap();
1191                path
1192            });
1193
1194            let loaded_history = app
1195                .read(|ctx| tree.read(ctx).load_history(&path, ctx))
1196                .await
1197                .unwrap();
1198            assert_eq!(loaded_history.base_text.as_ref(), buffer.text());
1199        });
1200    }
1201
1202    #[test]
1203    fn test_rescan_simple() {
1204        App::test_async((), |mut app| async move {
1205            let dir = temp_tree(json!({
1206                "a": {
1207                    "file1": "",
1208                },
1209                "b": {
1210                    "c": {
1211                        "file2": "",
1212                    }
1213                }
1214            }));
1215
1216            let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1217            app.read(|ctx| tree.read(ctx).scan_complete()).await;
1218            app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 2));
1219
1220            let file2 = app.read(|ctx| {
1221                let file2 = tree.file("b/c/file2", ctx).unwrap();
1222                assert_eq!(file2.path().as_ref(), Path::new("b/c/file2"));
1223                file2
1224            });
1225
1226            std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
1227
1228            app.read(|ctx| tree.read(ctx).next_scan_complete()).await;
1229
1230            app.read(|ctx| {
1231                assert_eq!(
1232                    tree.read(ctx)
1233                        .paths()
1234                        .map(|p| p.to_str().unwrap())
1235                        .collect::<Vec<_>>(),
1236                    vec!["a", "a/file1", "b", "d", "d/file2"]
1237                )
1238            });
1239
1240            // tree.condition(&app, move |_, _| {
1241            //     file2.path().as_ref() == Path::new("d/file2")
1242            // })
1243            // .await;
1244        });
1245    }
1246
1247    #[test]
1248    fn test_rescan_with_gitignore() {
1249        App::test_async((), |mut app| async move {
1250            let dir = temp_tree(json!({
1251                ".git": {},
1252                ".gitignore": "ignored-dir\n",
1253                "tracked-dir": {
1254                    "tracked-file1": "tracked contents",
1255                },
1256                "ignored-dir": {
1257                    "ignored-file1": "ignored contents",
1258                }
1259            }));
1260
1261            let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
1262            app.read(|ctx| tree.read(ctx).scan_complete()).await;
1263
1264            app.read(|ctx| {
1265                let paths = tree
1266                    .read(ctx)
1267                    .paths()
1268                    .map(|p| p.to_str().unwrap())
1269                    .collect::<Vec<_>>();
1270                println!("paths {:?}", paths);
1271            });
1272
1273            app.read(|ctx| {
1274                let tree = tree.read(ctx);
1275                let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
1276                let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
1277                assert_eq!(tracked.is_ignored(), false);
1278                assert_eq!(ignored.is_ignored(), true);
1279            });
1280
1281            return;
1282
1283            fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
1284            fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
1285            app.read(|ctx| tree.read(ctx).next_scan_complete()).await;
1286            app.read(|ctx| {
1287                let tree = tree.read(ctx);
1288                let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
1289                let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
1290                assert_eq!(tracked.is_ignored(), false);
1291                assert_eq!(ignored.is_ignored(), true);
1292            });
1293        });
1294    }
1295
1296    #[test]
1297    fn test_mounted_volume_paths() {
1298        let paths = mounted_volume_paths();
1299        assert!(paths.contains(&"/".into()));
1300    }
1301
1302    #[test]
1303    fn test_random() {
1304        let iterations = env::var("ITERATIONS")
1305            .map(|i| i.parse().unwrap())
1306            .unwrap_or(100);
1307        let operations = env::var("OPERATIONS")
1308            .map(|o| o.parse().unwrap())
1309            .unwrap_or(40);
1310        let initial_entries = env::var("INITIAL_ENTRIES")
1311            .map(|o| o.parse().unwrap())
1312            .unwrap_or(20);
1313        let seeds = if let Ok(seed) = env::var("SEED").map(|s| s.parse().unwrap()) {
1314            seed..seed + 1
1315        } else {
1316            0..iterations
1317        };
1318
1319        for seed in seeds {
1320            dbg!(seed);
1321            let mut rng = StdRng::seed_from_u64(seed);
1322
1323            let root_dir = tempdir::TempDir::new(&format!("test-{}", seed)).unwrap();
1324            for _ in 0..initial_entries {
1325                randomly_mutate_tree(root_dir.path(), 1.0, &mut rng).unwrap();
1326            }
1327            log::info!("Generated initial tree");
1328
1329            let (notify_tx, _notify_rx) = smol::channel::unbounded();
1330            let mut scanner = BackgroundScanner::new(
1331                Arc::new(Mutex::new(Snapshot {
1332                    id: 0,
1333                    scan_id: 0,
1334                    abs_path: root_dir.path().into(),
1335                    entries: Default::default(),
1336                    ignores: Default::default(),
1337                    root_name_chars: Default::default(),
1338                })),
1339                notify_tx,
1340                0,
1341            );
1342            scanner.scan_dirs().unwrap();
1343            scanner.snapshot().check_invariants();
1344
1345            let mut events = Vec::new();
1346            let mut mutations_len = operations;
1347            while mutations_len > 1 {
1348                if !events.is_empty() && rng.gen_bool(0.4) {
1349                    let len = rng.gen_range(0..=events.len());
1350                    let to_deliver = events.drain(0..len).collect::<Vec<_>>();
1351                    log::info!("Delivering events: {:#?}", to_deliver);
1352                    scanner.process_events(to_deliver);
1353                    scanner.snapshot().check_invariants();
1354                } else {
1355                    events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
1356                    mutations_len -= 1;
1357                }
1358            }
1359            log::info!("Quiescing: {:#?}", events);
1360            scanner.process_events(events);
1361            scanner.snapshot().check_invariants();
1362
1363            let (notify_tx, _notify_rx) = smol::channel::unbounded();
1364            let new_scanner = BackgroundScanner::new(
1365                Arc::new(Mutex::new(Snapshot {
1366                    id: 0,
1367                    scan_id: 0,
1368                    abs_path: root_dir.path().into(),
1369                    entries: Default::default(),
1370                    ignores: Default::default(),
1371                    root_name_chars: Default::default(),
1372                })),
1373                notify_tx,
1374                1,
1375            );
1376            new_scanner.scan_dirs().unwrap();
1377            assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
1378        }
1379    }
1380
1381    fn randomly_mutate_tree(
1382        root_path: &Path,
1383        insertion_probability: f64,
1384        rng: &mut impl Rng,
1385    ) -> Result<Vec<fsevent::Event>> {
1386        let root_path = root_path.canonicalize().unwrap();
1387        let (dirs, files) = read_dir_recursive(root_path.clone());
1388
1389        let mut events = Vec::new();
1390        let mut record_event = |path: PathBuf| {
1391            events.push(fsevent::Event {
1392                event_id: SystemTime::now()
1393                    .duration_since(UNIX_EPOCH)
1394                    .unwrap()
1395                    .as_secs(),
1396                flags: fsevent::StreamFlags::empty(),
1397                path,
1398            });
1399        };
1400
1401        if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
1402            let path = dirs.choose(rng).unwrap();
1403            let new_path = path.join(gen_name(rng));
1404
1405            if rng.gen() {
1406                log::info!("Creating dir {:?}", new_path.strip_prefix(root_path)?);
1407                fs::create_dir(&new_path)?;
1408            } else {
1409                log::info!("Creating file {:?}", new_path.strip_prefix(root_path)?);
1410                fs::write(&new_path, "")?;
1411            }
1412            record_event(new_path);
1413        } else if rng.gen_bool(0.05) {
1414            let ignore_dir_path = dirs.choose(rng).unwrap();
1415            let ignore_path = ignore_dir_path.join(&*GITIGNORE);
1416
1417            let (subdirs, subfiles) = read_dir_recursive(ignore_dir_path.clone());
1418            let files_to_ignore = {
1419                let len = rng.gen_range(0..=subfiles.len());
1420                subfiles.choose_multiple(rng, len)
1421            };
1422            let dirs_to_ignore = {
1423                let len = rng.gen_range(0..subdirs.len());
1424                subdirs.choose_multiple(rng, len)
1425            };
1426
1427            let mut ignore_contents = String::new();
1428            for path_to_ignore in files_to_ignore.chain(dirs_to_ignore) {
1429                write!(
1430                    ignore_contents,
1431                    "{}\n",
1432                    path_to_ignore
1433                        .strip_prefix(&ignore_dir_path)?
1434                        .to_str()
1435                        .unwrap()
1436                )
1437                .unwrap();
1438            }
1439            log::info!(
1440                "Creating {:?} with contents:\n{}",
1441                ignore_path.strip_prefix(&root_path)?,
1442                ignore_contents
1443            );
1444            fs::write(&ignore_path, ignore_contents).unwrap();
1445            record_event(ignore_path);
1446        } else {
1447            let old_path = {
1448                let file_path = files.choose(rng);
1449                let dir_path = dirs[1..].choose(rng);
1450                file_path.into_iter().chain(dir_path).choose(rng).unwrap()
1451            };
1452
1453            let is_rename = rng.gen();
1454            if is_rename {
1455                let new_path_parent = dirs
1456                    .iter()
1457                    .filter(|d| !d.starts_with(old_path))
1458                    .choose(rng)
1459                    .unwrap();
1460
1461                let overwrite_existing_dir =
1462                    !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
1463                let new_path = if overwrite_existing_dir {
1464                    fs::remove_dir_all(&new_path_parent).ok();
1465                    new_path_parent.to_path_buf()
1466                } else {
1467                    new_path_parent.join(gen_name(rng))
1468                };
1469
1470                log::info!(
1471                    "Renaming {:?} to {}{:?}",
1472                    old_path.strip_prefix(&root_path)?,
1473                    if overwrite_existing_dir {
1474                        "overwrite "
1475                    } else {
1476                        ""
1477                    },
1478                    new_path.strip_prefix(&root_path)?
1479                );
1480                fs::rename(&old_path, &new_path)?;
1481                record_event(old_path.clone());
1482                record_event(new_path);
1483            } else if old_path.is_dir() {
1484                let (dirs, files) = read_dir_recursive(old_path.clone());
1485
1486                log::info!("Deleting dir {:?}", old_path.strip_prefix(&root_path)?);
1487                fs::remove_dir_all(&old_path).unwrap();
1488                for file in files {
1489                    record_event(file);
1490                }
1491                for dir in dirs {
1492                    record_event(dir);
1493                }
1494            } else {
1495                log::info!("Deleting file {:?}", old_path.strip_prefix(&root_path)?);
1496                fs::remove_file(old_path).unwrap();
1497                record_event(old_path.clone());
1498            }
1499        }
1500
1501        Ok(events)
1502    }
1503
1504    fn read_dir_recursive(path: PathBuf) -> (Vec<PathBuf>, Vec<PathBuf>) {
1505        let child_entries = fs::read_dir(&path).unwrap();
1506        let mut dirs = vec![path];
1507        let mut files = Vec::new();
1508        for child_entry in child_entries {
1509            let child_path = child_entry.unwrap().path();
1510            if child_path.is_dir() {
1511                let (child_dirs, child_files) = read_dir_recursive(child_path);
1512                dirs.extend(child_dirs);
1513                files.extend(child_files);
1514            } else {
1515                files.push(child_path);
1516            }
1517        }
1518        (dirs, files)
1519    }
1520
1521    fn gen_name(rng: &mut impl Rng) -> String {
1522        (0..6)
1523            .map(|_| rng.sample(rand::distributions::Alphanumeric))
1524            .map(char::from)
1525            .collect()
1526    }
1527
1528    impl Snapshot {
1529        fn check_invariants(&self) {
1530            let mut files = self.files(0);
1531            let mut visible_files = self.visible_files(0);
1532            for entry in self.entries.cursor::<(), ()>() {
1533                if matches!(entry.kind, EntryKind::File(_)) {
1534                    assert_eq!(files.next().unwrap().inode(), entry.inode);
1535                    if !entry.is_ignored {
1536                        assert_eq!(visible_files.next().unwrap().inode(), entry.inode);
1537                    }
1538                }
1539            }
1540            assert!(files.next().is_none());
1541            assert!(visible_files.next().is_none());
1542
1543            // for (ignore_parent_path, _) in &self.ignores {
1544            //     assert!(self.entry_for_path(ignore_parent_path).is_some());
1545            //     assert!(self
1546            //         .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
1547            //         .is_some());
1548            // }
1549        }
1550
1551        fn to_vec(&self) -> Vec<(&Path, u64, bool)> {
1552            let mut paths = Vec::new();
1553            for entry in self.entries.cursor::<(), ()>() {
1554                paths.push((entry.path().as_ref(), entry.inode(), entry.is_ignored()));
1555            }
1556            paths.sort_by(|a, b| a.0.cmp(&b.0));
1557            paths
1558        }
1559    }
1560}