1 use crate::config::{Config, ConfigError, ConfigParseError}; |
1 use crate::config::{Config, ConfigError, ConfigParseError}; |
2 use crate::errors::{HgError, IoErrorContext, IoResultExt}; |
2 use crate::errors::HgError; |
3 use crate::exit_codes; |
3 use crate::exit_codes; |
4 use crate::requirements; |
4 use crate::requirements; |
5 use crate::utils::files::get_path_from_bytes; |
5 use crate::utils::files::get_path_from_bytes; |
6 use crate::utils::SliceExt; |
6 use crate::utils::SliceExt; |
7 use memmap::{Mmap, MmapOptions}; |
7 use crate::vfs::{is_dir, is_file, Vfs}; |
8 use std::collections::HashSet; |
8 use std::collections::HashSet; |
9 use std::io::ErrorKind; |
|
10 use std::path::{Path, PathBuf}; |
9 use std::path::{Path, PathBuf}; |
11 |
10 |
12 /// A repository on disk |
11 /// A repository on disk |
13 pub struct Repo { |
12 pub struct Repo { |
14 working_directory: PathBuf, |
13 working_directory: PathBuf, |
34 match error { |
33 match error { |
35 ConfigError::Parse(error) => error.into(), |
34 ConfigError::Parse(error) => error.into(), |
36 ConfigError::Other(error) => error.into(), |
35 ConfigError::Other(error) => error.into(), |
37 } |
36 } |
38 } |
37 } |
39 } |
|
40 |
|
41 /// Filesystem access abstraction for the contents of a given "base" diretory |
|
42 #[derive(Clone, Copy)] |
|
43 pub struct Vfs<'a> { |
|
44 pub(crate) base: &'a Path, |
|
45 } |
38 } |
46 |
39 |
47 impl Repo { |
40 impl Repo { |
48 /// tries to find nearest repository root in current working directory or |
41 /// tries to find nearest repository root in current working directory or |
49 /// its ancestors |
42 /// its ancestors |
249 .clone() |
242 .clone() |
250 }; |
243 }; |
251 Ok(parents) |
244 Ok(parents) |
252 } |
245 } |
253 } |
246 } |
254 |
|
255 impl Vfs<'_> { |
|
256 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf { |
|
257 self.base.join(relative_path) |
|
258 } |
|
259 |
|
260 pub fn read( |
|
261 &self, |
|
262 relative_path: impl AsRef<Path>, |
|
263 ) -> Result<Vec<u8>, HgError> { |
|
264 let path = self.join(relative_path); |
|
265 std::fs::read(&path).when_reading_file(&path) |
|
266 } |
|
267 |
|
268 pub fn mmap_open( |
|
269 &self, |
|
270 relative_path: impl AsRef<Path>, |
|
271 ) -> Result<Mmap, HgError> { |
|
272 let path = self.base.join(relative_path); |
|
273 let file = std::fs::File::open(&path).when_reading_file(&path)?; |
|
274 // TODO: what are the safety requirements here? |
|
275 let mmap = unsafe { MmapOptions::new().map(&file) } |
|
276 .when_reading_file(&path)?; |
|
277 Ok(mmap) |
|
278 } |
|
279 |
|
280 pub fn rename( |
|
281 &self, |
|
282 relative_from: impl AsRef<Path>, |
|
283 relative_to: impl AsRef<Path>, |
|
284 ) -> Result<(), HgError> { |
|
285 let from = self.join(relative_from); |
|
286 let to = self.join(relative_to); |
|
287 std::fs::rename(&from, &to) |
|
288 .with_context(|| IoErrorContext::RenamingFile { from, to }) |
|
289 } |
|
290 } |
|
291 |
|
292 fn fs_metadata( |
|
293 path: impl AsRef<Path>, |
|
294 ) -> Result<Option<std::fs::Metadata>, HgError> { |
|
295 let path = path.as_ref(); |
|
296 match std::fs::metadata(path) { |
|
297 Ok(meta) => Ok(Some(meta)), |
|
298 Err(error) => match error.kind() { |
|
299 // TODO: when we require a Rust version where `NotADirectory` is |
|
300 // stable, invert this logic and return None for it and `NotFound` |
|
301 // and propagate any other error. |
|
302 ErrorKind::PermissionDenied => Err(error).with_context(|| { |
|
303 IoErrorContext::ReadingMetadata(path.to_owned()) |
|
304 }), |
|
305 _ => Ok(None), |
|
306 }, |
|
307 } |
|
308 } |
|
309 |
|
310 fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> { |
|
311 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir())) |
|
312 } |
|
313 |
|
314 fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> { |
|
315 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file())) |
|
316 } |
|