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(target_os = "linux", target_os = "freebsd"))]
   9use ashpd::desktop::trash;
  10use gpui::App;
  11use gpui::BackgroundExecutor;
  12use gpui::Global;
  13use gpui::ReadGlobal as _;
  14use std::borrow::Cow;
  15use util::command::new_std_command;
  16
  17#[cfg(unix)]
  18use std::os::fd::{AsFd, AsRawFd};
  19
  20#[cfg(unix)]
  21use std::os::unix::fs::{FileTypeExt, MetadataExt};
  22
  23use async_tar::Archive;
  24use futures::{future::BoxFuture, AsyncRead, Stream, StreamExt};
  25use git::repository::{GitRepository, RealGitRepository};
  26use rope::Rope;
  27use serde::{Deserialize, Serialize};
  28use smol::io::AsyncWriteExt;
  29use std::{
  30    io::{self, Write},
  31    path::{Component, Path, PathBuf},
  32    pin::Pin,
  33    sync::Arc,
  34    time::{Duration, SystemTime, UNIX_EPOCH},
  35};
  36use tempfile::{NamedTempFile, TempDir};
  37use text::LineEnding;
  38
  39#[cfg(any(test, feature = "test-support"))]
  40mod fake_git_repo;
  41#[cfg(any(test, feature = "test-support"))]
  42use collections::{btree_map, BTreeMap};
  43#[cfg(any(test, feature = "test-support"))]
  44use fake_git_repo::FakeGitRepositoryState;
  45#[cfg(any(test, feature = "test-support"))]
  46use git::{
  47    repository::RepoPath,
  48    status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus},
  49};
  50#[cfg(any(test, feature = "test-support"))]
  51use parking_lot::Mutex;
  52#[cfg(any(test, feature = "test-support"))]
  53use smol::io::AsyncReadExt;
  54#[cfg(any(test, feature = "test-support"))]
  55use std::ffi::OsStr;
  56
  57pub trait Watcher: Send + Sync {
  58    fn add(&self, path: &Path) -> Result<()>;
  59    fn remove(&self, path: &Path) -> Result<()>;
  60}
  61
  62#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
  63pub enum PathEventKind {
  64    Removed,
  65    Created,
  66    Changed,
  67}
  68
  69#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
  70pub struct PathEvent {
  71    pub path: PathBuf,
  72    pub kind: Option<PathEventKind>,
  73}
  74
  75impl From<PathEvent> for PathBuf {
  76    fn from(event: PathEvent) -> Self {
  77        event.path
  78    }
  79}
  80
  81#[async_trait::async_trait]
  82pub trait Fs: Send + Sync {
  83    async fn create_dir(&self, path: &Path) -> Result<()>;
  84    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()>;
  85    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
  86    async fn create_file_with(
  87        &self,
  88        path: &Path,
  89        content: Pin<&mut (dyn AsyncRead + Send)>,
  90    ) -> Result<()>;
  91    async fn extract_tar_file(
  92        &self,
  93        path: &Path,
  94        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
  95    ) -> Result<()>;
  96    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
  97    async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
  98    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
  99    async fn trash_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 100        self.remove_dir(path, options).await
 101    }
 102    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
 103    async fn trash_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
 104        self.remove_file(path, options).await
 105    }
 106    async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>>;
 107    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read + Send + Sync>>;
 108    async fn load(&self, path: &Path) -> Result<String> {
 109        Ok(String::from_utf8(self.load_bytes(path).await?)?)
 110    }
 111    async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>>;
 112    async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
 113    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
 114    async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
 115    async fn is_file(&self, path: &Path) -> bool;
 116    async fn is_dir(&self, path: &Path) -> bool;
 117    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
 118    async fn read_link(&self, path: &Path) -> Result<PathBuf>;
 119    async fn read_dir(
 120        &self,
 121        path: &Path,
 122    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
 123
 124    async fn watch(
 125        &self,
 126        path: &Path,
 127        latency: Duration,
 128    ) -> (
 129        Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
 130        Arc<dyn Watcher>,
 131    );
 132
 133    fn home_dir(&self) -> Option<PathBuf>;
 134    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>>;
 135    fn git_init(&self, abs_work_directory: &Path, fallback_branch_name: String) -> Result<()>;
 136    fn is_fake(&self) -> bool;
 137    async fn is_case_sensitive(&self) -> Result<bool>;
 138
 139    #[cfg(any(test, feature = "test-support"))]
 140    fn as_fake(&self) -> Arc<FakeFs> {
 141        panic!("called as_fake on a real fs");
 142    }
 143}
 144
 145struct GlobalFs(Arc<dyn Fs>);
 146
 147impl Global for GlobalFs {}
 148
 149impl dyn Fs {
 150    /// Returns the global [`Fs`].
 151    pub fn global(cx: &App) -> Arc<Self> {
 152        GlobalFs::global(cx).0.clone()
 153    }
 154
 155    /// Sets the global [`Fs`].
 156    pub fn set_global(fs: Arc<Self>, cx: &mut App) {
 157        cx.set_global(GlobalFs(fs));
 158    }
 159}
 160
 161#[derive(Copy, Clone, Default)]
 162pub struct CreateOptions {
 163    pub overwrite: bool,
 164    pub ignore_if_exists: bool,
 165}
 166
 167#[derive(Copy, Clone, Default)]
 168pub struct CopyOptions {
 169    pub overwrite: bool,
 170    pub ignore_if_exists: bool,
 171}
 172
 173#[derive(Copy, Clone, Default)]
 174pub struct RenameOptions {
 175    pub overwrite: bool,
 176    pub ignore_if_exists: bool,
 177}
 178
 179#[derive(Copy, Clone, Default)]
 180pub struct RemoveOptions {
 181    pub recursive: bool,
 182    pub ignore_if_not_exists: bool,
 183}
 184
 185#[derive(Copy, Clone, Debug)]
 186pub struct Metadata {
 187    pub inode: u64,
 188    pub mtime: MTime,
 189    pub is_symlink: bool,
 190    pub is_dir: bool,
 191    pub len: u64,
 192    pub is_fifo: bool,
 193}
 194
 195/// Filesystem modification time. The purpose of this newtype is to discourage use of operations
 196/// that do not make sense for mtimes. In particular, it is not always valid to compare mtimes using
 197/// `<` or `>`, as there are many things that can cause the mtime of a file to be earlier than it
 198/// was. See ["mtime comparison considered harmful" - apenwarr](https://apenwarr.ca/log/20181113).
 199///
 200/// Do not derive Ord, PartialOrd, or arithmetic operation traits.
 201#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
 202#[serde(transparent)]
 203pub struct MTime(SystemTime);
 204
 205impl MTime {
 206    /// Conversion intended for persistence and testing.
 207    pub fn from_seconds_and_nanos(secs: u64, nanos: u32) -> Self {
 208        MTime(UNIX_EPOCH + Duration::new(secs, nanos))
 209    }
 210
 211    /// Conversion intended for persistence.
 212    pub fn to_seconds_and_nanos_for_persistence(self) -> Option<(u64, u32)> {
 213        self.0
 214            .duration_since(UNIX_EPOCH)
 215            .ok()
 216            .map(|duration| (duration.as_secs(), duration.subsec_nanos()))
 217    }
 218
 219    /// Returns the value wrapped by this `MTime`, for presentation to the user. The name including
 220    /// "_for_user" is to discourage misuse - this method should not be used when making decisions
 221    /// about file dirtiness.
 222    pub fn timestamp_for_user(self) -> SystemTime {
 223        self.0
 224    }
 225
 226    /// Temporary method to split out the behavior changes from introduction of this newtype.
 227    pub fn bad_is_greater_than(self, other: MTime) -> bool {
 228        self.0 > other.0
 229    }
 230}
 231
 232impl From<proto::Timestamp> for MTime {
 233    fn from(timestamp: proto::Timestamp) -> Self {
 234        MTime(timestamp.into())
 235    }
 236}
 237
 238impl From<MTime> for proto::Timestamp {
 239    fn from(mtime: MTime) -> Self {
 240        mtime.0.into()
 241    }
 242}
 243
 244pub struct RealFs {
 245    git_binary_path: Option<PathBuf>,
 246    executor: BackgroundExecutor,
 247}
 248
 249pub trait FileHandle: Send + Sync + std::fmt::Debug {
 250    fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf>;
 251}
 252
 253impl FileHandle for std::fs::File {
 254    #[cfg(target_os = "macos")]
 255    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
 256        use std::{
 257            ffi::{CStr, OsStr},
 258            os::unix::ffi::OsStrExt,
 259        };
 260
 261        let fd = self.as_fd();
 262        let mut path_buf: [libc::c_char; libc::PATH_MAX as usize] = [0; libc::PATH_MAX as usize];
 263
 264        let result = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETPATH, path_buf.as_mut_ptr()) };
 265        if result == -1 {
 266            anyhow::bail!("fcntl returned -1".to_string());
 267        }
 268
 269        let c_str = unsafe { CStr::from_ptr(path_buf.as_ptr()) };
 270        let path = PathBuf::from(OsStr::from_bytes(c_str.to_bytes()));
 271        Ok(path)
 272    }
 273
 274    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 275    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
 276        let fd = self.as_fd();
 277        let fd_path = format!("/proc/self/fd/{}", fd.as_raw_fd());
 278        let new_path = std::fs::read_link(fd_path)?;
 279        if new_path
 280            .file_name()
 281            .is_some_and(|f| f.to_string_lossy().ends_with(" (deleted)"))
 282        {
 283            anyhow::bail!("file was deleted")
 284        };
 285
 286        Ok(new_path)
 287    }
 288
 289    #[cfg(target_os = "windows")]
 290    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
 291        anyhow::bail!("unimplemented")
 292    }
 293}
 294
 295pub struct RealWatcher {}
 296
 297impl RealFs {
 298    pub fn new(git_binary_path: Option<PathBuf>, executor: BackgroundExecutor) -> Self {
 299        Self {
 300            git_binary_path,
 301            executor,
 302        }
 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, ResultExt as _};
 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        Some(Arc::new(RealGitRepository::new(
 759            dotgit_path,
 760            self.git_binary_path.clone(),
 761            self.executor.clone(),
 762        )?))
 763    }
 764
 765    fn git_init(&self, abs_work_directory_path: &Path, fallback_branch_name: String) -> Result<()> {
 766        let config = new_std_command("git")
 767            .current_dir(abs_work_directory_path)
 768            .args(&["config", "--global", "--get", "init.defaultBranch"])
 769            .output()?;
 770
 771        let branch_name;
 772
 773        if config.status.success() && !config.stdout.is_empty() {
 774            branch_name = String::from_utf8_lossy(&config.stdout);
 775        } else {
 776            branch_name = Cow::Borrowed(fallback_branch_name.as_str());
 777        }
 778
 779        new_std_command("git")
 780            .current_dir(abs_work_directory_path)
 781            .args(&["init", "-b"])
 782            .arg(branch_name.trim())
 783            .output()?;
 784
 785        Ok(())
 786    }
 787
 788    fn is_fake(&self) -> bool {
 789        false
 790    }
 791
 792    /// Checks whether the file system is case sensitive by attempting to create two files
 793    /// that have the same name except for the casing.
 794    ///
 795    /// It creates both files in a temporary directory it removes at the end.
 796    async fn is_case_sensitive(&self) -> Result<bool> {
 797        let temp_dir = TempDir::new()?;
 798        let test_file_1 = temp_dir.path().join("case_sensitivity_test.tmp");
 799        let test_file_2 = temp_dir.path().join("CASE_SENSITIVITY_TEST.TMP");
 800
 801        let create_opts = CreateOptions {
 802            overwrite: false,
 803            ignore_if_exists: false,
 804        };
 805
 806        // Create file1
 807        self.create_file(&test_file_1, create_opts).await?;
 808
 809        // Now check whether it's possible to create file2
 810        let case_sensitive = match self.create_file(&test_file_2, create_opts).await {
 811            Ok(_) => Ok(true),
 812            Err(e) => {
 813                if let Some(io_error) = e.downcast_ref::<io::Error>() {
 814                    if io_error.kind() == io::ErrorKind::AlreadyExists {
 815                        Ok(false)
 816                    } else {
 817                        Err(e)
 818                    }
 819                } else {
 820                    Err(e)
 821                }
 822            }
 823        };
 824
 825        temp_dir.close()?;
 826        case_sensitive
 827    }
 828
 829    fn home_dir(&self) -> Option<PathBuf> {
 830        Some(paths::home_dir().clone())
 831    }
 832}
 833
 834#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
 835impl Watcher for RealWatcher {
 836    fn add(&self, _: &Path) -> Result<()> {
 837        Ok(())
 838    }
 839
 840    fn remove(&self, _: &Path) -> Result<()> {
 841        Ok(())
 842    }
 843}
 844
 845#[cfg(any(test, feature = "test-support"))]
 846pub struct FakeFs {
 847    this: std::sync::Weak<Self>,
 848    // Use an unfair lock to ensure tests are deterministic.
 849    state: Mutex<FakeFsState>,
 850    executor: gpui::BackgroundExecutor,
 851}
 852
 853#[cfg(any(test, feature = "test-support"))]
 854struct FakeFsState {
 855    root: Arc<Mutex<FakeFsEntry>>,
 856    next_inode: u64,
 857    next_mtime: SystemTime,
 858    git_event_tx: smol::channel::Sender<PathBuf>,
 859    event_txs: Vec<smol::channel::Sender<Vec<PathEvent>>>,
 860    events_paused: bool,
 861    buffered_events: Vec<PathEvent>,
 862    metadata_call_count: usize,
 863    read_dir_call_count: usize,
 864    moves: std::collections::HashMap<u64, PathBuf>,
 865    home_dir: Option<PathBuf>,
 866}
 867
 868#[cfg(any(test, feature = "test-support"))]
 869#[derive(Debug)]
 870enum FakeFsEntry {
 871    File {
 872        inode: u64,
 873        mtime: MTime,
 874        len: u64,
 875        content: Vec<u8>,
 876    },
 877    Dir {
 878        inode: u64,
 879        mtime: MTime,
 880        len: u64,
 881        entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
 882        git_repo_state: Option<Arc<Mutex<FakeGitRepositoryState>>>,
 883    },
 884    Symlink {
 885        target: PathBuf,
 886    },
 887}
 888
 889#[cfg(any(test, feature = "test-support"))]
 890impl FakeFsState {
 891    fn get_and_increment_mtime(&mut self) -> MTime {
 892        let mtime = self.next_mtime;
 893        self.next_mtime += FakeFs::SYSTEMTIME_INTERVAL;
 894        MTime(mtime)
 895    }
 896
 897    fn get_and_increment_inode(&mut self) -> u64 {
 898        let inode = self.next_inode;
 899        self.next_inode += 1;
 900        inode
 901    }
 902
 903    fn read_path(&self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
 904        Ok(self
 905            .try_read_path(target, true)
 906            .ok_or_else(|| {
 907                anyhow!(io::Error::new(
 908                    io::ErrorKind::NotFound,
 909                    format!("not found: {}", target.display())
 910                ))
 911            })?
 912            .0)
 913    }
 914
 915    fn try_read_path(
 916        &self,
 917        target: &Path,
 918        follow_symlink: bool,
 919    ) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
 920        let mut path = target.to_path_buf();
 921        let mut canonical_path = PathBuf::new();
 922        let mut entry_stack = Vec::new();
 923        'outer: loop {
 924            let mut path_components = path.components().peekable();
 925            let mut prefix = None;
 926            while let Some(component) = path_components.next() {
 927                match component {
 928                    Component::Prefix(prefix_component) => prefix = Some(prefix_component),
 929                    Component::RootDir => {
 930                        entry_stack.clear();
 931                        entry_stack.push(self.root.clone());
 932                        canonical_path.clear();
 933                        match prefix {
 934                            Some(prefix_component) => {
 935                                canonical_path = PathBuf::from(prefix_component.as_os_str());
 936                                // Prefixes like `C:\\` are represented without their trailing slash, so we have to re-add it.
 937                                canonical_path.push(std::path::MAIN_SEPARATOR_STR);
 938                            }
 939                            None => canonical_path = PathBuf::from(std::path::MAIN_SEPARATOR_STR),
 940                        }
 941                    }
 942                    Component::CurDir => {}
 943                    Component::ParentDir => {
 944                        entry_stack.pop()?;
 945                        canonical_path.pop();
 946                    }
 947                    Component::Normal(name) => {
 948                        let current_entry = entry_stack.last().cloned()?;
 949                        let current_entry = current_entry.lock();
 950                        if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
 951                            let entry = entries.get(name.to_str().unwrap()).cloned()?;
 952                            if path_components.peek().is_some() || follow_symlink {
 953                                let entry = entry.lock();
 954                                if let FakeFsEntry::Symlink { target, .. } = &*entry {
 955                                    let mut target = target.clone();
 956                                    target.extend(path_components);
 957                                    path = target;
 958                                    continue 'outer;
 959                                }
 960                            }
 961                            entry_stack.push(entry.clone());
 962                            canonical_path = canonical_path.join(name);
 963                        } else {
 964                            return None;
 965                        }
 966                    }
 967                }
 968            }
 969            break;
 970        }
 971        Some((entry_stack.pop()?, canonical_path))
 972    }
 973
 974    fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
 975    where
 976        Fn: FnOnce(btree_map::Entry<String, Arc<Mutex<FakeFsEntry>>>) -> Result<T>,
 977    {
 978        let path = normalize_path(path);
 979        let filename = path
 980            .file_name()
 981            .ok_or_else(|| anyhow!("cannot overwrite the root"))?;
 982        let parent_path = path.parent().unwrap();
 983
 984        let parent = self.read_path(parent_path)?;
 985        let mut parent = parent.lock();
 986        let new_entry = parent
 987            .dir_entries(parent_path)?
 988            .entry(filename.to_str().unwrap().into());
 989        callback(new_entry)
 990    }
 991
 992    fn emit_event<I, T>(&mut self, paths: I)
 993    where
 994        I: IntoIterator<Item = (T, Option<PathEventKind>)>,
 995        T: Into<PathBuf>,
 996    {
 997        self.buffered_events
 998            .extend(paths.into_iter().map(|(path, kind)| PathEvent {
 999                path: path.into(),
1000                kind,
1001            }));
1002
1003        if !self.events_paused {
1004            self.flush_events(self.buffered_events.len());
1005        }
1006    }
1007
1008    fn flush_events(&mut self, mut count: usize) {
1009        count = count.min(self.buffered_events.len());
1010        let events = self.buffered_events.drain(0..count).collect::<Vec<_>>();
1011        self.event_txs.retain(|tx| {
1012            let _ = tx.try_send(events.clone());
1013            !tx.is_closed()
1014        });
1015    }
1016}
1017
1018#[cfg(any(test, feature = "test-support"))]
1019pub static FS_DOT_GIT: std::sync::LazyLock<&'static OsStr> =
1020    std::sync::LazyLock::new(|| OsStr::new(".git"));
1021
1022#[cfg(any(test, feature = "test-support"))]
1023impl FakeFs {
1024    /// We need to use something large enough for Windows and Unix to consider this a new file.
1025    /// https://doc.rust-lang.org/nightly/std/time/struct.SystemTime.html#platform-specific-behavior
1026    const SYSTEMTIME_INTERVAL: Duration = Duration::from_nanos(100);
1027
1028    pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
1029        let (tx, rx) = smol::channel::bounded::<PathBuf>(10);
1030
1031        let this = Arc::new_cyclic(|this| Self {
1032            this: this.clone(),
1033            executor: executor.clone(),
1034            state: Mutex::new(FakeFsState {
1035                root: Arc::new(Mutex::new(FakeFsEntry::Dir {
1036                    inode: 0,
1037                    mtime: MTime(UNIX_EPOCH),
1038                    len: 0,
1039                    entries: Default::default(),
1040                    git_repo_state: None,
1041                })),
1042                git_event_tx: tx,
1043                next_mtime: UNIX_EPOCH + Self::SYSTEMTIME_INTERVAL,
1044                next_inode: 1,
1045                event_txs: Default::default(),
1046                buffered_events: Vec::new(),
1047                events_paused: false,
1048                read_dir_call_count: 0,
1049                metadata_call_count: 0,
1050                moves: Default::default(),
1051                home_dir: None,
1052            }),
1053        });
1054
1055        executor.spawn({
1056            let this = this.clone();
1057            async move {
1058                while let Ok(git_event) = rx.recv().await {
1059                    if let Some(mut state) = this.state.try_lock() {
1060                        state.emit_event([(git_event, None)]);
1061                    } else {
1062                        panic!("Failed to lock file system state, this execution would have caused a test hang");
1063                    }
1064                }
1065            }
1066        }).detach();
1067
1068        this
1069    }
1070
1071    pub fn set_next_mtime(&self, next_mtime: SystemTime) {
1072        let mut state = self.state.lock();
1073        state.next_mtime = next_mtime;
1074    }
1075
1076    pub fn get_and_increment_mtime(&self) -> MTime {
1077        let mut state = self.state.lock();
1078        state.get_and_increment_mtime()
1079    }
1080
1081    pub async fn touch_path(&self, path: impl AsRef<Path>) {
1082        let mut state = self.state.lock();
1083        let path = path.as_ref();
1084        let new_mtime = state.get_and_increment_mtime();
1085        let new_inode = state.get_and_increment_inode();
1086        state
1087            .write_path(path, move |entry| {
1088                match entry {
1089                    btree_map::Entry::Vacant(e) => {
1090                        e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
1091                            inode: new_inode,
1092                            mtime: new_mtime,
1093                            content: Vec::new(),
1094                            len: 0,
1095                        })));
1096                    }
1097                    btree_map::Entry::Occupied(mut e) => match &mut *e.get_mut().lock() {
1098                        FakeFsEntry::File { mtime, .. } => *mtime = new_mtime,
1099                        FakeFsEntry::Dir { mtime, .. } => *mtime = new_mtime,
1100                        FakeFsEntry::Symlink { .. } => {}
1101                    },
1102                }
1103                Ok(())
1104            })
1105            .unwrap();
1106        state.emit_event([(path.to_path_buf(), None)]);
1107    }
1108
1109    pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
1110        self.write_file_internal(path, content).unwrap()
1111    }
1112
1113    pub async fn insert_symlink(&self, path: impl AsRef<Path>, target: PathBuf) {
1114        let mut state = self.state.lock();
1115        let path = path.as_ref();
1116        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
1117        state
1118            .write_path(path.as_ref(), move |e| match e {
1119                btree_map::Entry::Vacant(e) => {
1120                    e.insert(file);
1121                    Ok(())
1122                }
1123                btree_map::Entry::Occupied(mut e) => {
1124                    *e.get_mut() = file;
1125                    Ok(())
1126                }
1127            })
1128            .unwrap();
1129        state.emit_event([(path, None)]);
1130    }
1131
1132    fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
1133        let mut state = self.state.lock();
1134        let file = Arc::new(Mutex::new(FakeFsEntry::File {
1135            inode: state.get_and_increment_inode(),
1136            mtime: state.get_and_increment_mtime(),
1137            len: content.len() as u64,
1138            content,
1139        }));
1140        let mut kind = None;
1141        state.write_path(path.as_ref(), {
1142            let kind = &mut kind;
1143            move |entry| {
1144                match entry {
1145                    btree_map::Entry::Vacant(e) => {
1146                        *kind = Some(PathEventKind::Created);
1147                        e.insert(file);
1148                    }
1149                    btree_map::Entry::Occupied(mut e) => {
1150                        *kind = Some(PathEventKind::Changed);
1151                        *e.get_mut() = file;
1152                    }
1153                }
1154                Ok(())
1155            }
1156        })?;
1157        state.emit_event([(path.as_ref(), kind)]);
1158        Ok(())
1159    }
1160
1161    pub fn read_file_sync(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
1162        let path = path.as_ref();
1163        let path = normalize_path(path);
1164        let state = self.state.lock();
1165        let entry = state.read_path(&path)?;
1166        let entry = entry.lock();
1167        entry.file_content(&path).cloned()
1168    }
1169
1170    async fn load_internal(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> {
1171        let path = path.as_ref();
1172        let path = normalize_path(path);
1173        self.simulate_random_delay().await;
1174        let state = self.state.lock();
1175        let entry = state.read_path(&path)?;
1176        let entry = entry.lock();
1177        entry.file_content(&path).cloned()
1178    }
1179
1180    pub fn pause_events(&self) {
1181        self.state.lock().events_paused = true;
1182    }
1183
1184    pub fn unpause_events_and_flush(&self) {
1185        self.state.lock().events_paused = false;
1186        self.flush_events(usize::MAX);
1187    }
1188
1189    pub fn buffered_event_count(&self) -> usize {
1190        self.state.lock().buffered_events.len()
1191    }
1192
1193    pub fn flush_events(&self, count: usize) {
1194        self.state.lock().flush_events(count);
1195    }
1196
1197    #[must_use]
1198    pub fn insert_tree<'a>(
1199        &'a self,
1200        path: impl 'a + AsRef<Path> + Send,
1201        tree: serde_json::Value,
1202    ) -> futures::future::BoxFuture<'a, ()> {
1203        use futures::FutureExt as _;
1204        use serde_json::Value::*;
1205
1206        async move {
1207            let path = path.as_ref();
1208
1209            match tree {
1210                Object(map) => {
1211                    self.create_dir(path).await.unwrap();
1212                    for (name, contents) in map {
1213                        let mut path = PathBuf::from(path);
1214                        path.push(name);
1215                        self.insert_tree(&path, contents).await;
1216                    }
1217                }
1218                Null => {
1219                    self.create_dir(path).await.unwrap();
1220                }
1221                String(contents) => {
1222                    self.insert_file(&path, contents.into_bytes()).await;
1223                }
1224                _ => {
1225                    panic!("JSON object must contain only objects, strings, or null");
1226                }
1227            }
1228        }
1229        .boxed()
1230    }
1231
1232    pub fn insert_tree_from_real_fs<'a>(
1233        &'a self,
1234        path: impl 'a + AsRef<Path> + Send,
1235        src_path: impl 'a + AsRef<Path> + Send,
1236    ) -> futures::future::BoxFuture<'a, ()> {
1237        use futures::FutureExt as _;
1238
1239        async move {
1240            let path = path.as_ref();
1241            if std::fs::metadata(&src_path).unwrap().is_file() {
1242                let contents = std::fs::read(src_path).unwrap();
1243                self.insert_file(path, contents).await;
1244            } else {
1245                self.create_dir(path).await.unwrap();
1246                for entry in std::fs::read_dir(&src_path).unwrap() {
1247                    let entry = entry.unwrap();
1248                    self.insert_tree_from_real_fs(path.join(entry.file_name()), entry.path())
1249                        .await;
1250                }
1251            }
1252        }
1253        .boxed()
1254    }
1255
1256    pub fn with_git_state<T, F>(&self, dot_git: &Path, emit_git_event: bool, f: F) -> Result<T>
1257    where
1258        F: FnOnce(&mut FakeGitRepositoryState) -> T,
1259    {
1260        let mut state = self.state.lock();
1261        let entry = state.read_path(dot_git).context("open .git")?;
1262        let mut entry = entry.lock();
1263
1264        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
1265            let repo_state = git_repo_state.get_or_insert_with(|| {
1266                Arc::new(Mutex::new(FakeGitRepositoryState::new(
1267                    dot_git.to_path_buf(),
1268                    state.git_event_tx.clone(),
1269                )))
1270            });
1271            let mut repo_state = repo_state.lock();
1272
1273            let result = f(&mut repo_state);
1274
1275            if emit_git_event {
1276                state.emit_event([(dot_git, None)]);
1277            }
1278
1279            Ok(result)
1280        } else {
1281            Err(anyhow!("not a directory"))
1282        }
1283    }
1284
1285    pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
1286        self.with_git_state(dot_git, true, |state| {
1287            let branch = branch.map(Into::into);
1288            state.branches.extend(branch.clone());
1289            state.current_branch_name = branch
1290        })
1291        .unwrap();
1292    }
1293
1294    pub fn insert_branches(&self, dot_git: &Path, branches: &[&str]) {
1295        self.with_git_state(dot_git, true, |state| {
1296            if let Some(first) = branches.first() {
1297                if state.current_branch_name.is_none() {
1298                    state.current_branch_name = Some(first.to_string())
1299                }
1300            }
1301            state
1302                .branches
1303                .extend(branches.iter().map(ToString::to_string));
1304        })
1305        .unwrap();
1306    }
1307
1308    pub fn set_unmerged_paths_for_repo(
1309        &self,
1310        dot_git: &Path,
1311        unmerged_state: &[(RepoPath, UnmergedStatus)],
1312    ) {
1313        self.with_git_state(dot_git, true, |state| {
1314            state.unmerged_paths.clear();
1315            state.unmerged_paths.extend(
1316                unmerged_state
1317                    .iter()
1318                    .map(|(path, content)| (path.clone(), *content)),
1319            );
1320        })
1321        .unwrap();
1322    }
1323
1324    pub fn set_index_for_repo(&self, dot_git: &Path, index_state: &[(RepoPath, String)]) {
1325        self.with_git_state(dot_git, true, |state| {
1326            state.index_contents.clear();
1327            state.index_contents.extend(
1328                index_state
1329                    .iter()
1330                    .map(|(path, content)| (path.clone(), content.clone())),
1331            );
1332        })
1333        .unwrap();
1334    }
1335
1336    pub fn set_head_for_repo(&self, dot_git: &Path, head_state: &[(RepoPath, String)]) {
1337        self.with_git_state(dot_git, true, |state| {
1338            state.head_contents.clear();
1339            state.head_contents.extend(
1340                head_state
1341                    .iter()
1342                    .map(|(path, content)| (path.clone(), content.clone())),
1343            );
1344        })
1345        .unwrap();
1346    }
1347
1348    pub fn set_git_content_for_repo(
1349        &self,
1350        dot_git: &Path,
1351        head_state: &[(RepoPath, String, Option<String>)],
1352    ) {
1353        self.with_git_state(dot_git, true, |state| {
1354            state.head_contents.clear();
1355            state.head_contents.extend(
1356                head_state
1357                    .iter()
1358                    .map(|(path, head_content, _)| (path.clone(), head_content.clone())),
1359            );
1360            state.index_contents.clear();
1361            state.index_contents.extend(head_state.iter().map(
1362                |(path, head_content, index_content)| {
1363                    (
1364                        path.clone(),
1365                        index_content.as_ref().unwrap_or(head_content).clone(),
1366                    )
1367                },
1368            ));
1369        })
1370        .unwrap();
1371    }
1372
1373    pub fn set_head_and_index_for_repo(
1374        &self,
1375        dot_git: &Path,
1376        contents_by_path: &[(RepoPath, String)],
1377    ) {
1378        self.with_git_state(dot_git, true, |state| {
1379            state.head_contents.clear();
1380            state.index_contents.clear();
1381            state.head_contents.extend(contents_by_path.iter().cloned());
1382            state
1383                .index_contents
1384                .extend(contents_by_path.iter().cloned());
1385        })
1386        .unwrap();
1387    }
1388
1389    pub fn set_blame_for_repo(&self, dot_git: &Path, blames: Vec<(RepoPath, git::blame::Blame)>) {
1390        self.with_git_state(dot_git, true, |state| {
1391            state.blames.clear();
1392            state.blames.extend(blames);
1393        })
1394        .unwrap();
1395    }
1396
1397    /// Put the given git repository into a state with the given status,
1398    /// by mutating the head, index, and unmerged state.
1399    pub fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, FileStatus)]) {
1400        let workdir_path = dot_git.parent().unwrap();
1401        let workdir_contents = self.files_with_contents(&workdir_path);
1402        self.with_git_state(dot_git, true, |state| {
1403            state.index_contents.clear();
1404            state.head_contents.clear();
1405            state.unmerged_paths.clear();
1406            for (path, content) in workdir_contents {
1407                let repo_path: RepoPath = path.strip_prefix(&workdir_path).unwrap().into();
1408                let status = statuses
1409                    .iter()
1410                    .find_map(|(p, status)| (**p == *repo_path.0).then_some(status));
1411                let mut content = String::from_utf8_lossy(&content).to_string();
1412
1413                let mut index_content = None;
1414                let mut head_content = None;
1415                match status {
1416                    None => {
1417                        index_content = Some(content.clone());
1418                        head_content = Some(content);
1419                    }
1420                    Some(FileStatus::Untracked | FileStatus::Ignored) => {}
1421                    Some(FileStatus::Unmerged(unmerged_status)) => {
1422                        state
1423                            .unmerged_paths
1424                            .insert(repo_path.clone(), *unmerged_status);
1425                        content.push_str(" (unmerged)");
1426                        index_content = Some(content.clone());
1427                        head_content = Some(content);
1428                    }
1429                    Some(FileStatus::Tracked(TrackedStatus {
1430                        index_status,
1431                        worktree_status,
1432                    })) => {
1433                        match worktree_status {
1434                            StatusCode::Modified => {
1435                                let mut content = content.clone();
1436                                content.push_str(" (modified in working copy)");
1437                                index_content = Some(content);
1438                            }
1439                            StatusCode::TypeChanged | StatusCode::Unmodified => {
1440                                index_content = Some(content.clone());
1441                            }
1442                            StatusCode::Added => {}
1443                            StatusCode::Deleted | StatusCode::Renamed | StatusCode::Copied => {
1444                                panic!("cannot create these statuses for an existing file");
1445                            }
1446                        };
1447                        match index_status {
1448                            StatusCode::Modified => {
1449                                let mut content = index_content.clone().expect(
1450                                    "file cannot be both modified in index and created in working copy",
1451                                );
1452                                content.push_str(" (modified in index)");
1453                                head_content = Some(content);
1454                            }
1455                            StatusCode::TypeChanged | StatusCode::Unmodified => {
1456                                head_content = Some(index_content.clone().expect("file cannot be both unmodified in index and created in working copy"));
1457                            }
1458                            StatusCode::Added => {}
1459                            StatusCode::Deleted  => {
1460                                head_content = Some("".into());
1461                            }
1462                            StatusCode::Renamed | StatusCode::Copied => {
1463                                panic!("cannot create these statuses for an existing file");
1464                            }
1465                        };
1466                    }
1467                };
1468
1469                if let Some(content) = index_content {
1470                    state.index_contents.insert(repo_path.clone(), content);
1471                }
1472                if let Some(content) = head_content {
1473                    state.head_contents.insert(repo_path.clone(), content);
1474                }
1475            }
1476        }).unwrap();
1477    }
1478
1479    pub fn set_error_message_for_index_write(&self, dot_git: &Path, message: Option<String>) {
1480        self.with_git_state(dot_git, true, |state| {
1481            state.simulated_index_write_error_message = message;
1482        })
1483        .unwrap();
1484    }
1485
1486    pub fn paths(&self, include_dot_git: bool) -> Vec<PathBuf> {
1487        let mut result = Vec::new();
1488        let mut queue = collections::VecDeque::new();
1489        queue.push_back((
1490            PathBuf::from(util::path!("/")),
1491            self.state.lock().root.clone(),
1492        ));
1493        while let Some((path, entry)) = queue.pop_front() {
1494            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
1495                for (name, entry) in entries {
1496                    queue.push_back((path.join(name), entry.clone()));
1497                }
1498            }
1499            if include_dot_git
1500                || !path
1501                    .components()
1502                    .any(|component| component.as_os_str() == *FS_DOT_GIT)
1503            {
1504                result.push(path);
1505            }
1506        }
1507        result
1508    }
1509
1510    pub fn directories(&self, include_dot_git: bool) -> Vec<PathBuf> {
1511        let mut result = Vec::new();
1512        let mut queue = collections::VecDeque::new();
1513        queue.push_back((
1514            PathBuf::from(util::path!("/")),
1515            self.state.lock().root.clone(),
1516        ));
1517        while let Some((path, entry)) = queue.pop_front() {
1518            if let FakeFsEntry::Dir { entries, .. } = &*entry.lock() {
1519                for (name, entry) in entries {
1520                    queue.push_back((path.join(name), entry.clone()));
1521                }
1522                if include_dot_git
1523                    || !path
1524                        .components()
1525                        .any(|component| component.as_os_str() == *FS_DOT_GIT)
1526                {
1527                    result.push(path);
1528                }
1529            }
1530        }
1531        result
1532    }
1533
1534    pub fn files(&self) -> Vec<PathBuf> {
1535        let mut result = Vec::new();
1536        let mut queue = collections::VecDeque::new();
1537        queue.push_back((
1538            PathBuf::from(util::path!("/")),
1539            self.state.lock().root.clone(),
1540        ));
1541        while let Some((path, entry)) = queue.pop_front() {
1542            let e = entry.lock();
1543            match &*e {
1544                FakeFsEntry::File { .. } => result.push(path),
1545                FakeFsEntry::Dir { entries, .. } => {
1546                    for (name, entry) in entries {
1547                        queue.push_back((path.join(name), entry.clone()));
1548                    }
1549                }
1550                FakeFsEntry::Symlink { .. } => {}
1551            }
1552        }
1553        result
1554    }
1555
1556    pub fn files_with_contents(&self, prefix: &Path) -> Vec<(PathBuf, Vec<u8>)> {
1557        let mut result = Vec::new();
1558        let mut queue = collections::VecDeque::new();
1559        queue.push_back((
1560            PathBuf::from(util::path!("/")),
1561            self.state.lock().root.clone(),
1562        ));
1563        while let Some((path, entry)) = queue.pop_front() {
1564            let e = entry.lock();
1565            match &*e {
1566                FakeFsEntry::File { content, .. } => {
1567                    if path.starts_with(prefix) {
1568                        result.push((path, content.clone()));
1569                    }
1570                }
1571                FakeFsEntry::Dir { entries, .. } => {
1572                    for (name, entry) in entries {
1573                        queue.push_back((path.join(name), entry.clone()));
1574                    }
1575                }
1576                FakeFsEntry::Symlink { .. } => {}
1577            }
1578        }
1579        result
1580    }
1581
1582    /// How many `read_dir` calls have been issued.
1583    pub fn read_dir_call_count(&self) -> usize {
1584        self.state.lock().read_dir_call_count
1585    }
1586
1587    /// How many `metadata` calls have been issued.
1588    pub fn metadata_call_count(&self) -> usize {
1589        self.state.lock().metadata_call_count
1590    }
1591
1592    fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
1593        self.executor.simulate_random_delay()
1594    }
1595
1596    pub fn set_home_dir(&self, home_dir: PathBuf) {
1597        self.state.lock().home_dir = Some(home_dir);
1598    }
1599}
1600
1601#[cfg(any(test, feature = "test-support"))]
1602impl FakeFsEntry {
1603    fn is_file(&self) -> bool {
1604        matches!(self, Self::File { .. })
1605    }
1606
1607    fn is_symlink(&self) -> bool {
1608        matches!(self, Self::Symlink { .. })
1609    }
1610
1611    fn file_content(&self, path: &Path) -> Result<&Vec<u8>> {
1612        if let Self::File { content, .. } = self {
1613            Ok(content)
1614        } else {
1615            Err(anyhow!("not a file: {}", path.display()))
1616        }
1617    }
1618
1619    fn dir_entries(
1620        &mut self,
1621        path: &Path,
1622    ) -> Result<&mut BTreeMap<String, Arc<Mutex<FakeFsEntry>>>> {
1623        if let Self::Dir { entries, .. } = self {
1624            Ok(entries)
1625        } else {
1626            Err(anyhow!("not a directory: {}", path.display()))
1627        }
1628    }
1629}
1630
1631#[cfg(any(test, feature = "test-support"))]
1632struct FakeWatcher {}
1633
1634#[cfg(any(test, feature = "test-support"))]
1635impl Watcher for FakeWatcher {
1636    fn add(&self, _: &Path) -> Result<()> {
1637        Ok(())
1638    }
1639
1640    fn remove(&self, _: &Path) -> Result<()> {
1641        Ok(())
1642    }
1643}
1644
1645#[cfg(any(test, feature = "test-support"))]
1646#[derive(Debug)]
1647struct FakeHandle {
1648    inode: u64,
1649}
1650
1651#[cfg(any(test, feature = "test-support"))]
1652impl FileHandle for FakeHandle {
1653    fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf> {
1654        let fs = fs.as_fake();
1655        let state = fs.state.lock();
1656        let Some(target) = state.moves.get(&self.inode) else {
1657            anyhow::bail!("fake fd not moved")
1658        };
1659
1660        if state.try_read_path(&target, false).is_some() {
1661            return Ok(target.clone());
1662        }
1663        anyhow::bail!("fake fd target not found")
1664    }
1665}
1666
1667#[cfg(any(test, feature = "test-support"))]
1668#[async_trait::async_trait]
1669impl Fs for FakeFs {
1670    async fn create_dir(&self, path: &Path) -> Result<()> {
1671        self.simulate_random_delay().await;
1672
1673        let mut created_dirs = Vec::new();
1674        let mut cur_path = PathBuf::new();
1675        for component in path.components() {
1676            let should_skip = matches!(component, Component::Prefix(..) | Component::RootDir);
1677            cur_path.push(component);
1678            if should_skip {
1679                continue;
1680            }
1681            let mut state = self.state.lock();
1682
1683            let inode = state.get_and_increment_inode();
1684            let mtime = state.get_and_increment_mtime();
1685            state.write_path(&cur_path, |entry| {
1686                entry.or_insert_with(|| {
1687                    created_dirs.push((cur_path.clone(), Some(PathEventKind::Created)));
1688                    Arc::new(Mutex::new(FakeFsEntry::Dir {
1689                        inode,
1690                        mtime,
1691                        len: 0,
1692                        entries: Default::default(),
1693                        git_repo_state: None,
1694                    }))
1695                });
1696                Ok(())
1697            })?
1698        }
1699
1700        self.state.lock().emit_event(created_dirs);
1701        Ok(())
1702    }
1703
1704    async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
1705        self.simulate_random_delay().await;
1706        let mut state = self.state.lock();
1707        let inode = state.get_and_increment_inode();
1708        let mtime = state.get_and_increment_mtime();
1709        let file = Arc::new(Mutex::new(FakeFsEntry::File {
1710            inode,
1711            mtime,
1712            len: 0,
1713            content: Vec::new(),
1714        }));
1715        let mut kind = Some(PathEventKind::Created);
1716        state.write_path(path, |entry| {
1717            match entry {
1718                btree_map::Entry::Occupied(mut e) => {
1719                    if options.overwrite {
1720                        kind = Some(PathEventKind::Changed);
1721                        *e.get_mut() = file;
1722                    } else if !options.ignore_if_exists {
1723                        return Err(anyhow!("path already exists: {}", path.display()));
1724                    }
1725                }
1726                btree_map::Entry::Vacant(e) => {
1727                    e.insert(file);
1728                }
1729            }
1730            Ok(())
1731        })?;
1732        state.emit_event([(path, kind)]);
1733        Ok(())
1734    }
1735
1736    async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()> {
1737        let mut state = self.state.lock();
1738        let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
1739        state
1740            .write_path(path.as_ref(), move |e| match e {
1741                btree_map::Entry::Vacant(e) => {
1742                    e.insert(file);
1743                    Ok(())
1744                }
1745                btree_map::Entry::Occupied(mut e) => {
1746                    *e.get_mut() = file;
1747                    Ok(())
1748                }
1749            })
1750            .unwrap();
1751        state.emit_event([(path, None)]);
1752
1753        Ok(())
1754    }
1755
1756    async fn create_file_with(
1757        &self,
1758        path: &Path,
1759        mut content: Pin<&mut (dyn AsyncRead + Send)>,
1760    ) -> Result<()> {
1761        let mut bytes = Vec::new();
1762        content.read_to_end(&mut bytes).await?;
1763        self.write_file_internal(path, bytes)?;
1764        Ok(())
1765    }
1766
1767    async fn extract_tar_file(
1768        &self,
1769        path: &Path,
1770        content: Archive<Pin<&mut (dyn AsyncRead + Send)>>,
1771    ) -> Result<()> {
1772        let mut entries = content.entries()?;
1773        while let Some(entry) = entries.next().await {
1774            let mut entry = entry?;
1775            if entry.header().entry_type().is_file() {
1776                let path = path.join(entry.path()?.as_ref());
1777                let mut bytes = Vec::new();
1778                entry.read_to_end(&mut bytes).await?;
1779                self.create_dir(path.parent().unwrap()).await?;
1780                self.write_file_internal(&path, bytes)?;
1781            }
1782        }
1783        Ok(())
1784    }
1785
1786    async fn rename(&self, old_path: &Path, new_path: &Path, options: RenameOptions) -> Result<()> {
1787        self.simulate_random_delay().await;
1788
1789        let old_path = normalize_path(old_path);
1790        let new_path = normalize_path(new_path);
1791
1792        let mut state = self.state.lock();
1793        let moved_entry = state.write_path(&old_path, |e| {
1794            if let btree_map::Entry::Occupied(e) = e {
1795                Ok(e.get().clone())
1796            } else {
1797                Err(anyhow!("path does not exist: {}", &old_path.display()))
1798            }
1799        })?;
1800
1801        let inode = match *moved_entry.lock() {
1802            FakeFsEntry::File { inode, .. } => inode,
1803            FakeFsEntry::Dir { inode, .. } => inode,
1804            _ => 0,
1805        };
1806
1807        state.moves.insert(inode, new_path.clone());
1808
1809        state.write_path(&new_path, |e| {
1810            match e {
1811                btree_map::Entry::Occupied(mut e) => {
1812                    if options.overwrite {
1813                        *e.get_mut() = moved_entry;
1814                    } else if !options.ignore_if_exists {
1815                        return Err(anyhow!("path already exists: {}", new_path.display()));
1816                    }
1817                }
1818                btree_map::Entry::Vacant(e) => {
1819                    e.insert(moved_entry);
1820                }
1821            }
1822            Ok(())
1823        })?;
1824
1825        state
1826            .write_path(&old_path, |e| {
1827                if let btree_map::Entry::Occupied(e) = e {
1828                    Ok(e.remove())
1829                } else {
1830                    unreachable!()
1831                }
1832            })
1833            .unwrap();
1834
1835        state.emit_event([
1836            (old_path, Some(PathEventKind::Removed)),
1837            (new_path, Some(PathEventKind::Created)),
1838        ]);
1839        Ok(())
1840    }
1841
1842    async fn copy_file(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
1843        self.simulate_random_delay().await;
1844
1845        let source = normalize_path(source);
1846        let target = normalize_path(target);
1847        let mut state = self.state.lock();
1848        let mtime = state.get_and_increment_mtime();
1849        let inode = state.get_and_increment_inode();
1850        let source_entry = state.read_path(&source)?;
1851        let content = source_entry.lock().file_content(&source)?.clone();
1852        let mut kind = Some(PathEventKind::Created);
1853        state.write_path(&target, |e| match e {
1854            btree_map::Entry::Occupied(e) => {
1855                if options.overwrite {
1856                    kind = Some(PathEventKind::Changed);
1857                    Ok(Some(e.get().clone()))
1858                } else if !options.ignore_if_exists {
1859                    return Err(anyhow!("{target:?} already exists"));
1860                } else {
1861                    Ok(None)
1862                }
1863            }
1864            btree_map::Entry::Vacant(e) => Ok(Some(
1865                e.insert(Arc::new(Mutex::new(FakeFsEntry::File {
1866                    inode,
1867                    mtime,
1868                    len: content.len() as u64,
1869                    content,
1870                })))
1871                .clone(),
1872            )),
1873        })?;
1874        state.emit_event([(target, kind)]);
1875        Ok(())
1876    }
1877
1878    async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
1879        self.simulate_random_delay().await;
1880
1881        let path = normalize_path(path);
1882        let parent_path = path
1883            .parent()
1884            .ok_or_else(|| anyhow!("cannot remove the root"))?;
1885        let base_name = path.file_name().unwrap();
1886
1887        let mut state = self.state.lock();
1888        let parent_entry = state.read_path(parent_path)?;
1889        let mut parent_entry = parent_entry.lock();
1890        let entry = parent_entry
1891            .dir_entries(parent_path)?
1892            .entry(base_name.to_str().unwrap().into());
1893
1894        match entry {
1895            btree_map::Entry::Vacant(_) => {
1896                if !options.ignore_if_not_exists {
1897                    return Err(anyhow!("{path:?} does not exist"));
1898                }
1899            }
1900            btree_map::Entry::Occupied(e) => {
1901                {
1902                    let mut entry = e.get().lock();
1903                    let children = entry.dir_entries(&path)?;
1904                    if !options.recursive && !children.is_empty() {
1905                        return Err(anyhow!("{path:?} is not empty"));
1906                    }
1907                }
1908                e.remove();
1909            }
1910        }
1911        state.emit_event([(path, Some(PathEventKind::Removed))]);
1912        Ok(())
1913    }
1914
1915    async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
1916        self.simulate_random_delay().await;
1917
1918        let path = normalize_path(path);
1919        let parent_path = path
1920            .parent()
1921            .ok_or_else(|| anyhow!("cannot remove the root"))?;
1922        let base_name = path.file_name().unwrap();
1923        let mut state = self.state.lock();
1924        let parent_entry = state.read_path(parent_path)?;
1925        let mut parent_entry = parent_entry.lock();
1926        let entry = parent_entry
1927            .dir_entries(parent_path)?
1928            .entry(base_name.to_str().unwrap().into());
1929        match entry {
1930            btree_map::Entry::Vacant(_) => {
1931                if !options.ignore_if_not_exists {
1932                    return Err(anyhow!("{path:?} does not exist"));
1933                }
1934            }
1935            btree_map::Entry::Occupied(e) => {
1936                e.get().lock().file_content(&path)?;
1937                e.remove();
1938            }
1939        }
1940        state.emit_event([(path, Some(PathEventKind::Removed))]);
1941        Ok(())
1942    }
1943
1944    async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read + Send + Sync>> {
1945        let bytes = self.load_internal(path).await?;
1946        Ok(Box::new(io::Cursor::new(bytes)))
1947    }
1948
1949    async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>> {
1950        self.simulate_random_delay().await;
1951        let state = self.state.lock();
1952        let entry = state.read_path(&path)?;
1953        let entry = entry.lock();
1954        let inode = match *entry {
1955            FakeFsEntry::File { inode, .. } => inode,
1956            FakeFsEntry::Dir { inode, .. } => inode,
1957            _ => unreachable!(),
1958        };
1959        Ok(Arc::new(FakeHandle { inode }))
1960    }
1961
1962    async fn load(&self, path: &Path) -> Result<String> {
1963        let content = self.load_internal(path).await?;
1964        Ok(String::from_utf8(content.clone())?)
1965    }
1966
1967    async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
1968        self.load_internal(path).await
1969    }
1970
1971    async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
1972        self.simulate_random_delay().await;
1973        let path = normalize_path(path.as_path());
1974        self.write_file_internal(path, data.into_bytes())?;
1975        Ok(())
1976    }
1977
1978    async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
1979        self.simulate_random_delay().await;
1980        let path = normalize_path(path);
1981        let content = chunks(text, line_ending).collect::<String>();
1982        if let Some(path) = path.parent() {
1983            self.create_dir(path).await?;
1984        }
1985        self.write_file_internal(path, content.into_bytes())?;
1986        Ok(())
1987    }
1988
1989    async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
1990        let path = normalize_path(path);
1991        self.simulate_random_delay().await;
1992        let state = self.state.lock();
1993        if let Some((_, canonical_path)) = state.try_read_path(&path, true) {
1994            Ok(canonical_path)
1995        } else {
1996            Err(anyhow!("path does not exist: {}", path.display()))
1997        }
1998    }
1999
2000    async fn is_file(&self, path: &Path) -> bool {
2001        let path = normalize_path(path);
2002        self.simulate_random_delay().await;
2003        let state = self.state.lock();
2004        if let Some((entry, _)) = state.try_read_path(&path, true) {
2005            entry.lock().is_file()
2006        } else {
2007            false
2008        }
2009    }
2010
2011    async fn is_dir(&self, path: &Path) -> bool {
2012        self.metadata(path)
2013            .await
2014            .is_ok_and(|metadata| metadata.is_some_and(|metadata| metadata.is_dir))
2015    }
2016
2017    async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
2018        self.simulate_random_delay().await;
2019        let path = normalize_path(path);
2020        let mut state = self.state.lock();
2021        state.metadata_call_count += 1;
2022        if let Some((mut entry, _)) = state.try_read_path(&path, false) {
2023            let is_symlink = entry.lock().is_symlink();
2024            if is_symlink {
2025                if let Some(e) = state.try_read_path(&path, true).map(|e| e.0) {
2026                    entry = e;
2027                } else {
2028                    return Ok(None);
2029                }
2030            }
2031
2032            let entry = entry.lock();
2033            Ok(Some(match &*entry {
2034                FakeFsEntry::File {
2035                    inode, mtime, len, ..
2036                } => Metadata {
2037                    inode: *inode,
2038                    mtime: *mtime,
2039                    len: *len,
2040                    is_dir: false,
2041                    is_symlink,
2042                    is_fifo: false,
2043                },
2044                FakeFsEntry::Dir {
2045                    inode, mtime, len, ..
2046                } => Metadata {
2047                    inode: *inode,
2048                    mtime: *mtime,
2049                    len: *len,
2050                    is_dir: true,
2051                    is_symlink,
2052                    is_fifo: false,
2053                },
2054                FakeFsEntry::Symlink { .. } => unreachable!(),
2055            }))
2056        } else {
2057            Ok(None)
2058        }
2059    }
2060
2061    async fn read_link(&self, path: &Path) -> Result<PathBuf> {
2062        self.simulate_random_delay().await;
2063        let path = normalize_path(path);
2064        let state = self.state.lock();
2065        if let Some((entry, _)) = state.try_read_path(&path, false) {
2066            let entry = entry.lock();
2067            if let FakeFsEntry::Symlink { target } = &*entry {
2068                Ok(target.clone())
2069            } else {
2070                Err(anyhow!("not a symlink: {}", path.display()))
2071            }
2072        } else {
2073            Err(anyhow!("path does not exist: {}", path.display()))
2074        }
2075    }
2076
2077    async fn read_dir(
2078        &self,
2079        path: &Path,
2080    ) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
2081        self.simulate_random_delay().await;
2082        let path = normalize_path(path);
2083        let mut state = self.state.lock();
2084        state.read_dir_call_count += 1;
2085        let entry = state.read_path(&path)?;
2086        let mut entry = entry.lock();
2087        let children = entry.dir_entries(&path)?;
2088        let paths = children
2089            .keys()
2090            .map(|file_name| Ok(path.join(file_name)))
2091            .collect::<Vec<_>>();
2092        Ok(Box::pin(futures::stream::iter(paths)))
2093    }
2094
2095    async fn watch(
2096        &self,
2097        path: &Path,
2098        _: Duration,
2099    ) -> (
2100        Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
2101        Arc<dyn Watcher>,
2102    ) {
2103        self.simulate_random_delay().await;
2104        let (tx, rx) = smol::channel::unbounded();
2105        self.state.lock().event_txs.push(tx);
2106        let path = path.to_path_buf();
2107        let executor = self.executor.clone();
2108        (
2109            Box::pin(futures::StreamExt::filter(rx, move |events| {
2110                let result = events
2111                    .iter()
2112                    .any(|evt_path| evt_path.path.starts_with(&path));
2113                let executor = executor.clone();
2114                async move {
2115                    executor.simulate_random_delay().await;
2116                    result
2117                }
2118            })),
2119            Arc::new(FakeWatcher {}),
2120        )
2121    }
2122
2123    fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>> {
2124        let state = self.state.lock();
2125        let entry = state.read_path(abs_dot_git).unwrap();
2126        let mut entry = entry.lock();
2127        if let FakeFsEntry::Dir { git_repo_state, .. } = &mut *entry {
2128            git_repo_state.get_or_insert_with(|| {
2129                Arc::new(Mutex::new(FakeGitRepositoryState::new(
2130                    abs_dot_git.to_path_buf(),
2131                    state.git_event_tx.clone(),
2132                )))
2133            });
2134            Some(Arc::new(fake_git_repo::FakeGitRepository {
2135                fs: self.this.upgrade().unwrap(),
2136                executor: self.executor.clone(),
2137                dot_git_path: abs_dot_git.to_path_buf(),
2138            }))
2139        } else {
2140            None
2141        }
2142    }
2143
2144    fn git_init(
2145        &self,
2146        abs_work_directory_path: &Path,
2147        _fallback_branch_name: String,
2148    ) -> Result<()> {
2149        smol::block_on(self.create_dir(&abs_work_directory_path.join(".git")))
2150    }
2151
2152    fn is_fake(&self) -> bool {
2153        true
2154    }
2155
2156    async fn is_case_sensitive(&self) -> Result<bool> {
2157        Ok(true)
2158    }
2159
2160    #[cfg(any(test, feature = "test-support"))]
2161    fn as_fake(&self) -> Arc<FakeFs> {
2162        self.this.upgrade().unwrap()
2163    }
2164
2165    fn home_dir(&self) -> Option<PathBuf> {
2166        self.state.lock().home_dir.clone()
2167    }
2168}
2169
2170fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
2171    rope.chunks().flat_map(move |chunk| {
2172        let mut newline = false;
2173        chunk.split('\n').flat_map(move |line| {
2174            let ending = if newline {
2175                Some(line_ending.as_str())
2176            } else {
2177                None
2178            };
2179            newline = true;
2180            ending.into_iter().chain([line])
2181        })
2182    })
2183}
2184
2185pub fn normalize_path(path: &Path) -> PathBuf {
2186    let mut components = path.components().peekable();
2187    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
2188        components.next();
2189        PathBuf::from(c.as_os_str())
2190    } else {
2191        PathBuf::new()
2192    };
2193
2194    for component in components {
2195        match component {
2196            Component::Prefix(..) => unreachable!(),
2197            Component::RootDir => {
2198                ret.push(component.as_os_str());
2199            }
2200            Component::CurDir => {}
2201            Component::ParentDir => {
2202                ret.pop();
2203            }
2204            Component::Normal(c) => {
2205                ret.push(c);
2206            }
2207        }
2208    }
2209    ret
2210}
2211
2212pub async fn copy_recursive<'a>(
2213    fs: &'a dyn Fs,
2214    source: &'a Path,
2215    target: &'a Path,
2216    options: CopyOptions,
2217) -> Result<()> {
2218    for (is_dir, item) in read_dir_items(fs, source).await? {
2219        let Ok(item_relative_path) = item.strip_prefix(source) else {
2220            continue;
2221        };
2222        let target_item = if item_relative_path == Path::new("") {
2223            target.to_path_buf()
2224        } else {
2225            target.join(item_relative_path)
2226        };
2227        if is_dir {
2228            if !options.overwrite && fs.metadata(&target_item).await.is_ok_and(|m| m.is_some()) {
2229                if options.ignore_if_exists {
2230                    continue;
2231                } else {
2232                    return Err(anyhow!("{target_item:?} already exists"));
2233                }
2234            }
2235            let _ = fs
2236                .remove_dir(
2237                    &target_item,
2238                    RemoveOptions {
2239                        recursive: true,
2240                        ignore_if_not_exists: true,
2241                    },
2242                )
2243                .await;
2244            fs.create_dir(&target_item).await?;
2245        } else {
2246            fs.copy_file(&item, &target_item, options).await?;
2247        }
2248    }
2249    Ok(())
2250}
2251
2252async fn read_dir_items<'a>(fs: &'a dyn Fs, source: &'a Path) -> Result<Vec<(bool, PathBuf)>> {
2253    let mut items = Vec::new();
2254    read_recursive(fs, source, &mut items).await?;
2255    Ok(items)
2256}
2257
2258fn read_recursive<'a>(
2259    fs: &'a dyn Fs,
2260    source: &'a Path,
2261    output: &'a mut Vec<(bool, PathBuf)>,
2262) -> BoxFuture<'a, Result<()>> {
2263    use futures::future::FutureExt;
2264
2265    async move {
2266        let metadata = fs
2267            .metadata(source)
2268            .await?
2269            .ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
2270
2271        if metadata.is_dir {
2272            output.push((true, source.to_path_buf()));
2273            let mut children = fs.read_dir(source).await?;
2274            while let Some(child_path) = children.next().await {
2275                if let Ok(child_path) = child_path {
2276                    read_recursive(fs, &child_path, output).await?;
2277                }
2278            }
2279        } else {
2280            output.push((false, source.to_path_buf()));
2281        }
2282        Ok(())
2283    }
2284    .boxed()
2285}
2286
2287// todo(windows)
2288// can we get file id not open the file twice?
2289// https://github.com/rust-lang/rust/issues/63010
2290#[cfg(target_os = "windows")]
2291async fn file_id(path: impl AsRef<Path>) -> Result<u64> {
2292    use std::os::windows::io::AsRawHandle;
2293
2294    use smol::fs::windows::OpenOptionsExt;
2295    use windows::Win32::{
2296        Foundation::HANDLE,
2297        Storage::FileSystem::{
2298            GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS,
2299        },
2300    };
2301
2302    let file = smol::fs::OpenOptions::new()
2303        .read(true)
2304        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS.0)
2305        .open(path)
2306        .await?;
2307
2308    let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
2309    // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
2310    // This function supports Windows XP+
2311    smol::unblock(move || {
2312        unsafe { GetFileInformationByHandle(HANDLE(file.as_raw_handle() as _), &mut info)? };
2313
2314        Ok(((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64))
2315    })
2316    .await
2317}
2318
2319#[cfg(test)]
2320mod tests {
2321    use super::*;
2322    use gpui::BackgroundExecutor;
2323    use serde_json::json;
2324    use util::path;
2325
2326    #[gpui::test]
2327    async fn test_fake_fs(executor: BackgroundExecutor) {
2328        let fs = FakeFs::new(executor.clone());
2329        fs.insert_tree(
2330            path!("/root"),
2331            json!({
2332                "dir1": {
2333                    "a": "A",
2334                    "b": "B"
2335                },
2336                "dir2": {
2337                    "c": "C",
2338                    "dir3": {
2339                        "d": "D"
2340                    }
2341                }
2342            }),
2343        )
2344        .await;
2345
2346        assert_eq!(
2347            fs.files(),
2348            vec![
2349                PathBuf::from(path!("/root/dir1/a")),
2350                PathBuf::from(path!("/root/dir1/b")),
2351                PathBuf::from(path!("/root/dir2/c")),
2352                PathBuf::from(path!("/root/dir2/dir3/d")),
2353            ]
2354        );
2355
2356        fs.create_symlink(path!("/root/dir2/link-to-dir3").as_ref(), "./dir3".into())
2357            .await
2358            .unwrap();
2359
2360        assert_eq!(
2361            fs.canonicalize(path!("/root/dir2/link-to-dir3").as_ref())
2362                .await
2363                .unwrap(),
2364            PathBuf::from(path!("/root/dir2/dir3")),
2365        );
2366        assert_eq!(
2367            fs.canonicalize(path!("/root/dir2/link-to-dir3/d").as_ref())
2368                .await
2369                .unwrap(),
2370            PathBuf::from(path!("/root/dir2/dir3/d")),
2371        );
2372        assert_eq!(
2373            fs.load(path!("/root/dir2/link-to-dir3/d").as_ref())
2374                .await
2375                .unwrap(),
2376            "D",
2377        );
2378    }
2379
2380    #[gpui::test]
2381    async fn test_copy_recursive_with_single_file(executor: BackgroundExecutor) {
2382        let fs = FakeFs::new(executor.clone());
2383        fs.insert_tree(
2384            path!("/outer"),
2385            json!({
2386                "a": "A",
2387                "b": "B",
2388                "inner": {}
2389            }),
2390        )
2391        .await;
2392
2393        assert_eq!(
2394            fs.files(),
2395            vec![
2396                PathBuf::from(path!("/outer/a")),
2397                PathBuf::from(path!("/outer/b")),
2398            ]
2399        );
2400
2401        let source = Path::new(path!("/outer/a"));
2402        let target = Path::new(path!("/outer/a copy"));
2403        copy_recursive(fs.as_ref(), source, target, Default::default())
2404            .await
2405            .unwrap();
2406
2407        assert_eq!(
2408            fs.files(),
2409            vec![
2410                PathBuf::from(path!("/outer/a")),
2411                PathBuf::from(path!("/outer/a copy")),
2412                PathBuf::from(path!("/outer/b")),
2413            ]
2414        );
2415
2416        let source = Path::new(path!("/outer/a"));
2417        let target = Path::new(path!("/outer/inner/a copy"));
2418        copy_recursive(fs.as_ref(), source, target, Default::default())
2419            .await
2420            .unwrap();
2421
2422        assert_eq!(
2423            fs.files(),
2424            vec![
2425                PathBuf::from(path!("/outer/a")),
2426                PathBuf::from(path!("/outer/a copy")),
2427                PathBuf::from(path!("/outer/b")),
2428                PathBuf::from(path!("/outer/inner/a copy")),
2429            ]
2430        );
2431    }
2432
2433    #[gpui::test]
2434    async fn test_copy_recursive_with_single_dir(executor: BackgroundExecutor) {
2435        let fs = FakeFs::new(executor.clone());
2436        fs.insert_tree(
2437            path!("/outer"),
2438            json!({
2439                "a": "A",
2440                "empty": {},
2441                "non-empty": {
2442                    "b": "B",
2443                }
2444            }),
2445        )
2446        .await;
2447
2448        assert_eq!(
2449            fs.files(),
2450            vec![
2451                PathBuf::from(path!("/outer/a")),
2452                PathBuf::from(path!("/outer/non-empty/b")),
2453            ]
2454        );
2455        assert_eq!(
2456            fs.directories(false),
2457            vec![
2458                PathBuf::from(path!("/")),
2459                PathBuf::from(path!("/outer")),
2460                PathBuf::from(path!("/outer/empty")),
2461                PathBuf::from(path!("/outer/non-empty")),
2462            ]
2463        );
2464
2465        let source = Path::new(path!("/outer/empty"));
2466        let target = Path::new(path!("/outer/empty copy"));
2467        copy_recursive(fs.as_ref(), source, target, Default::default())
2468            .await
2469            .unwrap();
2470
2471        assert_eq!(
2472            fs.files(),
2473            vec![
2474                PathBuf::from(path!("/outer/a")),
2475                PathBuf::from(path!("/outer/non-empty/b")),
2476            ]
2477        );
2478        assert_eq!(
2479            fs.directories(false),
2480            vec![
2481                PathBuf::from(path!("/")),
2482                PathBuf::from(path!("/outer")),
2483                PathBuf::from(path!("/outer/empty")),
2484                PathBuf::from(path!("/outer/empty copy")),
2485                PathBuf::from(path!("/outer/non-empty")),
2486            ]
2487        );
2488
2489        let source = Path::new(path!("/outer/non-empty"));
2490        let target = Path::new(path!("/outer/non-empty copy"));
2491        copy_recursive(fs.as_ref(), source, target, Default::default())
2492            .await
2493            .unwrap();
2494
2495        assert_eq!(
2496            fs.files(),
2497            vec![
2498                PathBuf::from(path!("/outer/a")),
2499                PathBuf::from(path!("/outer/non-empty/b")),
2500                PathBuf::from(path!("/outer/non-empty copy/b")),
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/empty")),
2509                PathBuf::from(path!("/outer/empty copy")),
2510                PathBuf::from(path!("/outer/non-empty")),
2511                PathBuf::from(path!("/outer/non-empty copy")),
2512            ]
2513        );
2514    }
2515
2516    #[gpui::test]
2517    async fn test_copy_recursive(executor: BackgroundExecutor) {
2518        let fs = FakeFs::new(executor.clone());
2519        fs.insert_tree(
2520            path!("/outer"),
2521            json!({
2522                "inner1": {
2523                    "a": "A",
2524                    "b": "B",
2525                    "inner3": {
2526                        "d": "D",
2527                    },
2528                    "inner4": {}
2529                },
2530                "inner2": {
2531                    "c": "C",
2532                }
2533            }),
2534        )
2535        .await;
2536
2537        assert_eq!(
2538            fs.files(),
2539            vec![
2540                PathBuf::from(path!("/outer/inner1/a")),
2541                PathBuf::from(path!("/outer/inner1/b")),
2542                PathBuf::from(path!("/outer/inner2/c")),
2543                PathBuf::from(path!("/outer/inner1/inner3/d")),
2544            ]
2545        );
2546        assert_eq!(
2547            fs.directories(false),
2548            vec![
2549                PathBuf::from(path!("/")),
2550                PathBuf::from(path!("/outer")),
2551                PathBuf::from(path!("/outer/inner1")),
2552                PathBuf::from(path!("/outer/inner2")),
2553                PathBuf::from(path!("/outer/inner1/inner3")),
2554                PathBuf::from(path!("/outer/inner1/inner4")),
2555            ]
2556        );
2557
2558        let source = Path::new(path!("/outer"));
2559        let target = Path::new(path!("/outer/inner1/outer"));
2560        copy_recursive(fs.as_ref(), source, target, Default::default())
2561            .await
2562            .unwrap();
2563
2564        assert_eq!(
2565            fs.files(),
2566            vec![
2567                PathBuf::from(path!("/outer/inner1/a")),
2568                PathBuf::from(path!("/outer/inner1/b")),
2569                PathBuf::from(path!("/outer/inner2/c")),
2570                PathBuf::from(path!("/outer/inner1/inner3/d")),
2571                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2572                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
2573                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
2574                PathBuf::from(path!("/outer/inner1/outer/inner1/inner3/d")),
2575            ]
2576        );
2577        assert_eq!(
2578            fs.directories(false),
2579            vec![
2580                PathBuf::from(path!("/")),
2581                PathBuf::from(path!("/outer")),
2582                PathBuf::from(path!("/outer/inner1")),
2583                PathBuf::from(path!("/outer/inner2")),
2584                PathBuf::from(path!("/outer/inner1/inner3")),
2585                PathBuf::from(path!("/outer/inner1/inner4")),
2586                PathBuf::from(path!("/outer/inner1/outer")),
2587                PathBuf::from(path!("/outer/inner1/outer/inner1")),
2588                PathBuf::from(path!("/outer/inner1/outer/inner2")),
2589                PathBuf::from(path!("/outer/inner1/outer/inner1/inner3")),
2590                PathBuf::from(path!("/outer/inner1/outer/inner1/inner4")),
2591            ]
2592        );
2593    }
2594
2595    #[gpui::test]
2596    async fn test_copy_recursive_with_overwriting(executor: BackgroundExecutor) {
2597        let fs = FakeFs::new(executor.clone());
2598        fs.insert_tree(
2599            path!("/outer"),
2600            json!({
2601                "inner1": {
2602                    "a": "A",
2603                    "b": "B",
2604                    "outer": {
2605                        "inner1": {
2606                            "a": "B"
2607                        }
2608                    }
2609                },
2610                "inner2": {
2611                    "c": "C",
2612                }
2613            }),
2614        )
2615        .await;
2616
2617        assert_eq!(
2618            fs.files(),
2619            vec![
2620                PathBuf::from(path!("/outer/inner1/a")),
2621                PathBuf::from(path!("/outer/inner1/b")),
2622                PathBuf::from(path!("/outer/inner2/c")),
2623                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2624            ]
2625        );
2626        assert_eq!(
2627            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2628                .await
2629                .unwrap(),
2630            "B",
2631        );
2632
2633        let source = Path::new(path!("/outer"));
2634        let target = Path::new(path!("/outer/inner1/outer"));
2635        copy_recursive(
2636            fs.as_ref(),
2637            source,
2638            target,
2639            CopyOptions {
2640                overwrite: true,
2641                ..Default::default()
2642            },
2643        )
2644        .await
2645        .unwrap();
2646
2647        assert_eq!(
2648            fs.files(),
2649            vec![
2650                PathBuf::from(path!("/outer/inner1/a")),
2651                PathBuf::from(path!("/outer/inner1/b")),
2652                PathBuf::from(path!("/outer/inner2/c")),
2653                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2654                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
2655                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
2656                PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
2657            ]
2658        );
2659        assert_eq!(
2660            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2661                .await
2662                .unwrap(),
2663            "A"
2664        );
2665    }
2666
2667    #[gpui::test]
2668    async fn test_copy_recursive_with_ignoring(executor: BackgroundExecutor) {
2669        let fs = FakeFs::new(executor.clone());
2670        fs.insert_tree(
2671            path!("/outer"),
2672            json!({
2673                "inner1": {
2674                    "a": "A",
2675                    "b": "B",
2676                    "outer": {
2677                        "inner1": {
2678                            "a": "B"
2679                        }
2680                    }
2681                },
2682                "inner2": {
2683                    "c": "C",
2684                }
2685            }),
2686        )
2687        .await;
2688
2689        assert_eq!(
2690            fs.files(),
2691            vec![
2692                PathBuf::from(path!("/outer/inner1/a")),
2693                PathBuf::from(path!("/outer/inner1/b")),
2694                PathBuf::from(path!("/outer/inner2/c")),
2695                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2696            ]
2697        );
2698        assert_eq!(
2699            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2700                .await
2701                .unwrap(),
2702            "B",
2703        );
2704
2705        let source = Path::new(path!("/outer"));
2706        let target = Path::new(path!("/outer/inner1/outer"));
2707        copy_recursive(
2708            fs.as_ref(),
2709            source,
2710            target,
2711            CopyOptions {
2712                ignore_if_exists: true,
2713                ..Default::default()
2714            },
2715        )
2716        .await
2717        .unwrap();
2718
2719        assert_eq!(
2720            fs.files(),
2721            vec![
2722                PathBuf::from(path!("/outer/inner1/a")),
2723                PathBuf::from(path!("/outer/inner1/b")),
2724                PathBuf::from(path!("/outer/inner2/c")),
2725                PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
2726                PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
2727                PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
2728                PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
2729            ]
2730        );
2731        assert_eq!(
2732            fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
2733                .await
2734                .unwrap(),
2735            "B"
2736        );
2737    }
2738}