fs.rs

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