worktree.rs

   1mod char_bag;
   2mod fuzzy;
   3
   4use crate::{
   5    editor::{History, Snapshot as BufferSnapshot},
   6    sum_tree::{self, Edit, SumTree},
   7};
   8use anyhow::{anyhow, Result};
   9pub use fuzzy::match_paths;
  10use fuzzy::PathEntry;
  11use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
  12use ignore::dir::{Ignore, IgnoreBuilder};
  13use parking_lot::Mutex;
  14use smol::{channel::Sender, Timer};
  15use std::future::Future;
  16use std::{
  17    ffi::OsStr,
  18    fmt, fs,
  19    io::{self, Read, Write},
  20    ops::{AddAssign, Deref},
  21    os::unix::fs::MetadataExt,
  22    path::{Path, PathBuf},
  23    sync::Arc,
  24    time::Duration,
  25};
  26
  27pub use fuzzy::PathMatch;
  28
  29#[derive(Debug)]
  30enum ScanState {
  31    Idle,
  32    Scanning,
  33    Err(io::Error),
  34}
  35
  36pub struct Worktree {
  37    snapshot: Snapshot,
  38    scanner: Arc<BackgroundScanner>,
  39    scan_state: ScanState,
  40    poll_scheduled: bool,
  41}
  42
  43#[derive(Clone)]
  44pub struct FileHandle {
  45    worktree: ModelHandle<Worktree>,
  46    inode: u64,
  47}
  48
  49impl Worktree {
  50    pub fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
  51        let scan_state = smol::channel::unbounded();
  52        let snapshot = Snapshot {
  53            id: ctx.model_id(),
  54            path: path.into(),
  55            root_inode: None,
  56            entries: Default::default(),
  57        };
  58        let scanner = Arc::new(BackgroundScanner::new(snapshot.clone(), scan_state.0));
  59        let tree = Self {
  60            snapshot,
  61            scanner,
  62            scan_state: ScanState::Idle,
  63            poll_scheduled: false,
  64        };
  65
  66        let scanner = tree.scanner.clone();
  67        std::thread::spawn(move || scanner.run());
  68
  69        ctx.spawn_stream(scan_state.1, Self::observe_scan_state, |_, _| {})
  70            .detach();
  71
  72        tree
  73    }
  74
  75    fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
  76        self.scan_state = scan_state;
  77        self.poll_entries(ctx);
  78    }
  79
  80    fn poll_entries(&mut self, ctx: &mut ModelContext<Self>) {
  81        self.snapshot = self.scanner.snapshot();
  82        ctx.notify();
  83
  84        if self.is_scanning() && !self.poll_scheduled {
  85            ctx.spawn(Timer::after(Duration::from_millis(100)), |this, _, ctx| {
  86                this.poll_scheduled = false;
  87                this.poll_entries(ctx);
  88            })
  89            .detach();
  90            self.poll_scheduled = true;
  91        }
  92    }
  93
  94    fn is_scanning(&self) -> bool {
  95        if let ScanState::Scanning = self.scan_state {
  96            true
  97        } else {
  98            false
  99        }
 100    }
 101
 102    pub fn snapshot(&self) -> Snapshot {
 103        self.snapshot.clone()
 104    }
 105
 106    pub fn contains_path(&self, path: &Path) -> bool {
 107        path.starts_with(&self.snapshot.path)
 108    }
 109
 110    pub fn has_inode(&self, inode: u64) -> bool {
 111        self.snapshot.entries.get(&inode).is_some()
 112    }
 113
 114    pub fn file_count(&self) -> usize {
 115        self.snapshot.entries.summary().file_count
 116    }
 117
 118    pub fn abs_path_for_inode(&self, ino: u64) -> Result<PathBuf> {
 119        let mut result = self.snapshot.path.to_path_buf();
 120        result.push(self.path_for_inode(ino, false)?);
 121        Ok(result)
 122    }
 123
 124    pub fn load_history(
 125        &self,
 126        ino: u64,
 127        ctx: &AppContext,
 128    ) -> impl Future<Output = Result<History>> {
 129        let path = self.abs_path_for_inode(ino);
 130        ctx.background_executor().spawn(async move {
 131            let mut file = std::fs::File::open(&path?)?;
 132            let mut base_text = String::new();
 133            file.read_to_string(&mut base_text)?;
 134            Ok(History::new(Arc::from(base_text)))
 135        })
 136    }
 137
 138    pub fn save<'a>(
 139        &self,
 140        ino: u64,
 141        content: BufferSnapshot,
 142        ctx: &AppContext,
 143    ) -> Task<Result<()>> {
 144        let path = self.abs_path_for_inode(ino);
 145        ctx.background_executor().spawn(async move {
 146            let buffer_size = content.text_summary().bytes.min(10 * 1024);
 147            let file = std::fs::File::create(&path?)?;
 148            let mut writer = std::io::BufWriter::with_capacity(buffer_size, file);
 149            for chunk in content.fragments() {
 150                writer.write(chunk.as_bytes())?;
 151            }
 152            writer.flush()?;
 153            Ok(())
 154        })
 155    }
 156
 157    #[cfg(test)]
 158    pub fn files<'a>(&'a self) -> impl Iterator<Item = u64> + 'a {
 159        self.snapshot
 160            .entries
 161            .cursor::<(), ()>()
 162            .filter_map(|entry| {
 163                if let Entry::File { inode, .. } = entry {
 164                    Some(*inode)
 165                } else {
 166                    None
 167                }
 168            })
 169    }
 170}
 171
 172impl Entity for Worktree {
 173    type Event = ();
 174}
 175
 176impl Deref for Worktree {
 177    type Target = Snapshot;
 178
 179    fn deref(&self) -> &Self::Target {
 180        &self.snapshot
 181    }
 182}
 183
 184impl fmt::Debug for Worktree {
 185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 186        self.snapshot.fmt(f)
 187    }
 188}
 189
 190#[derive(Clone)]
 191pub struct Snapshot {
 192    id: usize,
 193    path: Arc<Path>,
 194    root_inode: Option<u64>,
 195    entries: SumTree<Entry>,
 196}
 197
 198impl Snapshot {
 199    pub fn file_count(&self) -> usize {
 200        self.entries.summary().file_count
 201    }
 202
 203    pub fn root_entry(&self) -> Option<&Entry> {
 204        self.root_inode.and_then(|inode| self.entries.get(&inode))
 205    }
 206
 207    pub fn root_name(&self) -> Option<&OsStr> {
 208        self.path.file_name()
 209    }
 210
 211    fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
 212        let path = path.as_ref();
 213        self.root_inode.and_then(|mut inode| {
 214            'components: for path_component in path {
 215                if let Some(Entry::Dir { children, .. }) = &self.entries.get(&inode) {
 216                    for (child_inode, name) in children.as_ref() {
 217                        if name.as_ref() == path_component {
 218                            inode = *child_inode;
 219                            continue 'components;
 220                        }
 221                    }
 222                }
 223                return None;
 224            }
 225            Some(inode)
 226        })
 227    }
 228
 229    fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
 230        self.inode_for_path(path)
 231            .and_then(|inode| self.entries.get(&inode))
 232    }
 233
 234    pub fn path_for_inode(&self, mut ino: u64, include_root: bool) -> Result<PathBuf> {
 235        let mut components = Vec::new();
 236        let mut entry = self
 237            .entries
 238            .get(&ino)
 239            .ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
 240        while let Some(parent) = entry.parent() {
 241            entry = self.entries.get(&parent).unwrap();
 242            if let Entry::Dir { children, .. } = entry {
 243                let (_, child_name) = children
 244                    .iter()
 245                    .find(|(child_inode, _)| *child_inode == ino)
 246                    .unwrap();
 247                components.push(child_name.as_ref());
 248                ino = parent;
 249            } else {
 250                unreachable!();
 251            }
 252        }
 253        if include_root {
 254            components.push(self.path.file_name().unwrap());
 255        }
 256
 257        Ok(components.into_iter().rev().collect())
 258    }
 259
 260    fn remove_path(&mut self, path: &Path) {
 261        let mut parent_entry = match path.parent().and_then(|p| self.entry_for_path(p).cloned()) {
 262            Some(e) => e,
 263            None => return,
 264        };
 265
 266        let mut edits = Vec::new();
 267        let parent_inode = parent_entry.inode();
 268        let mut entry_inode = None;
 269        if let Entry::Dir { children, .. } = &mut parent_entry {
 270            let mut new_children = Vec::new();
 271            for (child_inode, child_name) in children.as_ref() {
 272                if Some(child_name.as_ref()) == path.file_name() {
 273                    entry_inode = Some(*child_inode);
 274                } else {
 275                    new_children.push((*child_inode, child_name.clone()));
 276                }
 277            }
 278
 279            if new_children.iter().any(|c| Some(c.0) == entry_inode) {
 280                entry_inode = None;
 281            }
 282
 283            *children = new_children.into();
 284            edits.push(Edit::Insert(parent_entry));
 285        }
 286
 287        if let Some(entry_inode) = entry_inode {
 288            let mut descendant_stack = Vec::new();
 289            descendant_stack.push((entry_inode, parent_inode));
 290            while let Some((inode, parent_inode)) = descendant_stack.pop() {
 291                if let Some(entry) = self.entries.get(&inode) {
 292                    if entry.parent() == Some(parent_inode) {
 293                        edits.push(Edit::Remove(inode));
 294                        if let Entry::Dir { children, .. } = entry {
 295                            descendant_stack.extend(children.iter().map(|c| (c.0, entry.inode())));
 296                        }
 297                    }
 298                }
 299            }
 300        }
 301
 302        self.entries.edit(edits);
 303    }
 304
 305    fn fmt_entry(
 306        &self,
 307        f: &mut fmt::Formatter<'_>,
 308        ino: u64,
 309        name: &OsStr,
 310        indent: usize,
 311    ) -> fmt::Result {
 312        match self.entries.get(&ino).unwrap() {
 313            Entry::Dir { children, .. } => {
 314                write!(
 315                    f,
 316                    "{}{}/ ({})\n",
 317                    " ".repeat(indent),
 318                    name.to_string_lossy(),
 319                    ino
 320                )?;
 321                for (child_inode, child_name) in children.iter() {
 322                    self.fmt_entry(f, *child_inode, child_name, indent + 2)?;
 323                }
 324                Ok(())
 325            }
 326            Entry::File { .. } => write!(
 327                f,
 328                "{}{} ({})\n",
 329                " ".repeat(indent),
 330                name.to_string_lossy(),
 331                ino
 332            ),
 333        }
 334    }
 335}
 336
 337impl fmt::Debug for Snapshot {
 338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 339        if let Some(root_ino) = self.root_inode {
 340            self.fmt_entry(f, root_ino, self.path.file_name().unwrap(), 0)
 341        } else {
 342            write!(f, "Empty tree\n")
 343        }
 344    }
 345}
 346
 347impl FileHandle {
 348    pub fn path(&self, ctx: &AppContext) -> PathBuf {
 349        self.worktree
 350            .read(ctx)
 351            .path_for_inode(self.inode, false)
 352            .unwrap()
 353    }
 354
 355    pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
 356        self.worktree.read(ctx).load_history(self.inode, ctx)
 357    }
 358
 359    pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
 360        let worktree = self.worktree.read(ctx);
 361        worktree.save(self.inode, content, ctx)
 362    }
 363
 364    pub fn entry_id(&self) -> (usize, u64) {
 365        (self.worktree.id(), self.inode)
 366    }
 367}
 368
 369#[derive(Clone, Debug)]
 370pub enum Entry {
 371    Dir {
 372        inode: u64,
 373        parent: Option<u64>,
 374        is_symlink: bool,
 375        is_ignored: bool,
 376        children: Arc<[(u64, Arc<OsStr>)]>,
 377        pending: bool,
 378    },
 379    File {
 380        inode: u64,
 381        parent: Option<u64>,
 382        is_symlink: bool,
 383        is_ignored: bool,
 384        path: PathEntry,
 385    },
 386}
 387
 388impl Entry {
 389    fn inode(&self) -> u64 {
 390        match self {
 391            Entry::Dir { inode, .. } => *inode,
 392            Entry::File { inode, .. } => *inode,
 393        }
 394    }
 395
 396    fn is_dir(&self) -> bool {
 397        matches!(self, Entry::Dir { .. })
 398    }
 399
 400    fn parent(&self) -> Option<u64> {
 401        match self {
 402            Entry::Dir { parent, .. } => *parent,
 403            Entry::File { parent, .. } => *parent,
 404        }
 405    }
 406}
 407
 408impl sum_tree::Item for Entry {
 409    type Summary = EntrySummary;
 410
 411    fn summary(&self) -> Self::Summary {
 412        EntrySummary {
 413            max_ino: self.inode(),
 414            file_count: if matches!(self, Self::File { .. }) {
 415                1
 416            } else {
 417                0
 418            },
 419        }
 420    }
 421}
 422
 423impl sum_tree::KeyedItem for Entry {
 424    type Key = u64;
 425
 426    fn key(&self) -> Self::Key {
 427        self.inode()
 428    }
 429}
 430
 431#[derive(Clone, Debug, Default)]
 432pub struct EntrySummary {
 433    max_ino: u64,
 434    file_count: usize,
 435}
 436
 437impl<'a> AddAssign<&'a EntrySummary> for EntrySummary {
 438    fn add_assign(&mut self, rhs: &'a EntrySummary) {
 439        self.max_ino = rhs.max_ino;
 440        self.file_count += rhs.file_count;
 441    }
 442}
 443
 444impl<'a> sum_tree::Dimension<'a, EntrySummary> for u64 {
 445    fn add_summary(&mut self, summary: &'a EntrySummary) {
 446        *self = summary.max_ino;
 447    }
 448}
 449
 450#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
 451struct FileCount(usize);
 452
 453impl<'a> sum_tree::Dimension<'a, EntrySummary> for FileCount {
 454    fn add_summary(&mut self, summary: &'a EntrySummary) {
 455        self.0 += summary.file_count;
 456    }
 457}
 458
 459struct BackgroundScanner {
 460    snapshot: Mutex<Snapshot>,
 461    notify: Sender<ScanState>,
 462    thread_pool: scoped_pool::Pool,
 463}
 464
 465impl BackgroundScanner {
 466    fn new(snapshot: Snapshot, notify: Sender<ScanState>) -> Self {
 467        Self {
 468            snapshot: Mutex::new(snapshot),
 469            notify,
 470            thread_pool: scoped_pool::Pool::new(16),
 471        }
 472    }
 473
 474    fn path(&self) -> Arc<Path> {
 475        self.snapshot.lock().path.clone()
 476    }
 477
 478    fn snapshot(&self) -> Snapshot {
 479        self.snapshot.lock().clone()
 480    }
 481
 482    fn run(&self) {
 483        let path = {
 484            let mut snapshot = self.snapshot.lock();
 485            let canonical_path = snapshot
 486                .path
 487                .canonicalize()
 488                .map(Arc::from)
 489                .unwrap_or_else(|_| snapshot.path.clone());
 490            snapshot.path = canonical_path.clone();
 491            canonical_path
 492        };
 493
 494        // Create the event stream before we start scanning to ensure we receive events for changes
 495        // that occur in the middle of the scan.
 496        let event_stream =
 497            fsevent::EventStream::new(&[path.as_ref()], Duration::from_millis(100), |events| {
 498                if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
 499                    return false;
 500                }
 501
 502                self.process_events(events);
 503
 504                if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
 505                    return false;
 506                }
 507
 508                true
 509            });
 510
 511        if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
 512            return;
 513        }
 514
 515        if let Err(err) = self.scan_dirs() {
 516            if smol::block_on(self.notify.send(ScanState::Err(err))).is_err() {
 517                return;
 518            }
 519        }
 520
 521        if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
 522            return;
 523        }
 524
 525        event_stream.run();
 526    }
 527
 528    fn scan_dirs(&self) -> io::Result<()> {
 529        let path = self.path();
 530        let metadata = fs::metadata(&path)?;
 531        let inode = metadata.ino();
 532        let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
 533        let name = Arc::from(path.file_name().unwrap_or(OsStr::new("/")));
 534        let relative_path = PathBuf::from(&name);
 535
 536        let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap();
 537        if metadata.is_dir() {
 538            ignore = ignore.add_child(&path).unwrap();
 539        }
 540        let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
 541
 542        if metadata.file_type().is_dir() {
 543            let is_ignored = is_ignored || name.as_ref() == ".git";
 544            let dir_entry = Entry::Dir {
 545                parent: None,
 546                inode,
 547                is_symlink,
 548                is_ignored,
 549                children: Arc::from([]),
 550                pending: true,
 551            };
 552            self.insert_entries(Some(dir_entry.clone()));
 553            self.snapshot.lock().root_inode = Some(inode);
 554
 555            let (tx, rx) = crossbeam_channel::unbounded();
 556
 557            tx.send(Ok(ScanJob {
 558                inode,
 559                path: path.clone(),
 560                relative_path,
 561                dir_entry,
 562                ignore: Some(ignore),
 563                scan_queue: tx.clone(),
 564            }))
 565            .unwrap();
 566            drop(tx);
 567
 568            let mut results = Vec::new();
 569            results.resize_with(self.thread_pool.workers(), || Ok(()));
 570            self.thread_pool.scoped(|pool| {
 571                for result in &mut results {
 572                    pool.execute(|| {
 573                        let result = result;
 574                        while let Ok(job) = rx.recv() {
 575                            if let Err(err) = job.and_then(|job| self.scan_dir(job)) {
 576                                *result = Err(err);
 577                                break;
 578                            }
 579                        }
 580                    });
 581                }
 582            });
 583            results.into_iter().collect::<io::Result<()>>()?;
 584        } else {
 585            self.insert_entries(Some(Entry::File {
 586                parent: None,
 587                path: PathEntry::new(inode, &relative_path, is_ignored),
 588                inode,
 589                is_symlink,
 590                is_ignored,
 591            }));
 592            self.snapshot.lock().root_inode = Some(inode);
 593        }
 594
 595        Ok(())
 596    }
 597
 598    fn scan_dir(&self, job: ScanJob) -> io::Result<()> {
 599        let scan_queue = job.scan_queue;
 600        let mut dir_entry = job.dir_entry;
 601
 602        let mut new_children = Vec::new();
 603        let mut new_entries = Vec::new();
 604        let mut new_jobs = Vec::new();
 605
 606        for child_entry in fs::read_dir(&job.path)? {
 607            let child_entry = child_entry?;
 608            let name: Arc<OsStr> = child_entry.file_name().into();
 609            let relative_path = job.relative_path.join(name.as_ref());
 610            let metadata = child_entry.metadata()?;
 611            let ino = metadata.ino();
 612            let is_symlink = metadata.file_type().is_symlink();
 613            let path = job.path.join(name.as_ref());
 614
 615            new_children.push((ino, name.clone()));
 616            if metadata.is_dir() {
 617                let mut is_ignored = true;
 618                let mut ignore = None;
 619
 620                if let Some(parent_ignore) = job.ignore.as_ref() {
 621                    let child_ignore = parent_ignore.add_child(&path).unwrap();
 622                    is_ignored =
 623                        child_ignore.matched(&path, true).is_ignore() || name.as_ref() == ".git";
 624                    if !is_ignored {
 625                        ignore = Some(child_ignore);
 626                    }
 627                }
 628
 629                let dir_entry = Entry::Dir {
 630                    parent: Some(job.inode),
 631                    inode: ino,
 632                    is_symlink,
 633                    is_ignored,
 634                    children: Arc::from([]),
 635                    pending: true,
 636                };
 637                new_entries.push(dir_entry.clone());
 638                new_jobs.push(ScanJob {
 639                    inode: ino,
 640                    path: Arc::from(path),
 641                    relative_path,
 642                    dir_entry,
 643                    ignore,
 644                    scan_queue: scan_queue.clone(),
 645                });
 646            } else {
 647                let is_ignored = job
 648                    .ignore
 649                    .as_ref()
 650                    .map_or(true, |i| i.matched(&path, false).is_ignore());
 651                new_entries.push(Entry::File {
 652                    parent: Some(job.inode),
 653                    path: PathEntry::new(ino, &relative_path, is_ignored),
 654                    inode: ino,
 655                    is_symlink,
 656                    is_ignored,
 657                });
 658            };
 659        }
 660
 661        if let Entry::Dir {
 662            children, pending, ..
 663        } = &mut dir_entry
 664        {
 665            *children = Arc::from(new_children);
 666            *pending = false;
 667        } else {
 668            unreachable!()
 669        }
 670        new_entries.push(dir_entry);
 671
 672        self.insert_entries(new_entries);
 673        for new_job in new_jobs {
 674            scan_queue.send(Ok(new_job)).unwrap();
 675        }
 676
 677        Ok(())
 678    }
 679
 680    fn process_events(&self, mut events: Vec<fsevent::Event>) {
 681        let mut snapshot = self.snapshot();
 682
 683        events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
 684        let mut paths = events.into_iter().map(|e| e.path).peekable();
 685        let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
 686        while let Some(path) = paths.next() {
 687            let relative_path = match path.strip_prefix(&snapshot.path) {
 688                Ok(relative_path) => relative_path.to_path_buf(),
 689                Err(e) => {
 690                    log::error!("Unexpected event {:?}", e);
 691                    continue;
 692                }
 693            };
 694
 695            while paths.peek().map_or(false, |p| p.starts_with(&path)) {
 696                paths.next();
 697            }
 698
 699            snapshot.remove_path(&relative_path);
 700
 701            match self.fs_entry_for_path(&snapshot.path, &path) {
 702                Ok(Some((fs_entry, ignore))) => {
 703                    let mut edits = Vec::new();
 704                    edits.push(Edit::Insert(fs_entry.clone()));
 705                    if let Some(parent) = fs_entry.parent() {
 706                        let mut parent_entry = snapshot.entries.get(&parent).unwrap().clone();
 707                        if let Entry::Dir { children, .. } = &mut parent_entry {
 708                            let name = Arc::from(path.file_name().unwrap());
 709                            *children = children
 710                                .into_iter()
 711                                .cloned()
 712                                .chain(Some((fs_entry.inode(), name)))
 713                                .collect::<Vec<_>>()
 714                                .into();
 715                            edits.push(Edit::Insert(parent_entry));
 716                        } else {
 717                            unreachable!();
 718                        }
 719                    }
 720                    snapshot.entries.edit(edits);
 721
 722                    if fs_entry.is_dir() {
 723                        let relative_path = snapshot
 724                            .path
 725                            .parent()
 726                            .map_or(path.as_path(), |parent| path.strip_prefix(parent).unwrap())
 727                            .to_path_buf();
 728                        scan_queue_tx
 729                            .send(Ok(ScanJob {
 730                                inode: fs_entry.inode(),
 731                                path: Arc::from(path),
 732                                relative_path,
 733                                dir_entry: fs_entry,
 734                                ignore: Some(ignore),
 735                                scan_queue: scan_queue_tx.clone(),
 736                            }))
 737                            .unwrap();
 738                    }
 739                }
 740                Ok(None) => {}
 741                Err(err) => {
 742                    // TODO - create a special 'error' entry in the entries tree to mark this
 743                    log::error!("Error reading file on event {:?}", err);
 744                }
 745            }
 746        }
 747
 748        *self.snapshot.lock() = snapshot;
 749
 750        // Scan any directories that were moved into this worktree as part of this event batch.
 751        drop(scan_queue_tx);
 752        self.thread_pool.scoped(|pool| {
 753            for _ in 0..self.thread_pool.workers() {
 754                pool.execute(|| {
 755                    while let Ok(job) = scan_queue_rx.recv() {
 756                        if let Err(err) = job.and_then(|job| self.scan_dir(job)) {
 757                            log::error!("Error scanning {:?}", err);
 758                        }
 759                    }
 760                });
 761            }
 762        });
 763    }
 764
 765    fn fs_entry_for_path(&self, root_path: &Path, path: &Path) -> Result<Option<(Entry, Ignore)>> {
 766        match fs::metadata(&path) {
 767            Ok(metadata) => {
 768                let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap();
 769                if metadata.is_dir() {
 770                    ignore = ignore.add_child(&path).unwrap();
 771                }
 772                let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
 773
 774                let inode = metadata.ino();
 775                let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
 776                let parent = if path == root_path {
 777                    None
 778                } else {
 779                    Some(fs::metadata(path.parent().unwrap())?.ino())
 780                };
 781                if metadata.file_type().is_dir() {
 782                    Ok(Some((
 783                        Entry::Dir {
 784                            parent,
 785                            inode,
 786                            is_symlink,
 787                            is_ignored,
 788                            children: Arc::from([]),
 789                            pending: true,
 790                        },
 791                        ignore,
 792                    )))
 793                } else {
 794                    Ok(Some((
 795                        Entry::File {
 796                            parent,
 797                            path: PathEntry::new(
 798                                inode,
 799                                root_path
 800                                    .parent()
 801                                    .map_or(path, |parent| path.strip_prefix(parent).unwrap()),
 802                                is_ignored,
 803                            ),
 804                            inode,
 805                            is_symlink,
 806                            is_ignored,
 807                        },
 808                        ignore,
 809                    )))
 810                }
 811            }
 812            Err(err) => {
 813                if err.kind() == io::ErrorKind::NotFound {
 814                    Ok(None)
 815                } else {
 816                    Err(anyhow::Error::new(err))
 817                }
 818            }
 819        }
 820    }
 821
 822    fn insert_entries(&self, entries: impl IntoIterator<Item = Entry>) {
 823        self.snapshot
 824            .lock()
 825            .entries
 826            .edit(entries.into_iter().map(Edit::Insert).collect::<Vec<_>>());
 827    }
 828}
 829
 830impl Drop for BackgroundScanner {
 831    fn drop(&mut self) {
 832        self.thread_pool.shutdown();
 833    }
 834}
 835
 836struct ScanJob {
 837    inode: u64,
 838    path: Arc<Path>,
 839    relative_path: PathBuf,
 840    dir_entry: Entry,
 841    ignore: Option<Ignore>,
 842    scan_queue: crossbeam_channel::Sender<io::Result<ScanJob>>,
 843}
 844
 845pub trait WorktreeHandle {
 846    fn file(&self, entry_id: u64, app: &AppContext) -> Result<FileHandle>;
 847}
 848
 849impl WorktreeHandle for ModelHandle<Worktree> {
 850    fn file(&self, inode: u64, app: &AppContext) -> Result<FileHandle> {
 851        if self.read(app).has_inode(inode) {
 852            Ok(FileHandle {
 853                worktree: self.clone(),
 854                inode,
 855            })
 856        } else {
 857            Err(anyhow!("entry does not exist in tree"))
 858        }
 859    }
 860}
 861
 862trait UnwrapIgnoreTuple {
 863    fn unwrap(self) -> Ignore;
 864}
 865
 866impl UnwrapIgnoreTuple for (Ignore, Option<ignore::Error>) {
 867    fn unwrap(self) -> Ignore {
 868        if let Some(error) = self.1 {
 869            log::error!("error loading gitignore data: {}", error);
 870        }
 871        self.0
 872    }
 873}
 874
 875#[cfg(test)]
 876mod tests {
 877    use super::*;
 878    use crate::editor::Buffer;
 879    use crate::test::*;
 880    use anyhow::Result;
 881    use gpui::App;
 882    use log::LevelFilter;
 883    use rand::prelude::*;
 884    use serde_json::json;
 885    use simplelog::SimpleLogger;
 886    use std::env;
 887    use std::os::unix;
 888    use std::time::{SystemTime, UNIX_EPOCH};
 889
 890    #[test]
 891    fn test_populate_and_search() {
 892        App::test_async((), |mut app| async move {
 893            let dir = temp_tree(json!({
 894                "root": {
 895                    "apple": "",
 896                    "banana": {
 897                        "carrot": {
 898                            "date": "",
 899                            "endive": "",
 900                        }
 901                    },
 902                    "fennel": {
 903                        "grape": "",
 904                    }
 905                }
 906            }));
 907
 908            let root_link_path = dir.path().join("root_link");
 909            unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
 910
 911            let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
 912            assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 4)).await;
 913            app.read(|ctx| {
 914                let tree = tree.read(ctx);
 915                let results = match_paths(
 916                    Some(tree.snapshot()).iter(),
 917                    "bna",
 918                    false,
 919                    false,
 920                    false,
 921                    10,
 922                    ctx.thread_pool().clone(),
 923                )
 924                .iter()
 925                .map(|result| tree.path_for_inode(result.entry_id, true))
 926                .collect::<Result<Vec<PathBuf>, _>>()
 927                .unwrap();
 928                assert_eq!(
 929                    results,
 930                    vec![
 931                        PathBuf::from("root_link/banana/carrot/date"),
 932                        PathBuf::from("root_link/banana/carrot/endive"),
 933                    ]
 934                );
 935            })
 936        });
 937    }
 938
 939    #[test]
 940    fn test_save_file() {
 941        App::test_async((), |mut app| async move {
 942            let dir = temp_tree(json!({
 943                "file1": "the old contents",
 944            }));
 945
 946            let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
 947            assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 1)).await;
 948
 949            let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
 950
 951            let file_inode = app.read(|ctx| {
 952                let tree = tree.read(ctx);
 953                let inode = tree.files().next().unwrap();
 954                assert_eq!(
 955                    tree.path_for_inode(inode, false)
 956                        .unwrap()
 957                        .file_name()
 958                        .unwrap(),
 959                    "file1"
 960                );
 961                inode
 962            });
 963
 964            tree.update(&mut app, |tree, ctx| {
 965                smol::block_on(tree.save(file_inode, buffer.snapshot(), ctx.as_ref())).unwrap()
 966            });
 967
 968            let loaded_history = app
 969                .read(|ctx| tree.read(ctx).load_history(file_inode, ctx))
 970                .await
 971                .unwrap();
 972            assert_eq!(loaded_history.base_text.as_ref(), buffer.text());
 973        });
 974    }
 975
 976    #[test]
 977    fn test_rescan() {
 978        App::test_async((), |mut app| async move {
 979            let dir2 = temp_tree(json!({
 980                "dir1": {
 981                    "dir3": {
 982                        "file": "contents",
 983                    }
 984                },
 985                "dir2": {
 986                }
 987            }));
 988            let dir = temp_tree(json!({
 989                "dir1": {
 990                    "dir3": {
 991                        "file": "contents",
 992                    }
 993                },
 994                "dir2": {
 995                }
 996            }));
 997
 998            let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
 999            assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 1)).await;
1000
1001            let dir_inode = app.read(|ctx| {
1002                tree.read(ctx)
1003                    .snapshot()
1004                    .inode_for_path("dir1/dir3")
1005                    .unwrap()
1006            });
1007            app.read(|ctx| {
1008                let tree = tree.read(ctx);
1009                assert_eq!(
1010                    tree.path_for_inode(dir_inode, false)
1011                        .unwrap()
1012                        .to_str()
1013                        .unwrap(),
1014                    "dir1/dir3"
1015                );
1016            });
1017
1018            std::fs::rename(dir2.path(), dir.path().join("foo")).unwrap();
1019            assert_condition(1, 300, || {
1020                app.read(|ctx| {
1021                    let tree = tree.read(ctx);
1022                    tree.path_for_inode(dir_inode, false)
1023                        .unwrap()
1024                        .to_str()
1025                        .unwrap()
1026                        == "dir2/dir3"
1027                })
1028            })
1029            .await;
1030            app.read(|ctx| {
1031                let tree = tree.read(ctx);
1032                assert_eq!(tree.snapshot().inode_for_path("dir2/dir3"), Some(dir_inode));
1033            });
1034        });
1035    }
1036
1037    #[test]
1038    fn test_random() {
1039        let iterations = env::var("ITERATIONS")
1040            .map(|i| i.parse().unwrap())
1041            .unwrap_or(100);
1042        let operations = env::var("OPERATIONS")
1043            .map(|o| o.parse().unwrap())
1044            .unwrap_or(40);
1045        let initial_entries = env::var("INITIAL_ENTRIES")
1046            .map(|o| o.parse().unwrap())
1047            .unwrap_or(20);
1048        let seeds = if let Ok(seed) = env::var("SEED").map(|s| s.parse().unwrap()) {
1049            // Init logging so that we can debug the operations for this seed.
1050            SimpleLogger::init(LevelFilter::Info, Default::default()).unwrap();
1051            seed..seed + 1
1052        } else {
1053            0..iterations
1054        };
1055
1056        for seed in seeds {
1057            dbg!(seed);
1058            let mut rng = StdRng::seed_from_u64(seed);
1059
1060            let root_dir = tempdir::TempDir::new(&format!("test-{}", seed)).unwrap();
1061            for _ in 0..initial_entries {
1062                randomly_mutate_tree(root_dir.path(), 1.0, &mut rng).unwrap();
1063            }
1064            log::info!("Generated initial tree");
1065
1066            let (notify_tx, _notify_rx) = smol::channel::unbounded();
1067            let scanner = BackgroundScanner::new(
1068                Snapshot {
1069                    id: 0,
1070                    path: root_dir.path().into(),
1071                    root_inode: None,
1072                    entries: Default::default(),
1073                },
1074                notify_tx,
1075            );
1076            scanner.scan_dirs().unwrap();
1077
1078            let mut events = Vec::new();
1079            let mut mutations_len = operations;
1080            while mutations_len > 1 {
1081                if !events.is_empty() && rng.gen_bool(0.4) {
1082                    let len = rng.gen_range(0..=events.len());
1083                    let to_deliver = events.drain(0..len).collect::<Vec<_>>();
1084                    log::info!("Delivering events: {:#?}", to_deliver);
1085                    scanner.process_events(to_deliver);
1086                } else {
1087                    events.extend(randomly_mutate_tree(root_dir.path(), 0.6, &mut rng).unwrap());
1088                    mutations_len -= 1;
1089                }
1090            }
1091            log::info!("Quiescing: {:#?}", events);
1092            scanner.process_events(events);
1093
1094            let (notify_tx, _notify_rx) = smol::channel::unbounded();
1095            let new_scanner = BackgroundScanner::new(
1096                Snapshot {
1097                    id: 0,
1098                    path: root_dir.path().into(),
1099                    root_inode: None,
1100                    entries: Default::default(),
1101                },
1102                notify_tx,
1103            );
1104            new_scanner.scan_dirs().unwrap();
1105            assert_eq!(scanner.snapshot().to_vec(), new_scanner.snapshot().to_vec());
1106        }
1107    }
1108
1109    fn randomly_mutate_tree(
1110        root_path: &Path,
1111        insertion_probability: f64,
1112        rng: &mut impl Rng,
1113    ) -> Result<Vec<fsevent::Event>> {
1114        let (dirs, files) = read_dir_recursive(root_path.to_path_buf());
1115
1116        let mut events = Vec::new();
1117        let mut record_event = |path: PathBuf| {
1118            events.push(fsevent::Event {
1119                event_id: SystemTime::now()
1120                    .duration_since(UNIX_EPOCH)
1121                    .unwrap()
1122                    .as_secs(),
1123                flags: fsevent::StreamFlags::empty(),
1124                path,
1125            });
1126        };
1127
1128        if (files.is_empty() && dirs.len() == 1) || rng.gen_bool(insertion_probability) {
1129            let path = dirs.choose(rng).unwrap();
1130            let new_path = path.join(gen_name(rng));
1131
1132            if rng.gen() {
1133                log::info!("Creating dir {:?}", new_path.strip_prefix(root_path)?);
1134                fs::create_dir(&new_path)?;
1135            } else {
1136                log::info!("Creating file {:?}", new_path.strip_prefix(root_path)?);
1137                fs::write(&new_path, "")?;
1138            }
1139            record_event(new_path);
1140        } else {
1141            let old_path = {
1142                let file_path = files.choose(rng);
1143                let dir_path = dirs[1..].choose(rng);
1144                file_path.into_iter().chain(dir_path).choose(rng).unwrap()
1145            };
1146
1147            let is_rename = rng.gen();
1148            if is_rename {
1149                let new_path_parent = dirs
1150                    .iter()
1151                    .filter(|d| !d.starts_with(old_path))
1152                    .choose(rng)
1153                    .unwrap();
1154
1155                let overwrite_existing_dir =
1156                    !old_path.starts_with(&new_path_parent) && rng.gen_bool(0.3);
1157                let new_path = if overwrite_existing_dir {
1158                    fs::remove_dir_all(&new_path_parent).ok();
1159                    new_path_parent.to_path_buf()
1160                } else {
1161                    new_path_parent.join(gen_name(rng))
1162                };
1163
1164                log::info!(
1165                    "Renaming {:?} to {}{:?}",
1166                    old_path.strip_prefix(&root_path)?,
1167                    if overwrite_existing_dir {
1168                        "overwrite "
1169                    } else {
1170                        ""
1171                    },
1172                    new_path.strip_prefix(&root_path)?
1173                );
1174                fs::rename(&old_path, &new_path)?;
1175                record_event(old_path.clone());
1176                record_event(new_path);
1177            } else if old_path.is_dir() {
1178                let (dirs, files) = read_dir_recursive(old_path.clone());
1179
1180                log::info!("Deleting dir {:?}", old_path.strip_prefix(&root_path)?);
1181                fs::remove_dir_all(&old_path).unwrap();
1182                for file in files {
1183                    record_event(file);
1184                }
1185                for dir in dirs {
1186                    record_event(dir);
1187                }
1188            } else {
1189                log::info!("Deleting file {:?}", old_path.strip_prefix(&root_path)?);
1190                fs::remove_file(old_path).unwrap();
1191                record_event(old_path.clone());
1192            }
1193        }
1194
1195        Ok(events)
1196    }
1197
1198    fn read_dir_recursive(path: PathBuf) -> (Vec<PathBuf>, Vec<PathBuf>) {
1199        let child_entries = fs::read_dir(&path).unwrap();
1200        let mut dirs = vec![path];
1201        let mut files = Vec::new();
1202        for child_entry in child_entries {
1203            let child_path = child_entry.unwrap().path();
1204            if child_path.is_dir() {
1205                let (child_dirs, child_files) = read_dir_recursive(child_path);
1206                dirs.extend(child_dirs);
1207                files.extend(child_files);
1208            } else {
1209                files.push(child_path);
1210            }
1211        }
1212        (dirs, files)
1213    }
1214
1215    fn gen_name(rng: &mut impl Rng) -> String {
1216        (0..6)
1217            .map(|_| rng.sample(rand::distributions::Alphanumeric))
1218            .map(char::from)
1219            .collect()
1220    }
1221
1222    impl Snapshot {
1223        fn to_vec(&self) -> Vec<(PathBuf, u64)> {
1224            use std::iter::FromIterator;
1225
1226            let mut paths = Vec::new();
1227
1228            let mut stack = Vec::new();
1229            stack.extend(self.root_inode);
1230            while let Some(inode) = stack.pop() {
1231                let computed_path = self.path_for_inode(inode, true).unwrap();
1232                match self.entries.get(&inode).unwrap() {
1233                    Entry::Dir { children, .. } => {
1234                        stack.extend(children.iter().map(|c| c.0));
1235                    }
1236                    Entry::File { path, .. } => {
1237                        assert_eq!(
1238                            String::from_iter(path.path.iter()),
1239                            computed_path.to_str().unwrap()
1240                        );
1241                    }
1242                }
1243                paths.push((computed_path, inode));
1244            }
1245
1246            paths.sort_by(|a, b| a.0.cmp(&b.0));
1247            paths
1248        }
1249    }
1250}