rust-cpython: add a util to get a `Repo` from a python path
I suspect this will not be the last time we need to do something like this.
--- a/rust/hg-core/src/config/layer.rs Tue Oct 01 13:20:40 2024 +0200
+++ b/rust/hg-core/src/config/layer.rs Tue Oct 01 13:45:18 2024 +0200
@@ -8,7 +8,7 @@
// GNU General Public License version 2 or any later version.
use crate::errors::HgError;
-use crate::exit_codes::CONFIG_PARSE_ERROR_ABORT;
+use crate::exit_codes::{CONFIG_ERROR_ABORT, CONFIG_PARSE_ERROR_ABORT};
use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
use format_bytes::{format_bytes, write_bytes, DisplayBytes};
use lazy_static::lazy_static;
@@ -324,9 +324,7 @@
ConfigOrigin::Tweakdefaults => {
write_bytes!(out, b"ui.tweakdefaults")
}
- ConfigOrigin::Defaults => {
- write_bytes!(out, b"configitems.toml")
- }
+ ConfigOrigin::Defaults => write_bytes!(out, b"configitems.toml"),
}
}
}
@@ -338,12 +336,49 @@
pub message: Vec<u8>,
}
+impl From<ConfigParseError> for HgError {
+ fn from(error: ConfigParseError) -> Self {
+ let ConfigParseError {
+ origin,
+ line,
+ message,
+ } = error;
+ let line_message = if let Some(line_number) = line {
+ format_bytes!(b":{}", line_number.to_string().into_bytes())
+ } else {
+ Vec::new()
+ };
+ HgError::Abort {
+ message: String::from_utf8_lossy(&format_bytes!(
+ b"config error at {}{}: {}",
+ origin,
+ line_message,
+ message
+ ))
+ .to_string(),
+ detailed_exit_code: CONFIG_ERROR_ABORT,
+ hint: None,
+ }
+ }
+}
+
#[derive(Debug, derive_more::From)]
pub enum ConfigError {
Parse(ConfigParseError),
Other(HgError),
}
+impl From<ConfigError> for HgError {
+ fn from(error: ConfigError) -> Self {
+ match error {
+ ConfigError::Parse(config_parse_error) => {
+ Self::from(config_parse_error)
+ }
+ ConfigError::Other(hg_error) => hg_error,
+ }
+ }
+}
+
fn make_regex(pattern: &'static str) -> Regex {
Regex::new(pattern).expect("expected a valid regex")
}
--- a/rust/hg-core/src/repo.rs Tue Oct 01 13:20:40 2024 +0200
+++ b/rust/hg-core/src/repo.rs Tue Oct 01 13:45:18 2024 +0200
@@ -20,7 +20,7 @@
use crate::utils::SliceExt;
use crate::vfs::{is_dir, is_file, VfsImpl};
use crate::{
- requirements, NodePrefix, RevlogDataConfig, RevlogDeltaConfig,
+ exit_codes, requirements, NodePrefix, RevlogDataConfig, RevlogDeltaConfig,
RevlogFeatureConfig, RevlogType, RevlogVersionOptions, UncheckedRevision,
};
use crate::{DirstateError, RevlogOpenOptions};
@@ -68,6 +68,32 @@
}
}
+impl From<RepoError> for HgError {
+ fn from(value: RepoError) -> Self {
+ match value {
+ RepoError::NotFound { at } => HgError::abort(
+ format!(
+ "abort: no repository found in '{}' (.hg not found)!",
+ at.display()
+ ),
+ exit_codes::ABORT,
+ None,
+ ),
+ RepoError::ConfigParseError(config_parse_error) => {
+ HgError::Abort {
+ message: String::from_utf8_lossy(
+ &config_parse_error.message,
+ )
+ .to_string(),
+ detailed_exit_code: exit_codes::CONFIG_PARSE_ERROR_ABORT,
+ hint: None,
+ }
+ }
+ RepoError::Other(hg_error) => hg_error,
+ }
+ }
+}
+
impl Repo {
/// tries to find nearest repository root in current working directory or
/// its ancestors
--- a/rust/hg-cpython/src/utils.rs Tue Oct 01 13:20:40 2024 +0200
+++ b/rust/hg-cpython/src/utils.rs Tue Oct 01 13:45:18 2024 +0200
@@ -1,6 +1,12 @@
use cpython::exc::ValueError;
use cpython::{PyBytes, PyDict, PyErr, PyObject, PyResult, PyTuple, Python};
+use hg::config::Config;
+use hg::errors::HgError;
+use hg::repo::{Repo, RepoError};
use hg::revlog::Node;
+use hg::utils::files::get_path_from_bytes;
+
+use crate::exceptions::FallbackError;
#[allow(unused)]
pub fn print_python_trace(py: Python) -> PyResult<PyObject> {
@@ -14,6 +20,47 @@
traceback.call(py, "print_stack", PyTuple::new(py, &[]), Some(&kwargs))
}
+pub fn hgerror_to_pyerr<T>(
+ py: Python,
+ error: Result<T, HgError>,
+) -> PyResult<T> {
+ error.map_err(|e| match e {
+ HgError::IoError { .. } => {
+ PyErr::new::<cpython::exc::IOError, _>(py, e.to_string())
+ }
+ HgError::UnsupportedFeature(e) => {
+ let as_string = e.to_string();
+ log::trace!("Update from null fallback: {}", as_string);
+ PyErr::new::<FallbackError, _>(py, &as_string)
+ }
+ HgError::RaceDetected(_) => {
+ unreachable!("must not surface to the user")
+ }
+ e => PyErr::new::<cpython::exc::RuntimeError, _>(py, e.to_string()),
+ })
+}
+
+pub fn repo_error_to_pyerr<T>(
+ py: Python,
+ error: Result<T, RepoError>,
+) -> PyResult<T> {
+ hgerror_to_pyerr(py, error.map_err(HgError::from))
+}
+
+/// Get a repository from a given [`PyObject`] path, and bubble up any error
+/// that comes up.
+pub fn repo_from_path(py: Python, repo_path: PyObject) -> Result<Repo, PyErr> {
+ let config =
+ hgerror_to_pyerr(py, Config::load_non_repo().map_err(HgError::from))?;
+ let py_bytes = &repo_path.extract::<PyBytes>(py)?;
+ let repo_path = py_bytes.data(py);
+ let repo = repo_error_to_pyerr(
+ py,
+ Repo::find(&config, Some(get_path_from_bytes(repo_path).to_owned())),
+ )?;
+ Ok(repo)
+}
+
// Necessary evil for the time being, could maybe be moved to
// a TryFrom in Node itself
const NODE_BYTES_LENGTH: usize = 20;