diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -36,6 +36,7 @@ pub mod operations; pub mod revset; pub mod utils; +pub mod vfs; use crate::utils::hg_path::{HgPathBuf, HgPathError}; pub use filepatterns::{ diff --git a/rust/hg-core/src/logging.rs b/rust/hg-core/src/logging.rs --- a/rust/hg-core/src/logging.rs +++ b/rust/hg-core/src/logging.rs @@ -1,5 +1,5 @@ use crate::errors::{HgError, HgResultExt, IoErrorContext, IoResultExt}; -use crate::repo::Vfs; +use crate::vfs::Vfs; use std::io::Write; /// An utility to append to a log file with the given name, and optionally diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -1,12 +1,11 @@ use crate::config::{Config, ConfigError, ConfigParseError}; -use crate::errors::{HgError, IoErrorContext, IoResultExt}; +use crate::errors::HgError; use crate::exit_codes; use crate::requirements; use crate::utils::files::get_path_from_bytes; use crate::utils::SliceExt; -use memmap::{Mmap, MmapOptions}; +use crate::vfs::{is_dir, is_file, Vfs}; use std::collections::HashSet; -use std::io::ErrorKind; use std::path::{Path, PathBuf}; /// A repository on disk @@ -38,12 +37,6 @@ } } -/// Filesystem access abstraction for the contents of a given "base" diretory -#[derive(Clone, Copy)] -pub struct Vfs<'a> { - pub(crate) base: &'a Path, -} - impl Repo { /// tries to find nearest repository root in current working directory or /// its ancestors @@ -251,66 +244,3 @@ Ok(parents) } } - -impl Vfs<'_> { - pub fn join(&self, relative_path: impl AsRef) -> PathBuf { - self.base.join(relative_path) - } - - pub fn read( - &self, - relative_path: impl AsRef, - ) -> Result, HgError> { - let path = self.join(relative_path); - std::fs::read(&path).when_reading_file(&path) - } - - pub fn mmap_open( - &self, - relative_path: impl AsRef, - ) -> Result { - let path = self.base.join(relative_path); - let file = std::fs::File::open(&path).when_reading_file(&path)?; - // TODO: what are the safety requirements here? - let mmap = unsafe { MmapOptions::new().map(&file) } - .when_reading_file(&path)?; - Ok(mmap) - } - - pub fn rename( - &self, - relative_from: impl AsRef, - relative_to: impl AsRef, - ) -> Result<(), HgError> { - let from = self.join(relative_from); - let to = self.join(relative_to); - std::fs::rename(&from, &to) - .with_context(|| IoErrorContext::RenamingFile { from, to }) - } -} - -fn fs_metadata( - path: impl AsRef, -) -> Result, HgError> { - let path = path.as_ref(); - match std::fs::metadata(path) { - Ok(meta) => Ok(Some(meta)), - Err(error) => match error.kind() { - // TODO: when we require a Rust version where `NotADirectory` is - // stable, invert this logic and return None for it and `NotFound` - // and propagate any other error. - ErrorKind::PermissionDenied => Err(error).with_context(|| { - IoErrorContext::ReadingMetadata(path.to_owned()) - }), - _ => Ok(None), - }, - } -} - -fn is_dir(path: impl AsRef) -> Result { - Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir())) -} - -fn is_file(path: impl AsRef) -> Result { - Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file())) -} diff --git a/rust/hg-core/src/requirements.rs b/rust/hg-core/src/requirements.rs --- a/rust/hg-core/src/requirements.rs +++ b/rust/hg-core/src/requirements.rs @@ -1,6 +1,7 @@ use crate::errors::{HgError, HgResultExt}; -use crate::repo::{Repo, Vfs}; +use crate::repo::Repo; use crate::utils::join_display; +use crate::vfs::Vfs; use std::collections::HashSet; fn parse(bytes: &[u8]) -> Result, HgError> { diff --git a/rust/hg-core/src/vfs.rs b/rust/hg-core/src/vfs.rs new file mode 100644 --- /dev/null +++ b/rust/hg-core/src/vfs.rs @@ -0,0 +1,73 @@ +use crate::errors::{HgError, IoErrorContext, IoResultExt}; +use memmap::{Mmap, MmapOptions}; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; + +/// Filesystem access abstraction for the contents of a given "base" diretory +#[derive(Clone, Copy)] +pub struct Vfs<'a> { + pub(crate) base: &'a Path, +} + +impl Vfs<'_> { + pub fn join(&self, relative_path: impl AsRef) -> PathBuf { + self.base.join(relative_path) + } + + pub fn read( + &self, + relative_path: impl AsRef, + ) -> Result, HgError> { + let path = self.join(relative_path); + std::fs::read(&path).when_reading_file(&path) + } + + pub fn mmap_open( + &self, + relative_path: impl AsRef, + ) -> Result { + let path = self.base.join(relative_path); + let file = std::fs::File::open(&path).when_reading_file(&path)?; + // TODO: what are the safety requirements here? + let mmap = unsafe { MmapOptions::new().map(&file) } + .when_reading_file(&path)?; + Ok(mmap) + } + + pub fn rename( + &self, + relative_from: impl AsRef, + relative_to: impl AsRef, + ) -> Result<(), HgError> { + let from = self.join(relative_from); + let to = self.join(relative_to); + std::fs::rename(&from, &to) + .with_context(|| IoErrorContext::RenamingFile { from, to }) + } +} + +fn fs_metadata( + path: impl AsRef, +) -> Result, HgError> { + let path = path.as_ref(); + match std::fs::metadata(path) { + Ok(meta) => Ok(Some(meta)), + Err(error) => match error.kind() { + // TODO: when we require a Rust version where `NotADirectory` is + // stable, invert this logic and return None for it and `NotFound` + // and propagate any other error. + ErrorKind::PermissionDenied => Err(error).with_context(|| { + IoErrorContext::ReadingMetadata(path.to_owned()) + }), + _ => Ok(None), + }, + } +} + +pub(crate) fn is_dir(path: impl AsRef) -> Result { + Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir())) +} + +pub(crate) fn is_file(path: impl AsRef) -> Result { + Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file())) +}