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