rust-cpython: add a util to get a `Repo` from a python path
authorRaphaël Gomès <rgomes@octobus.net>
Tue, 01 Oct 2024 13:45:18 +0200
changeset 52046 28a0eb21ff04
parent 52045 652149ed64f0
child 52047 e1fe336c007a
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.
rust/hg-core/src/config/layer.rs
rust/hg-core/src/repo.rs
rust/hg-cpython/src/utils.rs
--- 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;