utils.rs
144 lines
| 4.7 KiB
| application/rls-services+xml
|
RustLexer
r53109 | use cpython::exc::{KeyboardInterrupt, ValueError}; | |||
Raphaël Gomès
|
r52942 | use cpython::{ | ||
r53109 | ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult, | |||
PyTuple, Python, ToPyObject, | ||||
Raphaël Gomès
|
r52942 | }; | ||
Raphaël Gomès
|
r52938 | use hg::config::Config; | ||
use hg::errors::HgError; | ||||
use hg::repo::{Repo, RepoError}; | ||||
Georges Racinet
|
r44992 | use hg::revlog::Node; | ||
Raphaël Gomès
|
r52938 | use hg::utils::files::get_path_from_bytes; | ||
use crate::exceptions::FallbackError; | ||||
Raphaël Gomès
|
r43545 | |||
#[allow(unused)] | ||||
pub fn print_python_trace(py: Python) -> PyResult<PyObject> { | ||||
eprintln!("==============================="); | ||||
eprintln!("Printing Python stack from Rust"); | ||||
eprintln!("==============================="); | ||||
let traceback = py.import("traceback")?; | ||||
let sys = py.import("sys")?; | ||||
let kwargs = PyDict::new(py); | ||||
kwargs.set_item(py, "file", sys.get(py, "stderr")?)?; | ||||
traceback.call(py, "print_stack", PyTuple::new(py, &[]), Some(&kwargs)) | ||||
} | ||||
Georges Racinet
|
r44992 | |||
Raphaël Gomès
|
r52938 | 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") | ||||
} | ||||
Raphaël Gomès
|
r52942 | HgError::Path(path_error) => { | ||
let msg = PyBytes::new(py, path_error.to_string().as_bytes()); | ||||
let cls = py | ||||
.import("mercurial.error") | ||||
.and_then(|m| m.get(py, "InputError")) | ||||
.unwrap(); | ||||
PyErr::from_instance( | ||||
py, | ||||
cls.call(py, (msg,), None).ok().into_py_object(py), | ||||
) | ||||
} | ||||
r53109 | HgError::InterruptReceived => { | |||
PyErr::new::<KeyboardInterrupt, _>(py, "") | ||||
} | ||||
Raphaël Gomès
|
r52938 | 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> { | ||||
Matt Harbison
|
r53081 | // TODO make the Config a Python class and downcast it here, otherwise we | ||
// lose CLI args and runtime overrides done in Python. | ||||
Raphaël Gomès
|
r52938 | 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) | ||||
} | ||||
Georges Racinet
|
r44992 | // Necessary evil for the time being, could maybe be moved to | ||
// a TryFrom in Node itself | ||||
const NODE_BYTES_LENGTH: usize = 20; | ||||
type NodeData = [u8; NODE_BYTES_LENGTH]; | ||||
/// Copy incoming Python bytes given as `PyObject` into `Node`, | ||||
/// doing the necessary checks | ||||
pub fn node_from_py_object<'a>( | ||||
py: Python, | ||||
bytes: &'a PyObject, | ||||
) -> PyResult<Node> { | ||||
let as_py_bytes: &'a PyBytes = bytes.extract(py)?; | ||||
node_from_py_bytes(py, as_py_bytes) | ||||
} | ||||
/// Clone incoming Python bytes given as `PyBytes` as a `Node`, | ||||
/// doing the necessary checks. | ||||
Raphaël Gomès
|
r45500 | pub fn node_from_py_bytes(py: Python, bytes: &PyBytes) -> PyResult<Node> { | ||
Georges Racinet
|
r44992 | <NodeData>::try_from(bytes.data(py)) | ||
.map_err(|_| { | ||||
PyErr::new::<ValueError, _>( | ||||
py, | ||||
format!("{}-byte hash required", NODE_BYTES_LENGTH), | ||||
) | ||||
}) | ||||
Raphaël Gomès
|
r45500 | .map(Into::into) | ||
Georges Racinet
|
r44992 | } | ||
r53109 | ||||
/// Wrap a call to `func` so that Python's `SIGINT` handler is first stored, | ||||
/// then restored after the call to `func` and finally raised if | ||||
/// `func` returns a [`HgError::InterruptReceived`] | ||||
pub fn with_sigint_wrapper<R>( | ||||
py: Python, | ||||
func: impl Fn() -> Result<R, HgError>, | ||||
) -> PyResult<Result<R, HgError>> { | ||||
let signal_py_mod = py.import("signal")?; | ||||
let sigint_py_const = signal_py_mod.get(py, "SIGINT")?; | ||||
let old_handler = signal_py_mod.call( | ||||
py, | ||||
"getsignal", | ||||
PyTuple::new(py, &[sigint_py_const.clone_ref(py)]), | ||||
None, | ||||
)?; | ||||
let res = func(); | ||||
// Reset the old signal handler in Python because we've may have changed it | ||||
signal_py_mod.call( | ||||
py, | ||||
"signal", | ||||
PyTuple::new(py, &[sigint_py_const.clone_ref(py), old_handler]), | ||||
None, | ||||
)?; | ||||
if let Err(HgError::InterruptReceived) = res { | ||||
// Trigger the signal in Python | ||||
signal_py_mod.call( | ||||
py, | ||||
"raise_signal", | ||||
PyTuple::new(py, &[sigint_py_const]), | ||||
None, | ||||
)?; | ||||
} | ||||
Ok(res) | ||||
} | ||||