##// END OF EJS Templates
rhg: Add support for automatic fallback to Python...
rhg: Add support for automatic fallback to Python `rhg` is a command-line application that can do a small subset of what `hg` can. It is written entirely in Rust, which avoids the cost of starting a Python interpreter and importing many Python modules. In a script that runs many `hg` commands, this cost can add up. However making users decide when to use `rhg` instead of `hg` is not practical as we want the subset of supported functionality to grow over time. Instead we introduce "fallback" behavior where, when `rhg` encounters something (a sub-command, a repository format, …) that is not implemented in Rust-only, it does nothing but silently start a subprocess of Python-based `hg` running the same command. That way `rhg` becomes a drop-in replacement for `hg` that sometimes goes faster. Whether Python is used should be an implementation detail not apparent to users (other than through speed). A new `fallback` value is added to the previously introduced `rhg.on-unsupported` configuration key. When in this mode, the new `rhg.fallback-executable` config is determine what command to use to run a Python-based `hg`. The previous `rhg.on-unsupported = abort-silent` configuration was designed to let a wrapper script call `rhg` and then fall back to `hg` based on the exit code. This is still available, but having fallback behavior built-in in rhg might be easier for users instead of leaving that script "as an exercise for the reader". Using a subprocess like this is not idea, especially when `rhg` is to be installed in `$PATH` as `hg`, since the other `hg.py` executable needs to still be available… somewhere. Eventually this could be replaced by using PyOxidizer to a have a single executable that embeds a Python interpreter, but only starts it when needed. Differential Revision: https://phab.mercurial-scm.org/D10093

File last commit:

r47169:776b9717 default
r47425:93e9f448 default
Show More
dirstate.rs
143 lines | 4.4 KiB | application/rls-services+xml | RustLexer
// dirstate.rs
//
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
//! Bindings for the `hg::dirstate` module provided by the
//! `hg-core` package.
//!
//! From Python, this will be seen as `mercurial.rustext.dirstate`
mod copymap;
mod dirs_multiset;
mod dirstate_map;
mod non_normal_entries;
mod status;
use crate::{
dirstate::{
dirs_multiset::Dirs, dirstate_map::DirstateMap, status::status_wrapper,
},
exceptions,
};
use cpython::{
exc, PyBytes, PyDict, PyErr, PyList, PyModule, PyObject, PyResult,
PySequence, Python,
};
use hg::{utils::hg_path::HgPathBuf, DirstateEntry, EntryState, StateMap};
use libc::{c_char, c_int};
use std::convert::TryFrom;
// C code uses a custom `dirstate_tuple` type, checks in multiple instances
// for this type, and raises a Python `Exception` if the check does not pass.
// Because this type differs only in name from the regular Python tuple, it
// would be a good idea in the near future to remove it entirely to allow
// for a pure Python tuple of the same effective structure to be used,
// rendering this type and the capsule below useless.
py_capsule_fn!(
from mercurial.cext.parsers import make_dirstate_tuple_CAPI
as make_dirstate_tuple_capi
signature (
state: c_char,
mode: c_int,
size: c_int,
mtime: c_int,
) -> *mut RawPyObject
);
pub fn make_dirstate_tuple(
py: Python,
entry: &DirstateEntry,
) -> PyResult<PyObject> {
// might be silly to retrieve capsule function in hot loop
let make = make_dirstate_tuple_capi::retrieve(py)?;
let &DirstateEntry {
state,
mode,
size,
mtime,
} = entry;
// Explicitly go through u8 first, then cast to platform-specific `c_char`
// because Into<u8> has a specific implementation while `as c_char` would
// just do a naive enum cast.
let state_code: u8 = state.into();
let maybe_obj = unsafe {
let ptr = make(state_code as c_char, mode, size, mtime);
PyObject::from_owned_ptr_opt(py, ptr)
};
maybe_obj.ok_or_else(|| PyErr::fetch(py))
}
pub fn extract_dirstate(py: Python, dmap: &PyDict) -> Result<StateMap, PyErr> {
dmap.items(py)
.iter()
.map(|(filename, stats)| {
let stats = stats.extract::<PySequence>(py)?;
let state = stats.get_item(py, 0)?.extract::<PyBytes>(py)?;
let state =
EntryState::try_from(state.data(py)[0]).map_err(|e| {
PyErr::new::<exc::ValueError, _>(py, e.to_string())
})?;
let mode = stats.get_item(py, 1)?.extract(py)?;
let size = stats.get_item(py, 2)?.extract(py)?;
let mtime = stats.get_item(py, 3)?.extract(py)?;
let filename = filename.extract::<PyBytes>(py)?;
let filename = filename.data(py);
Ok((
HgPathBuf::from(filename.to_owned()),
DirstateEntry {
state,
mode,
size,
mtime,
},
))
})
.collect()
}
/// Create the module, with `__package__` given from parent
pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
let dotted_name = &format!("{}.dirstate", package);
let m = PyModule::new(py, dotted_name)?;
env_logger::init();
m.add(py, "__package__", package)?;
m.add(py, "__doc__", "Dirstate - Rust implementation")?;
m.add(
py,
"FallbackError",
py.get_type::<exceptions::FallbackError>(),
)?;
m.add_class::<Dirs>(py)?;
m.add_class::<DirstateMap>(py)?;
m.add(
py,
"status",
py_fn!(
py,
status_wrapper(
dmap: DirstateMap,
root_dir: PyObject,
matcher: PyObject,
ignorefiles: PyList,
check_exec: bool,
last_normal_time: i64,
list_clean: bool,
list_ignored: bool,
list_unknown: bool,
collect_traversed_dirs: bool
)
),
)?;
let sys = PyModule::import(py, "sys")?;
let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
sys_modules.set_item(py, dotted_name, &m)?;
Ok(m)
}