fs.rs

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