Mercurial > public > mercurial-scm > hg-stable
view rust/hg-core/src/errors.rs @ 48750:94e36b230990
status: prefer relative paths in Rust code
? when the repository root is under the current directory,
so the kernel needs to traverse fewer directory in every call
to `read_dir` or `symlink_metadata`.
Better yet would be to use libc functions like `openat` and `fstatat`
to remove such repeated traversals entirely, but the standard library
does not provide APIs based on those.
Maybe with a crate like https://crates.io/crates/openat instead?
Benchmarks of `rhg status` show that this patch is neutral in some configurations,
and makes the command up to ~20% faster in others.
Below is semi-arbitrary subset of results. The four numeric columns are:
time (in seconds) with this changeset?s parent, time with this changeset,
time difference (negative is better), time ratio (less than 1?is better).
```
mercurial-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.0061 -> 0.0059: -0.0002 (0.97)
mercurial-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0029 -> 0.0028: -0.0001 (0.97)
mozilla-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.2110 -> 0.2102: -0.0007 (1.00)
mozilla-dirstate-v2 | default-copies-clean.ignored.pbr | 0.0489 -> 0.0401: -0.0088 (0.82)
mozilla-dirstate-v2 | default-copies-clean.no-iu.pbr | 0.0479 -> 0.0393: -0.0085 (0.82)
mozilla-dirstate-v2 | default-copies-large.all.pbr | 0.1262 -> 0.1210: -0.0051 (0.96)
mozilla-dirstate-v2 | default-copies-small.ignored-unknown.pbr | 0.1262 -> 0.1200: -0.0062 (0.95)
mozilla-dirstate-v2 | default-copies-small.ignored.pbr | 0.0536 -> 0.0417: -0.0119 (0.78)
mozilla-dirstate-v2 | default-copies-small.no-iu.pbr | 0.0482 -> 0.0393: -0.0089 (0.81)
mozilla-dirstate-v2 | default-plain-clean.ignored.pbr | 0.0518 -> 0.0402: -0.0116 (0.78)
mozilla-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0481 -> 0.0392: -0.0088 (0.82)
mozilla-dirstate-v2 | default-plain-large.all.pbr | 0.1271 -> 0.1218: -0.0052 (0.96)
mozilla-dirstate-v2 | default-plain-small.ignored-unknown.pbr | 0.1225 -> 0.1202: -0.0022 (0.98)
mozilla-dirstate-v2 | default-plain-small.ignored.pbr | 0.0510 -> 0.0418: -0.0092 (0.82)
mozilla-dirstate-v2 | default-plain-small.no-iu.pbr | 0.0480 -> 0.0394: -0.0086 (0.82)
netbeans-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.1442 -> 0.1422: -0.0020 (0.99)
netbeans-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0325 -> 0.0282: -0.0043 (0.87)
```
Differential Revision: https://phab.mercurial-scm.org/D12175
author | Simon Sapin <simon.sapin@octobus.net> |
---|---|
date | Fri, 21 Jan 2022 17:54:03 +0100 |
parents | abeae090ce67 |
children | 3f86ee422095 |
line wrap: on
line source
use crate::config::ConfigValueParseError; use crate::exit_codes; use std::fmt; /// Common error cases that can happen in many different APIs #[derive(Debug, derive_more::From)] pub enum HgError { IoError { error: std::io::Error, context: IoErrorContext, }, /// A file under `.hg/` normally only written by Mercurial is not in the /// expected format. This indicates a bug in Mercurial, filesystem /// corruption, or hardware failure. /// /// The given string is a short explanation for users, not intended to be /// machine-readable. CorruptedRepository(String), /// The respository or requested operation involves a feature not /// supported by the Rust implementation. Falling back to the Python /// implementation may or may not work. /// /// The given string is a short explanation for users, not intended to be /// machine-readable. UnsupportedFeature(String), /// Operation cannot proceed for some other reason. /// /// The message is a short explanation for users, not intended to be /// machine-readable. Abort { message: String, detailed_exit_code: exit_codes::ExitCode, }, /// A configuration value is not in the expected syntax. /// /// These errors can happen in many places in the code because values are /// parsed lazily as the file-level parser does not know the expected type /// and syntax of each value. #[from] ConfigValueParseError(ConfigValueParseError), } /// Details about where an I/O error happened #[derive(Debug)] pub enum IoErrorContext { /// `std::fs::metadata` ReadingMetadata(std::path::PathBuf), ReadingFile(std::path::PathBuf), WritingFile(std::path::PathBuf), RemovingFile(std::path::PathBuf), RenamingFile { from: std::path::PathBuf, to: std::path::PathBuf, }, /// `std::fs::canonicalize` CanonicalizingPath(std::path::PathBuf), /// `std::env::current_dir` CurrentDir, /// `std::env::current_exe` CurrentExe, } impl HgError { pub fn corrupted(explanation: impl Into<String>) -> Self { // TODO: capture a backtrace here and keep it in the error value // to aid debugging? // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html HgError::CorruptedRepository(explanation.into()) } pub fn unsupported(explanation: impl Into<String>) -> Self { HgError::UnsupportedFeature(explanation.into()) } pub fn abort( explanation: impl Into<String>, exit_code: exit_codes::ExitCode, ) -> Self { HgError::Abort { message: explanation.into(), detailed_exit_code: exit_code, } } } // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? impl fmt::Display for HgError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { HgError::Abort { message, .. } => write!(f, "{}", message), HgError::IoError { error, context } => { write!(f, "abort: {}: {}", context, error) } HgError::CorruptedRepository(explanation) => { write!(f, "abort: {}", explanation) } HgError::UnsupportedFeature(explanation) => { write!(f, "unsupported feature: {}", explanation) } HgError::ConfigValueParseError(error) => error.fmt(f), } } } // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly? impl fmt::Display for IoErrorContext { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { IoErrorContext::ReadingMetadata(path) => { write!(f, "when reading metadata of {}", path.display()) } IoErrorContext::ReadingFile(path) => { write!(f, "when reading {}", path.display()) } IoErrorContext::WritingFile(path) => { write!(f, "when writing {}", path.display()) } IoErrorContext::RemovingFile(path) => { write!(f, "when removing {}", path.display()) } IoErrorContext::RenamingFile { from, to } => write!( f, "when renaming {} to {}", from.display(), to.display() ), IoErrorContext::CanonicalizingPath(path) => { write!(f, "when canonicalizing {}", path.display()) } IoErrorContext::CurrentDir => { write!(f, "error getting current working directory") } IoErrorContext::CurrentExe => { write!(f, "error getting current executable") } } } } pub trait IoResultExt<T> { /// Annotate a possible I/O error as related to a reading a file at the /// given path. /// /// This allows printing something like “File not found when reading /// example.txt” instead of just “File not found”. /// /// Converts a `Result` with `std::io::Error` into one with `HgError`. fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>; fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError>; fn with_context( self, context: impl FnOnce() -> IoErrorContext, ) -> Result<T, HgError>; } impl<T> IoResultExt<T> for std::io::Result<T> { fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> { self.with_context(|| IoErrorContext::ReadingFile(path.to_owned())) } fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError> { self.with_context(|| IoErrorContext::WritingFile(path.to_owned())) } fn with_context( self, context: impl FnOnce() -> IoErrorContext, ) -> Result<T, HgError> { self.map_err(|error| HgError::IoError { error, context: context(), }) } } pub trait HgResultExt<T> { /// Handle missing files separately from other I/O error cases. /// /// Wraps the `Ok` type in an `Option`: /// /// * `Ok(x)` becomes `Ok(Some(x))` /// * An I/O "not found" error becomes `Ok(None)` /// * Other errors are unchanged fn io_not_found_as_none(self) -> Result<Option<T>, HgError>; } impl<T> HgResultExt<T> for Result<T, HgError> { fn io_not_found_as_none(self) -> Result<Option<T>, HgError> { match self { Ok(x) => Ok(Some(x)), Err(HgError::IoError { error, .. }) if error.kind() == std::io::ErrorKind::NotFound => { Ok(None) } Err(other_error) => Err(other_error), } } }