fs.rs

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