fs.rs

   1pub mod repository;
   2
   3use anyhow::{anyhow, Result};
   4use fsevent::EventStream;
   5use futures::{future::BoxFuture, Stream, StreamExt};
   6use git2::Repository as LibGitRepository;
   7use parking_lot::Mutex;
   8use repository::GitRepository;
   9use rope::Rope;
  10use smol::io::{AsyncReadExt, AsyncWriteExt};
  11use std::io::Write;
  12use std::sync::Arc;
  13use std::{
  14    io,
  15    os::unix::fs::MetadataExt,
  16    path::{Component, Path, PathBuf},
  17    pin::Pin,
  18    time::{Duration, SystemTime},
  19};
  20use tempfile::NamedTempFile;
  21use text::LineEnding;
  22use util::ResultExt;
  23
  24#[cfg(any(test, feature = "test-support"))]
  25use collections::{btree_map, BTreeMap};
  26#[cfg(any(test, feature = "test-support"))]
  27use repository::{FakeGitRepositoryState, GitFileStatus};
  28#[cfg(any(test, feature = "test-support"))]
  29use std::ffi::OsStr;
  30
  31#[async_trait::async_trait]
  32pub trait Fs: Send + Sync {
  33    async fn create_dir(&self, path: &Path) -> Result<()>;
  34    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
  35    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
  36    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
  37    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
  38    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
  39    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
  40    async fn load(&self, path: &Path) -> Result<String>;
  41    async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
  42    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
  43    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
  44    async fn is_file(&self, path: &Path) -> bool;
  45    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
  46    async fn read_link(&self, path: &Path) -> Result<PathBuf>;
  47    async fn read_dir(
  48        &self,
  49        path: &Path,
  50    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
  51    async fn watch(
  52        &self,
  53        path: &Path,
  54        latency: Duration,
  55    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
  56    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<Mutex<dyn GitRepository>>>;
  57    fn is_fake(&self) -> bool;
  58    #[cfg(any(test, feature = "test-support"))]
  59    fn as_fake(&self) -> &FakeFs;
  60}
  61
  62#[derive(Copy, Clone, Default)]
  63pub struct CreateOptions {
  64    pub overwrite: bool,
  65    pub ignore_if_exists: bool,
  66}
  67
  68#[derive(Copy, Clone, Default)]
  69pub struct CopyOptions {
  70    pub overwrite: bool,
  71    pub ignore_if_exists: bool,
  72}
  73
  74#[derive(Copy, Clone, Default)]
  75pub struct RenameOptions {
  76    pub overwrite: bool,
  77    pub ignore_if_exists: bool,
  78}
  79
  80#[derive(Copy, Clone, Default)]
  81pub struct RemoveOptions {
  82    pub recursive: bool,
  83    pub ignore_if_not_exists: bool,
  84}
  85
  86#[derive(Copy, Clone, Debug)]
  87pub struct Metadata {
  88    pub inode: u64,
  89    pub mtime: SystemTime,
  90    pub is_symlink: bool,
  91    pub is_dir: bool,
  92}
  93
  94pub struct RealFs;
  95
  96#[async_trait::async_trait]
  97impl Fs for RealFs {
  98    async fn create_dir(&self, path: &Path) -> Result<()> {
  99        Ok(smol::fs::create_dir_all(path).await?)
 100    }
 101
 102    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 103        let mut open_options = smol::fs::OpenOptions::new();
 104        open_options.write(true).create(true);
 105        if options.overwrite {
 106            open_options.truncate(true);
 107        } else if !options.ignore_if_exists {
 108            open_options.create_new(true);
 109        }
 110        open_options.open(path).await?;
 111        Ok(())
 112    }
 113
 114    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 115        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 116            if options.ignore_if_exists {
 117                return Ok(());
 118            } else {
 119                return Err(anyhow!("{target:?} already exists"));
 120            }
 121        }
 122
 123        smol::fs::copy(source, target).await?;
 124        Ok(())
 125    }
 126
 127    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
 128        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 129            if options.ignore_if_exists {
 130                return Ok(());
 131            } else {
 132                return Err(anyhow!("{target:?} already exists"));
 133            }
 134        }
 135
 136        smol::fs::rename(source, target).await?;
 137        Ok(())
 138    }
 139
 140    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 141        let result = if options.recursive {
 142            smol::fs::remove_dir_all(path).await
 143        } else {
 144            smol::fs::remove_dir(path).await
 145        };
 146        match result {
 147            Ok(()) => Ok(()),
 148            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 149                Ok(())
 150            }
 151            Err(err) => Err(err)?,
 152        }
 153    }
 154
 155    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 156        match smol::fs::remove_file(path).await {
 157            Ok(()) => Ok(()),
 158            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 159                Ok(())
 160            }
 161            Err(err) => Err(err)?,
 162        }
 163    }
 164
 165    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
 166        Ok(Box::new(std::fs::File::open(path)?))
 167    }
 168
 169    async fn load(&self, path: &Path) -> Result<String> {
 170        let mut file = smol::fs::File::open(path).await?;
 171        let mut text = String::new();
 172        file.read_to_string(&mut text).await?;
 173        Ok(text)
 174    }
 175
 176    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
 177        smol::unblock(move || {
 178            let mut tmp_file = NamedTempFile::new()?;
 179            tmp_file.write_all(data.as_bytes())?;
 180            tmp_file.persist(path)?;
 181            Ok::<(), anyhow::Error>(())
 182        })
 183        .await?;
 184
 185        Ok(())
 186    }
 187
 188    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
 189        let buffer_size = text.summary().len.min(10 * 1024);
 190        if let Some(path) = path.parent() {
 191            self.create_dir(path).await?;
 192        }
 193        let file = smol::fs::File::create(path).await?;
 194        let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
 195        for chunk in chunks(text, line_ending) {
 196            writer.write_all(chunk.as_bytes()).await?;
 197        }
 198        writer.flush().await?;
 199        Ok(())
 200    }
 201
 202    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
 203        Ok(smol::fs::canonicalize(path).await?)
 204    }
 205
 206    async fn is_file(&self, path: &Path) -> bool {
 207        smol::fs::metadata(path)
 208            .await
 209            .map_or(false, |metadata| metadata.is_file())
 210    }
 211
 212    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
 213        let symlink_metadata = match smol::fs::symlink_metadata(path).await {
 214            Ok(metadata) => metadata,
 215            Err(err) => {
 216                return match (err.kind(), err.raw_os_error()) {
 217                    (io::ErrorKind::NotFound, _) => Ok(None),
 218                    (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
 219                    _ => Err(anyhow::Error::new(err)),
 220                }
 221            }
 222        };
 223
 224        let is_symlink = symlink_metadata.file_type().is_symlink();
 225        let metadata = if is_symlink {
 226            smol::fs::metadata(path).await?
 227        } else {
 228            symlink_metadata
 229        };
 230        Ok(Some(Metadata {
 231            inode: metadata.ino(),
 232            mtime: metadata.modified().unwrap(),
 233            is_symlink,
 234            is_dir: metadata.file_type().is_dir(),
 235        }))
 236    }
 237
 238    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
 239        let path = smol::fs::read_link(path).await?;
 240        Ok(path)
 241    }
 242
 243    async fn read_dir(
 244        &self,
 245        path: &Path,
 246    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
 247        let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
 248            Ok(entry) => Ok(entry.path()),
 249            Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
 250        });
 251        Ok(Box::pin(result))
 252    }
 253
 254    async fn watch(
 255        &self,
 256        path: &Path,
 257        latency: Duration,
 258    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
 259        let (tx, rx) = smol::channel::unbounded();
 260        let (stream, handle) = EventStream::new(&[path], latency);
 261        std::thread::spawn(move || {
 262            stream.run(move |events| smol::block_on(tx.send(events)).is_ok());
 263        });
 264        Box::pin(rx.chain(futures::stream::once(async move {
 265            drop(handle);
 266            vec![]
 267        })))
 268    }
 269
 270    fn open_repo(&self, dotgit_path: &Path) -> Option<Arc<Mutex<dyn GitRepository>>> {
 271        LibGitRepository::open(&dotgit_path)
 272            .log_err()
 273            .and_then::<Arc<Mutex<dyn GitRepository>>, _>(|libgit_repository| {
 274                Some(Arc::new(Mutex::new(libgit_repository)))
 275            })
 276    }
 277
 278    fn is_fake(&self) -> bool {
 279        false
 280    }
 281    #[cfg(any(test, feature = "test-support"))]
 282    fn as_fake(&self) -> &FakeFs {
 283        panic!("called `RealFs::as_fake`")
 284    }
 285}
 286
 287#[cfg(any(test, feature = "test-support"))]
 288pub struct FakeFs {
 289    // Use an unfair lock to ensure tests are deterministic.
 290    state: Mutex<FakeFsState>,
 291    executor: gpui::BackgroundExecutor,
 292}
 293
 294#[cfg(any(test, feature = "test-support"))]
 295struct FakeFsState {
 296    root: Arc<Mutex<FakeFsEntry>>,
 297    next_inode: u64,
 298    next_mtime: SystemTime,
 299    event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
 300    events_paused: bool,
 301    buffered_events: Vec<fsevent::Event>,
 302    metadata_call_count: usize,
 303    read_dir_call_count: usize,
 304}
 305
 306#[cfg(any(test, feature = "test-support"))]
 307#[derive(Debug)]
 308enum FakeFsEntry {
 309    File {
 310        inode: u64,
 311        mtime: SystemTime,
 312        content: String,
 313    },
 314    Dir {
 315        inode: u64,
 316        mtime: SystemTime,
 317        entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
 318        git_repo_state: Option<Arc<Mutex<repository::FakeGitRepositoryState>>>,
 319    },
 320    Symlink {
 321        target: PathBuf,
 322    },
 323}
 324
 325#[cfg(any(test, feature = "test-support"))]
 326impl FakeFsState {
 327    fn read_path<'a>(&'a self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
 328        Ok(self
 329            .try_read_path(target, true)
 330            .ok_or_else(|| anyhow!("path does not exist: {}", target.display()))?
 331            .0)
 332    }
 333
 334    fn try_read_path<'a>(
 335        &'a self,
 336        target: &Path,
 337        follow_symlink: bool,
 338    ) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
 339        let mut path = target.to_path_buf();
 340        let mut canonical_path = PathBuf::new();
 341        let mut entry_stack = Vec::new();
 342        'outer: loop {
 343            let mut path_components = path.components().peekable();
 344            while let Some(component) = path_components.next() {
 345                match component {
 346                    Component::Prefix(_) => panic!("prefix paths aren't supported"),
 347                    Component::RootDir => {
 348                        entry_stack.clear();
 349                        entry_stack.push(self.root.clone());
 350                        canonical_path.clear();
 351                        canonical_path.push("/");
 352                    }
 353                    Component::CurDir => {}
 354                    Component::ParentDir => {
 355                        entry_stack.pop()?;
 356                        canonical_path.pop();
 357                    }
 358                    Component::Normal(name) => {
 359                        let current_entry = entry_stack.last().cloned()?;
 360                        let current_entry = current_entry.lock();
 361                        if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
 362                            let entry = entries.get(name.to_str().unwrap()).cloned()?;
 363                            if path_components.peek().is_some() || follow_symlink {
 364                                let entry = entry.lock();
 365                                if let FakeFsEntry::Symlink { target, .. } = &*entry {
 366                                    let mut target = target.clone();
 367                                    target.extend(path_components);
 368                                    path = target;
 369                                    continue 'outer;
 370                                }
 371                            }
 372                            entry_stack.push(entry.clone());
 373                            canonical_path.push(name);
 374                        } else {
 375                            return None;
 376                        }
 377                    }
 378                }
 379            }
 380            break;
 381        }
 382        Some((entry_stack.pop()?, canonical_path))
 383    }
 384
 385    fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
 386    where
 387        Fn: FnOnce(btree_map::Entry<String, Arc<Mutex<FakeFsEntry>>>) -> Result<T>,
 388    {
 389        let path = normalize_path(path);
 390        let filename = path
 391            .file_name()
 392            .ok_or_else(|| anyhow!("cannot overwrite the root"))?;
 393        let parent_path = path.parent().unwrap();
 394
 395        let parent = self.read_path(parent_path)?;
 396        let mut parent = parent.lock();
 397        let new_entry = parent
 398            .dir_entries(parent_path)?
 399            .entry(filename.to_str().unwrap().into());
 400        callback(new_entry)
 401    }
 402
 403    fn emit_event<I, T>(&mut self, paths: I)
 404    where
 405        I: IntoIterator<Item = T>,
 406        T: Into<PathBuf>,
 407    {
 408        self.buffered_events
 409            .extend(paths.into_iter().map(|path| fsevent::Event {
 410                event_id: 0,
 411                flags: fsevent::StreamFlags::empty(),
 412                path: path.into(),
 413            }));
 414
 415        if !self.events_paused {
 416            self.flush_events(self.buffered_events.len());
 417        }
 418    }
 419
 420    fn flush_events(&mut self, mut count: usize) {
 421        count = count.min(self.buffered_events.len());
 422        let events = self.buffered_events.drain(0..count).collect::<Vec<_>>();
 423        self.event_txs.retain(|tx| {
 424            let _ = tx.try_send(events.clone());
 425            !tx.is_closed()
 426        });
 427    }
 428}
 429
 430#[cfg(any(test, feature = "test-support"))]
 431lazy_static::lazy_static! {
 432    pub static ref FS_DOT_GIT: &'static OsStr = OsStr::new(".git");
 433}
 434
 435#[cfg(any(test, feature = "test-support"))]
 436impl FakeFs {
 437    pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
 438        Arc::new(Self {
 439            executor,
 440            state: Mutex::new(FakeFsState {
 441                root: Arc::new(Mutex::new(FakeFsEntry::Dir {
 442                    inode: 0,
 443                    mtime: SystemTime::UNIX_EPOCH,
 444                    entries: Default::default(),
 445                    git_repo_state: None,
 446                })),
 447                next_mtime: SystemTime::UNIX_EPOCH,
 448                next_inode: 1,
 449                event_txs: Default::default(),
 450                buffered_events: Vec::new(),
 451                events_paused: false,
 452                read_dir_call_count: 0,
 453                metadata_call_count: 0,
 454            }),
 455        })
 456    }
 457
 458    pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
 459        self.write_file_internal(path, content).unwrap()
 460    }
 461
 462    pub async fn insert_symlink(&self, path: impl AsRef<Path>, target: PathBuf) {
 463        let mut state = self.state.lock();
 464        let path = path.as_ref();
 465        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
 466        state
 467            .write_path(path.as_ref(), move |e| match e {
 468                btree_map::Entry::Vacant(e) => {
 469                    e.insert(file);
 470                    Ok(())
 471                }
 472                btree_map::Entry::Occupied(mut e) => {
 473                    *e.get_mut() = file;
 474                    Ok(())
 475                }
 476            })
 477            .unwrap();
 478        state.emit_event(&[path]);
 479    }
 480
 481    pub fn write_file_internal(&self, path: impl AsRef<Path>, content: String) -> Result<()> {
 482        let mut state = self.state.lock();
 483        let path = path.as_ref();
 484        let inode = state.next_inode;
 485        let mtime = state.next_mtime;
 486        state.next_inode += 1;
 487        state.next_mtime += Duration::from_nanos(1);
 488        let file = Arc::new(Mutex::new(FakeFsEntry::File {
 489            inode,
 490            mtime,
 491            content,
 492        }));
 493        state.write_path(path, move |entry| {
 494            match entry {
 495                btree_map::Entry::Vacant(e) => {
 496                    e.insert(file);
 497                }
 498                btree_map::Entry::Occupied(mut e) => {
 499                    *e.get_mut() = file;
 500                }
 501            }
 502            Ok(())
 503        })?;
 504        state.emit_event(&[path]);
 505        Ok(())
 506    }
 507
 508    pub fn pause_events(&self) {
 509        self.state.lock().events_paused = true;
 510    }
 511
 512    pub fn buffered_event_count(&self) -> usize {
 513        self.state.lock().buffered_events.len()
 514    }
 515
 516    pub fn flush_events(&self, count: usize) {
 517        self.state.lock().flush_events(count);
 518    }
 519
 520    #[must_use]
 521    pub fn insert_tree<'a>(
 522        &'a self,
 523        path: impl 'a + AsRef<Path> + Send,
 524        tree: serde_json::Value,
 525    ) -> futures::future::BoxFuture<'a, ()> {
 526        use futures::FutureExt as _;
 527        use serde_json::Value::*;
 528
 529        async move {
 530            let path = path.as_ref();
 531
 532            match tree {
 533                Object(map) => {
 534                    self.create_dir(path).await.unwrap();
 535                    for (name, contents) in map {
 536                        let mut path = PathBuf::from(path);
 537                        path.push(name);
 538                        self.insert_tree(&path, contents).await;
 539                    }
 540                }
 541                Null => {
 542                    self.create_dir(path).await.unwrap();
 543                }
 544                String(contents) => {
 545                    self.insert_file(&path, contents).await;
 546                }
 547                _ => {
 548                    panic!("JSON object must contain only objects, strings, or null");
 549                }
 550            }
 551        }
 552        .boxed()
 553    }
 554
 555    pub fn with_git_state<F>(&self, dot_git: &Path, emit_git_event: bool, f: F)
 556    where
 557        F: FnOnce(&mut FakeGitRepositoryState),
 558    {
 559        let mut state = self.state.lock();
 560        let entry = state.read_path(dot_git).unwrap();
 561        let mut entry = entry.lock();
 562
 563        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
 564            let repo_state = git_repo_state.get_or_insert_with(Default::default);
 565            let mut repo_state = repo_state.lock();
 566
 567            f(&mut repo_state);
 568
 569            if emit_git_event {
 570                state.emit_event([dot_git]);
 571            }
 572        } else {
 573            panic!("not a directory");
 574        }
 575    }
 576
 577    pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
 578        self.with_git_state(dot_git, true, |state| {
 579            state.branch_name = branch.map(Into::into)
 580        })
 581    }
 582
 583    pub fn set_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
 584        self.with_git_state(dot_git, true, |state| {
 585            state.index_contents.clear();
 586            state.index_contents.extend(
 587                head_state
 588                    .iter()
 589                    .map(|(path, content)| (path.to_path_buf(), content.clone())),
 590            );
 591        });
 592    }
 593
 594    pub fn set_status_for_repo_via_working_copy_change(
 595        &self,
 596        dot_git: &Path,
 597        statuses: &[(&Path, GitFileStatus)],
 598    ) {
 599        self.with_git_state(dot_git, false, |state| {
 600            state.worktree_statuses.clear();
 601            state.worktree_statuses.extend(
 602                statuses
 603                    .iter()
 604                    .map(|(path, content)| ((**path).into(), content.clone())),
 605            );
 606        });
 607        self.state.lock().emit_event(
 608            statuses
 609                .iter()
 610                .map(|(path, _)| dot_git.parent().unwrap().join(path)),
 611        );
 612    }
 613
 614    pub fn set_status_for_repo_via_git_operation(
 615        &self,
 616        dot_git: &Path,
 617        statuses: &[(&Path, GitFileStatus)],
 618    ) {
 619        self.with_git_state(dot_git, true, |state| {
 620            state.worktree_statuses.clear();
 621            state.worktree_statuses.extend(
 622                statuses
 623                    .iter()
 624                    .map(|(path, content)| ((**path).into(), content.clone())),
 625            );
 626        });
 627    }
 628
 629    pub fn paths(&self, include_dot_git: bool) -> Vec<PathBuf> {
 630        let mut result = Vec::new();
 631        let mut queue = collections::VecDeque::new();
 632        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
 633        while let Some((path, entry)) = queue.pop_front() {
 634            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
 635                for (name, entry) in entries {
 636                    queue.push_back((path.join(name), entry.clone()));
 637                }
 638            }
 639            if include_dot_git
 640                || !path
 641                    .components()
 642                    .any(|component| component.as_os_str() == *FS_DOT_GIT)
 643            {
 644                result.push(path);
 645            }
 646        }
 647        result
 648    }
 649
 650    pub fn directories(&self, include_dot_git: bool) -> Vec<PathBuf> {
 651        let mut result = Vec::new();
 652        let mut queue = collections::VecDeque::new();
 653        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
 654        while let Some((path, entry)) = queue.pop_front() {
 655            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
 656                for (name, entry) in entries {
 657                    queue.push_back((path.join(name), entry.clone()));
 658                }
 659                if include_dot_git
 660                    || !path
 661                        .components()
 662                        .any(|component| component.as_os_str() == *FS_DOT_GIT)
 663                {
 664                    result.push(path);
 665                }
 666            }
 667        }
 668        result
 669    }
 670
 671    pub fn files(&self) -> Vec<PathBuf> {
 672        let mut result = Vec::new();
 673        let mut queue = collections::VecDeque::new();
 674        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
 675        while let Some((path, entry)) = queue.pop_front() {
 676            let e = entry.lock();
 677            match &*e {
 678                FakeFsEntry::File { .. } => result.push(path),
 679                FakeFsEntry::Dir { entries, .. } => {
 680                    for (name, entry) in entries {
 681                        queue.push_back((path.join(name), entry.clone()));
 682                    }
 683                }
 684                FakeFsEntry::Symlink { .. } => {}
 685            }
 686        }
 687        result
 688    }
 689
 690    /// How many `read_dir` calls have been issued.
 691    pub fn read_dir_call_count(&self) -> usize {
 692        self.state.lock().read_dir_call_count
 693    }
 694
 695    /// How many `metadata` calls have been issued.
 696    pub fn metadata_call_count(&self) -> usize {
 697        self.state.lock().metadata_call_count
 698    }
 699
 700    fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
 701        self.executor.simulate_random_delay()
 702    }
 703}
 704
 705#[cfg(any(test, feature = "test-support"))]
 706impl FakeFsEntry {
 707    fn is_file(&self) -> bool {
 708        matches!(self, Self::File { .. })
 709    }
 710
 711    fn is_symlink(&self) -> bool {
 712        matches!(self, Self::Symlink { .. })
 713    }
 714
 715    fn file_content(&self, path: &Path) -> Result<&String> {
 716        if let Self::File { content, .. } = self {
 717            Ok(content)
 718        } else {
 719            Err(anyhow!("not a file: {}", path.display()))
 720        }
 721    }
 722
 723    fn set_file_content(&mut self, path: &Path, new_content: String) -> Result<()> {
 724        if let Self::File { content, mtime, .. } = self {
 725            *mtime = SystemTime::now();
 726            *content = new_content;
 727            Ok(())
 728        } else {
 729            Err(anyhow!("not a file: {}", path.display()))
 730        }
 731    }
 732
 733    fn dir_entries(
 734        &mut self,
 735        path: &Path,
 736    ) -> Result<&mut BTreeMap<String, Arc<Mutex<FakeFsEntry>>>> {
 737        if let Self::Dir { entries, .. } = self {
 738            Ok(entries)
 739        } else {
 740            Err(anyhow!("not a directory: {}", path.display()))
 741        }
 742    }
 743}
 744
 745#[cfg(any(test, feature = "test-support"))]
 746#[async_trait::async_trait]
 747impl Fs for FakeFs {
 748    async fn create_dir(&self, path: &Path) -> Result<()> {
 749        self.simulate_random_delay().await;
 750
 751        let mut created_dirs = Vec::new();
 752        let mut cur_path = PathBuf::new();
 753        for component in path.components() {
 754            let mut state = self.state.lock();
 755            cur_path.push(component);
 756            if cur_path == Path::new("/") {
 757                continue;
 758            }
 759
 760            let inode = state.next_inode;
 761            let mtime = state.next_mtime;
 762            state.next_mtime += Duration::from_nanos(1);
 763            state.next_inode += 1;
 764            state.write_path(&cur_path, |entry| {
 765                entry.or_insert_with(|| {
 766                    created_dirs.push(cur_path.clone());
 767                    Arc::new(Mutex::new(FakeFsEntry::Dir {
 768                        inode,
 769                        mtime,
 770                        entries: Default::default(),
 771                        git_repo_state: None,
 772                    }))
 773                });
 774                Ok(())
 775            })?
 776        }
 777
 778        self.state.lock().emit_event(&created_dirs);
 779        Ok(())
 780    }
 781
 782    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 783        self.simulate_random_delay().await;
 784        let mut state = self.state.lock();
 785        let inode = state.next_inode;
 786        let mtime = state.next_mtime;
 787        state.next_mtime += Duration::from_nanos(1);
 788        state.next_inode += 1;
 789        let file = Arc::new(Mutex::new(FakeFsEntry::File {
 790            inode,
 791            mtime,
 792            content: String::new(),
 793        }));
 794        state.write_path(path, |entry| {
 795            match entry {
 796                btree_map::Entry::Occupied(mut e) => {
 797                    if options.overwrite {
 798                        *e.get_mut() = file;
 799                    } else if !options.ignore_if_exists {
 800                        return Err(anyhow!("path already exists: {}", path.display()));
 801                    }
 802                }
 803                btree_map::Entry::Vacant(e) => {
 804                    e.insert(file);
 805                }
 806            }
 807            Ok(())
 808        })?;
 809        state.emit_event(&[path]);
 810        Ok(())
 811    }
 812
 813    async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
 814        self.simulate_random_delay().await;
 815
 816        let old_path = normalize_path(old_path);
 817        let new_path = normalize_path(new_path);
 818
 819        let mut state = self.state.lock();
 820        let moved_entry = state.write_path(&old_path, |e| {
 821            if let btree_map::Entry::Occupied(e) = e {
 822                Ok(e.get().clone())
 823            } else {
 824                Err(anyhow!("path does not exist: {}", &old_path.display()))
 825            }
 826        })?;
 827
 828        state.write_path(&new_path, |e| {
 829            match e {
 830                btree_map::Entry::Occupied(mut e) => {
 831                    if options.overwrite {
 832                        *e.get_mut() = moved_entry;
 833                    } else if !options.ignore_if_exists {
 834                        return Err(anyhow!("path already exists: {}", new_path.display()));
 835                    }
 836                }
 837                btree_map::Entry::Vacant(e) => {
 838                    e.insert(moved_entry);
 839                }
 840            }
 841            Ok(())
 842        })?;
 843
 844        state
 845            .write_path(&old_path, |e| {
 846                if let btree_map::Entry::Occupied(e) = e {
 847                    Ok(e.remove())
 848                } else {
 849                    unreachable!()
 850                }
 851            })
 852            .unwrap();
 853
 854        state.emit_event(&[old_path, new_path]);
 855        Ok(())
 856    }
 857
 858    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 859        self.simulate_random_delay().await;
 860
 861        let source = normalize_path(source);
 862        let target = normalize_path(target);
 863        let mut state = self.state.lock();
 864        let mtime = state.next_mtime;
 865        let inode = util::post_inc(&mut state.next_inode);
 866        state.next_mtime += Duration::from_nanos(1);
 867        let source_entry = state.read_path(&source)?;
 868        let content = source_entry.lock().file_content(&source)?.clone();
 869        let entry = state.write_path(&target, |e| match e {
 870            btree_map::Entry::Occupied(e) => {
 871                if options.overwrite {
 872                    Ok(Some(e.get().clone()))
 873                } else if !options.ignore_if_exists {
 874                    return Err(anyhow!("{target:?} already exists"));
 875                } else {
 876                    Ok(None)
 877                }
 878            }
 879            btree_map::Entry::Vacant(e) => Ok(Some(
 880                e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
 881                    inode,
 882                    mtime,
 883                    content: String::new(),
 884                })))
 885                .clone(),
 886            )),
 887        })?;
 888        if let Some(entry) = entry {
 889            entry.lock().set_file_content(&target, content)?;
 890        }
 891        state.emit_event(&[target]);
 892        Ok(())
 893    }
 894
 895    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 896        self.simulate_random_delay().await;
 897
 898        let path = normalize_path(path);
 899        let parent_path = path
 900            .parent()
 901            .ok_or_else(|| anyhow!("cannot remove the root"))?;
 902        let base_name = path.file_name().unwrap();
 903
 904        let mut state = self.state.lock();
 905        let parent_entry = state.read_path(parent_path)?;
 906        let mut parent_entry = parent_entry.lock();
 907        let entry = parent_entry
 908            .dir_entries(parent_path)?
 909            .entry(base_name.to_str().unwrap().into());
 910
 911        match entry {
 912            btree_map::Entry::Vacant(_) => {
 913                if !options.ignore_if_not_exists {
 914                    return Err(anyhow!("{path:?} does not exist"));
 915                }
 916            }
 917            btree_map::Entry::Occupied(e) => {
 918                {
 919                    let mut entry = e.get().lock();
 920                    let children = entry.dir_entries(&path)?;
 921                    if !options.recursive && !children.is_empty() {
 922                        return Err(anyhow!("{path:?} is not empty"));
 923                    }
 924                }
 925                e.remove();
 926            }
 927        }
 928        state.emit_event(&[path]);
 929        Ok(())
 930    }
 931
 932    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 933        self.simulate_random_delay().await;
 934
 935        let path = normalize_path(path);
 936        let parent_path = path
 937            .parent()
 938            .ok_or_else(|| anyhow!("cannot remove the root"))?;
 939        let base_name = path.file_name().unwrap();
 940        let mut state = self.state.lock();
 941        let parent_entry = state.read_path(parent_path)?;
 942        let mut parent_entry = parent_entry.lock();
 943        let entry = parent_entry
 944            .dir_entries(parent_path)?
 945            .entry(base_name.to_str().unwrap().into());
 946        match entry {
 947            btree_map::Entry::Vacant(_) => {
 948                if !options.ignore_if_not_exists {
 949                    return Err(anyhow!("{path:?} does not exist"));
 950                }
 951            }
 952            btree_map::Entry::Occupied(e) => {
 953                e.get().lock().file_content(&path)?;
 954                e.remove();
 955            }
 956        }
 957        state.emit_event(&[path]);
 958        Ok(())
 959    }
 960
 961    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
 962        let text = self.load(path).await?;
 963        Ok(Box::new(io::Cursor::new(text)))
 964    }
 965
 966    async fn load(&self, path: &Path) -> Result<String> {
 967        let path = normalize_path(path);
 968        self.simulate_random_delay().await;
 969        let state = self.state.lock();
 970        let entry = state.read_path(&path)?;
 971        let entry = entry.lock();
 972        entry.file_content(&path).cloned()
 973    }
 974
 975    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
 976        self.simulate_random_delay().await;
 977        let path = normalize_path(path.as_path());
 978        self.write_file_internal(path, data.to_string())?;
 979
 980        Ok(())
 981    }
 982
 983    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
 984        self.simulate_random_delay().await;
 985        let path = normalize_path(path);
 986        let content = chunks(text, line_ending).collect();
 987        if let Some(path) = path.parent() {
 988            self.create_dir(path).await?;
 989        }
 990        self.write_file_internal(path, content)?;
 991        Ok(())
 992    }
 993
 994    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
 995        let path = normalize_path(path);
 996        self.simulate_random_delay().await;
 997        let state = self.state.lock();
 998        if let Some((_, canonical_path)) = state.try_read_path(&path, true) {
 999            Ok(canonical_path)
1000        } else {
1001            Err(anyhow!("path does not exist: {}", path.display()))
1002        }
1003    }
1004
1005    async fn is_file(&self, path: &Path) -> bool {
1006        let path = normalize_path(path);
1007        self.simulate_random_delay().await;
1008        let state = self.state.lock();
1009        if let Some((entry, _)) = state.try_read_path(&path, true) {
1010            entry.lock().is_file()
1011        } else {
1012            false
1013        }
1014    }
1015
1016    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
1017        self.simulate_random_delay().await;
1018        let path = normalize_path(path);
1019        let mut state = self.state.lock();
1020        state.metadata_call_count += 1;
1021        if let Some((mut entry, _)) = state.try_read_path(&path, false) {
1022            let is_symlink = entry.lock().is_symlink();
1023            if is_symlink {
1024                if let Some(e) = state.try_read_path(&path, true).map(|e| e.0) {
1025                    entry = e;
1026                } else {
1027                    return Ok(None);
1028                }
1029            }
1030
1031            let entry = entry.lock();
1032            Ok(Some(match &*entry {
1033                FakeFsEntry::File { inode, mtime, .. } => Metadata {
1034                    inode: *inode,
1035                    mtime: *mtime,
1036                    is_dir: false,
1037                    is_symlink,
1038                },
1039                FakeFsEntry::Dir { inode, mtime, .. } => Metadata {
1040                    inode: *inode,
1041                    mtime: *mtime,
1042                    is_dir: true,
1043                    is_symlink,
1044                },
1045                FakeFsEntry::Symlink { .. } => unreachable!(),
1046            }))
1047        } else {
1048            Ok(None)
1049        }
1050    }
1051
1052    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
1053        self.simulate_random_delay().await;
1054        let path = normalize_path(path);
1055        let state = self.state.lock();
1056        if let Some((entry, _)) = state.try_read_path(&path, false) {
1057            let entry = entry.lock();
1058            if let FakeFsEntry::Symlink { target } = &*entry {
1059                Ok(target.clone())
1060            } else {
1061                Err(anyhow!("not a symlink: {}", path.display()))
1062            }
1063        } else {
1064            Err(anyhow!("path does not exist: {}", path.display()))
1065        }
1066    }
1067
1068    async fn read_dir(
1069        &self,
1070        path: &Path,
1071    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
1072        self.simulate_random_delay().await;
1073        let path = normalize_path(path);
1074        let mut state = self.state.lock();
1075        state.read_dir_call_count += 1;
1076        let entry = state.read_path(&path)?;
1077        let mut entry = entry.lock();
1078        let children = entry.dir_entries(&path)?;
1079        let paths = children
1080            .keys()
1081            .map(|file_name| Ok(path.join(file_name)))
1082            .collect::<Vec<_>>();
1083        Ok(Box::pin(futures::stream::iter(paths)))
1084    }
1085
1086    async fn watch(
1087        &self,
1088        path: &Path,
1089        _: Duration,
1090    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
1091        self.simulate_random_delay().await;
1092        let (tx, rx) = smol::channel::unbounded();
1093        self.state.lock().event_txs.push(tx);
1094        let path = path.to_path_buf();
1095        let executor = self.executor.clone();
1096        Box::pin(futures::StreamExt::filter(rx, move |events| {
1097            let result = events.iter().any(|event| event.path.starts_with(&path));
1098            let executor = executor.clone();
1099            async move {
1100                executor.simulate_random_delay().await;
1101                result
1102            }
1103        }))
1104    }
1105
1106    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<Mutex<dyn GitRepository>>> {
1107        let state = self.state.lock();
1108        let entry = state.read_path(abs_dot_git).unwrap();
1109        let mut entry = entry.lock();
1110        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
1111            let state = git_repo_state
1112                .get_or_insert_with(|| Arc::new(Mutex::new(FakeGitRepositoryState::default())))
1113                .clone();
1114            Some(repository::FakeGitRepository::open(state))
1115        } else {
1116            None
1117        }
1118    }
1119
1120    fn is_fake(&self) -> bool {
1121        true
1122    }
1123
1124    #[cfg(any(test, feature = "test-support"))]
1125    fn as_fake(&self) -> &FakeFs {
1126        self
1127    }
1128}
1129
1130fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
1131    rope.chunks().flat_map(move |chunk| {
1132        let mut newline = false;
1133        chunk.split('\n').flat_map(move |line| {
1134            let ending = if newline {
1135                Some(line_ending.as_str())
1136            } else {
1137                None
1138            };
1139            newline = true;
1140            ending.into_iter().chain([line])
1141        })
1142    })
1143}
1144
1145pub fn normalize_path(path: &Path) -> PathBuf {
1146    let mut components = path.components().peekable();
1147    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1148        components.next();
1149        PathBuf::from(c.as_os_str())
1150    } else {
1151        PathBuf::new()
1152    };
1153
1154    for component in components {
1155        match component {
1156            Component::Prefix(..) => unreachable!(),
1157            Component::RootDir => {
1158                ret.push(component.as_os_str());
1159            }
1160            Component::CurDir => {}
1161            Component::ParentDir => {
1162                ret.pop();
1163            }
1164            Component::Normal(c) => {
1165                ret.push(c);
1166            }
1167        }
1168    }
1169    ret
1170}
1171
1172pub fn copy_recursive<'a>(
1173    fs: &'a dyn Fs,
1174    source: &'a Path,
1175    target: &'a Path,
1176    options: CopyOptions,
1177) -> BoxFuture<'a, Result<()>> {
1178    use futures::future::FutureExt;
1179
1180    async move {
1181        let metadata = fs
1182            .metadata(source)
1183            .await?
1184            .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
1185        if metadata.is_dir {
1186            if !options.overwrite && fs.metadata(target).await.is_ok() {
1187                if options.ignore_if_exists {
1188                    return Ok(());
1189                } else {
1190                    return Err(anyhow!("{target:?} already exists"));
1191                }
1192            }
1193
1194            let _ = fs
1195                .remove_dir(
1196                    target,
1197                    RemoveOptions {
1198                        recursive: true,
1199                        ignore_if_not_exists: true,
1200                    },
1201                )
1202                .await;
1203            fs.create_dir(target).await?;
1204            let mut children = fs.read_dir(source).await?;
1205            while let Some(child_path) = children.next().await {
1206                if let Ok(child_path) = child_path {
1207                    if let Some(file_name) = child_path.file_name() {
1208                        let child_target_path = target.join(file_name);
1209                        copy_recursive(fs, &child_path, &child_target_path, options).await?;
1210                    }
1211                }
1212            }
1213
1214            Ok(())
1215        } else {
1216            fs.copy_file(source, target, options).await
1217        }
1218    }
1219    .boxed()
1220}
1221
1222#[cfg(test)]
1223mod tests {
1224    use super::*;
1225    use gpui::BackgroundExecutor;
1226    use serde_json::json;
1227
1228    #[gpui::test]
1229    async fn test_fake_fs(executor: BackgroundExecutor) {
1230        let fs = FakeFs::new(executor.clone());
1231        fs.insert_tree(
1232            "/root",
1233            json!({
1234                "dir1": {
1235                    "a": "A",
1236                    "b": "B"
1237                },
1238                "dir2": {
1239                    "c": "C",
1240                    "dir3": {
1241                        "d": "D"
1242                    }
1243                }
1244            }),
1245        )
1246        .await;
1247
1248        assert_eq!(
1249            fs.files(),
1250            vec![
1251                PathBuf::from("/root/dir1/a"),
1252                PathBuf::from("/root/dir1/b"),
1253                PathBuf::from("/root/dir2/c"),
1254                PathBuf::from("/root/dir2/dir3/d"),
1255            ]
1256        );
1257
1258        fs.insert_symlink("/root/dir2/link-to-dir3", "./dir3".into())
1259            .await;
1260
1261        assert_eq!(
1262            fs.canonicalize("/root/dir2/link-to-dir3".as_ref())
1263                .await
1264                .unwrap(),
1265            PathBuf::from("/root/dir2/dir3"),
1266        );
1267        assert_eq!(
1268            fs.canonicalize("/root/dir2/link-to-dir3/d".as_ref())
1269                .await
1270                .unwrap(),
1271            PathBuf::from("/root/dir2/dir3/d"),
1272        );
1273        assert_eq!(
1274            fs.load("/root/dir2/link-to-dir3/d".as_ref()).await.unwrap(),
1275            "D",
1276        );
1277    }
1278}