fs.rs

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