fs.rs

   1#[cfg(target_os = "macos")]
   2mod mac_watcher;
   3
   4#[cfg(not(target_os = "macos"))]
   5pub mod fs_watcher;
   6
   7use anyhow::{anyhow, Context as _, Result};
   8#[cfg(any(test, feature = "test-support"))]
   9use collections::HashMap;
  10#[cfg(any(test, feature = "test-support"))]
  11use git::status::StatusCode;
  12#[cfg(any(test, feature = "test-support"))]
  13use git::status::TrackedStatus;
  14#[cfg(any(test, feature = "test-support"))]
  15use git::{repository::RepoPath, status::FileStatus};
  16
  17#[cfg(any(target_os = "linux", target_os = "freebsd"))]
  18use ashpd::desktop::trash;
  19#[cfg(any(test, feature = "test-support"))]
  20use std::collections::HashSet;
  21#[cfg(unix)]
  22use std::os::fd::AsFd;
  23#[cfg(unix)]
  24use std::os::fd::AsRawFd;
  25
  26#[cfg(unix)]
  27use std::os::unix::fs::MetadataExt;
  28
  29#[cfg(unix)]
  30use std::os::unix::fs::FileTypeExt;
  31
  32use async_tar::Archive;
  33use futures::{future::BoxFuture, AsyncRead, Stream, StreamExt};
  34use git::repository::{GitRepository, RealGitRepository};
  35use gpui::{App, Global, ReadGlobal};
  36use rope::Rope;
  37use serde::{Deserialize, Serialize};
  38use smol::io::AsyncWriteExt;
  39use std::{
  40    io::{self, Write},
  41    path::{Component, Path, PathBuf},
  42    pin::Pin,
  43    sync::Arc,
  44    time::{Duration, SystemTime, UNIX_EPOCH},
  45};
  46use tempfile::{NamedTempFile, TempDir};
  47use text::LineEnding;
  48use util::ResultExt;
  49
  50#[cfg(any(test, feature = "test-support"))]
  51use collections::{btree_map, BTreeMap};
  52#[cfg(any(test, feature = "test-support"))]
  53use git::repository::FakeGitRepositoryState;
  54#[cfg(any(test, feature = "test-support"))]
  55use parking_lot::Mutex;
  56#[cfg(any(test, feature = "test-support"))]
  57use smol::io::AsyncReadExt;
  58#[cfg(any(test, feature = "test-support"))]
  59use std::ffi::OsStr;
  60
  61pub trait Watcher: Send + Sync {
  62    fn add(&self, path: &Path) -> Result<()>;
  63    fn remove(&self, path: &Path) -> Result<()>;
  64}
  65
  66#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
  67pub enum PathEventKind {
  68    Removed,
  69    Created,
  70    Changed,
  71}
  72
  73#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
  74pub struct PathEvent {
  75    pub path: PathBuf,
  76    pub kind: Option<PathEventKind>,
  77}
  78
  79impl From<PathEvent> for PathBuf {
  80    fn from(event: PathEvent) -> Self {
  81        event.path
  82    }
  83}
  84
  85#[async_trait::async_trait]
  86pub trait Fs: Send + Sync {
  87    async fn create_dir(&self, path: &Path) -> Result<()>;
  88    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()>;
  89    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
  90    async fn create_file_with(
  91        &self,
  92        path: &Path,
  93        content: Pin<&mut (dyn AsyncRead + Send)>,
  94    ) -> Result<()>;
  95    async fn extract_tar_file(
  96        &self,
  97        path: &Path,
  98        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
  99    ) -> Result<()>;
 100    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
 101    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
 102    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
 103    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 104        self.remove_dir(path, options).await
 105    }
 106    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
 107    async fn trash_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 108        self.remove_file(path, options).await
 109    }
 110    async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>>;
 111    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read + Send + Sync>>;
 112    async fn load(&self, path: &Path) -> Result<String> {
 113        Ok(String::from_utf8(self.load_bytes(path).await?)?)
 114    }
 115    async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>>;
 116    async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
 117    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
 118    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
 119    async fn is_file(&self, path: &Path) -> bool;
 120    async fn is_dir(&self, path: &Path) -> bool;
 121    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
 122    async fn read_link(&self, path: &Path) -> Result<PathBuf>;
 123    async fn read_dir(
 124        &self,
 125        path: &Path,
 126    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
 127
 128    async fn watch(
 129        &self,
 130        path: &Path,
 131        latency: Duration,
 132    ) -> (
 133        Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
 134        Arc<dyn Watcher>,
 135    );
 136
 137    fn home_dir(&self) -> Option<PathBuf>;
 138    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>>;
 139    fn is_fake(&self) -> bool;
 140    async fn is_case_sensitive(&self) -> Result<bool>;
 141
 142    #[cfg(any(test, feature = "test-support"))]
 143    fn as_fake(&self) -> Arc<FakeFs> {
 144        panic!("called as_fake on a real fs");
 145    }
 146}
 147
 148struct GlobalFs(Arc<dyn Fs>);
 149
 150impl Global for GlobalFs {}
 151
 152impl dyn Fs {
 153    /// Returns the global [`Fs`].
 154    pub fn global(cx: &App) -> Arc<Self> {
 155        GlobalFs::global(cx).0.clone()
 156    }
 157
 158    /// Sets the global [`Fs`].
 159    pub fn set_global(fs: Arc<Self>, cx: &mut App) {
 160        cx.set_global(GlobalFs(fs));
 161    }
 162}
 163
 164#[derive(Copy, Clone, Default)]
 165pub struct CreateOptions {
 166    pub overwrite: bool,
 167    pub ignore_if_exists: bool,
 168}
 169
 170#[derive(Copy, Clone, Default)]
 171pub struct CopyOptions {
 172    pub overwrite: bool,
 173    pub ignore_if_exists: bool,
 174}
 175
 176#[derive(Copy, Clone, Default)]
 177pub struct RenameOptions {
 178    pub overwrite: bool,
 179    pub ignore_if_exists: bool,
 180}
 181
 182#[derive(Copy, Clone, Default)]
 183pub struct RemoveOptions {
 184    pub recursive: bool,
 185    pub ignore_if_not_exists: bool,
 186}
 187
 188#[derive(Copy, Clone, Debug)]
 189pub struct Metadata {
 190    pub inode: u64,
 191    pub mtime: MTime,
 192    pub is_symlink: bool,
 193    pub is_dir: bool,
 194    pub len: u64,
 195    pub is_fifo: bool,
 196}
 197
 198/// Filesystem modification time. The purpose of this newtype is to discourage use of operations
 199/// that do not make sense for mtimes. In particular, it is not always valid to compare mtimes using
 200/// `<` or `>`, as there are many things that can cause the mtime of a file to be earlier than it
 201/// was. See ["mtime comparison considered harmful" - apenwarr](https://apenwarr.ca/log/20181113).
 202///
 203/// Do not derive Ord, PartialOrd, or arithmetic operation traits.
 204#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
 205#[serde(transparent)]
 206pub struct MTime(SystemTime);
 207
 208impl MTime {
 209    /// Conversion intended for persistence and testing.
 210    pub fn from_seconds_and_nanos(secs: u64, nanos: u32) -> Self {
 211        MTime(UNIX_EPOCH + Duration::new(secs, nanos))
 212    }
 213
 214    /// Conversion intended for persistence.
 215    pub fn to_seconds_and_nanos_for_persistence(self) -> Option<(u64, u32)> {
 216        self.0
 217            .duration_since(UNIX_EPOCH)
 218            .ok()
 219            .map(|duration| (duration.as_secs(), duration.subsec_nanos()))
 220    }
 221
 222    /// Returns the value wrapped by this `MTime`, for presentation to the user. The name including
 223    /// "_for_user" is to discourage misuse - this method should not be used when making decisions
 224    /// about file dirtiness.
 225    pub fn timestamp_for_user(self) -> SystemTime {
 226        self.0
 227    }
 228
 229    /// Temporary method to split out the behavior changes from introduction of this newtype.
 230    pub fn bad_is_greater_than(self, other: MTime) -> bool {
 231        self.0 > other.0
 232    }
 233}
 234
 235impl From<proto::Timestamp> for MTime {
 236    fn from(timestamp: proto::Timestamp) -> Self {
 237        MTime(timestamp.into())
 238    }
 239}
 240
 241impl From<MTime> for proto::Timestamp {
 242    fn from(mtime: MTime) -> Self {
 243        mtime.0.into()
 244    }
 245}
 246
 247#[derive(Default)]
 248pub struct RealFs {
 249    git_binary_path: Option<PathBuf>,
 250}
 251
 252pub trait FileHandle: Send + Sync + std::fmt::Debug {
 253    fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf>;
 254}
 255
 256impl FileHandle for std::fs::File {
 257    #[cfg(target_os = "macos")]
 258    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
 259        use std::{
 260            ffi::{CStr, OsStr},
 261            os::unix::ffi::OsStrExt,
 262        };
 263
 264        let fd = self.as_fd();
 265        let mut path_buf: [libc::c_char; libc::PATH_MAX as usize] = [0; libc::PATH_MAX as usize];
 266
 267        let result = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETPATH, path_buf.as_mut_ptr()) };
 268        if result == -1 {
 269            anyhow::bail!("fcntl returned -1".to_string());
 270        }
 271
 272        let c_str = unsafe { CStr::from_ptr(path_buf.as_ptr()) };
 273        let path = PathBuf::from(OsStr::from_bytes(c_str.to_bytes()));
 274        Ok(path)
 275    }
 276
 277    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 278    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
 279        let fd = self.as_fd();
 280        let fd_path = format!("/proc/self/fd/{}", fd.as_raw_fd());
 281        let new_path = std::fs::read_link(fd_path)?;
 282        if new_path
 283            .file_name()
 284            .is_some_and(|f| f.to_string_lossy().ends_with(" (deleted)"))
 285        {
 286            anyhow::bail!("file was deleted")
 287        };
 288
 289        Ok(new_path)
 290    }
 291
 292    #[cfg(target_os = "windows")]
 293    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
 294        anyhow::bail!("unimplemented")
 295    }
 296}
 297
 298pub struct RealWatcher {}
 299
 300impl RealFs {
 301    pub fn new(git_binary_path: Option<PathBuf>) -> Self {
 302        Self { git_binary_path }
 303    }
 304}
 305
 306#[async_trait::async_trait]
 307impl Fs for RealFs {
 308    async fn create_dir(&self, path: &Path) -> Result<()> {
 309        Ok(smol::fs::create_dir_all(path).await?)
 310    }
 311
 312    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()> {
 313        #[cfg(unix)]
 314        smol::fs::unix::symlink(target, path).await?;
 315
 316        #[cfg(windows)]
 317        if smol::fs::metadata(&target).await?.is_dir() {
 318            smol::fs::windows::symlink_dir(target, path).await?
 319        } else {
 320            smol::fs::windows::symlink_file(target, path).await?
 321        }
 322
 323        Ok(())
 324    }
 325
 326    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
 327        let mut open_options = smol::fs::OpenOptions::new();
 328        open_options.write(true).create(true);
 329        if options.overwrite {
 330            open_options.truncate(true);
 331        } else if !options.ignore_if_exists {
 332            open_options.create_new(true);
 333        }
 334        open_options.open(path).await?;
 335        Ok(())
 336    }
 337
 338    async fn create_file_with(
 339        &self,
 340        path: &Path,
 341        content: Pin<&mut (dyn AsyncRead + Send)>,
 342    ) -> Result<()> {
 343        let mut file = smol::fs::File::create(&path).await?;
 344        futures::io::copy(content, &mut file).await?;
 345        Ok(())
 346    }
 347
 348    async fn extract_tar_file(
 349        &self,
 350        path: &Path,
 351        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
 352    ) -> Result<()> {
 353        content.unpack(path).await?;
 354        Ok(())
 355    }
 356
 357    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
 358        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 359            if options.ignore_if_exists {
 360                return Ok(());
 361            } else {
 362                return Err(anyhow!("{target:?} already exists"));
 363            }
 364        }
 365
 366        smol::fs::copy(source, target).await?;
 367        Ok(())
 368    }
 369
 370    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
 371        if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
 372            if options.ignore_if_exists {
 373                return Ok(());
 374            } else {
 375                return Err(anyhow!("{target:?} already exists"));
 376            }
 377        }
 378
 379        smol::fs::rename(source, target).await?;
 380        Ok(())
 381    }
 382
 383    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 384        let result = if options.recursive {
 385            smol::fs::remove_dir_all(path).await
 386        } else {
 387            smol::fs::remove_dir(path).await
 388        };
 389        match result {
 390            Ok(()) => Ok(()),
 391            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 392                Ok(())
 393            }
 394            Err(err) => Err(err)?,
 395        }
 396    }
 397
 398    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 399        #[cfg(windows)]
 400        if let Ok(Some(metadata)) = self.metadata(path).await {
 401            if metadata.is_symlink && metadata.is_dir {
 402                self.remove_dir(
 403                    path,
 404                    RemoveOptions {
 405                        recursive: false,
 406                        ignore_if_not_exists: true,
 407                    },
 408                )
 409                .await?;
 410                return Ok(());
 411            }
 412        }
 413
 414        match smol::fs::remove_file(path).await {
 415            Ok(()) => Ok(()),
 416            Err(err) if err.kind() == io::ErrorKind::NotFound && options.ignore_if_not_exists => {
 417                Ok(())
 418            }
 419            Err(err) => Err(err)?,
 420        }
 421    }
 422
 423    #[cfg(target_os = "macos")]
 424    async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
 425        use cocoa::{
 426            base::{id, nil},
 427            foundation::{NSAutoreleasePool, NSString},
 428        };
 429        use objc::{class, msg_send, sel, sel_impl};
 430
 431        unsafe {
 432            unsafe fn ns_string(string: &str) -> id {
 433                NSString::alloc(nil).init_str(string).autorelease()
 434            }
 435
 436            let url: id = msg_send![class!(NSURL), fileURLWithPath: ns_string(path.to_string_lossy().as_ref())];
 437            let array: id = msg_send![class!(NSArray), arrayWithObject: url];
 438            let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
 439
 440            let _: id = msg_send![workspace, recycleURLs: array completionHandler: nil];
 441        }
 442        Ok(())
 443    }
 444
 445    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 446    async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
 447        if let Ok(Some(metadata)) = self.metadata(path).await {
 448            if metadata.is_symlink {
 449                // TODO: trash_file does not support trashing symlinks yet - https://github.com/bilelmoussaoui/ashpd/issues/255
 450                return self.remove_file(path, RemoveOptions::default()).await;
 451            }
 452        }
 453        let file = smol::fs::File::open(path).await?;
 454        match trash::trash_file(&file.as_fd()).await {
 455            Ok(_) => Ok(()),
 456            Err(err) => Err(anyhow::Error::new(err)),
 457        }
 458    }
 459
 460    #[cfg(target_os = "windows")]
 461    async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
 462        use util::paths::SanitizedPath;
 463        use windows::{
 464            core::HSTRING,
 465            Storage::{StorageDeleteOption, StorageFile},
 466        };
 467        // todo(windows)
 468        // When new version of `windows-rs` release, make this operation `async`
 469        let path = SanitizedPath::from(path.canonicalize()?);
 470        let path_string = path.to_string();
 471        let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path_string))?.get()?;
 472        file.DeleteAsync(StorageDeleteOption::Default)?.get()?;
 473        Ok(())
 474    }
 475
 476    #[cfg(target_os = "macos")]
 477    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 478        self.trash_file(path, options).await
 479    }
 480
 481    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 482    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 483        self.trash_file(path, options).await
 484    }
 485
 486    #[cfg(target_os = "windows")]
 487    async fn trash_dir(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
 488        use util::paths::SanitizedPath;
 489        use windows::{
 490            core::HSTRING,
 491            Storage::{StorageDeleteOption, StorageFolder},
 492        };
 493
 494        // todo(windows)
 495        // When new version of `windows-rs` release, make this operation `async`
 496        let path = SanitizedPath::from(path.canonicalize()?);
 497        let path_string = path.to_string();
 498        let folder = StorageFolder::GetFolderFromPathAsync(&HSTRING::from(path_string))?.get()?;
 499        folder.DeleteAsync(StorageDeleteOption::Default)?.get()?;
 500        Ok(())
 501    }
 502
 503    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read + Send + Sync>> {
 504        Ok(Box::new(std::fs::File::open(path)?))
 505    }
 506
 507    async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>> {
 508        Ok(Arc::new(std::fs::File::open(path)?))
 509    }
 510
 511    async fn load(&self, path: &Path) -> Result<String> {
 512        let path = path.to_path_buf();
 513        let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
 514        Ok(text)
 515    }
 516    async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
 517        let path = path.to_path_buf();
 518        let bytes = smol::unblock(|| std::fs::read(path)).await?;
 519        Ok(bytes)
 520    }
 521
 522    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
 523        smol::unblock(move || {
 524            let mut tmp_file = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
 525                // Use the directory of the destination as temp dir to avoid
 526                // invalid cross-device link error, and XDG_CACHE_DIR for fallback.
 527                // See https://github.com/zed-industries/zed/pull/8437 for more details.
 528                NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))
 529            } else if cfg!(target_os = "windows") {
 530                // If temp dir is set to a different drive than the destination,
 531                // we receive error:
 532                //
 533                // failed to persist temporary file:
 534                // The system cannot move the file to a different disk drive. (os error 17)
 535                //
 536                // So we use the directory of the destination as a temp dir to avoid it.
 537                // https://github.com/zed-industries/zed/issues/16571
 538                NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))
 539            } else {
 540                NamedTempFile::new()
 541            }?;
 542            tmp_file.write_all(data.as_bytes())?;
 543            tmp_file.persist(path)?;
 544            Ok::<(), anyhow::Error>(())
 545        })
 546        .await?;
 547
 548        Ok(())
 549    }
 550
 551    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
 552        let buffer_size = text.summary().len.min(10 * 1024);
 553        if let Some(path) = path.parent() {
 554            self.create_dir(path).await?;
 555        }
 556        let file = smol::fs::File::create(path).await?;
 557        let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
 558        for chunk in chunks(text, line_ending) {
 559            writer.write_all(chunk.as_bytes()).await?;
 560        }
 561        writer.flush().await?;
 562        Ok(())
 563    }
 564
 565    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
 566        Ok(smol::fs::canonicalize(path).await?)
 567    }
 568
 569    async fn is_file(&self, path: &Path) -> bool {
 570        smol::fs::metadata(path)
 571            .await
 572            .map_or(false, |metadata| metadata.is_file())
 573    }
 574
 575    async fn is_dir(&self, path: &Path) -> bool {
 576        smol::fs::metadata(path)
 577            .await
 578            .map_or(false, |metadata| metadata.is_dir())
 579    }
 580
 581    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
 582        let symlink_metadata = match smol::fs::symlink_metadata(path).await {
 583            Ok(metadata) => metadata,
 584            Err(err) => {
 585                return match (err.kind(), err.raw_os_error()) {
 586                    (io::ErrorKind::NotFound, _) => Ok(None),
 587                    (io::ErrorKind::Other, Some(libc::ENOTDIR)) => Ok(None),
 588                    _ => Err(anyhow::Error::new(err)),
 589                }
 590            }
 591        };
 592
 593        let path_buf = path.to_path_buf();
 594        let path_exists = smol::unblock(move || {
 595            path_buf
 596                .try_exists()
 597                .with_context(|| format!("checking existence for path {path_buf:?}"))
 598        })
 599        .await?;
 600        let is_symlink = symlink_metadata.file_type().is_symlink();
 601        let metadata = match (is_symlink, path_exists) {
 602            (true, true) => smol::fs::metadata(path)
 603                .await
 604                .with_context(|| "accessing symlink for path {path}")?,
 605            _ => symlink_metadata,
 606        };
 607
 608        #[cfg(unix)]
 609        let inode = metadata.ino();
 610
 611        #[cfg(windows)]
 612        let inode = file_id(path).await?;
 613
 614        #[cfg(windows)]
 615        let is_fifo = false;
 616
 617        #[cfg(unix)]
 618        let is_fifo = metadata.file_type().is_fifo();
 619
 620        Ok(Some(Metadata {
 621            inode,
 622            mtime: MTime(metadata.modified().unwrap()),
 623            len: metadata.len(),
 624            is_symlink,
 625            is_dir: metadata.file_type().is_dir(),
 626            is_fifo,
 627        }))
 628    }
 629
 630    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
 631        let path = smol::fs::read_link(path).await?;
 632        Ok(path)
 633    }
 634
 635    async fn read_dir(
 636        &self,
 637        path: &Path,
 638    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
 639        let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
 640            Ok(entry) => Ok(entry.path()),
 641            Err(error) => Err(anyhow!("failed to read dir entry {:?}", error)),
 642        });
 643        Ok(Box::pin(result))
 644    }
 645
 646    #[cfg(target_os = "macos")]
 647    async fn watch(
 648        &self,
 649        path: &Path,
 650        latency: Duration,
 651    ) -> (
 652        Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
 653        Arc<dyn Watcher>,
 654    ) {
 655        use fsevent::StreamFlags;
 656
 657        let (events_tx, events_rx) = smol::channel::unbounded();
 658        let handles = Arc::new(parking_lot::Mutex::new(collections::BTreeMap::default()));
 659        let watcher = Arc::new(mac_watcher::MacWatcher::new(
 660            events_tx,
 661            Arc::downgrade(&handles),
 662            latency,
 663        ));
 664        watcher.add(path).expect("handles can't be dropped");
 665
 666        (
 667            Box::pin(
 668                events_rx
 669                    .map(|events| {
 670                        events
 671                            .into_iter()
 672                            .map(|event| {
 673                                let kind = if event.flags.contains(StreamFlags::ITEM_REMOVED) {
 674                                    Some(PathEventKind::Removed)
 675                                } else if event.flags.contains(StreamFlags::ITEM_CREATED) {
 676                                    Some(PathEventKind::Created)
 677                                } else if event.flags.contains(StreamFlags::ITEM_MODIFIED) {
 678                                    Some(PathEventKind::Changed)
 679                                } else {
 680                                    None
 681                                };
 682                                PathEvent {
 683                                    path: event.path,
 684                                    kind,
 685                                }
 686                            })
 687                            .collect()
 688                    })
 689                    .chain(futures::stream::once(async move {
 690                        drop(handles);
 691                        vec![]
 692                    })),
 693            ),
 694            watcher,
 695        )
 696    }
 697
 698    #[cfg(not(target_os = "macos"))]
 699    async fn watch(
 700        &self,
 701        path: &Path,
 702        latency: Duration,
 703    ) -> (
 704        Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
 705        Arc<dyn Watcher>,
 706    ) {
 707        use parking_lot::Mutex;
 708        use util::paths::SanitizedPath;
 709
 710        let (tx, rx) = smol::channel::unbounded();
 711        let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
 712        let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone()));
 713
 714        if watcher.add(path).is_err() {
 715            // If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created.
 716            if let Some(parent) = path.parent() {
 717                if let Err(e) = watcher.add(parent) {
 718                    log::warn!("Failed to watch: {e}");
 719                }
 720            }
 721        }
 722
 723        // Check if path is a symlink and follow the target parent
 724        if let Some(mut target) = self.read_link(&path).await.ok() {
 725            // Check if symlink target is relative path, if so make it absolute
 726            if target.is_relative() {
 727                if let Some(parent) = path.parent() {
 728                    target = parent.join(target);
 729                    if let Ok(canonical) = self.canonicalize(&target).await {
 730                        target = SanitizedPath::from(canonical).as_path().to_path_buf();
 731                    }
 732                }
 733            }
 734            watcher.add(&target).ok();
 735            if let Some(parent) = target.parent() {
 736                watcher.add(parent).log_err();
 737            }
 738        }
 739
 740        (
 741            Box::pin(rx.filter_map({
 742                let watcher = watcher.clone();
 743                move |_| {
 744                    let _ = watcher.clone();
 745                    let pending_paths = pending_paths.clone();
 746                    async move {
 747                        smol::Timer::after(latency).await;
 748                        let paths = std::mem::take(&mut *pending_paths.lock());
 749                        (!paths.is_empty()).then_some(paths)
 750                    }
 751                }
 752            })),
 753            watcher,
 754        )
 755    }
 756
 757    fn open_repo(&self, dotgit_path: &Path) -> Option<Arc<dyn GitRepository>> {
 758        // with libgit2, we can open git repo from an existing work dir
 759        // https://libgit2.org/docs/reference/main/repository/git_repository_open.html
 760        let workdir_root = dotgit_path.parent()?;
 761        let repo = git2::Repository::open(workdir_root).log_err()?;
 762        Some(Arc::new(RealGitRepository::new(
 763            repo,
 764            self.git_binary_path.clone(),
 765        )))
 766    }
 767
 768    fn is_fake(&self) -> bool {
 769        false
 770    }
 771
 772    /// Checks whether the file system is case sensitive by attempting to create two files
 773    /// that have the same name except for the casing.
 774    ///
 775    /// It creates both files in a temporary directory it removes at the end.
 776    async fn is_case_sensitive(&self) -> Result<bool> {
 777        let temp_dir = TempDir::new()?;
 778        let test_file_1 = temp_dir.path().join("case_sensitivity_test.tmp");
 779        let test_file_2 = temp_dir.path().join("CASE_SENSITIVITY_TEST.TMP");
 780
 781        let create_opts = CreateOptions {
 782            overwrite: false,
 783            ignore_if_exists: false,
 784        };
 785
 786        // Create file1
 787        self.create_file(&test_file_1, create_opts).await?;
 788
 789        // Now check whether it's possible to create file2
 790        let case_sensitive = match self.create_file(&test_file_2, create_opts).await {
 791            Ok(_) => Ok(true),
 792            Err(e) => {
 793                if let Some(io_error) = e.downcast_ref::<io::Error>() {
 794                    if io_error.kind() == io::ErrorKind::AlreadyExists {
 795                        Ok(false)
 796                    } else {
 797                        Err(e)
 798                    }
 799                } else {
 800                    Err(e)
 801                }
 802            }
 803        };
 804
 805        temp_dir.close()?;
 806        case_sensitive
 807    }
 808
 809    fn home_dir(&self) -> Option<PathBuf> {
 810        Some(paths::home_dir().clone())
 811    }
 812}
 813
 814#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
 815impl Watcher for RealWatcher {
 816    fn add(&self, _: &Path) -> Result<()> {
 817        Ok(())
 818    }
 819
 820    fn remove(&self, _: &Path) -> Result<()> {
 821        Ok(())
 822    }
 823}
 824
 825#[cfg(any(test, feature = "test-support"))]
 826pub struct FakeFs {
 827    this: std::sync::Weak<Self>,
 828    // Use an unfair lock to ensure tests are deterministic.
 829    state: Mutex<FakeFsState>,
 830    executor: gpui::BackgroundExecutor,
 831}
 832
 833#[cfg(any(test, feature = "test-support"))]
 834struct FakeFsState {
 835    root: Arc<Mutex<FakeFsEntry>>,
 836    next_inode: u64,
 837    next_mtime: SystemTime,
 838    git_event_tx: smol::channel::Sender<PathBuf>,
 839    event_txs: Vec<smol::channel::Sender<Vec<PathEvent>>>,
 840    events_paused: bool,
 841    buffered_events: Vec<PathEvent>,
 842    metadata_call_count: usize,
 843    read_dir_call_count: usize,
 844    moves: std::collections::HashMap<u64, PathBuf>,
 845    home_dir: Option<PathBuf>,
 846}
 847
 848#[cfg(any(test, feature = "test-support"))]
 849#[derive(Debug)]
 850enum FakeFsEntry {
 851    File {
 852        inode: u64,
 853        mtime: MTime,
 854        len: u64,
 855        content: Vec<u8>,
 856    },
 857    Dir {
 858        inode: u64,
 859        mtime: MTime,
 860        len: u64,
 861        entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
 862        git_repo_state: Option<Arc<Mutex<git::repository::FakeGitRepositoryState>>>,
 863    },
 864    Symlink {
 865        target: PathBuf,
 866    },
 867}
 868
 869#[cfg(any(test, feature = "test-support"))]
 870impl FakeFsState {
 871    fn get_and_increment_mtime(&mut self) -> MTime {
 872        let mtime = self.next_mtime;
 873        self.next_mtime += FakeFs::SYSTEMTIME_INTERVAL;
 874        MTime(mtime)
 875    }
 876
 877    fn get_and_increment_inode(&mut self) -> u64 {
 878        let inode = self.next_inode;
 879        self.next_inode += 1;
 880        inode
 881    }
 882
 883    fn read_path(&self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
 884        Ok(self
 885            .try_read_path(target, true)
 886            .ok_or_else(|| {
 887                anyhow!(io::Error::new(
 888                    io::ErrorKind::NotFound,
 889                    format!("not found: {}", target.display())
 890                ))
 891            })?
 892            .0)
 893    }
 894
 895    fn try_read_path(
 896        &self,
 897        target: &Path,
 898        follow_symlink: bool,
 899    ) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
 900        let mut path = target.to_path_buf();
 901        let mut canonical_path = PathBuf::new();
 902        let mut entry_stack = Vec::new();
 903        'outer: loop {
 904            let mut path_components = path.components().peekable();
 905            let mut prefix = None;
 906            while let Some(component) = path_components.next() {
 907                match component {
 908                    Component::Prefix(prefix_component) => prefix = Some(prefix_component),
 909                    Component::RootDir => {
 910                        entry_stack.clear();
 911                        entry_stack.push(self.root.clone());
 912                        canonical_path.clear();
 913                        match prefix {
 914                            Some(prefix_component) => {
 915                                canonical_path = PathBuf::from(prefix_component.as_os_str());
 916                                // Prefixes like `C:\\` are represented without their trailing slash, so we have to re-add it.
 917                                canonical_path.push(std::path::MAIN_SEPARATOR_STR);
 918                            }
 919                            None => canonical_path = PathBuf::from(std::path::MAIN_SEPARATOR_STR),
 920                        }
 921                    }
 922                    Component::CurDir => {}
 923                    Component::ParentDir => {
 924                        entry_stack.pop()?;
 925                        canonical_path.pop();
 926                    }
 927                    Component::Normal(name) => {
 928                        let current_entry = entry_stack.last().cloned()?;
 929                        let current_entry = current_entry.lock();
 930                        if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
 931                            let entry = entries.get(name.to_str().unwrap()).cloned()?;
 932                            if path_components.peek().is_some() || follow_symlink {
 933                                let entry = entry.lock();
 934                                if let FakeFsEntry::Symlink { target, .. } = &*entry {
 935                                    let mut target = target.clone();
 936                                    target.extend(path_components);
 937                                    path = target;
 938                                    continue 'outer;
 939                                }
 940                            }
 941                            entry_stack.push(entry.clone());
 942                            canonical_path = canonical_path.join(name);
 943                        } else {
 944                            return None;
 945                        }
 946                    }
 947                }
 948            }
 949            break;
 950        }
 951        Some((entry_stack.pop()?, canonical_path))
 952    }
 953
 954    fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
 955    where
 956        Fn: FnOnce(btree_map::Entry<String, Arc<Mutex<FakeFsEntry>>>) -> Result<T>,
 957    {
 958        let path = normalize_path(path);
 959        let filename = path
 960            .file_name()
 961            .ok_or_else(|| anyhow!("cannot overwrite the root"))?;
 962        let parent_path = path.parent().unwrap();
 963
 964        let parent = self.read_path(parent_path)?;
 965        let mut parent = parent.lock();
 966        let new_entry = parent
 967            .dir_entries(parent_path)?
 968            .entry(filename.to_str().unwrap().into());
 969        callback(new_entry)
 970    }
 971
 972    fn emit_event<I, T>(&mut self, paths: I)
 973    where
 974        I: IntoIterator<Item = (T, Option<PathEventKind>)>,
 975        T: Into<PathBuf>,
 976    {
 977        self.buffered_events
 978            .extend(paths.into_iter().map(|(path, kind)| PathEvent {
 979                path: path.into(),
 980                kind,
 981            }));
 982
 983        if !self.events_paused {
 984            self.flush_events(self.buffered_events.len());
 985        }
 986    }
 987
 988    fn flush_events(&mut self, mut count: usize) {
 989        count = count.min(self.buffered_events.len());
 990        let events = self.buffered_events.drain(0..count).collect::<Vec<_>>();
 991        self.event_txs.retain(|tx| {
 992            let _ = tx.try_send(events.clone());
 993            !tx.is_closed()
 994        });
 995    }
 996}
 997
 998#[cfg(any(test, feature = "test-support"))]
 999pub static FS_DOT_GIT: std::sync::LazyLock<&'static OsStr> =
