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