Mercurial > hg
changeset 52046:28a0eb21ff04
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.
author | Raphaël Gomès <rgomes@octobus.net> |
---|---|
date | Tue, 01 Oct 2024 13:45:18 +0200 |
parents | 652149ed64f0 |
children | e1fe336c007a |
files | rust/hg-core/src/config/layer.rs rust/hg-core/src/repo.rs rust/hg-cpython/src/utils.rs |
diffstat | 3 files changed, 113 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- 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;