1000    std::sync::LazyLock::new(|| OsStr::new(".git"));
1001
1002#[cfg(any(test, feature = "test-support"))]
1003impl FakeFs {
1004    /// We need to use something large enough for Windows and Unix to consider this a new file.
1005    /// https://doc.rust-lang.org/nightly/std/time/struct.SystemTime.html#platform-specific-behavior
1006    const SYSTEMTIME_INTERVAL: Duration = Duration::from_nanos(100);
1007
1008    pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
1009        let (tx, rx) = smol::channel::bounded::<PathBuf>(10);
1010
1011        let this = Arc::new_cyclic(|this| Self {
1012            this: this.clone(),
1013            executor: executor.clone(),
1014            state: Mutex::new(FakeFsState {
1015                root: Arc::new(Mutex::new(FakeFsEntry::Dir {
1016                    inode: 0,
1017                    mtime: MTime(UNIX_EPOCH),
1018                    len: 0,
1019                    entries: Default::default(),
1020                    git_repo_state: None,
1021                })),
1022                git_event_tx: tx,
1023                next_mtime: UNIX_EPOCH + Self::SYSTEMTIME_INTERVAL,
1024                next_inode: 1,
1025                event_txs: Default::default(),
1026                buffered_events: Vec::new(),
1027                events_paused: false,
1028                read_dir_call_count: 0,
1029                metadata_call_count: 0,
1030                moves: Default::default(),
1031                home_dir: None,
1032            }),
1033        });
1034
1035        executor.spawn({
1036            let this = this.clone();
1037            async move {
1038                while let Ok(git_event) = rx.recv().await {
1039                    if let Some(mut state) = this.state.try_lock() {
1040                        state.emit_event([(git_event, None)]);
1041                    } else {
1042                        panic!("Failed to lock file system state, this execution would have caused a test hang");
1043                    }
1044                }
1045            }
1046        }).detach();
1047
1048        this
1049    }
1050
1051    pub fn set_next_mtime(&self, next_mtime: SystemTime) {
1052        let mut state = self.state.lock();
1053        state.next_mtime = next_mtime;
1054    }
1055
1056    pub fn get_and_increment_mtime(&self) -> MTime {
1057        let mut state = self.state.lock();
1058        state.get_and_increment_mtime()
1059    }
1060
1061    pub async fn touch_path(&self, path: impl AsRef<Path>) {
1062        let mut state = self.state.lock();
1063        let path = path.as_ref();
1064        let new_mtime = state.get_and_increment_mtime();
1065        let new_inode = state.get_and_increment_inode();
1066        state
1067            .write_path(path, move |entry| {
1068                match entry {
1069                    btree_map::Entry::Vacant(e) => {
1070                        e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
1071                            inode: new_inode,
1072                            mtime: new_mtime,
1073                            content: Vec::new(),
1074                            len: 0,
1075                        })));
1076                    }
1077                    btree_map::Entry::Occupied(mut e) => match &mut *e.get_mut().lock() {
1078                        FakeFsEntry::File { mtime, .. } => *mtime = new_mtime,
1079                        FakeFsEntry::Dir { mtime, .. } => *mtime = new_mtime,
1080                        FakeFsEntry::Symlink { .. } => {}
1081                    },
1082                }
1083                Ok(())
1084            })
1085            .unwrap();
1086        state.emit_event([(path.to_path_buf(), None)]);
1087    }
1088
1089    pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
1090        self.write_file_internal(path, content).unwrap()
1091    }
1092
1093    pub async fn insert_symlink(&self, path: impl AsRef<Path>, target: PathBuf) {
1094        let mut state = self.state.lock();
1095        let path = path.as_ref();
1096        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
1097        state
1098            .write_path(path.as_ref(), move |e| match e {
1099                btree_map::Entry::Vacant(e) => {
1100                    e.insert(file);
1101                    Ok(())
1102                }
1103                btree_map::Entry::Occupied(mut e) => {
1104                    *e.get_mut() = file;
1105                    Ok(())
1106                }
1107            })
1108            .unwrap();
1109        state.emit_event([(path, None)]);
1110    }
1111
1112    fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
1113        let mut state = self.state.lock();
1114        let file = Arc::new(Mutex::new(FakeFsEntry::File {
1115            inode: state.get_and_increment_inode(),
1116            mtime: state.get_and_increment_mtime(),
1117            len: content.len() as u64,
1118            content,
1119        }));
1120        let mut kind = None;
1121        state.write_path(path.as_ref(), {
1122            let kind = &mut kind;
1123            move |entry| {
1124                match entry {
1125                    btree_map::Entry::Vacant(e) => {
1126                        *kind = Some(PathEventKind::Created);
1127                        e.insert(file);
1128                    }
1129                    btree_map::Entry::Occupied(mut e) => {
1130                        *kind = Some(PathEventKind::Changed);
1131                        *e.get_mut() = file;
1132                    }
1133                }
1134                Ok(())
1135            }
1136        })?;
1137        state.emit_event([(path.as_ref(), kind)]);
1138        Ok(())
1139    }
1140
1141    pub fn read_file_sync(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
1142        let path = path.as_ref();
1143        let path = normalize_path(path);
1144        let state = self.state.lock();
1145        let entry = state.read_path(&path)?;
1146        let entry = entry.lock();
1147        entry.file_content(&path).cloned()
1148    }
1149
1150    async fn load_internal(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
1151        let path = path.as_ref();
1152        let path = normalize_path(path);
1153        self.simulate_random_delay().await;
1154        let state = self.state.lock();
1155        let entry = state.read_path(&path)?;
1156        let entry = entry.lock();
1157        entry.file_content(&path).cloned()
1158    }
1159
1160    pub fn pause_events(&self) {
1161        self.state.lock().events_paused = true;
1162    }
1163
1164    pub fn buffered_event_count(&self) -> usize {
1165        self.state.lock().buffered_events.len()
1166    }
1167
1168    pub fn flush_events(&self, count: usize) {
1169        self.state.lock().flush_events(count);
1170    }
1171
1172    #[must_use]
1173    pub fn insert_tree<'a>(
1174        &'a self,
1175        path: impl 'a + AsRef<Path> + Send,
1176        tree: serde_json::Value,
1177    ) -> futures::future::BoxFuture<'a, ()> {
1178        use futures::FutureExt as _;
1179        use serde_json::Value::*;
1180
1181        async move {
1182            let path = path.as_ref();
1183
1184            match tree {
1185                Object(map) => {
1186                    self.create_dir(path).await.unwrap();
1187                    for (name, contents) in map {
1188                        let mut path = PathBuf::from(path);
1189                        path.push(name);
1190                        self.insert_tree(&path, contents).await;
1191                    }
1192                }
1193                Null => {
1194                    self.create_dir(path).await.unwrap();
1195                }
1196                String(contents) => {
1197                    self.insert_file(&path, contents.into_bytes()).await;
1198                }
1199                _ => {
1200                    panic!("JSON object must contain only objects, strings, or null");
1201                }
1202            }
1203        }
1204        .boxed()
1205    }
1206
1207    pub fn insert_tree_from_real_fs<'a>(
1208        &'a self,
1209        path: impl 'a + AsRef<Path> + Send,
1210        src_path: impl 'a + AsRef<Path> + Send,
1211    ) -> futures::future::BoxFuture<'a, ()> {
1212        use futures::FutureExt as _;
1213
1214        async move {
1215            let path = path.as_ref();
1216            if std::fs::metadata(&src_path).unwrap().is_file() {
1217                let contents = std::fs::read(src_path).unwrap();
1218                self.insert_file(path, contents).await;
1219            } else {
1220                self.create_dir(path).await.unwrap();
1221                for entry in std::fs::read_dir(&src_path).unwrap() {
1222                    let entry = entry.unwrap();
1223                    self.insert_tree_from_real_fs(path.join(entry.file_name()), entry.path())
1224                        .await;
1225                }
1226            }
1227        }
1228        .boxed()
1229    }
1230
1231    pub fn with_git_state<F>(&self, dot_git: &Path, emit_git_event: bool, f: F)
1232    where
1233        F: FnOnce(&mut FakeGitRepositoryState),
1234    {
1235        let mut state = self.state.lock();
1236        let entry = state.read_path(dot_git).unwrap();
1237        let mut entry = entry.lock();
1238
1239        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
1240            let repo_state = git_repo_state.get_or_insert_with(|| {
1241                Arc::new(Mutex::new(FakeGitRepositoryState::new(
1242                    dot_git.to_path_buf(),
1243                    state.git_event_tx.clone(),
1244                )))
1245            });
1246            let mut repo_state = repo_state.lock();
1247
1248            f(&mut repo_state);
1249
1250            if emit_git_event {
1251                state.emit_event([(dot_git, None)]);
1252            }
1253        } else {
1254            panic!("not a directory");
1255        }
1256    }
1257
1258    pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
1259        self.with_git_state(dot_git, true, |state| {
1260            let branch = branch.map(Into::into);
1261            state.branches.extend(branch.clone());
1262            state.current_branch_name = branch
1263        })
1264    }
1265
1266    pub fn insert_branches(&self, dot_git: &Path, branches: &[&str]) {
1267        self.with_git_state(dot_git, true, |state| {
1268            if let Some(first) = branches.first() {
1269                if state.current_branch_name.is_none() {
1270                    state.current_branch_name = Some(first.to_string())
1271                }
1272            }
1273            state
1274                .branches
1275                .extend(branches.iter().map(ToString::to_string));
1276        })
1277    }
1278
1279    pub fn set_index_for_repo(&self, dot_git: &Path, index_state: &[(RepoPath, String)]) {
1280        self.with_git_state(dot_git, true, |state| {
1281            state.index_contents.clear();
1282            state.index_contents.extend(
1283                index_state
1284                    .iter()
1285                    .map(|(path, content)| (path.clone(), content.clone())),
1286            );
1287        });
1288    }
1289
1290    pub fn set_head_for_repo(&self, dot_git: &Path, head_state: &[(RepoPath, String)]) {
1291        self.with_git_state(dot_git, true, |state| {
1292            state.head_contents.clear();
1293            state.head_contents.extend(
1294                head_state
1295                    .iter()
1296                    .map(|(path, content)| (path.clone(), content.clone())),
1297            );
1298        });
1299    }
1300
1301    pub fn set_git_content_for_repo(
1302        &self,
1303        dot_git: &Path,
1304        head_state: &[(RepoPath, String, Option<String>)],
1305    ) {
1306        self.with_git_state(dot_git, true, |state| {
1307            state.head_contents.clear();
1308            state.head_contents.extend(
1309                head_state
1310                    .iter()
1311                    .map(|(path, head_content, _)| (path.clone(), head_content.clone())),
1312            );
1313            state.index_contents.clear();
1314            state.index_contents.extend(head_state.iter().map(
1315                |(path, head_content, index_content)| {
1316                    (
1317                        path.clone(),
1318                        index_content.as_ref().unwrap_or(head_content).clone(),
1319                    )
1320                },
1321            ));
1322        });
1323        self.recalculate_git_status(dot_git);
1324    }
1325
1326    pub fn recalculate_git_status(&self, dot_git: &Path) {
1327        let git_files: HashMap<_, _> = self
1328            .files()
1329            .iter()
1330            .filter_map(|path| {
1331                let repo_path =
1332                    RepoPath::new(path.strip_prefix(dot_git.parent().unwrap()).ok()?.into());
1333                let content = self
1334                    .read_file_sync(path)
1335                    .ok()
1336                    .map(|content| String::from_utf8(content).unwrap());
1337                Some((repo_path, content?))
1338            })
1339            .collect();
1340        self.with_git_state(dot_git, false, |state| {
1341            state.statuses.clear();
1342            let mut paths: HashSet<_> = state.head_contents.keys().collect();
1343            paths.extend(state.index_contents.keys());
1344            paths.extend(git_files.keys());
1345            for path in paths {
1346                let head = state.head_contents.get(path);
1347                let index = state.index_contents.get(path);
1348                let fs = git_files.get(path);
1349                let status = match (head, index, fs) {
1350                    (Some(head), Some(index), Some(fs)) => FileStatus::Tracked(TrackedStatus {
1351                        index_status: if head == index {
1352                            StatusCode::Unmodified
1353                        } else {
1354                            StatusCode::Modified
1355                        },
1356                        worktree_status: if fs == index {
1357                            StatusCode::Unmodified
1358                        } else {
1359                            StatusCode::Modified
1360                        },
1361                    }),
1362                    (Some(head), Some(index), None) => FileStatus::Tracked(TrackedStatus {
1363                        index_status: if head == index {
1364                            StatusCode::Unmodified
1365                        } else {
1366                            StatusCode::Modified
1367                        },
1368                        worktree_status: StatusCode::Deleted,
1369                    }),
1370                    (Some(_), None, Some(_)) => FileStatus::Tracked(TrackedStatus {
1371                        index_status: StatusCode::Deleted,
1372                        worktree_status: StatusCode::Added,
1373                    }),
1374                    (Some(_), None, None) => FileStatus::Tracked(TrackedStatus {
1375                        index_status: StatusCode::Deleted,
1376                        worktree_status: StatusCode::Deleted,
1377                    }),
1378                    (None, Some(index), Some(fs)) => FileStatus::Tracked(TrackedStatus {
1379                        index_status: StatusCode::Added,
1380                        worktree_status: if fs == index {
1381                            StatusCode::Unmodified
1382                        } else {
1383                            StatusCode::Modified
1384                        },
1385                    }),
1386                    (None, Some(_), None) => FileStatus::Tracked(TrackedStatus {
1387                        index_status: StatusCode::Added,
1388                        worktree_status: StatusCode::Deleted,
1389                    }),
1390                    (None, None, Some(_)) => FileStatus::Untracked,
1391                    (None, None, None) => {
1392                        unreachable!();
1393                    }
1394                };
1395                state.statuses.insert(path.clone(), status);
1396            }
1397        });
1398    }
1399
1400    pub fn set_blame_for_repo(&self, dot_git: &Path, blames: Vec<(RepoPath, git::blame::Blame)>) {
1401        self.with_git_state(dot_git, true, |state| {
1402            state.blames.clear();
1403            state.blames.extend(blames);
1404        });
1405    }
1406
1407    pub fn set_status_for_repo_via_working_copy_change(
1408        &self,
1409        dot_git: &Path,
1410        statuses: &[(&Path, FileStatus)],
1411    ) {
1412        self.with_git_state(dot_git, false, |state| {
1413            state.statuses.clear();
1414            state.statuses.extend(
1415                statuses
1416                    .iter()
1417                    .map(|(path, content)| ((**path).into(), *content)),
1418            );
1419        });
1420        self.state.lock().emit_event(
1421            statuses
1422                .iter()
1423                .map(|(path, _)| (dot_git.parent().unwrap().join(path), None)),
1424        );
1425    }
1426
1427    pub fn set_status_for_repo_via_git_operation(
1428        &self,
1429        dot_git: &Path,
1430        statuses: &[(&Path, FileStatus)],
1431    ) {
1432        self.with_git_state(dot_git, true, |state| {
1433            state.statuses.clear();
1434            state.statuses.extend(
1435                statuses
1436                    .iter()
1437                    .map(|(path, content)| ((**path).into(), *content)),
1438            );
1439        });
1440    }
1441
1442    pub fn set_error_message_for_index_write(&self, dot_git: &Path, message: Option<String>) {
1443        self.with_git_state(dot_git, true, |state| {
1444            state.simulated_index_write_error_message = message;
1445        });
1446    }
1447
1448    pub fn paths(&self, include_dot_git: bool) -> Vec<PathBuf> {
1449        let mut result = Vec::new();
1450        let mut queue = collections::VecDeque::new();
1451        queue.push_back((
1452            PathBuf::from(util::path!("/")),
1453            self.state.lock().root.clone(),
1454        ));
1455        while let Some((path, entry)) = queue.pop_front() {
1456            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
1457                for (name, entry) in entries {
1458                    queue.push_back((path.join(name), entry.clone()));
1459                }
1460            }
1461            if include_dot_git
1462                || !path
1463                    .components()
1464                    .any(|component| component.as_os_str() == *FS_DOT_GIT)
1465            {
1466                result.push(path);
1467            }
1468        }
1469        result
1470    }
1471
1472    pub fn directories(&self, include_dot_git: bool) -> Vec<PathBuf> {
1473        let mut result = Vec::new();
1474        let mut queue = collections::VecDeque::new();
1475        queue.push_back((
1476            PathBuf::from(util::path!("/")),
1477            self.state.lock().root.clone(),
1478        ));
1479        while let Some((path, entry)) = queue.pop_front() {
1480            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
1481                for (name, entry) in entries {
1482                    queue.push_back((path.join(name), entry.clone()));
1483                }
1484                if include_dot_git
1485                    || !path
1486                        .components()
1487                        .any(|component| component.as_os_str() == *FS_DOT_GIT)
1488                {
1489                    result.push(path);
1490                }
1491            }
1492        }
1493        result
1494    }
1495
1496    pub fn files(&self) -> Vec<PathBuf> {
1497        let mut result = Vec::new();
1498        let mut queue = collections::VecDeque::new();
1499        queue.push_back((
1500            PathBuf::from(util::path!("/")),
1501            self.state.lock().root.clone(),
1502        ));
1503        while let Some((path, entry)) = queue.pop_front() {
1504            let e = entry.lock();
1505            match &*e {
1506                FakeFsEntry::File { .. } => result.push(path),
1507                FakeFsEntry::Dir { entries, .. } => {
1508                    for (name, entry) in entries {
1509                        queue.push_back((path.join(name), entry.clone()));
1510                    }
1511                }
1512                FakeFsEntry::Symlink { .. } => {}
1513            }
1514        }
1515        result
1516    }
1517
1518    /// How many `read_dir` calls have been issued.
1519    pub fn read_dir_call_count(&self) -> usize {
1520        self.state.lock().read_dir_call_count
1521    }
1522
1523    /// How many `metadata` calls have been issued.
1524    pub fn metadata_call_count(&self) -> usize {
1525        self.state.lock().metadata_call_count
1526    }
1527
1528    fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
1529        self.executor.simulate_random_delay()
1530    }
1531
1532    pub fn set_home_dir(&self, home_dir: PathBuf) {
1533        self.state.lock().home_dir = Some(home_dir);
1534    }
1535}
1536
1537#[cfg(any(test, feature = "test-support"))]
1538impl FakeFsEntry {
1539    fn is_file(&self) -> bool {
1540        matches!(self, Self::File { .. })
1541    }
1542
1543    fn is_symlink(&self) -> bool {
1544        matches!(self, Self::Symlink { .. })
1545    }
1546
1547    fn file_content(&self, path: &Path) -> Result<&Vec<u8>> {
1548        if let Self::File { content, .. } = self {
1549            Ok(content)
1550        } else {
1551            Err(anyhow!("not a file: {}", path.display()))
1552        }
1553    }
1554
1555    fn dir_entries(
1556        &mut self,
1557        path: &Path,
1558    ) -> Result<&mut BTreeMap<String, Arc<Mutex<FakeFsEntry>>>> {
1559        if let Self::Dir { entries, .. } = self {
1560            Ok(entries)
1561        } else {
1562            Err(anyhow!("not a directory: {}", path.display()))
1563        }
1564    }
1565}
1566
1567#[cfg(any(test, feature = "test-support"))]
1568struct FakeWatcher {}
1569
1570#[cfg(any(test, feature = "test-support"))]
1571impl Watcher for FakeWatcher {
1572    fn add(&self, _: &Path) -> Result<()> {
1573        Ok(())
1574    }
1575
1576    fn remove(&self, _: &Path) -> Result<()> {
1577        Ok(())
1578    }
1579}
1580
1581#[cfg(any(test, feature = "test-support"))]
1582#[derive(Debug)]
1583struct FakeHandle {
1584    inode: u64,
1585}
1586
1587#[cfg(any(test, feature = "test-support"))]
1588impl FileHandle for FakeHandle {
1589    fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf> {
1590        let fs = fs.as_fake();
1591        let state = fs.state.lock();
1592        let Some(target) = state.moves.get(&self.inode) else {
1593            anyhow::bail!("fake fd not moved")
1594        };
1595
1596        if state.try_read_path(&target, false).is_some() {
1597            return Ok(target.clone());
1598        }
1599        anyhow::bail!("fake fd target not found")
1600    }
1601}
1602
1603#[cfg(any(test, feature = "test-support"))]
1604#[async_trait::async_trait]
1605impl Fs for FakeFs {
1606    async fn create_dir(&self, path: &Path) -> Result<()> {
1607        self.simulate_random_delay().await;
1608
1609        let mut created_dirs = Vec::new();
1610        let mut cur_path = PathBuf::new();
1611        for component in path.components() {
1612            let should_skip = matches!(component, Component::Prefix(..) | Component::RootDir);
1613            cur_path.push(component);
1614            if should_skip {
1615                continue;
1616            }
1617            let mut state = self.state.lock();
1618
1619            let inode = state.get_and_increment_inode();
1620            let mtime = state.get_and_increment_mtime();
1621            state.write_path(&cur_path, |entry| {
1622                entry.or_insert_with(|| {
1623                    created_dirs.push((cur_path.clone(), Some(PathEventKind::Created)));
1624                    Arc::new(Mutex::new(FakeFsEntry::Dir {
1625                        inode,
1626                        mtime,
1627                        len: 0,
1628                        entries: Default::default(),
1629                        git_repo_state: None,
1630                    }))
1631                });
1632                Ok(())
1633            })?
1634        }
1635
1636        self.state.lock().emit_event(created_dirs);
1637        Ok(())
1638    }
1639
1640    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
1641        self.simulate_random_delay().await;
1642        let mut state = self.state.lock();
1643        let inode = state.get_and_increment_inode();
1644        let mtime = state.get_and_increment_mtime();
1645        let file = Arc::new(Mutex::new(FakeFsEntry::File {
1646            inode,
1647            mtime,
1648            len: 0,
1649            content: Vec::new(),
1650        }));
1651        let mut kind = Some(PathEventKind::Created);
1652        state.write_path(path, |entry| {
1653            match entry {
1654                btree_map::Entry::Occupied(mut e) => {
1655                    if options.overwrite {
1656                        kind = Some(PathEventKind::Changed);
1657                        *e.get_mut() = file;
1658                    } else if !options.ignore_if_exists {
1659                        return Err(anyhow!("path already exists: {}", path.display()));
1660                    }
1661                }
1662                btree_map::Entry::Vacant(e) => {
1663                    e.insert(file);
1664                }
1665            }
1666            Ok(())
1667        })?;
1668        state.emit_event([(path, kind)]);
1669        Ok(())
1670    }
1671
1672    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()> {
1673        let mut state = self.state.lock();
1674        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
1675        state
1676            .write_path(path.as_ref(), move |e| match e {
1677                btree_map::Entry::Vacant(e) => {
1678                    e.insert(file);
1679                    Ok(())
1680                }
1681                btree_map::Entry::Occupied(mut e) => {
1682                    *e.get_mut() = file;
1683                    Ok(())
1684                }
1685            })
1686            .unwrap();
1687        state.emit_event([(path, None)]);
1688
1689        Ok(())
1690    }
1691
1692    async fn create_file_with(
1693        &self,
1694        path: &Path,
1695        mut content: Pin<&mut (dyn AsyncRead + Send)>,
1696    ) -> Result<()> {
1697        let mut bytes = Vec::new();
1698        content.read_to_end(&mut bytes).await?;
1699        self.write_file_internal(path, bytes)?;
1700        Ok(())
1701    }
1702
1703    async fn extract_tar_file(
1704        &self,
1705        path: &Path,
1706        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
1707    ) -> Result<()> {
1708        let mut entries = content.entries()?;
1709        while let Some(entry) = entries.next().await {
1710            let mut entry = entry?;
1711            if entry.header().entry_type().is_file() {
1712                let path = path.join(entry.path()?.as_ref());
1713                let mut bytes = Vec::new();
1714                entry.read_to_end(&mut bytes).await?;
1715                self.create_dir(path.parent().unwrap()).await?;
1716                self.write_file_internal(&path, bytes)?;
1717            }
1718        }
1719        Ok(())
1720    }
1721
1722    async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
1723        self.simulate_random_delay().await;
1724
1725        let old_path = normalize_path(old_path);
1726        let new_path = normalize_path(new_path);
1727
1728        let mut state = self.state.lock();
1729        let moved_entry = state.write_path(&old_path, |e| {
1730            if let btree_map::Entry::Occupied(e) = e {
1731                Ok(e.get().clone())
1732            } else {
1733                Err(anyhow!("path does not exist: {}", &old_path.display()))
1734            }
1735        })?;
1736
1737        let inode = match *moved_entry.lock() {
1738            FakeFsEntry::File { inode, .. } => inode,
1739            FakeFsEntry::Dir { inode, .. } => inode,
1740            _ => 0,
1741        };
1742
1743        state.moves.insert(inode, new_path.clone());
1744
1745        state.write_path(&new_path, |e| {
1746            match e {
1747                btree_map::Entry::Occupied(mut e) => {
1748                    if options.overwrite {
1749                        *e.get_mut() = moved_entry;
1750                    } else if !options.ignore_if_exists {
1751                        return Err(anyhow!("path already exists: {}", new_path.display()));
1752                    }
1753                }
1754                btree_map::Entry::Vacant(e) => {
1755                    e.insert(moved_entry);
1756                }
1757            }
1758            Ok(())
1759        })?;
1760
1761        state
1762            .write_path(&old_path, |e| {
1763                if let btree_map::Entry::Occupied(e) = e {
1764                    Ok(e.remove())
1765                } else {
1766                    unreachable!()
1767                }
1768            })
1769            .unwrap();
1770
1771        state.emit_event([
1772            (old_path, Some(PathEventKind::Removed)),
1773            (new_path, Some(PathEventKind::Created)),
1774        ]);
1775        Ok(())
1776    }
1777
1778    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
1779        self.simulate_random_delay().await;
1780
1781        let source = normalize_path(source);
1782        let target = normalize_path(target);
1783        let mut state = self.state.lock();
1784        let mtime = state.get_and_increment_mtime();
1785        let inode = state.get_and_increment_inode();
1786        let source_entry = state.read_path(&source)?;
1787        let content = source_entry.lock().file_content(&source)?.clone();
1788        let mut kind = Some(PathEventKind::Created);
1789        state.write_path(&target, |e| match e {
1790            btree_map::Entry::Occupied(e) => {
1791                if options.overwrite {
1792                    kind = Some(PathEventKind::Changed);
1793                    Ok(Some(e.get().clone()))
1794                } else if !options.ignore_if_exists {
1795                    return Err(anyhow!("{target:?} already exists"));
1796                } else {
1797                    Ok(None)
1798                }
1799            }
1800            btree_map::Entry::Vacant(e) => Ok(Some(
1801                e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
1802                    inode,
1803                    mtime,
1804                    len: content.len() as u64,
1805                    content,
1806                })))
1807                .clone(),
1808            )),
1809        })?;
1810        state.emit_event([(target, kind)]);
1811        Ok(())
1812    }
1813
1814    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
1815        self.simulate_random_delay().await;
1816
1817        let path = normalize_path(path);
1818        let parent_path = path
1819            .parent()
1820            .ok_or_else(|| anyhow!("cannot remove the root"))?;
1821        let base_name = path.file_name().unwrap();
1822
1823        let mut state = self.state.lock();
1824        let parent_entry = state.read_path(parent_path)?;
1825        let mut parent_entry = parent_entry.lock();
1826        let entry = parent_entry
1827            .dir_entries(parent_path)?
1828            .entry(base_name.to_str().unwrap().into());
1829
1830        match entry {
1831            btree_map::Entry::Vacant(_) => {
1832                if !options.ignore_if_not_exists {
1833                    return Err(anyhow!("{path:?} does not exist"));
1834                }
1835            }
1836            btree_map::Entry::Occupied(e) => {
1837                {
1838                    let mut entry = e.get().lock();
1839                    let children = entry.dir_entries(&path)?;
1840                    if !options.recursive && !children.is_empty() {
1841                        return Err(anyhow!("{path:?} is not empty"));
1842                    }
1843                }
1844                e.remove();
1845            }
1846        }
1847        state.emit_event([(path, Some(PathEventKind::Removed))]);
1848        Ok(())
1849    }
1850
1851    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
1852        self.simulate_random_delay().await;
1853
1854        let path = normalize_path(path);
1855        let parent_path = path
1856            .parent()
1857            .ok_or_else(|| anyhow!("cannot remove the root"))?;
1858        let base_name = path.file_name().unwrap();
1859        let mut state = self.state.lock();
1860        let parent_entry = state.read_path(parent_path)?;
1861        let mut parent_entry = parent_entry.lock();
1862        let entry = parent_entry
1863            .dir_entries(parent_path)?
1864            .entry(base_name.to_str().unwrap().into());
1865        match entry {
1866            btree_map::Entry::Vacant(_) => {
1867                if !options.ignore_if_not_exists {
1868                    return Err(anyhow!("{path:?} does not exist"));
1869                }
1870            }
1871            btree_map::Entry::Occupied(e) => {
1872                e.get().lock().file_content(&path)?;
1873                e.remove();
1874            }
1875        }
1876        state.emit_event([(path, Some(PathEventKind::Removed))]);
1877        Ok(())
1878    }
1879
1880    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read + Send + Sync>> {
1881        let bytes = self.load_internal(path).await?;
1882        Ok(Box::new(io::Cursor::new(bytes)))
1883    }
1884
1885    async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>> {
1886        self.simulate_random_delay().await;
1887        let state = self.state.lock();
1888        let entry = state.read_path(&path)?;
1889        let entry = entry.lock();
1890        let inode = match *entry {
1891            FakeFsEntry::File { inode, .. } => inode,
1892            FakeFsEntry::Dir { inode, .. } => inode,
1893            _ => unreachable!(),
1894        };
1895        Ok(Arc::new(FakeHandle { inode }))
1896    }
1897
1898    async fn load(&self, path: &Path) -> Result<String> {
1899        let content = self.load_internal(path).await?;
1900        Ok(String::from_utf8(content.clone())?)
1901    }
1902
1903    async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
1904        self.load_internal(path).await
1905    }
1906
1907    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
1908        self.simulate_random_delay().await;
1909        let path = normalize_path(path.as_path());
1910        self.write_file_internal(path, data.into_bytes())?;
1911        Ok(())
1912    }
1913
1914    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
1915        self.simulate_random_delay().await;
1916        let path = normalize_path(path);
1917        let content = chunks(text, line_ending).collect::<String>();
1918        if let Some(path) = path.parent() {
1919            self.create_dir(path).await?;
1920        }
1921        self.write_file_internal(path, content.into_bytes())?;
1922        Ok(())
1923    }
1924
1925    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
1926        let path = normalize_path(path);
1927        self.simulate_random_delay().await;
1928        let state = self.state.lock();
1929        if let Some((_, canonical_path)) = state.try_read_path(&path, true) {
1930            Ok(canonical_path)
1931        } else {
1932            Err(anyhow!("path does not exist: {}", path.display()))
1933        }
1934    }
1935
1936    async fn is_file(&self, path: &Path) -> bool {
1937        let path = normalize_path(path);
1938        self.simulate_random_delay().await;
1939        let state = self.state.lock();
1940        if let Some((entry, _)) = state.try_read_path(&path, true) {
1941            entry.lock().is_file()
1942        } else {
1943            false
1944        }
1945    }
1946
1947    async fn is_dir(&self, path: &Path) -> bool {
1948        self.metadata(path)
1949            .await
1950            .is_ok_and(|metadata| metadata.is_some_and(|metadata| metadata.is_dir))
1951    }
1952
1953    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
1954        self.simulate_random_delay().await;
1955        let path = normalize_path(path);
1956        let mut state = self.state.lock();
1957        state.metadata_call_count += 1;
1958        if let Some((mut entry, _)) = state.try_read_path(&path, false) {
1959            let is_symlink = entry.lock().is_symlink();
1960            if is_symlink {
1961                if let Some(e) = state.try_read_path(&path, true).map(|e| e.0) {
1962                    entry = e;
1963                } else {
1964                    return Ok(None);
1965                }
1966            }
1967
1968            let entry = entry.lock();
1969            Ok(Some(match &*entry {
1970                FakeFsEntry::File {
1971                    inode, mtime, len, ..
1972                } => Metadata {
1973                    inode: *inode,
1974                    mtime: *mtime,
1975                    len: *len,
1976                    is_dir: false,
1977                    is_symlink,
1978                    is_fifo: false,
1979                },
1980                FakeFsEntry::Dir {
1981                    inode, mtime, len, ..
1982                } => Metadata {
1983                    inode: *inode,
1984                    mtime: *mtime,
1985                    len: *len,
1986                    is_dir: true,
1987                    is_symlink,
1988                    is_fifo: false,
1989                },
1990                FakeFsEntry::Symlink { .. } => unreachable!(),
1991            }))
1992        } else {
1993            Ok(None)
1994        }
1995    }
1996
1997    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
1998        self.simulate_random_delay().await;
1999        let path = normalize_path(path);
2000        let state = self.state.lock();
2001        if let Some((entry, _)) = state.try_read_path(&path, false) {
2002            let entry = entry.lock();
2003            if let FakeFsEntry::Symlink { target } = &*entry {
2004                Ok(target.clone())
2005            } else {
2006                Err(anyhow!("not a symlink: {}", path.display()))
2007            }
2008        } else {
2009            Err(anyhow!("path does not exist: {}", path.display()))
2010        }
2011    }
2012
2013    async fn read_dir(
2014        &self,
2015        path: &Path,
2016    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
2017        self.simulate_random_delay().await;
2018        let path = normalize_path(path);
2019        let mut state = self.state.lock();
2020        state.read_dir_call_count += 1;
2021        let entry = state.read_path(&path)?;
2022        let mut entry = entry.lock();
2023        let children = entry.dir_entries(&path)?;
2024        let paths = children
2025            .keys()
2026            .map(|file_name| Ok(path.join(file_name)))
2027            .collect::<Vec<_>>();
2028        Ok(Box::pin(futures::stream::iter(paths)))
2029    }
2030
2031    async fn watch(
2032        &self,
2033        path: &Path,
2034        _: Duration,
2035    ) -> (
2036        Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
2037        Arc<dyn Watcher>,
2038    ) {
2039        self.simulate_random_delay().await;
2040        let (tx, rx) = smol::channel::unbounded();
2041        self.state.lock().event_txs.push(tx);
2042        let path = path.to_path_buf();
2043        let executor = self.executor.clone();
2044        (
2045            Box::pin(futures::StreamExt::filter(rx, move |events| {
2046                let result = events
2047                    .iter()
2048                    .any(|evt_path| evt_path.path.starts_with(&path));
2049                let executor = executor.clone();
2050                async move {
2051                    executor.simulate_random_delay().await;
2052                    result
2053                }
2054            })),
2055            Arc::new(FakeWatcher {}),
2056        )
2057    }
2058
2059    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>> {
2060        let state = self.state.lock();
2061        let entry = state.read_path(abs_dot_git).unwrap();
2062        let mut entry = entry.lock();
2063        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
2064            let state = git_repo_state
2065                .get_or_insert_with(|| {
2066                    Arc::new(Mutex::new(FakeGitRepositoryState::new(
2067                        abs_dot_git.to_path_buf(),
2068                        state.git_event_tx.clone(),
2069                    )))
2070                })
2071                .clone();
2072            Some(git::repository::FakeGitRepository::open(state))
2073        } else {
2074            None
2075        }
2076    }
2077
2078    fn is_fake(&self) -> bool {
2079        true
2080    }
2081
2082    async fn is_case_sensitive(&self) -> Result<bool> {
2083        Ok(true)
2084    }
2085
2086    #[cfg(any(test, feature = "test-support"))]
2087    fn as_fake(&self) -> Arc<FakeFs> {
2088        self.this.upgrade().unwrap()
2089    }
2090
2091    fn home_dir(&self) -> Option<PathBuf> {
2092        self.state.lock().home_dir.clone()
2093    }
2094}
2095
2096fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
2097    rope.chunks().flat_map(move |chunk| {
2098        let mut newline = false;
2099        chunk.split('\n').flat_map(move |line| {
2100            let ending = if newline {
2101                Some(line_ending.as_str())
2102            } else {
2103                None
2104            };
2105            newline = true;
2106            ending.into_iter().chain([line])
2107        })
2108    })
2109}
2110
2111pub fn normalize_path(path: &Path) -> PathBuf {
2112    let mut components = path.components().peekable();
2113    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
2114        components.next();
2115        PathBuf::from(c.as_os_str())
2116    } else {
2117        PathBuf::new()
2118    };
2119
2120    for component in components {
2121        match component {
2122            Component::Prefix(..) => unreachable!(),
2123            Component::RootDir => {
2124                ret.push(component.as_os_str());
2125            }
2126            Component::CurDir => {}
2127            Component::ParentDir => {
2128                ret.pop();
2129            }
2130            Component::Normal(c) => {
2131                ret.push(c);
2132            }
2133        }
2134    }
2135    ret
2136}
2137
2138pub async fn copy_recursive<'a>(
2139    fs: &'a dyn Fs,
2140    source: &'a Path,
2141    target: &'a Path,
2142    options: CopyOptions,
2143) -> Result<()> {
2144    for (is_dir, item) in read_dir_items(fs, source).await? {
2145        let Ok(item_relative_path) = item.strip_prefix(source) else {
2146            continue;
2147        };
2148        let target_item = if item_relative_path == Path::new("") {
2149            target.to_path_buf()
2150        } else {
2151            target.join(item_relative_path)
2152        };
2153        if is_dir {
2154            if !options.overwrite && fs.metadata(&target_item).await.is_ok_and(|m| m.is_some()) {
2155                if options.ignore_if_exists {
2156                    continue;
2157                } else {
2158                    return Err(anyhow!("{target_item:?} already exists"));
2159                }
2160            }
2161            let _ = fs
2162                .remove_dir(
2163                    &target_item,
2164                    RemoveOptions {
2165                        recursive: true,
2166                        ignore_if_not_exists: true,
2167                    },
2168                )
2169                .await;
2170            fs.create_dir(&target_item).await?;
2171        } else {
2172            fs.copy_file(&item, &target_item, options).await?;
2173        }
2174    }
2175    Ok(())
2176}
2177
2178async fn read_dir_items<'a>(fs: &'a dyn Fs, source: &'a Path) -> Result<Vec<(bool, PathBuf)>> {
2179    let mut items = Vec::new();
2180    read_recursive(fs, source, &mut items).await?;
2181    Ok(items)
2182}
2183
2184fn read_recursive<'a>(
2185    fs: &'a dyn Fs,
2186    source: &'a Path,
2187    output: &'a mut Vec<(bool, PathBuf)>,
2188) -> BoxFuture<'a, Result<()>> {
2189    use futures::future::FutureExt;
2190
2191    async move {
2192        let metadata = fs
2193            .metadata(source)
2194            .await?
2195            .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
2196
2197        if metadata.is_dir {
2198            output.push((true, source.to_path_buf()));
2199            let mut children = fs.read_dir(source).await?;
2200            while let Some(child_path) = children.next().await {
2201                if let Ok(child_path) = child_path {
2202                    read_recursive(fs, &child_path, output).await?;
2203                }
2204            }
2205        } else {
2206            output.push((false, source.to_path_buf()));
2207        }
2208        Ok(())
2209    }
2210    .boxed()
2211}
2212
2213// todo(windows)
2214// can we get file id not open the file twice?
2215// https://github.com/rust-lang/rust/issues/63010
2216#[cfg(target_os = "windows")]
2217async fn file_id(path: impl AsRef<Path>) -> Result<u64> {
2218    use std::os::windows::io::AsRawHandle;
2219
2220    use smol::fs::windows::OpenOptionsExt;
2221    use windows::Win32::{
2222        Foundation::HANDLE,
2223        Storage::FileSystem::{
2224            GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS,
2225        },
2226    };
2227
2228    let file = smol::fs::OpenOptions::new()
2229        .read(true)
2230        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS.0)
2231        .open(path)
2232        .await?;
2233
2234    let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
2235    // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
2236    // This function supports Windows XP+
2237    smol::unblock(move || {
2238        unsafe { GetFileInformationByHandle(HANDLE(file.as_raw_handle() as _), &mut info)? };
2239
2240        Ok(((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64))
2241    })
2242    .await
2243}
2244
2245#[cfg(test)]
2246mod tests {
2247    use super::*;
2248    use gpui::BackgroundExecutor;
2249    use serde_json::json;
2250    use util::path;
2251
2252    #[gpui::test]
2253    async fn test_fake_fs(executor: BackgroundExecutor) {
2254        let fs = FakeFs::new(executor.clone());
2255        fs.insert_tree(
2256            path!("/root"),
2257            json!({
2258                "dir1": {
2259                    "a": "A",
2260                    "b": "B"
2261                },
2262                "dir2": {
2263                    "c": "C",
2264                    "dir3": {
2265                        "d": "D"
2266                    }
2267                }
2268            }),
2269        )
2270        .await;
2271
2272        assert_eq!(
2273            fs.files(),
2274            vec![
2275                PathBuf::from(path!("/root/dir1/a")),
2276                PathBuf::from(path!("/root/dir1/b")),
2277                PathBuf::from(path!("/root/dir2/c")),
2278                PathBuf::from(path!("/root/dir2/dir3/d")),
2279            ]
2280        );
2281
2282        fs.create_symlink(path!("/root/dir2/link-to-dir3").as_ref(), "./dir3".into())
2283            .await
2284            .unwrap();
2285
2286        assert_eq!(
2287            fs.canonicalize(path!("/root/dir2/link-to-dir3").as_ref())
2288                .await
2289                .unwrap(),
2290            PathBuf::from(path!("/root/dir2/dir3")),
2291        );
2292        assert_eq!(
2293            fs.canonicalize(path!("/root/dir2/link-to-dir3/d").as_ref())
2294                .await
2295                .unwrap(),
2296            PathBuf::from(path!("/root/dir2/dir3/d")),
2297        );
2298        assert_eq!(
2299            fs.load(path!("/root/dir2/link-to-dir3/d").as_ref())
2300                .await
2301                .unwrap(),
2302            "D",
2303        );
2304    }
2305
2306    #[gpui::test]
2307    async fn test_copy_recursive_with_single_file(executor: BackgroundExecutor) {
2308        let fs = FakeFs::new(executor.clone());
2309        fs.insert_tree(
2310            path!("/outer"),
2311            json!({
2312                "a": "A",
2313                "b": "B",
2314                "inner": {}
2315            }),
2316        )
2317        .await;
2318
2319        assert_eq!(
2320            fs.files(),
2321            vec![
2322                PathBuf::from(path!("/outer/a")),
2323                PathBuf::from(path!("/outer/b")),
2324            ]
2325        );
2326
2327        let source = Path::new(path!("/outer/a"));
2328        let target = Path::new(path!("/outer/a copy"));
2329        copy_recursive(fs.as_ref(), source, target, Default::default())
2330            .await
2331            .unwrap();
2332
2333        assert_eq!(
2334            fs.files(),
2335            vec![
2336                PathBuf::from(path!("/outer/a")),
2337                PathBuf::from(path!("/outer/a copy")),
2338                PathBuf::from(path!("/outer/b")),
2339            ]
2340        );
2341
2342        let source = Path::new(path!("/outer/a"));
2343        let target = Path::new(path!("/outer/inner/a copy"));
2344        copy_recursive(fs.as_ref(), source, target, Default::default())
2345            .await
2346            .unwrap();
2347
2348        assert_eq!(
2349            fs.files(),
2350            vec![
2351                PathBuf::from(path!("/outer/a")),
2352                PathBuf::from(path!("/outer/a copy")),
2353                PathBuf::from(path!("/outer/b")),
2354                PathBuf::from(path!("/outer/inner/a copy")),
2355            ]
2356        );
2357    }
2358
2359    #[gpui::test]
2360    async fn test_copy_recursive_with_single_dir(executor: BackgroundExecutor) {
2361        let fs = FakeFs::new(executor.clone());
2362        fs.insert_tree(
2363            path!("/outer"),
2364            json!({
2365                "a": "A",
2366                "empty": {},
2367                "non-empty": {
2368                    "b": "B",
2369                }
2370            }),
2371        )
2372        .await;
2373
2374        assert_eq!(
2375            fs.files(),
2376            vec![
2377                PathBuf::from(path!("/outer/a")),
2378                PathBuf::from(path!("/outer/non-empty/b")),
2379            ]
2380        );
2381        assert_eq!(
2382            fs.directories(false),
2383            vec![
2384                PathBuf::from(path!("/")),
2385                PathBuf::from(path!("/outer")),
2386                PathBuf::from(path!("/outer/empty")),
2387                PathBuf::from(path!("/outer/non-empty")),
2388            ]
2389        );
2390
2391        let source = Path::new(path!("/outer/empty"));
2392        let target = Path::new(path!("/outer/empty copy"));
2393        copy_recursive(fs.as_ref(), source, target, Default::default())
2394            .await
2395            .unwrap();
2396
2397        assert_eq!(
2398            fs.files(),
2399            vec![
2400                PathBuf::from(path!("/outer/a")),
2401                PathBuf::from(path!("/outer/non-empty/b")),
2402            ]
2403        );
2404        assert_eq!(
2405            fs.directories(false),
2406            vec![
2407                PathBuf::from(path!("/")),
2408                PathBuf::from(path!("/outer")),
2409                PathBuf::from(path!("/outer/empty")),
2410                PathBuf::from(path!("/outer/empty copy")),
2411                PathBuf::from(path!("/outer/non-empty")),
2412            ]
2413        );
2414
2415        let source = Path::new(path!("/outer/non-empty"));
2416        let target = Path::new(path!("/outer/non-empty copy"));
2417        copy_recursive(fs.as_ref(), source, target, Default::default())
2418            .await
2419            .unwrap();
2420
2421        assert_eq!(
2422            fs.files(),
2423            vec![
2424                PathBuf::from(path!("/outer/a")),
2425                PathBuf::from(path!("/outer/non-empty/b")),
2426                PathBuf::from(path!("/outer/non-empty copy/b")),
2427            ]
2428        );
2429        assert_eq!(
2430            fs.directories(false),
2431            vec![
2432                PathBuf::from(path!("/")),
2433                PathBuf::from(path!("/outer")),
2434                PathBuf::from(path!("/outer/empty")),
2435                PathBuf::from(path!("/outer/empty copy")),
2436                PathBuf::from(path!("/outer/non-empty")),
2437                PathBuf::from(path!("/outer/non-empty copy")),
2438            ]
2439        );
2440    }
2441
2442    #[gpui::test]
2443    async fn test_copy_recursive(executor: BackgroundExecutor) {
2444        let fs = FakeFs::new(executor.clone());
2445        fs.insert_tree(
2446            path!("/outer"),
2447            json!({
2448                "inner1": {
2449                    "a": "A",
2450                    "b": "B",
2451                    "inner3": {
2452                        "d": "D",
2453                    },
2454                    "inner4": {}
2455                },
2456                "inner2": {
2457                    "c": "C",
2458                }
2459            }),
2460        )
2461        .await;
2462
2463        assert_eq!(
2464            fs.files(),
2465            vec![
2466                PathBuf::from(path!("/outer/inner1/a")),
2467                PathBuf::from(path!("/outer/inner1/b")),
2468                PathBuf::from(path!("/outer/inner2/c")),
2469                PathBuf::from(path!("/outer/inner1/inner3/d")),
2470            ]
2471        );
2472        assert_eq!(
2473            fs.directories(false),
2474            vec![
2475                PathBuf::from(path!("/")),
2476                PathBuf::from(path!("/outer")),
2477                PathBuf::from(path!("/outer/inner1")),
2478                PathBuf::from(path!("/outer/inner2")),
2479                PathBuf::from(path!("/outer/inner1/inner3")),
2480                PathBuf::from(path!("/outer/inner1/inner4")),
2481            ]
2482        );
2483
2484        let source = Path::new(path!("/outer"));
2485        let target = Path::new(path!("/outer/inner1/outer"));
2486        copy_recursive(fs.as_ref(), source, target, Default::default())
2487            .await
2488            .unwrap();
2489
2490        assert_eq!(
2491            fs.files(),
2492            vec![
2493                PathBuf::from(path!("/outer/inner1/a")),
2494                PathBuf::from(path!("/outer/inner1/b")),
2495                PathBuf::from(path!("/outer/inner2/c")),
2496                PathBuf::from(path!("/outer/inner1/inner3/d")),
2497                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2498                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
2499                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
2500                PathBuf::from(path!("/outer/inner1/outer/inner1/inner3/d")),
2501            ]
2502        );
2503        assert_eq!(
2504            fs.directories(false),
2505            vec![
2506                PathBuf::from(path!("/")),
2507                PathBuf::from(path!("/outer")),
2508                PathBuf::from(path!("/outer/inner1")),
2509                PathBuf::from(path!("/outer/inner2")),
2510                PathBuf::from(path!("/outer/inner1/inner3")),
2511                PathBuf::from(path!("/outer/inner1/inner4")),
2512                PathBuf::from(path!("/outer/inner1/outer")),
2513                PathBuf::from(path!("/outer/inner1/outer/inner1")),
2514                PathBuf::from(path!("/outer/inner1/outer/inner2")),
2515                PathBuf::from(path!("/outer/inner1/outer/inner1/inner3")),
2516                PathBuf::from(path!("/outer/inner1/outer/inner1/inner4")),
2517            ]
2518        );
2519    }
2520
2521    #[gpui::test]
2522    async fn test_copy_recursive_with_overwriting(executor: BackgroundExecutor) {
2523        let fs = FakeFs::new(executor.clone());
2524        fs.insert_tree(
2525            path!("/outer"),
2526            json!({
2527                "inner1": {
2528                    "a": "A",
2529                    "b": "B",
2530                    "outer": {
2531                        "inner1": {
2532                            "a": "B"
2533                        }
2534                    }
2535                },
2536                "inner2": {
2537                    "c": "C",
2538                }
2539            }),
2540        )
2541        .await;
2542
2543        assert_eq!(
2544            fs.files(),
2545            vec![
2546                PathBuf::from(path!("/outer/inner1/a")),
2547                PathBuf::from(path!("/outer/inner1/b")),
2548                PathBuf::from(path!("/outer/inner2/c")),
2549                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2550            ]
2551        );
2552        assert_eq!(
2553            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2554                .await
2555                .unwrap(),
2556            "B",
2557        );
2558
2559        let source = Path::new(path!("/outer"));
2560        let target = Path::new(path!("/outer/inner1/outer"));
2561        copy_recursive(
2562            fs.as_ref(),
2563            source,
2564            target,
2565            CopyOptions {
2566                overwrite: true,
2567                ..Default::default()
2568            },
2569        )
2570        .await
2571        .unwrap();
2572
2573        assert_eq!(
2574            fs.files(),
2575            vec![
2576                PathBuf::from(path!("/outer/inner1/a")),
2577                PathBuf::from(path!("/outer/inner1/b")),
2578                PathBuf::from(path!("/outer/inner2/c")),
2579                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2580                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
2581                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
2582                PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
2583            ]
2584        );
2585        assert_eq!(
2586            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2587                .await
2588                .unwrap(),
2589            "A"
2590        );
2591    }
2592
2593    #[gpui::test]
2594    async fn test_copy_recursive_with_ignoring(executor: BackgroundExecutor) {
2595        let fs = FakeFs::new(executor.clone());
2596        fs.insert_tree(
2597            path!("/outer"),
2598            json!({
2599                "inner1": {
2600                    "a": "A",
2601                    "b": "B",
2602                    "outer": {
2603                        "inner1": {
2604                            "a": "B"
2605                        }
2606                    }
2607                },
2608                "inner2": {
2609                    "c": "C",
2610                }
2611            }),
2612        )
2613        .await;
2614
2615        assert_eq!(
2616            fs.files(),
2617            vec![
2618                PathBuf::from(path!("/outer/inner1/a")),
2619                PathBuf::from(path!("/outer/inner1/b")),
2620                PathBuf::from(path!("/outer/inner2/c")),
2621                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2622            ]
2623        );
2624        assert_eq!(
2625            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2626                .await
2627                .unwrap(),
2628            "B",
2629        );
2630
2631        let source = Path::new(path!("/outer"));
2632        let target = Path::new(path!("/outer/inner1/outer"));
2633        copy_recursive(
2634            fs.as_ref(),
2635            source,
2636            target,
2637            CopyOptions {
2638                ignore_if_exists: true,
2639                ..Default::default()
2640            },
2641        )
2642        .await
2643        .unwrap();
2644
2645        assert_eq!(
2646            fs.files(),
2647            vec![
2648                PathBuf::from(path!("/outer/inner1/a")),
2649                PathBuf::from(path!("/outer/inner1/b")),
2650                PathBuf::from(path!("/outer/inner2/c")),
2651                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2652                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
2653                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
2654                PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
2655            ]
2656        );
2657        assert_eq!(
2658            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2659                .await
2660                .unwrap(),
2661            "B"
2662        );
2663    }
2664}