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_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
 494        let mut state = self.state.lock().await;
 495        let entry = state.read_path(dot_git).await.unwrap();
 496        let mut entry = entry.lock().await;
 497
 498        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
 499            let repo_state = git_repo_state.get_or_insert_with(Default::default);
 500            let mut repo_state = repo_state.lock();
 501
 502            repo_state.index_contents.clear();
 503            repo_state.index_contents.extend(
 504                head_state
 505                    .iter()
 506                    .map(|(path, content)| (path.to_path_buf(), content.clone())),
 507            );
 508
 509            state.emit_event([dot_git]);
 510        } else {
 511            panic!("not a directory");
 512        }
 513    }
 514
 515    pub async fn files(&self) -> Vec<PathBuf> {
 516        let mut result = Vec::new();
 517        let mut queue = collections::VecDeque::new();
 518        queue.push_back((PathBuf::from("/"), self.state.lock().await.root.clone()));
 519        while let Some((path, entry)) = queue.pop_front() {
 520            let e = entry.lock().await;
 521            match &*e {
 522                FakeFsEntry::File { .. } => result.push(path),
 523                FakeFsEntry::Dir { entries, .. } => {
 524                    for (name, entry) in entries {
 525                        queue.push_back((path.join(name), entry.clone()));
 526                    }
 527                }
 528                FakeFsEntry::Symlink { .. } => {}
 529            }
 530        }
 531        result
 532    }
 533
 534    async fn simulate_random_delay(&self) {
 535        self.executor
 536            .upgrade()
 537            .expect("executor has been dropped")
 538            .simulate_random_delay()
 539            .await;
 540    }
 541}
 542
 543#[cfg(any(test, feature = "test-support"))]
 544impl FakeFsEntry {
 545    fn is_file(&self) -> bool {
 546        matches!(self, Self::File { .. })
 547    }
 548
 549    fn file_content(&self, path: &Path) -> Result<&String> {
 550        if let Self::File { content, .. } = self {
 551            Ok(content)
 552        } else {
 553            Err(anyhow!("not a file: {}", path.display()))
 554        }
 555    }
 556
 557    fn set_file_content(&mut self, path: &Path, new_content: String) -> Result<()> {
 558        if let Self::File { content, mtime, .. } = self {
 559            *mtime = SystemTime::now();
 560            *content = new_content;
 561            Ok(())
 562        } else {
 563            Err(anyhow!("not a file: {}", path.display()))
 564        }
 565    }
 566
 567    fn dir_entries(
 568        &mut self,
 569        path: &Path,
 570    ) -> Result<&mut BTreeMap<String, Arc<Mutex<FakeFsEntry>>>> {
 571        if let Self::Dir { entries, .. } = self {
 572            Ok(entries)
 573        } else {
 574            Err(anyhow!("not a directory: {}", path.display()))
 575        }
 576    }
 577}
 578
 579#[cfg(any(test, feature = "test-support"))]
 580#[async_trait::async_trait]
 581impl Fs for FakeFs {
 582    async fn create_dir(&self, path: &Path) -> Result<()> {
 583        self.simulate_random_delay().await;
 584        let mut state = self.state.lock().await;
 585
 586        let mut created_dirs = Vec::new();
 587        let mut cur_path = PathBuf::new();
 588        for component in path.components() {
 589            cur_path.push(component);
 590            if cur_path == Path::new("/") {
 591                continue;
 592            }
 593
 594            let inode = state.next_inode;
 595            state.next_inode += 1;
 596            state
 597                .write_path(&cur_path, |entry| {
 598                    entry.or_insert_with(|| {
 599                        created_dirs.push(cur_path.clone());
 600                        Arc::new(Mutex::new(FakeFsEntry::Dir {
 601                            inode,
 602                            mtime: SystemTime::now(),
 603                            entries: Default::default(),
 604                            git_repo_state: None,
 605                        }))
 606                    });
 607                    Ok(())
 608                })
 609                .await?;
 610        }
 611
 612        state.emit_event(&created_dirs);
 613        Ok(())
 614    }
 615
 616    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 617        self.simulate_random_delay().await;
 618        let mut state = self.state.lock().await;
 619        let inode = state.next_inode;
 620        state.next_inode += 1;
 621        let file = Arc::new(Mutex::new(FakeFsEntry::File {
 622            inode,
 623            mtime: SystemTime::now(),
 624            content: String::new(),
 625        }));
 626        state
 627            .write_path(path, |entry| {
 628                match entry {
 629                    btree_map::Entry::Occupied(mut e) => {
 630                        if options.overwrite {
 631                            *e.get_mut() = file;
 632                        } else if !options.ignore_if_exists {
 633                            return Err(anyhow!("path already exists: {}", path.display()));
 634                        }
 635                    }
 636                    btree_map::Entry::Vacant(e) => {
 637                        e.insert(file);
 638                    }
 639                }
 640                Ok(())
 641            })
 642            .await?;
 643        state.emit_event(&[path]);
 644        Ok(())
 645    }
 646
 647    async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
 648        let old_path = normalize_path(old_path);
 649        let new_path = normalize_path(new_path);
 650        let mut state = self.state.lock().await;
 651        let moved_entry = state
 652            .write_path(&old_path, |e| {
 653                if let btree_map::Entry::Occupied(e) = e {
 654                    Ok(e.remove())
 655                } else {
 656                    Err(anyhow!("path does not exist: {}", &old_path.display()))
 657                }
 658            })
 659            .await?;
 660        state
 661            .write_path(&new_path, |e| {
 662                match e {
 663                    btree_map::Entry::Occupied(mut e) => {
 664                        if options.overwrite {
 665                            *e.get_mut() = moved_entry;
 666                        } else if !options.ignore_if_exists {
 667                            return Err(anyhow!("path already exists: {}", new_path.display()));
 668                        }
 669                    }
 670                    btree_map::Entry::Vacant(e) => {
 671                        e.insert(moved_entry);
 672                    }
 673                }
 674                Ok(())
 675            })
 676            .await?;
 677        state.emit_event(&[old_path, new_path]);
 678        Ok(())
 679    }
 680
 681    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 682        let source = normalize_path(source);
 683        let target = normalize_path(target);
 684        let mut state = self.state.lock().await;
 685        let source_entry = state.read_path(&source).await?;
 686        let content = source_entry.lock().await.file_content(&source)?.clone();
 687        let entry = state
 688            .write_path(&target, |e| match e {
 689                btree_map::Entry::Occupied(e) => {
 690                    if options.overwrite {
 691                        Ok(Some(e.get().clone()))
 692                    } else if !options.ignore_if_exists {
 693                        return Err(anyhow!("{target:?} already exists"));
 694                    } else {
 695                        Ok(None)
 696                    }
 697                }
 698                btree_map::Entry::Vacant(e) => Ok(Some(
 699                    e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
 700                        inode: 0,
 701                        mtime: SystemTime::now(),
 702                        content: String::new(),
 703                    })))
 704                    .clone(),
 705                )),
 706            })
 707            .await?;
 708        if let Some(entry) = entry {
 709            entry.lock().await.set_file_content(&target, content)?;
 710        }
 711        state.emit_event(&[target]);
 712        Ok(())
 713    }
 714
 715    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 716        let path = normalize_path(path);
 717        let parent_path = path
 718            .parent()
 719            .ok_or_else(|| anyhow!("cannot remove the root"))?;
 720        let base_name = path.file_name().unwrap();
 721
 722        let state = self.state.lock().await;
 723        let parent_entry = state.read_path(parent_path).await?;
 724        let mut parent_entry = parent_entry.lock().await;
 725        let entry = parent_entry
 726            .dir_entries(parent_path)?
 727            .entry(base_name.to_str().unwrap().into());
 728
 729        match entry {
 730            btree_map::Entry::Vacant(_) => {
 731                if !options.ignore_if_not_exists {
 732                    return Err(anyhow!("{path:?} does not exist"));
 733                }
 734            }
 735            btree_map::Entry::Occupied(e) => {
 736                {
 737                    let mut entry = e.get().lock().await;
 738                    let children = entry.dir_entries(&path)?;
 739                    if !options.recursive && !children.is_empty() {
 740                        return Err(anyhow!("{path:?} is not empty"));
 741                    }
 742                }
 743                e.remove();
 744            }
 745        }
 746
 747        Ok(())
 748    }
 749
 750    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 751        let path = normalize_path(path);
 752        let parent_path = path
 753            .parent()
 754            .ok_or_else(|| anyhow!("cannot remove the root"))?;
 755        let base_name = path.file_name().unwrap();
 756        let mut state = self.state.lock().await;
 757        let parent_entry = state.read_path(parent_path).await?;
 758        let mut parent_entry = parent_entry.lock().await;
 759        let entry = parent_entry
 760            .dir_entries(parent_path)?
 761            .entry(base_name.to_str().unwrap().into());
 762        match entry {
 763            btree_map::Entry::Vacant(_) => {
 764                if !options.ignore_if_not_exists {
 765                    return Err(anyhow!("{path:?} does not exist"));
 766                }
 767            }
 768            btree_map::Entry::Occupied(e) => {
 769                e.get().lock().await.file_content(&path)?;
 770                e.remove();
 771            }
 772        }
 773        state.emit_event(&[path]);
 774        Ok(())
 775    }
 776
 777    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
 778        let text = self.load(path).await?;
 779        Ok(Box::new(io::Cursor::new(text)))
 780    }
 781
 782    async fn load(&self, path: &Path) -> Result<String> {
 783        let path = normalize_path(path);
 784        self.simulate_random_delay().await;
 785        let state = self.state.lock().await;
 786        let entry = state.read_path(&path).await?;
 787        let entry = entry.lock().await;
 788        entry.file_content(&path).cloned()
 789    }
 790
 791    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
 792        self.simulate_random_delay().await;
 793        let path = normalize_path(path);
 794        let content = chunks(text, line_ending).collect();
 795        self.insert_file(path, content).await;
 796        Ok(())
 797    }
 798
 799    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
 800        let path = normalize_path(path);
 801        self.simulate_random_delay().await;
 802        let state = self.state.lock().await;
 803        if let Some((_, real_path)) = state.try_read_path(&path).await {
 804            Ok(real_path)
 805        } else {
 806            Err(anyhow!("path does not exist: {}", path.display()))
 807        }
 808    }
 809
 810    async fn is_file(&self, path: &Path) -> bool {
 811        let path = normalize_path(path);
 812        self.simulate_random_delay().await;
 813        let state = self.state.lock().await;
 814        if let Some((entry, _)) = state.try_read_path(&path).await {
 815            entry.lock().await.is_file()
 816        } else {
 817            false
 818        }
 819    }
 820
 821    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
 822        self.simulate_random_delay().await;
 823        let path = normalize_path(path);
 824        let state = self.state.lock().await;
 825        if let Some((entry, real_path)) = state.try_read_path(&path).await {
 826            let entry = entry.lock().await;
 827            let is_symlink = real_path != path;
 828
 829            Ok(Some(match &*entry {
 830                FakeFsEntry::File { inode, mtime, .. } => Metadata {
 831                    inode: *inode,
 832                    mtime: *mtime,
 833                    is_dir: false,
 834                    is_symlink,
 835                },
 836                FakeFsEntry::Dir { inode, mtime, .. } => Metadata {
 837                    inode: *inode,
 838                    mtime: *mtime,
 839                    is_dir: true,
 840                    is_symlink,
 841                },
 842                FakeFsEntry::Symlink { .. } => unreachable!(),
 843            }))
 844        } else {
 845            Ok(None)
 846        }
 847    }
 848
 849    async fn read_dir(
 850        &self,
 851        path: &Path,
 852    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
 853        self.simulate_random_delay().await;
 854        let path = normalize_path(path);
 855        let state = self.state.lock().await;
 856        let entry = state.read_path(&path).await?;
 857        let mut entry = entry.lock().await;
 858        let children = entry.dir_entries(&path)?;
 859        let paths = children
 860            .keys()
 861            .map(|file_name| Ok(path.join(file_name)))
 862            .collect::<Vec<_>>();
 863        Ok(Box::pin(futures::stream::iter(paths)))
 864    }
 865
 866    async fn watch(
 867        &self,
 868        path: &Path,
 869        _: Duration,
 870    ) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
 871        let mut state = self.state.lock().await;
 872        self.simulate_random_delay().await;
 873        let (tx, rx) = smol::channel::unbounded();
 874        state.event_txs.push(tx);
 875        let path = path.to_path_buf();
 876        let executor = self.executor.clone();
 877        Box::pin(futures::StreamExt::filter(rx, move |events| {
 878            let result = events.iter().any(|event| event.path.starts_with(&path));
 879            let executor = executor.clone();
 880            async move {
 881                if let Some(executor) = executor.clone().upgrade() {
 882                    executor.simulate_random_delay().await;
 883                }
 884                result
 885            }
 886        }))
 887    }
 888
 889    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<SyncMutex<dyn GitRepository>>> {
 890        smol::block_on(async move {
 891            let state = self.state.lock().await;
 892            let entry = state.read_path(abs_dot_git).await.unwrap();
 893            let mut entry = entry.lock().await;
 894            if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
 895                let state = git_repo_state
 896                    .get_or_insert_with(|| {
 897                        Arc::new(SyncMutex::new(FakeGitRepositoryState::default()))
 898                    })
 899                    .clone();
 900                Some(git::repository::FakeGitRepository::open(state))
 901            } else {
 902                None
 903            }
 904        })
 905    }
 906
 907    fn is_fake(&self) -> bool {
 908        true
 909    }
 910
 911    #[cfg(any(test, feature = "test-support"))]
 912    fn as_fake(&self) -> &FakeFs {
 913        self
 914    }
 915}
 916
 917fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
 918    rope.chunks().flat_map(move |chunk| {
 919        let mut newline = false;
 920        chunk.split('\n').flat_map(move |line| {
 921            let ending = if newline {
 922                Some(line_ending.as_str())
 923            } else {
 924                None
 925            };
 926            newline = true;
 927            ending.into_iter().chain([line])
 928        })
 929    })
 930}
 931
 932pub fn normalize_path(path: &Path) -> PathBuf {
 933    let mut components = path.components().peekable();
 934    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
 935        components.next();
 936        PathBuf::from(c.as_os_str())
 937    } else {
 938        PathBuf::new()
 939    };
 940
 941    for component in components {
 942        match component {
 943            Component::Prefix(..) => unreachable!(),
 944            Component::RootDir => {
 945                ret.push(component.as_os_str());
 946            }
 947            Component::CurDir => {}
 948            Component::ParentDir => {
 949                ret.pop();
 950            }
 951            Component::Normal(c) => {
 952                ret.push(c);
 953            }
 954        }
 955    }
 956    ret
 957}
 958
 959pub fn copy_recursive<'a>(
 960    fs: &'a dyn Fs,
 961    source: &'a Path,
 962    target: &'a Path,
 963    options: CopyOptions,
 964) -> BoxFuture<'a, Result<()>> {
 965    use futures::future::FutureExt;
 966
 967    async move {
 968        let metadata = fs
 969            .metadata(source)
 970            .await?
 971            .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
 972        if metadata.is_dir {
 973            if !options.overwrite && fs.metadata(target).await.is_ok() {
 974                if options.ignore_if_exists {
 975                    return Ok(());
 976                } else {
 977                    return Err(anyhow!("{target:?} already exists"));
 978                }
 979            }
 980
 981            let _ = fs
 982                .remove_dir(
 983                    target,
 984                    RemoveOptions {
 985                        recursive: true,
 986                        ignore_if_not_exists: true,
 987                    },
 988                )
 989                .await;
 990            fs.create_dir(target).await?;
 991            let mut children = fs.read_dir(source).await?;
 992            while let Some(child_path) = children.next().await {
 993                if let Ok(child_path) = child_path {
 994                    if let Some(file_name) = child_path.file_name() {
 995                        let child_target_path = target.join(file_name);
 996                        copy_recursive(fs, &child_path, &child_target_path, options).await?;
 997                    }
 998                }
 999            }
1000
1001            Ok(())
1002        } else {
1003            fs.copy_file(source, target, options).await
1004        }
1005    }
1006    .boxed()
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011    use super::*;
1012    use gpui::TestAppContext;
1013    use serde_json::json;
1014
1015    #[gpui::test]
1016    async fn test_fake_fs(cx: &mut TestAppContext) {
1017        let fs = FakeFs::new(cx.background());
1018
1019        fs.insert_tree(
1020            "/root",
1021            json!({
1022                "dir1": {
1023                    "a": "A",
1024                    "b": "B"
1025                },
1026                "dir2": {
1027                    "c": "C",
1028                    "dir3": {
1029                        "d": "D"
1030                    }
1031                }
1032            }),
1033        )
1034        .await;
1035
1036        assert_eq!(
1037            fs.files().await,
1038            vec![
1039                PathBuf::from("/root/dir1/a"),
1040                PathBuf::from("/root/dir1/b"),
1041                PathBuf::from("/root/dir2/c"),
1042                PathBuf::from("/root/dir2/dir3/d"),
1043            ]
1044        );
1045
1046        fs.insert_symlink("/root/dir2/link-to-dir3", "./dir3".into())
1047            .await;
1048
1049        assert_eq!(
1050            fs.canonicalize("/root/dir2/link-to-dir3".as_ref())
1051                .await
1052                .unwrap(),
1053            PathBuf::from("/root/dir2/dir3"),
1054        );
1055        assert_eq!(
1056            fs.canonicalize("/root/dir2/link-to-dir3/d".as_ref())
1057                .await
1058                .unwrap(),
1059            PathBuf::from("/root/dir2/dir3/d"),
1060        );
1061        assert_eq!(
1062            fs.load("/root/dir2/link-to-dir3/d".as_ref()).await.unwrap(),
1063            "D",
1064        );
1065    }
1066}