fs.rs

   1use anyhow::{anyhow, Result};
   2use git::GitHostingProviderRegistry;
   3
   4#[cfg(target_os = "linux")]
   5use ashpd::desktop::trash;
   6#[cfg(target_os = "linux")]
   7use std::{fs::File, os::fd::AsFd};
   8
   9#[cfg(unix)]
  10use std::os::unix::fs::MetadataExt;
  11
  12use async_tar::Archive;
  13use futures::{future::BoxFuture, AsyncRead, Stream, StreamExt};
  14use git::repository::{GitRepository, RealGitRepository};
  15use git2::Repository as LibGitRepository;
  16use parking_lot::Mutex;
  17use rope::Rope;
  18
  19#[cfg(any(test, feature = "test-support"))]
  20use smol::io::AsyncReadExt;
  21use smol::io::AsyncWriteExt;
  22use std::io::Write;
  23use std::sync::Arc;
  24use std::{
  25    io,
  26    path::{Component, Path, PathBuf},
  27    pin::Pin,
  28    time::{Duration, SystemTime},
  29};
  30use tempfile::{NamedTempFile, TempDir};
  31use text::LineEnding;
  32use util::{paths, ResultExt};
  33
  34#[cfg(any(test, feature = "test-support"))]
  35use collections::{btree_map, BTreeMap};
  36#[cfg(any(test, feature = "test-support"))]
  37use git::repository::{FakeGitRepositoryState, GitFileStatus};
  38#[cfg(any(test, feature = "test-support"))]
  39use std::ffi::OsStr;
  40
  41#[async_trait::async_trait]
  42pub trait Fs: Send + Sync {
  43    async fn create_dir(&self, path: &Path) -> Result<()>;
  44    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()>;
  45    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
  46    async fn create_file_with(
  47        &self,
  48        path: &Path,
  49        content: Pin<&mut (dyn AsyncRead + Send)>,
  50    ) -> Result<()>;
  51    async fn extract_tar_file(
  52        &self,
  53        path: &Path,
  54        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
  55    ) -> Result<()>;
  56    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
  57    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
  58    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
  59    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
  60        self.remove_dir(path, options).await
  61    }
  62    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
  63    async fn trash_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
  64        self.remove_file(path, options).await
  65    }
  66    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
  67    async fn load(&self, path: &Path) -> Result<String>;
  68    async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
  69    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
  70    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
  71    async fn is_file(&self, path: &Path) -> bool;
  72    async fn is_dir(&self, path: &Path) -> bool;
  73    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
  74    async fn read_link(&self, path: &Path) -> Result<PathBuf>;
  75    async fn read_dir(
  76        &self,
  77        path: &Path,
  78    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
  79
  80    async fn watch(
  81        &self,
  82        path: &Path,
  83        latency: Duration,
  84    ) -> Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>;
  85
  86    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<Mutex<dyn GitRepository>>>;
  87    fn is_fake(&self) -> bool;
  88    async fn is_case_sensitive(&self) -> Result<bool>;
  89    #[cfg(any(test, feature = "test-support"))]
  90    fn as_fake(&self) -> &FakeFs;
  91}
  92
  93#[derive(Copy, Clone, Default)]
  94pub struct CreateOptions {
  95    pub overwrite: bool,
  96    pub ignore_if_exists: bool,
  97}
  98
  99#[derive(Copy, Clone, Default)]
 100pub struct CopyOptions {
 101    pub overwrite: bool,
 102    pub ignore_if_exists: bool,
 103}
 104
 105#[derive(Copy, Clone, Default)]
 106pub struct RenameOptions {
 107    pub overwrite: bool,
 108    pub ignore_if_exists: bool,
 109}
 110
 111#[derive(Copy, Clone, Default)]
 112pub struct RemoveOptions {
 113    pub recursive: bool,
 114    pub ignore_if_not_exists: bool,
 115}
 116
 117#[derive(Copy, Clone, Debug)]
 118pub struct Metadata {
 119    pub inode: u64,
 120    pub mtime: SystemTime,
 121    pub is_symlink: bool,
 122    pub is_dir: bool,
 123}
 124
 125#[derive(Default)]
 126pub struct RealFs {
 127    git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
 128    git_binary_path: Option<PathBuf>,
 129}
 130
 131impl RealFs {
 132    pub fn new(
 133        git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
 134        git_binary_path: Option<PathBuf>,
 135    ) -> Self {
 136        Self {
 137            git_hosting_provider_registry,
 138            git_binary_path,
 139        }
 140    }
 141}
 142
 143#[async_trait::async_trait]
 144impl Fs for RealFs {
 145    async fn create_dir(&self, path: &Path) -> Result<()> {
 146        Ok(smol::fs::create_dir_all(path).await?)
 147    }
 148
 149    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()> {
 150        #[cfg(unix)]
 151        smol::fs::unix::symlink(target, path).await?;
 152
 153        #[cfg(windows)]
 154        if smol::fs::metadata(&target).await?.is_dir() {
 155            smol::fs::windows::symlink_dir(target, path).await?
 156        } else {
 157            smol::fs::windows::symlink_file(target, path).await?
 158        }
 159
 160        Ok(())
 161    }
 162
 163    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 164        let mut open_options = smol::fs::OpenOptions::new();
 165        open_options.write(true).create(true);
 166        if options.overwrite {
 167            open_options.truncate(true);
 168        } else if !options.ignore_if_exists {
 169            open_options.create_new(true);
 170        }
 171        open_options.open(path).await?;
 172        Ok(())
 173    }
 174
 175    async fn create_file_with(
 176        &self,
 177        path: &Path,
 178        content: Pin<&mut (dyn AsyncRead + Send)>,
 179    ) -> Result<()> {
 180        let mut file = smol::fs::File::create(&path).await?;
 181        futures::io::copy(content, &mut file).await?;
 182        Ok(())
 183    }
 184
 185    async fn extract_tar_file(
 186        &self,
 187        path: &Path,
 188        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
 189    ) -> Result<()> {
 190        content.unpack(path).await?;
 191        Ok(())
 192    }
 193
 194    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 195        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 196            if options.ignore_if_exists {
 197                return Ok(());
 198            } else {
 199                return Err(anyhow!("{target:?} already exists"));
 200            }
 201        }
 202
 203        smol::fs::copy(source, target).await?;
 204        Ok(())
 205    }
 206
 207    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
 208        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 209            if options.ignore_if_exists {
 210                return Ok(());
 211            } else {
 212                return Err(anyhow!("{target:?} already exists"));
 213            }
 214        }
 215
 216        smol::fs::rename(source, target).await?;
 217        Ok(())
 218    }
 219
 220    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 221        let result = if options.recursive {
 222            smol::fs::remove_dir_all(path).await
 223        } else {
 224            smol::fs::remove_dir(path).await
 225        };
 226        match result {
 227            Ok(()) => Ok(()),
 228            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 229                Ok(())
 230            }
 231            Err(err) => Err(err)?,
 232        }
 233    }
 234
 235    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 236        #[cfg(windows)]
 237        if let Ok(Some(metadata)) = self.metadata(path).await {
 238            if metadata.is_symlink && metadata.is_dir {
 239                self.remove_dir(
 240                    path,
 241                    RemoveOptions {
 242                        recursive: false,
 243                        ignore_if_not_exists: true,
 244                    },
 245                )
 246                .await?;
 247                return Ok(());
 248            }
 249        }
 250
 251        match smol::fs::remove_file(path).await {
 252            Ok(()) => Ok(()),
 253            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 254                Ok(())
 255            }
 256            Err(err) => Err(err)?,
 257        }
 258    }
 259
 260    #[cfg(target_os = "macos")]
 261    async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
 262        use cocoa::{
 263            base::{id, nil},
 264            foundation::{NSAutoreleasePool, NSString},
 265        };
 266        use objc::{class, msg_send, sel, sel_impl};
 267
 268        unsafe {
 269            unsafe fn ns_string(string: &str) -> id {
 270                NSString::alloc(nil).init_str(string).autorelease()
 271            }
 272
 273            let url: id = msg_send![class!(NSURL), fileURLWithPath: ns_string(path.to_string_lossy().as_ref())];
 274            let array: id = msg_send![class!(NSArray), arrayWithObject: url];
 275            let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
 276
 277            let _: id = msg_send![workspace, recycleURLs: array completionHandler: nil];
 278        }
 279        Ok(())
 280    }
 281
 282    #[cfg(target_os = "linux")]
 283    async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
 284        let file = File::open(path)?;
 285        match trash::trash_file(&file.as_fd()).await {
 286            Ok(_) => Ok(()),
 287            Err(err) => Err(anyhow::Error::new(err)),
 288        }
 289    }
 290
 291    #[cfg(target_os = "macos")]
 292    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 293        self.trash_file(path, options).await
 294    }
 295
 296    #[cfg(target_os = "linux")]
 297    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 298        self.trash_file(path, options).await
 299    }
 300
 301    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
 302        Ok(Box::new(std::fs::File::open(path)?))
 303    }
 304
 305    async fn load(&self, path: &Path) -> Result<String> {
 306        let path = path.to_path_buf();
 307        let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
 308        Ok(text)
 309    }
 310
 311    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
 312        smol::unblock(move || {
 313            let mut tmp_file = if cfg!(target_os = "linux") {
 314                // Use the directory of the destination as temp dir to avoid
 315                // invalid cross-device link error, and XDG_CACHE_DIR for fallback.
 316                // See https://github.com/zed-industries/zed/pull/8437 for more details.
 317                NamedTempFile::new_in(path.parent().unwrap_or(&paths::TEMP_DIR))
 318            } else {
 319                NamedTempFile::new()
 320            }?;
 321            tmp_file.write_all(data.as_bytes())?;
 322            tmp_file.persist(path)?;
 323            Ok::<(), anyhow::Error>(())
 324        })
 325        .await?;
 326
 327        Ok(())
 328    }
 329
 330    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
 331        let buffer_size = text.summary().len.min(10 * 1024);
 332        if let Some(path) = path.parent() {
 333            self.create_dir(path).await?;
 334        }
 335        let file = smol::fs::File::create(path).await?;
 336        let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
 337        for chunk in chunks(text, line_ending) {
 338            writer.write_all(chunk.as_bytes()).await?;
 339        }
 340        writer.flush().await?;
 341        Ok(())
 342    }
 343
 344    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
 345        Ok(smol::fs::canonicalize(path).await?)
 346    }
 347
 348    async fn is_file(&self, path: &Path) -> bool {
 349        smol::fs::metadata(path)
 350            .await
 351            .map_or(false, |metadata| metadata.is_file())
 352    }
 353
 354    async fn is_dir(&self, path: &Path) -> bool {
 355        smol::fs::metadata(path)
 356            .await
 357            .map_or(false, |metadata| metadata.is_dir())
 358    }
 359
 360    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
 361        let symlink_metadata = match smol::fs::symlink_metadata(path).await {
 362            Ok(metadata) => metadata,
 363            Err(err) => {
 364                return match (err.kind(), err.raw_os_error()) {
 365                    (io::ErrorKind::NotFound, _) => Ok(None),
 366                    (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
 367                    _ => Err(anyhow::Error::new(err)),
 368                }
 369            }
 370        };
 371
 372        let is_symlink = symlink_metadata.file_type().is_symlink();
 373        let metadata = if is_symlink {
 374            smol::fs::metadata(path).await?
 375        } else {
 376            symlink_metadata
 377        };
 378
 379        #[cfg(unix)]
 380        let inode = metadata.ino();
 381
 382        #[cfg(windows)]
 383        let inode = file_id(path).await?;
 384
 385        Ok(Some(Metadata {
 386            inode,
 387            mtime: metadata.modified().unwrap(),
 388            is_symlink,
 389            is_dir: metadata.file_type().is_dir(),
 390        }))
 391    }
 392
 393    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
 394        let path = smol::fs::read_link(path).await?;
 395        Ok(path)
 396    }
 397
 398    async fn read_dir(
 399        &self,
 400        path: &Path,
 401    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
 402        let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
 403            Ok(entry) => Ok(entry.path()),
 404            Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
 405        });
 406        Ok(Box::pin(result))
 407    }
 408
 409    #[cfg(target_os = "macos")]
 410    async fn watch(
 411        &self,
 412        path: &Path,
 413        latency: Duration,
 414    ) -> Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>> {
 415        use fsevent::EventStream;
 416
 417        let (tx, rx) = smol::channel::unbounded();
 418        let (stream, handle) = EventStream::new(&[path], latency);
 419        std::thread::spawn(move || {
 420            stream.run(move |events| {
 421                smol::block_on(tx.send(events.into_iter().map(|event| event.path).collect()))
 422                    .is_ok()
 423            });
 424        });
 425
 426        Box::pin(rx.chain(futures::stream::once(async move {
 427            drop(handle);
 428            vec![]
 429        })))
 430    }
 431
 432    #[cfg(not(target_os = "macos"))]
 433    async fn watch(
 434        &self,
 435        path: &Path,
 436        _latency: Duration,
 437    ) -> Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>> {
 438        use notify::{event::EventKind, event::ModifyKind, Watcher};
 439        // todo(linux): This spawns two threads, while the macOS impl
 440        // only spawns one. Can we use a OnceLock or some such to make
 441        // this better
 442
 443        let (tx, rx) = smol::channel::unbounded();
 444
 445        let mut file_watcher = notify::recommended_watcher({
 446            let tx = tx.clone();
 447            move |event: Result<notify::Event, _>| {
 448                if let Some(event) = event.log_err() {
 449                    tx.try_send(event.paths).ok();
 450                }
 451            }
 452        })
 453        .expect("Could not start file watcher");
 454
 455        file_watcher
 456            .watch(path, notify::RecursiveMode::Recursive)
 457            .ok(); // It's ok if this fails, the parent watcher will add it.
 458
 459        let mut parent_watcher = notify::recommended_watcher({
 460            let watched_path = path.to_path_buf();
 461            let tx = tx.clone();
 462            move |event: Result<notify::Event, _>| {
 463                if let Some(event) = event.ok() {
 464                    if event.paths.into_iter().any(|path| *path == watched_path) {
 465                        match event.kind {
 466                            EventKind::Modify(ev) => {
 467                                if matches!(ev, ModifyKind::Name(_)) {
 468                                    file_watcher
 469                                        .watch(
 470                                            watched_path.as_path(),
 471                                            notify::RecursiveMode::Recursive,
 472                                        )
 473                                        .log_err();
 474                                    let _ = tx.try_send(vec![watched_path.clone()]).ok();
 475                                }
 476                            }
 477                            EventKind::Create(_) => {
 478                                file_watcher
 479                                    .watch(watched_path.as_path(), notify::RecursiveMode::Recursive)
 480                                    .log_err();
 481                                let _ = tx.try_send(vec![watched_path.clone()]).ok();
 482                            }
 483                            EventKind::Remove(_) => {
 484                                file_watcher.unwatch(&watched_path).log_err();
 485                                let _ = tx.try_send(vec![watched_path.clone()]).ok();
 486                            }
 487                            _ => {}
 488                        }
 489                    }
 490                }
 491            }
 492        })
 493        .expect("Could not start file watcher");
 494
 495        parent_watcher
 496            .watch(
 497                path.parent()
 498                    .expect("Watching root is probably not what you want"),
 499                notify::RecursiveMode::NonRecursive,
 500            )
 501            .expect("Could not start watcher on parent directory");
 502
 503        Box::pin(rx.chain(futures::stream::once(async move {
 504            drop(parent_watcher);
 505            vec![]
 506        })))
 507    }
 508
 509    fn open_repo(&self, dotgit_path: &Path) -> Option<Arc<Mutex<dyn GitRepository>>> {
 510        LibGitRepository::open(dotgit_path)
 511            .log_err()
 512            .map::<Arc<Mutex<dyn GitRepository>>, _>(|libgit_repository| {
 513                Arc::new(Mutex::new(RealGitRepository::new(
 514                    libgit_repository,
 515                    self.git_binary_path.clone(),
 516                    self.git_hosting_provider_registry.clone(),
 517                )))
 518            })
 519    }
 520
 521    fn is_fake(&self) -> bool {
 522        false
 523    }
 524
 525    /// Checks whether the file system is case sensitive by attempting to create two files
 526    /// that have the same name except for the casing.
 527    ///
 528    /// It creates both files in a temporary directory it removes at the end.
 529    async fn is_case_sensitive(&self) -> Result<bool> {
 530        let temp_dir = TempDir::new()?;
 531        let test_file_1 = temp_dir.path().join("case_sensitivity_test.tmp");
 532        let test_file_2 = temp_dir.path().join("CASE_SENSITIVITY_TEST.TMP");
 533
 534        let create_opts = CreateOptions {
 535            overwrite: false,
 536            ignore_if_exists: false,
 537        };
 538
 539        // Create file1
 540        self.create_file(&test_file_1, create_opts).await?;
 541
 542        // Now check whether it's possible to create file2
 543        let case_sensitive = match self.create_file(&test_file_2, create_opts).await {
 544            Ok(_) => Ok(true),
 545            Err(e) => {
 546                if let Some(io_error) = e.downcast_ref::<io::Error>() {
 547                    if io_error.kind() == io::ErrorKind::AlreadyExists {
 548                        Ok(false)
 549                    } else {
 550                        Err(e)
 551                    }
 552                } else {
 553                    Err(e)
 554                }
 555            }
 556        };
 557
 558        temp_dir.close()?;
 559        case_sensitive
 560    }
 561
 562    #[cfg(any(test, feature = "test-support"))]
 563    fn as_fake(&self) -> &FakeFs {
 564        panic!("called `RealFs::as_fake`")
 565    }
 566}
 567
 568#[cfg(any(test, feature = "test-support"))]
 569pub struct FakeFs {
 570    // Use an unfair lock to ensure tests are deterministic.
 571    state: Mutex<FakeFsState>,
 572    executor: gpui::BackgroundExecutor,
 573}
 574
 575#[cfg(any(test, feature = "test-support"))]
 576struct FakeFsState {
 577    root: Arc<Mutex<FakeFsEntry>>,
 578    next_inode: u64,
 579    next_mtime: SystemTime,
 580    event_txs: Vec<smol::channel::Sender<Vec<PathBuf>>>,
 581    events_paused: bool,
 582    buffered_events: Vec<PathBuf>,
 583    metadata_call_count: usize,
 584    read_dir_call_count: usize,
 585}
 586
 587#[cfg(any(test, feature = "test-support"))]
 588#[derive(Debug)]
 589enum FakeFsEntry {
 590    File {
 591        inode: u64,
 592        mtime: SystemTime,
 593        content: Vec<u8>,
 594    },
 595    Dir {
 596        inode: u64,
 597        mtime: SystemTime,
 598        entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
 599        git_repo_state: Option<Arc<Mutex<git::repository::FakeGitRepositoryState>>>,
 600    },
 601    Symlink {
 602        target: PathBuf,
 603    },
 604}
 605
 606#[cfg(any(test, feature = "test-support"))]
 607impl FakeFsState {
 608    fn read_path(&self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
 609        Ok(self
 610            .try_read_path(target, true)
 611            .ok_or_else(|| {
 612                anyhow!(io::Error::new(
 613                    io::ErrorKind::NotFound,
 614                    format!("not found: {}", target.display())
 615                ))
 616            })?
 617            .0)
 618    }
 619
 620    fn try_read_path(
 621        &self,
 622        target: &Path,
 623        follow_symlink: bool,
 624    ) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
 625        let mut path = target.to_path_buf();
 626        let mut canonical_path = PathBuf::new();
 627        let mut entry_stack = Vec::new();
 628        'outer: loop {
 629            let mut path_components = path.components().peekable();
 630            while let Some(component) = path_components.next() {
 631                match component {
 632                    Component::Prefix(_) => panic!("prefix paths aren't supported"),
 633                    Component::RootDir => {
 634                        entry_stack.clear();
 635                        entry_stack.push(self.root.clone());
 636                        canonical_path.clear();
 637                        canonical_path.push("/");
 638                    }
 639                    Component::CurDir => {}
 640                    Component::ParentDir => {
 641                        entry_stack.pop()?;
 642                        canonical_path.pop();
 643                    }
 644                    Component::Normal(name) => {
 645                        let current_entry = entry_stack.last().cloned()?;
 646                        let current_entry = current_entry.lock();
 647                        if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
 648                            let entry = entries.get(name.to_str().unwrap()).cloned()?;
 649                            if path_components.peek().is_some() || follow_symlink {
 650                                let entry = entry.lock();
 651                                if let FakeFsEntry::Symlink { target, .. } = &*entry {
 652                                    let mut target = target.clone();
 653                                    target.extend(path_components);
 654                                    path = target;
 655                                    continue 'outer;
 656                                }
 657                            }
 658                            entry_stack.push(entry.clone());
 659                            canonical_path.push(name);
 660                        } else {
 661                            return None;
 662                        }
 663                    }
 664                }
 665            }
 666            break;
 667        }
 668        Some((entry_stack.pop()?, canonical_path))
 669    }
 670
 671    fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
 672    where
 673        Fn: FnOnce(btree_map::Entry<String, Arc<Mutex<FakeFsEntry>>>) -> Result<T>,
 674    {
 675        let path = normalize_path(path);
 676        let filename = path
 677            .file_name()
 678            .ok_or_else(|| anyhow!("cannot overwrite the root"))?;
 679        let parent_path = path.parent().unwrap();
 680
 681        let parent = self.read_path(parent_path)?;
 682        let mut parent = parent.lock();
 683        let new_entry = parent
 684            .dir_entries(parent_path)?
 685            .entry(filename.to_str().unwrap().into());
 686        callback(new_entry)
 687    }
 688
 689    fn emit_event<I, T>(&mut self, paths: I)
 690    where
 691        I: IntoIterator<Item = T>,
 692        T: Into<PathBuf>,
 693    {
 694        self.buffered_events
 695            .extend(paths.into_iter().map(Into::into));
 696
 697        if !self.events_paused {
 698            self.flush_events(self.buffered_events.len());
 699        }
 700    }
 701
 702    fn flush_events(&mut self, mut count: usize) {
 703        count = count.min(self.buffered_events.len());
 704        let events = self.buffered_events.drain(0..count).collect::<Vec<_>>();
 705        self.event_txs.retain(|tx| {
 706            let _ = tx.try_send(events.clone());
 707            !tx.is_closed()
 708        });
 709    }
 710}
 711
 712#[cfg(any(test, feature = "test-support"))]
 713lazy_static::lazy_static! {
 714    pub static ref FS_DOT_GIT: &'static OsStr = OsStr::new(".git");
 715}
 716
 717#[cfg(any(test, feature = "test-support"))]
 718impl FakeFs {
 719    pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
 720        Arc::new(Self {
 721            executor,
 722            state: Mutex::new(FakeFsState {
 723                root: Arc::new(Mutex::new(FakeFsEntry::Dir {
 724                    inode: 0,
 725                    mtime: SystemTime::UNIX_EPOCH,
 726                    entries: Default::default(),
 727                    git_repo_state: None,
 728                })),
 729                next_mtime: SystemTime::UNIX_EPOCH,
 730                next_inode: 1,
 731                event_txs: Default::default(),
 732                buffered_events: Vec::new(),
 733                events_paused: false,
 734                read_dir_call_count: 0,
 735                metadata_call_count: 0,
 736            }),
 737        })
 738    }
 739
 740    pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
 741        self.write_file_internal(path, content).unwrap()
 742    }
 743
 744    pub async fn insert_symlink(&self, path: impl AsRef<Path>, target: PathBuf) {
 745        let mut state = self.state.lock();
 746        let path = path.as_ref();
 747        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
 748        state
 749            .write_path(path.as_ref(), move |e| match e {
 750                btree_map::Entry::Vacant(e) => {
 751                    e.insert(file);
 752                    Ok(())
 753                }
 754                btree_map::Entry::Occupied(mut e) => {
 755                    *e.get_mut() = file;
 756                    Ok(())
 757                }
 758            })
 759            .unwrap();
 760        state.emit_event([path]);
 761    }
 762
 763    fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
 764        let mut state = self.state.lock();
 765        let path = path.as_ref();
 766        let inode = state.next_inode;
 767        let mtime = state.next_mtime;
 768        state.next_inode += 1;
 769        state.next_mtime += Duration::from_nanos(1);
 770        let file = Arc::new(Mutex::new(FakeFsEntry::File {
 771            inode,
 772            mtime,
 773            content,
 774        }));
 775        state.write_path(path, move |entry| {
 776            match entry {
 777                btree_map::Entry::Vacant(e) => {
 778                    e.insert(file);
 779                }
 780                btree_map::Entry::Occupied(mut e) => {
 781                    *e.get_mut() = file;
 782                }
 783            }
 784            Ok(())
 785        })?;
 786        state.emit_event([path]);
 787        Ok(())
 788    }
 789
 790    pub fn read_file_sync(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
 791        let path = path.as_ref();
 792        let path = normalize_path(path);
 793        let state = self.state.lock();
 794        let entry = state.read_path(&path)?;
 795        let entry = entry.lock();
 796        entry.file_content(&path).cloned()
 797    }
 798
 799    async fn load_internal(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
 800        let path = path.as_ref();
 801        let path = normalize_path(path);
 802        self.simulate_random_delay().await;
 803        let state = self.state.lock();
 804        let entry = state.read_path(&path)?;
 805        let entry = entry.lock();
 806        entry.file_content(&path).cloned()
 807    }
 808
 809    pub fn pause_events(&self) {
 810        self.state.lock().events_paused = true;
 811    }
 812
 813    pub fn buffered_event_count(&self) -> usize {
 814        self.state.lock().buffered_events.len()
 815    }
 816
 817    pub fn flush_events(&self, count: usize) {
 818        self.state.lock().flush_events(count);
 819    }
 820
 821    #[must_use]
 822    pub fn insert_tree<'a>(
 823        &'a self,
 824        path: impl 'a + AsRef<Path> + Send,
 825        tree: serde_json::Value,
 826    ) -> futures::future::BoxFuture<'a, ()> {
 827        use futures::FutureExt as _;
 828        use serde_json::Value::*;
 829
 830        async move {
 831            let path = path.as_ref();
 832
 833            match tree {
 834                Object(map) => {
 835                    self.create_dir(path).await.unwrap();
 836                    for (name, contents) in map {
 837                        let mut path = PathBuf::from(path);
 838                        path.push(name);
 839                        self.insert_tree(&path, contents).await;
 840                    }
 841                }
 842                Null => {
 843                    self.create_dir(path).await.unwrap();
 844                }
 845                String(contents) => {
 846                    self.insert_file(&path, contents.into_bytes()).await;
 847                }
 848                _ => {
 849                    panic!("JSON object must contain only objects, strings, or null");
 850                }
 851            }
 852        }
 853        .boxed()
 854    }
 855
 856    pub fn insert_tree_from_real_fs<'a>(
 857        &'a self,
 858        path: impl 'a + AsRef<Path> + Send,
 859        src_path: impl 'a + AsRef<Path> + Send,
 860    ) -> futures::future::BoxFuture<'a, ()> {
 861        use futures::FutureExt as _;
 862
 863        async move {
 864            let path = path.as_ref();
 865            if std::fs::metadata(&src_path).unwrap().is_file() {
 866                let contents = std::fs::read(src_path).unwrap();
 867                self.insert_file(path, contents).await;
 868            } else {
 869                self.create_dir(path).await.unwrap();
 870                for entry in std::fs::read_dir(&src_path).unwrap() {
 871                    let entry = entry.unwrap();
 872                    self.insert_tree_from_real_fs(&path.join(entry.file_name()), &entry.path())
 873                        .await;
 874                }
 875            }
 876        }
 877        .boxed()
 878    }
 879
 880    pub fn with_git_state<F>(&self, dot_git: &Path, emit_git_event: bool, f: F)
 881    where
 882        F: FnOnce(&mut FakeGitRepositoryState),
 883    {
 884        let mut state = self.state.lock();
 885        let entry = state.read_path(dot_git).unwrap();
 886        let mut entry = entry.lock();
 887
 888        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
 889            let repo_state = git_repo_state.get_or_insert_with(Default::default);
 890            let mut repo_state = repo_state.lock();
 891
 892            f(&mut repo_state);
 893
 894            if emit_git_event {
 895                state.emit_event([dot_git]);
 896            }
 897        } else {
 898            panic!("not a directory");
 899        }
 900    }
 901
 902    pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
 903        self.with_git_state(dot_git, true, |state| {
 904            state.branch_name = branch.map(Into::into)
 905        })
 906    }
 907
 908    pub fn set_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
 909        self.with_git_state(dot_git, true, |state| {
 910            state.index_contents.clear();
 911            state.index_contents.extend(
 912                head_state
 913                    .iter()
 914                    .map(|(path, content)| (path.to_path_buf(), content.clone())),
 915            );
 916        });
 917    }
 918
 919    pub fn set_blame_for_repo(&self, dot_git: &Path, blames: Vec<(&Path, git::blame::Blame)>) {
 920        self.with_git_state(dot_git, true, |state| {
 921            state.blames.clear();
 922            state.blames.extend(
 923                blames
 924                    .into_iter()
 925                    .map(|(path, blame)| (path.to_path_buf(), blame)),
 926            );
 927        });
 928    }
 929
 930    pub fn set_status_for_repo_via_working_copy_change(
 931        &self,
 932        dot_git: &Path,
 933        statuses: &[(&Path, GitFileStatus)],
 934    ) {
 935        self.with_git_state(dot_git, false, |state| {
 936            state.worktree_statuses.clear();
 937            state.worktree_statuses.extend(
 938                statuses
 939                    .iter()
 940                    .map(|(path, content)| ((**path).into(), *content)),
 941            );
 942        });
 943        self.state.lock().emit_event(
 944            statuses
 945                .iter()
 946                .map(|(path, _)| dot_git.parent().unwrap().join(path)),
 947        );
 948    }
 949
 950    pub fn set_status_for_repo_via_git_operation(
 951        &self,
 952        dot_git: &Path,
 953        statuses: &[(&Path, GitFileStatus)],
 954    ) {
 955        self.with_git_state(dot_git, true, |state| {
 956            state.worktree_statuses.clear();
 957            state.worktree_statuses.extend(
 958                statuses
 959                    .iter()
 960                    .map(|(path, content)| ((**path).into(), *content)),
 961            );
 962        });
 963    }
 964
 965    pub fn paths(&self, include_dot_git: bool) -> Vec<PathBuf> {
 966        let mut result = Vec::new();
 967        let mut queue = collections::VecDeque::new();
 968        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
 969        while let Some((path, entry)) = queue.pop_front() {
 970            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
 971                for (name, entry) in entries {
 972                    queue.push_back((path.join(name), entry.clone()));
 973                }
 974            }
 975            if include_dot_git
 976                || !path
 977                    .components()
 978                    .any(|component| component.as_os_str() == *FS_DOT_GIT)
 979            {
 980                result.push(path);
 981            }
 982        }
 983        result
 984    }
 985
 986    pub fn directories(&self, include_dot_git: bool) -> Vec<PathBuf> {
 987        let mut result = Vec::new();
 988        let mut queue = collections::VecDeque::new();
 989        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
 990        while let Some((path, entry)) = queue.pop_front() {
 991            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
 992                for (name, entry) in entries {
 993                    queue.push_back((path.join(name), entry.clone()));
 994                }
 995                if include_dot_git
 996                    || !path
 997                        .components()
 998                        .any(|component| component.as_os_str() == *FS_DOT_GIT)
 999                {
1000                    result.push(path);
1001                }
1002            }
1003        }
1004        result
1005    }
1006
1007    pub fn files(&self) -> Vec<PathBuf> {
1008        let mut result = Vec::new();
1009        let mut queue = collections::VecDeque::new();
1010        queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
1011        while let Some((path, entry)) = queue.pop_front() {
1012            let e = entry.lock();
1013            match &*e {
1014                FakeFsEntry::File { .. } => result.push(path),
1015                FakeFsEntry::Dir { entries, .. } => {
1016                    for (name, entry) in entries {
1017                        queue.push_back((path.join(name), entry.clone()));
1018                    }
1019                }
1020                FakeFsEntry::Symlink { .. } => {}
1021            }
1022        }
1023        result
1024    }
1025
1026    /// How many `read_dir` calls have been issued.
1027    pub fn read_dir_call_count(&self) -> usize {
1028        self.state.lock().read_dir_call_count
1029    }
1030
1031    /// How many `metadata` calls have been issued.
1032    pub fn metadata_call_count(&self) -> usize {
1033        self.state.lock().metadata_call_count
1034    }
1035
1036    fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
1037        self.executor.simulate_random_delay()
1038    }
1039}
1040
1041#[cfg(any(test, feature = "test-support"))]
1042impl FakeFsEntry {
1043    fn is_file(&self) -> bool {
1044        matches!(self, Self::File { .. })
1045    }
1046
1047    fn is_symlink(&self) -> bool {
1048        matches!(self, Self::Symlink { .. })
1049    }
1050
1051    fn file_content(&self, path: &Path) -> Result<&Vec<u8>> {
1052        if let Self::File { content, .. } = self {
1053            Ok(content)
1054        } else {
1055            Err(anyhow!("not a file: {}", path.display()))
1056        }
1057    }
1058
1059    fn set_file_content(&mut self, path: &Path, new_content: Vec<u8>) -> Result<()> {
1060        if let Self::File { content, mtime, .. } = self {
1061            *mtime = SystemTime::now();
1062            *content = new_content;
1063            Ok(())
1064        } else {
1065            Err(anyhow!("not a file: {}", path.display()))
1066        }
1067    }
1068
1069    fn dir_entries(
1070        &mut self,
1071        path: &Path,
1072    ) -> Result<&mut BTreeMap<String, Arc<Mutex<FakeFsEntry>>>> {
1073        if let Self::Dir { entries, .. } = self {
1074            Ok(entries)
1075        } else {
1076            Err(anyhow!("not a directory: {}", path.display()))
1077        }
1078    }
1079}
1080
1081#[cfg(any(test, feature = "test-support"))]
1082#[async_trait::async_trait]
1083impl Fs for FakeFs {
1084    async fn create_dir(&self, path: &Path) -> Result<()> {
1085        self.simulate_random_delay().await;
1086
1087        let mut created_dirs = Vec::new();
1088        let mut cur_path = PathBuf::new();
1089        for component in path.components() {
1090            let mut state = self.state.lock();
1091            cur_path.push(component);
1092            if cur_path == Path::new("/") {
1093                continue;
1094            }
1095
1096            let inode = state.next_inode;
1097            let mtime = state.next_mtime;
1098            state.next_mtime += Duration::from_nanos(1);
1099            state.next_inode += 1;
1100            state.write_path(&cur_path, |entry| {
1101                entry.or_insert_with(|| {
1102                    created_dirs.push(cur_path.clone());
1103                    Arc::new(Mutex::new(FakeFsEntry::Dir {
1104                        inode,
1105                        mtime,
1106                        entries: Default::default(),
1107                        git_repo_state: None,
1108                    }))
1109                });
1110                Ok(())
1111            })?
1112        }
1113
1114        self.state.lock().emit_event(&created_dirs);
1115        Ok(())
1116    }
1117
1118    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
1119        self.simulate_random_delay().await;
1120        let mut state = self.state.lock();
1121        let inode = state.next_inode;
1122        let mtime = state.next_mtime;
1123        state.next_mtime += Duration::from_nanos(1);
1124        state.next_inode += 1;
1125        let file = Arc::new(Mutex::new(FakeFsEntry::File {
1126            inode,
1127            mtime,
1128            content: Vec::new(),
1129        }));
1130        state.write_path(path, |entry| {
1131            match entry {
1132                btree_map::Entry::Occupied(mut e) => {
1133                    if options.overwrite {
1134                        *e.get_mut() = file;
1135                    } else if !options.ignore_if_exists {
1136                        return Err(anyhow!("path already exists: {}", path.display()));
1137                    }
1138                }
1139                btree_map::Entry::Vacant(e) => {
1140                    e.insert(file);
1141                }
1142            }
1143            Ok(())
1144        })?;
1145        state.emit_event([path]);
1146        Ok(())
1147    }
1148
1149    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()> {
1150        let mut state = self.state.lock();
1151        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
1152        state
1153            .write_path(path.as_ref(), move |e| match e {
1154                btree_map::Entry::Vacant(e) => {
1155                    e.insert(file);
1156                    Ok(())
1157                }
1158                btree_map::Entry::Occupied(mut e) => {
1159                    *e.get_mut() = file;
1160                    Ok(())
1161                }
1162            })
1163            .unwrap();
1164        state.emit_event(&[path]);
1165        Ok(())
1166    }
1167
1168    async fn create_file_with(
1169        &self,
1170        path: &Path,
1171        mut content: Pin<&mut (dyn AsyncRead + Send)>,
1172    ) -> Result<()> {
1173        let mut bytes = Vec::new();
1174        content.read_to_end(&mut bytes).await?;
1175        self.write_file_internal(path, bytes)?;
1176        Ok(())
1177    }
1178
1179    async fn extract_tar_file(
1180        &self,
1181        path: &Path,
1182        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
1183    ) -> Result<()> {
1184        let mut entries = content.entries()?;
1185        while let Some(entry) = entries.next().await {
1186            let mut entry = entry?;
1187            if entry.header().entry_type().is_file() {
1188                let path = path.join(entry.path()?.as_ref());
1189                let mut bytes = Vec::new();
1190                entry.read_to_end(&mut bytes).await?;
1191                self.create_dir(path.parent().unwrap()).await?;
1192                self.write_file_internal(&path, bytes)?;
1193            }
1194        }
1195        Ok(())
1196    }
1197
1198    async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
1199        self.simulate_random_delay().await;
1200
1201        let old_path = normalize_path(old_path);
1202        let new_path = normalize_path(new_path);
1203
1204        let mut state = self.state.lock();
1205        let moved_entry = state.write_path(&old_path, |e| {
1206            if let btree_map::Entry::Occupied(e) = e {
1207                Ok(e.get().clone())
1208            } else {
1209                Err(anyhow!("path does not exist: {}", &old_path.display()))
1210            }
1211        })?;
1212
1213        state.write_path(&new_path, |e| {
1214            match e {
1215                btree_map::Entry::Occupied(mut e) => {
1216                    if options.overwrite {
1217                        *e.get_mut() = moved_entry;
1218                    } else if !options.ignore_if_exists {
1219                        return Err(anyhow!("path already exists: {}", new_path.display()));
1220                    }
1221                }
1222                btree_map::Entry::Vacant(e) => {
1223                    e.insert(moved_entry);
1224                }
1225            }
1226            Ok(())
1227        })?;
1228
1229        state
1230            .write_path(&old_path, |e| {
1231                if let btree_map::Entry::Occupied(e) = e {
1232                    Ok(e.remove())
1233                } else {
1234                    unreachable!()
1235                }
1236            })
1237            .unwrap();
1238
1239        state.emit_event(&[old_path, new_path]);
1240        Ok(())
1241    }
1242
1243    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
1244        self.simulate_random_delay().await;
1245
1246        let source = normalize_path(source);
1247        let target = normalize_path(target);
1248        let mut state = self.state.lock();
1249        let mtime = state.next_mtime;
1250        let inode = util::post_inc(&mut state.next_inode);
1251        state.next_mtime += Duration::from_nanos(1);
1252        let source_entry = state.read_path(&source)?;
1253        let content = source_entry.lock().file_content(&source)?.clone();
1254        let entry = state.write_path(&target, |e| match e {
1255            btree_map::Entry::Occupied(e) => {
1256                if options.overwrite {
1257                    Ok(Some(e.get().clone()))
1258                } else if !options.ignore_if_exists {
1259                    return Err(anyhow!("{target:?} already exists"));
1260                } else {
1261                    Ok(None)
1262                }
1263            }
1264            btree_map::Entry::Vacant(e) => Ok(Some(
1265                e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
1266                    inode,
1267                    mtime,
1268                    content: Vec::new(),
1269                })))
1270                .clone(),
1271            )),
1272        })?;
1273        if let Some(entry) = entry {
1274            entry.lock().set_file_content(&target, content)?;
1275        }
1276        state.emit_event(&[target]);
1277        Ok(())
1278    }
1279
1280    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
1281        self.simulate_random_delay().await;
1282
1283        let path = normalize_path(path);
1284        let parent_path = path
1285            .parent()
1286            .ok_or_else(|| anyhow!("cannot remove the root"))?;
1287        let base_name = path.file_name().unwrap();
1288
1289        let mut state = self.state.lock();
1290        let parent_entry = state.read_path(parent_path)?;
1291        let mut parent_entry = parent_entry.lock();
1292        let entry = parent_entry
1293            .dir_entries(parent_path)?
1294            .entry(base_name.to_str().unwrap().into());
1295
1296        match entry {
1297            btree_map::Entry::Vacant(_) => {
1298                if !options.ignore_if_not_exists {
1299                    return Err(anyhow!("{path:?} does not exist"));
1300                }
1301            }
1302            btree_map::Entry::Occupied(e) => {
1303                {
1304                    let mut entry = e.get().lock();
1305                    let children = entry.dir_entries(&path)?;
1306                    if !options.recursive && !children.is_empty() {
1307                        return Err(anyhow!("{path:?} is not empty"));
1308                    }
1309                }
1310                e.remove();
1311            }
1312        }
1313        state.emit_event(&[path]);
1314        Ok(())
1315    }
1316
1317    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
1318        self.simulate_random_delay().await;
1319
1320        let path = normalize_path(path);
1321        let parent_path = path
1322            .parent()
1323            .ok_or_else(|| anyhow!("cannot remove the root"))?;
1324        let base_name = path.file_name().unwrap();
1325        let mut state = self.state.lock();
1326        let parent_entry = state.read_path(parent_path)?;
1327        let mut parent_entry = parent_entry.lock();
1328        let entry = parent_entry
1329            .dir_entries(parent_path)?
1330            .entry(base_name.to_str().unwrap().into());
1331        match entry {
1332            btree_map::Entry::Vacant(_) => {
1333                if !options.ignore_if_not_exists {
1334                    return Err(anyhow!("{path:?} does not exist"));
1335                }
1336            }
1337            btree_map::Entry::Occupied(e) => {
1338                e.get().lock().file_content(&path)?;
1339                e.remove();
1340            }
1341        }
1342        state.emit_event(&[path]);
1343        Ok(())
1344    }
1345
1346    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>> {
1347        let bytes = self.load_internal(path).await?;
1348        Ok(Box::new(io::Cursor::new(bytes)))
1349    }
1350
1351    async fn load(&self, path: &Path) -> Result<String> {
1352        let content = self.load_internal(path).await?;
1353        Ok(String::from_utf8(content.clone())?)
1354    }
1355
1356    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
1357        self.simulate_random_delay().await;
1358        let path = normalize_path(path.as_path());
1359        self.write_file_internal(path, data.into_bytes())?;
1360        Ok(())
1361    }
1362
1363    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
1364        self.simulate_random_delay().await;
1365        let path = normalize_path(path);
1366        let content = chunks(text, line_ending).collect::<String>();
1367        if let Some(path) = path.parent() {
1368            self.create_dir(path).await?;
1369        }
1370        self.write_file_internal(path, content.into_bytes())?;
1371        Ok(())
1372    }
1373
1374    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
1375        let path = normalize_path(path);
1376        self.simulate_random_delay().await;
1377        let state = self.state.lock();
1378        if let Some((_, canonical_path)) = state.try_read_path(&path, true) {
1379            Ok(canonical_path)
1380        } else {
1381            Err(anyhow!("path does not exist: {}", path.display()))
1382        }
1383    }
1384
1385    async fn is_file(&self, path: &Path) -> bool {
1386        let path = normalize_path(path);
1387        self.simulate_random_delay().await;
1388        let state = self.state.lock();
1389        if let Some((entry, _)) = state.try_read_path(&path, true) {
1390            entry.lock().is_file()
1391        } else {
1392            false
1393        }
1394    }
1395
1396    async fn is_dir(&self, path: &Path) -> bool {
1397        self.metadata(path)
1398            .await
1399            .is_ok_and(|metadata| metadata.is_some_and(|metadata| metadata.is_dir))
1400    }
1401
1402    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
1403        self.simulate_random_delay().await;
1404        let path = normalize_path(path);
1405        let mut state = self.state.lock();
1406        state.metadata_call_count += 1;
1407        if let Some((mut entry, _)) = state.try_read_path(&path, false) {
1408            let is_symlink = entry.lock().is_symlink();
1409            if is_symlink {
1410                if let Some(e) = state.try_read_path(&path, true).map(|e| e.0) {
1411                    entry = e;
1412                } else {
1413                    return Ok(None);
1414                }
1415            }
1416
1417            let entry = entry.lock();
1418            Ok(Some(match &*entry {
1419                FakeFsEntry::File { inode, mtime, .. } => Metadata {
1420                    inode: *inode,
1421                    mtime: *mtime,
1422                    is_dir: false,
1423                    is_symlink,
1424                },
1425                FakeFsEntry::Dir { inode, mtime, .. } => Metadata {
1426                    inode: *inode,
1427                    mtime: *mtime,
1428                    is_dir: true,
1429                    is_symlink,
1430                },
1431                FakeFsEntry::Symlink { .. } => unreachable!(),
1432            }))
1433        } else {
1434            Ok(None)
1435        }
1436    }
1437
1438    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
1439        self.simulate_random_delay().await;
1440        let path = normalize_path(path);
1441        let state = self.state.lock();
1442        if let Some((entry, _)) = state.try_read_path(&path, false) {
1443            let entry = entry.lock();
1444            if let FakeFsEntry::Symlink { target } = &*entry {
1445                Ok(target.clone())
1446            } else {
1447                Err(anyhow!("not a symlink: {}", path.display()))
1448            }
1449        } else {
1450            Err(anyhow!("path does not exist: {}", path.display()))
1451        }
1452    }
1453
1454    async fn read_dir(
1455        &self,
1456        path: &Path,
1457    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
1458        self.simulate_random_delay().await;
1459        let path = normalize_path(path);
1460        let mut state = self.state.lock();
1461        state.read_dir_call_count += 1;
1462        let entry = state.read_path(&path)?;
1463        let mut entry = entry.lock();
1464        let children = entry.dir_entries(&path)?;
1465        let paths = children
1466            .keys()
1467            .map(|file_name| Ok(path.join(file_name)))
1468            .collect::<Vec<_>>();
1469        Ok(Box::pin(futures::stream::iter(paths)))
1470    }
1471
1472    async fn watch(
1473        &self,
1474        path: &Path,
1475        _: Duration,
1476    ) -> Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>> {
1477        self.simulate_random_delay().await;
1478        let (tx, rx) = smol::channel::unbounded();
1479        self.state.lock().event_txs.push(tx);
1480        let path = path.to_path_buf();
1481        let executor = self.executor.clone();
1482        Box::pin(futures::StreamExt::filter(rx, move |events| {
1483            let result = events.iter().any(|evt_path| evt_path.starts_with(&path));
1484            let executor = executor.clone();
1485            async move {
1486                executor.simulate_random_delay().await;
1487                result
1488            }
1489        }))
1490    }
1491
1492    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<Mutex<dyn GitRepository>>> {
1493        let state = self.state.lock();
1494        let entry = state.read_path(abs_dot_git).unwrap();
1495        let mut entry = entry.lock();
1496        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
1497            let state = git_repo_state
1498                .get_or_insert_with(|| Arc::new(Mutex::new(FakeGitRepositoryState::default())))
1499                .clone();
1500            Some(git::repository::FakeGitRepository::open(state))
1501        } else {
1502            None
1503        }
1504    }
1505
1506    fn is_fake(&self) -> bool {
1507        true
1508    }
1509
1510    async fn is_case_sensitive(&self) -> Result<bool> {
1511        Ok(true)
1512    }
1513
1514    #[cfg(any(test, feature = "test-support"))]
1515    fn as_fake(&self) -> &FakeFs {
1516        self
1517    }
1518}
1519
1520fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
1521    rope.chunks().flat_map(move |chunk| {
1522        let mut newline = false;
1523        chunk.split('\n').flat_map(move |line| {
1524            let ending = if newline {
1525                Some(line_ending.as_str())
1526            } else {
1527                None
1528            };
1529            newline = true;
1530            ending.into_iter().chain([line])
1531        })
1532    })
1533}
1534
1535pub fn normalize_path(path: &Path) -> PathBuf {
1536    let mut components = path.components().peekable();
1537    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1538        components.next();
1539        PathBuf::from(c.as_os_str())
1540    } else {
1541        PathBuf::new()
1542    };
1543
1544    for component in components {
1545        match component {
1546            Component::Prefix(..) => unreachable!(),
1547            Component::RootDir => {
1548                ret.push(component.as_os_str());
1549            }
1550            Component::CurDir => {}
1551            Component::ParentDir => {
1552                ret.pop();
1553            }
1554            Component::Normal(c) => {
1555                ret.push(c);
1556            }
1557        }
1558    }
1559    ret
1560}
1561
1562pub fn copy_recursive<'a>(
1563    fs: &'a dyn Fs,
1564    source: &'a Path,
1565    target: &'a Path,
1566    options: CopyOptions,
1567) -> BoxFuture<'a, Result<()>> {
1568    use futures::future::FutureExt;
1569
1570    async move {
1571        let metadata = fs
1572            .metadata(source)
1573            .await?
1574            .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
1575        if metadata.is_dir {
1576            if !options.overwrite && fs.metadata(target).await.is_ok_and(|m| m.is_some()) {
1577                if options.ignore_if_exists {
1578                    return Ok(());
1579                } else {
1580                    return Err(anyhow!("{target:?} already exists"));
1581                }
1582            }
1583
1584            let _ = fs
1585                .remove_dir(
1586                    target,
1587                    RemoveOptions {
1588                        recursive: true,
1589                        ignore_if_not_exists: true,
1590                    },
1591                )
1592                .await;
1593            fs.create_dir(target).await?;
1594            let mut children = fs.read_dir(source).await?;
1595            while let Some(child_path) = children.next().await {
1596                if let Ok(child_path) = child_path {
1597                    if let Some(file_name) = child_path.file_name() {
1598                        let child_target_path = target.join(file_name);
1599                        copy_recursive(fs, &child_path, &child_target_path, options).await?;
1600                    }
1601                }
1602            }
1603
1604            Ok(())
1605        } else {
1606            fs.copy_file(source, target, options).await
1607        }
1608    }
1609    .boxed()
1610}
1611
1612// todo(windows)
1613// can we get file id not open the file twice?
1614// https://github.com/rust-lang/rust/issues/63010
1615#[cfg(target_os = "windows")]
1616async fn file_id(path: impl AsRef<Path>) -> Result<u64> {
1617    use std::os::windows::io::AsRawHandle;
1618
1619    use smol::fs::windows::OpenOptionsExt;
1620    use windows::Win32::{
1621        Foundation::HANDLE,
1622        Storage::FileSystem::{
1623            GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS,
1624        },
1625    };
1626
1627    let file = smol::fs::OpenOptions::new()
1628        .read(true)
1629        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS.0)
1630        .open(path)
1631        .await?;
1632
1633    let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
1634    // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
1635    // This function supports Windows XP+
1636    smol::unblock(move || {
1637        unsafe { GetFileInformationByHandle(HANDLE(file.as_raw_handle() as _), &mut info)? };
1638
1639        Ok(((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64))
1640    })
1641    .await
1642}
1643
1644#[cfg(test)]
1645mod tests {
1646    use super::*;
1647    use gpui::BackgroundExecutor;
1648    use serde_json::json;
1649
1650    #[gpui::test]
1651    async fn test_fake_fs(executor: BackgroundExecutor) {
1652        let fs = FakeFs::new(executor.clone());
1653        fs.insert_tree(
1654            "/root",
1655            json!({
1656                "dir1": {
1657                    "a": "A",
1658                    "b": "B"
1659                },
1660                "dir2": {
1661                    "c": "C",
1662                    "dir3": {
1663                        "d": "D"
1664                    }
1665                }
1666            }),
1667        )
1668        .await;
1669
1670        assert_eq!(
1671            fs.files(),
1672            vec![
1673                PathBuf::from("/root/dir1/a"),
1674                PathBuf::from("/root/dir1/b"),
1675                PathBuf::from("/root/dir2/c"),
1676                PathBuf::from("/root/dir2/dir3/d"),
1677            ]
1678        );
1679
1680        fs.create_symlink("/root/dir2/link-to-dir3".as_ref(), "./dir3".into())
1681            .await
1682            .unwrap();
1683
1684        assert_eq!(
1685            fs.canonicalize("/root/dir2/link-to-dir3".as_ref())
1686                .await
1687                .unwrap(),
1688            PathBuf::from("/root/dir2/dir3"),
1689        );
1690        assert_eq!(
1691            fs.canonicalize("/root/dir2/link-to-dir3/d".as_ref())
1692                .await
1693                .unwrap(),
1694            PathBuf::from("/root/dir2/dir3/d"),
1695        );
1696        assert_eq!(
1697            fs.load("/root/dir2/link-to-dir3/d".as_ref()).await.unwrap(),
1698            "D",
1699        );
1700    }
1701}