...
 
Commits (3)
......@@ -39,6 +39,8 @@ typedef struct {
typedef struct {
int abi_version;
Py_ssize_t (*index_length)(const indexObject *);
const char *(*index_node)(indexObject *, Py_ssize_t);
int (*index_parents)(PyObject *, int, int *);
} Revlog_CAPI;
......@@ -3040,7 +3042,9 @@ static PyTypeObject rustlazyancestorsType = {
static Revlog_CAPI CAPI = {
/* increment the abi_version field upon each change in the Revlog_CAPI
struct or in the ABI of the listed functions */
1,
2,
index_length,
index_node,
HgRevlogIndex_GetParents,
};
......
......@@ -10,15 +10,25 @@
//! Ideally, we should use an Index entirely implemented in Rust,
//! but this will take some time to get there.
use cpython::{exc::ImportError, PyClone, PyErr, PyObject, PyResult, Python};
use cpython::{
exc::ImportError, ObjectProtocol, PyClone, PyErr, PyObject, PyResult,
PyTuple, Python, PythonObject,
};
use hg::revlog::{Node, RevlogIndex};
use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION};
use libc::c_int;
const REVLOG_CABI_VERSION: c_int = 1;
const REVLOG_CABI_VERSION: c_int = 2;
#[repr(C)]
pub struct Revlog_CAPI {
abi_version: c_int,
index_length:
unsafe extern "C" fn(index: *mut revlog_capi::RawPyObject) -> c_int,
index_node: unsafe extern "C" fn(
index: *mut revlog_capi::RawPyObject,
rev: c_int,
) -> *const Node,
index_parents: unsafe extern "C" fn(
index: *mut revlog_capi::RawPyObject,
rev: c_int,
......@@ -90,6 +100,15 @@ impl Index {
pub fn inner(&self) -> &PyObject {
&self.index
}
pub fn append(&mut self, py: Python, tup: PyTuple) -> PyResult<PyObject> {
self.index.call_method(
py,
"append",
PyTuple::new(py, &[tup.into_object()]),
None,
)
}
}
impl Clone for Index {
......@@ -131,3 +150,22 @@ impl Graph for Index {
}
}
}
impl RevlogIndex for Index {
/// Note C return type is Py_ssize_t (hence signed), but we shall
/// force it to unsigned, because it's a length
fn len(&self) -> usize {
unsafe { (self.capi.index_length)(self.index.as_ptr()) as usize }
}
fn node<'a>(&'a self, rev: Revision) -> Option<&'a Node> {
let raw = unsafe {
(self.capi.index_node)(self.index.as_ptr(), rev as c_int)
};
if raw.is_null() {
None
} else {
Some(unsafe { &*raw })
}
}
}
......@@ -6,15 +6,22 @@
// GNU General Public License version 2 or any later version.
use crate::cindex;
use crate::utils::{node_from_py_bytes, node_from_py_object};
use cpython::exc::{IndexError, ValueError};
use cpython::{
ObjectProtocol, PyClone, PyDict, PyModule, PyObject, PyResult, PyTuple, Python, PythonObject,
ToPyObject,
ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyModule, PyObject,
PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject,
};
use hg::revlog::nodemap::{NodeMap, NodeMapError, NodeTree};
use hg::revlog::{NodeError, NodePrefixRef, RevlogIndex};
use hg::Revision;
use std::cell::RefCell;
/// Return a Struct implementing the Graph trait
pub(crate) fn pyindex_to_graph(py: Python, index: PyObject) -> PyResult<cindex::Index> {
pub(crate) fn pyindex_to_graph(
py: Python,
index: PyObject,
) -> PyResult<cindex::Index> {
match index.extract::<MixedIndex>(py) {
Ok(midx) => Ok(midx.clone_cindex(py)),
Err(_) => cindex::Index::new(py, index),
......@@ -23,10 +30,10 @@ pub(crate) fn pyindex_to_graph(py: Python, index: PyObject) -> PyResult<cindex::
py_class!(pub class MixedIndex |py| {
data cindex: RefCell<cindex::Index>;
data nt: RefCell<NodeTree>;
def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
Self::create_instance(py, RefCell::new(
cindex::Index::new(py, cindex)?))
Self::new(py, cindex)
}
/// Compatibility layer used for Python consumers needing access to the C index
......@@ -40,8 +47,100 @@ py_class!(pub class MixedIndex |py| {
Ok(self.cindex(py).borrow().inner().clone_ref(py))
}
/// This is scaffolding at this point, but it could also become
/// a way to start a persistent nodemap or perform a
/// vacuum / repack operation
def _fill_nodemap(&self) -> PyResult<PyObject> {
let idx = self.cindex(py).borrow();
let mut nt = self.nt(py).borrow_mut();
for r in 0..idx.len() {
let rev = r as Revision;
nt.insert(&*idx, idx.node(rev).unwrap(), rev);
}
Ok(py.None())
}
// Index API involving nodemap, as defined in mercurial/pure/parsers.py
/// Return Revision if found, raises a bare `error.RevlogError`
/// in case of ambiguity, same as C version does
def get_rev(&self, node: PyBytes) -> PyResult<Option<Revision>> {
let nt = self.nt(py).borrow();
let idx = &*self.cindex(py).borrow();
let prefix: NodePrefixRef = node_from_py_bytes(py, &node)?.into();
nt.find_bin(idx, prefix).map_err(
|e| match e {
NodeMapError::MultipleResults => revlog_error(py),
NodeMapError::InvalidNodePrefix(s) => invalid_node_prefix(py, &s),
}
)
}
/// same as `get_rev()` but raises a bare `error.RevlogError` if node
/// is not found.
///
/// No need to repeat `node` in the exception, `mercurial/revlog.py`
/// will catch and rewrap with it
def rev(&self, node: PyBytes) -> PyResult<Revision> {
self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
}
/// return True if the node exist in the index
def has_node(&self, node: PyBytes) -> PyResult<bool> {
self.get_rev(py, node).map(|opt| opt.is_some())
}
/// find length of shortest hex nodeid of a binary ID
def shortest(&self, node: PyBytes) -> PyResult<usize> {
let nt = self.nt(py).borrow();
let idx = &*self.cindex(py).borrow();
match nt.shortest_bin(idx, node_from_py_bytes(py, &node)?.into()) {
Ok(Some(l)) => Ok(l),
Ok(None) => Err(revlog_error(py)),
Err(NodeMapError::MultipleResults) => Err(revlog_error(py)),
Err(NodeMapError::InvalidNodePrefix(s)) => Err(
invalid_node_prefix(py, &s)),
}
}
// TODO in py3, I suppose we get PyBytes, actually
def partialmatch(&self, node: PyString) -> PyResult<Option<PyBytes>> {
let nt = self.nt(py).borrow();
let idx = &*self.cindex(py).borrow();
nt.find_hex(idx, &node.to_string(py)?)
// TODO make an inner API returning the node directly
.map(|opt| opt.map(|rev| PyBytes::new(py, idx.node(rev).unwrap())))
.map_err(
|e| match e {
NodeMapError::MultipleResults => revlog_error(py),
NodeMapError::InvalidNodePrefix(s) =>
invalid_node_prefix(py, &s),
})
}
/// append an index entry
def append(&self, tup: PyTuple) -> PyResult<PyObject> {
if tup.len(py) < 8 {
// this is better than the panic promised by tup.get_item()
return Err(
PyErr::new::<IndexError, _>(py, "tuple index out of range"))
}
let node_bytes = tup.get_item(py, 7);
let node = node_from_py_object(py, &node_bytes.extract(py)?)?;
let mut idx = self.cindex(py).borrow_mut();
let rev = idx.len() as Revision;
self.nt(py).borrow_mut().insert(&*idx, &node, rev);
// `idx.append()` will revalidate the length of node,
// raising same exception as we did a few lines above
idx.append(py, tup)?;
Ok(py.None())
}
//
// Reforwarded C index API
//
// index_methods (tp_methods). Same ordering as in revlog.c
......@@ -65,21 +164,6 @@ py_class!(pub class MixedIndex |py| {
self.call_cindex(py, "get", args, kw)
}
/// return `rev` associated with a node or None
def get_rev(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "get_rev", args, kw)
}
/// return True if the node exist in the index
def has_node(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "has_node", args, kw)
}
/// return `rev` associated with a node or raise RevlogError
def rev(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "rev", args, kw)
}
/// compute phases
def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "computephasesmapsets", args, kw)
......@@ -120,21 +204,6 @@ py_class!(pub class MixedIndex |py| {
self.call_cindex(py, "slicechunktodensity", args, kw)
}
/// append an index entry
def append(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "append", args, kw)
}
/// match a potentially ambiguous node ID
def partialmatch(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "partialmatch", args, kw)
}
/// find length of shortest hex nodeid of a binary ID
def shortest(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "shortest", args, kw)
}
/// stats for the index
def stats(&self, *args, **kw) -> PyResult<PyObject> {
self.call_cindex(py, "stats", args, kw)
......@@ -196,6 +265,17 @@ py_class!(pub class MixedIndex |py| {
});
impl MixedIndex {
fn new(py: Python, cindex: PyObject) -> PyResult<MixedIndex> {
let readonly = Box::new(Vec::new());
let nt = NodeTree::load_bytes(readonly, 0, 0);
Self::create_instance(
py,
RefCell::new(cindex::Index::new(py, cindex)?),
RefCell::new(nt),
)
}
/// forward a method call to the underlying C index
fn call_cindex(
&self,
......@@ -215,6 +295,23 @@ impl MixedIndex {
}
}
fn revlog_error(py: Python) -> PyErr {
match py
.import("mercurial.error")
.and_then(|m| m.get(py, "RevlogError"))
{
Err(e) => e,
Ok(cls) => PyErr::from_instance(py, cls),
}
}
fn invalid_node_prefix(py: Python, ne: &NodeError) -> PyErr {
PyErr::new::<ValueError, _>(
py,
format!("Invalid node or prefix: {:?}", ne),
)
}
/// Create the module, with __package__ given from parent
pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
let dotted_name = &format!("{}.revlog", package);
......
use cpython::{PyDict, PyObject, PyResult, PyTuple, Python};
use cpython::exc::ValueError;
use cpython::{PyBytes, PyDict, PyErr, PyObject, PyResult, PyTuple, Python};
use hg::revlog::Node;
use std::convert::TryFrom;
#[allow(unused)]
pub fn print_python_trace(py: Python) -> PyResult<PyObject> {
......@@ -11,3 +14,23 @@ pub fn print_python_trace(py: Python) -> PyResult<PyObject> {
kwargs.set_item(py, "file", sys.get(py, "stderr")?)?;
traceback.call(py, "print_stack", PyTuple::new(py, &[]), Some(&kwargs))
}
/// Convert 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<&'a Node> {
let as_py_bytes: &'a PyBytes = bytes.extract(py)?;
node_from_py_bytes(py, as_py_bytes)
}
/// Convert incoming Python bytes given as `PyBytes` into `Node`,
/// doing the necessary checks.
pub fn node_from_py_bytes<'a>(
py: Python,
bytes: &'a PyBytes,
) -> PyResult<&'a Node> {
<&Node>::try_from(bytes.data(py))
.map_err(|_| PyErr::new::<ValueError, _>(py, "20-byte hash required"))
}