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