// revlog.rs // // Copyright 2019 Georges Racinet // // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. use crate::cindex; use cpython::{ ObjectProtocol, PyClone, PyDict, PyModule, PyObject, PyResult, PyTuple, Python, PythonObject, ToPyObject, }; 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 { match index.extract::(py) { Ok(midx) => Ok(midx.clone_cindex(py)), Err(_) => cindex::Index::new(py, index), } } py_class!(pub class MixedIndex |py| { data cindex: RefCell; def __new__(_cls, cindex: PyObject) -> PyResult { Self::create_instance(py, RefCell::new( cindex::Index::new(py, cindex)?)) } /// Compatibility layer used for Python consumers needing access to the C index /// /// Only use case so far is `scmutil.shortesthexnodeidprefix`, /// that may need to build a custom `nodetree`, based on a specified revset. /// With a Rust implementation of the nodemap, we will be able to get rid of /// this, by exposing our own standalone nodemap class, /// ready to accept `MixedIndex`. def get_cindex(&self) -> PyResult { Ok(self.cindex(py).borrow().inner().clone_ref(py)) } // Reforwarded C index API // index_methods (tp_methods). Same ordering as in revlog.c /// return the gca set of the given revs def ancestors(&self, *args, **kw) -> PyResult { self.call_cindex(py, "ancestors", args, kw) } /// return the heads of the common ancestors of the given revs def commonancestorsheads(&self, *args, **kw) -> PyResult { self.call_cindex(py, "commonancestorsheads", args, kw) } /// clear the index caches def clearcaches(&self, *args, **kw) -> PyResult { self.call_cindex(py, "clearcaches", args, kw) } /// get an index entry def get(&self, *args, **kw) -> PyResult { self.call_cindex(py, "get", args, kw) } /// return `rev` associated with a node or None def get_rev(&self, *args, **kw) -> PyResult { self.call_cindex(py, "get_rev", args, kw) } /// return True if the node exist in the index def has_node(&self, *args, **kw) -> PyResult { self.call_cindex(py, "has_node", args, kw) } /// return `rev` associated with a node or raise RevlogError def rev(&self, *args, **kw) -> PyResult { self.call_cindex(py, "rev", args, kw) } /// compute phases def computephasesmapsets(&self, *args, **kw) -> PyResult { self.call_cindex(py, "computephasesmapsets", args, kw) } /// reachableroots def reachableroots2(&self, *args, **kw) -> PyResult { self.call_cindex(py, "reachableroots2", args, kw) } /// get head revisions def headrevs(&self, *args, **kw) -> PyResult { self.call_cindex(py, "headrevs", args, kw) } /// get filtered head revisions def headrevsfiltered(&self, *args, **kw) -> PyResult { self.call_cindex(py, "headrevsfiltered", args, kw) } /// True if the object is a snapshot def issnapshot(&self, *args, **kw) -> PyResult { self.call_cindex(py, "issnapshot", args, kw) } /// Gather snapshot data in a cache dict def findsnapshots(&self, *args, **kw) -> PyResult { self.call_cindex(py, "findsnapshots", args, kw) } /// determine revisions with deltas to reconstruct fulltext def deltachain(&self, *args, **kw) -> PyResult { self.call_cindex(py, "deltachain", args, kw) } /// slice planned chunk read to reach a density threshold def slicechunktodensity(&self, *args, **kw) -> PyResult { self.call_cindex(py, "slicechunktodensity", args, kw) } /// append an index entry def append(&self, *args, **kw) -> PyResult { self.call_cindex(py, "append", args, kw) } /// match a potentially ambiguous node ID def partialmatch(&self, *args, **kw) -> PyResult { self.call_cindex(py, "partialmatch", args, kw) } /// find length of shortest hex nodeid of a binary ID def shortest(&self, *args, **kw) -> PyResult { self.call_cindex(py, "shortest", args, kw) } /// stats for the index def stats(&self, *args, **kw) -> PyResult { self.call_cindex(py, "stats", args, kw) } // index_sequence_methods and index_mapping_methods. // // Since we call back through the high level Python API, // there's no point making a distinction between index_get // and index_getitem. def __len__(&self) -> PyResult { self.cindex(py).borrow().inner().len(py) } def __getitem__(&self, key: PyObject) -> PyResult { // this conversion seems needless, but that's actually because // `index_getitem` does not handle conversion from PyLong, // which expressions such as [e for e in index] internally use. // Note that we don't seem to have a direct way to call // PySequence_GetItem (does the job), which would be better for // for performance let key = match key.extract::(py) { Ok(rev) => rev.to_py_object(py).into_object(), Err(_) => key, }; self.cindex(py).borrow().inner().get_item(py, key) } def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> { self.cindex(py).borrow().inner().set_item(py, key, value) } def __delitem__(&self, key: PyObject) -> PyResult<()> { self.cindex(py).borrow().inner().del_item(py, key) } def __contains__(&self, item: PyObject) -> PyResult { // ObjectProtocol does not seem to provide contains(), so // this is an equivalent implementation of the index_contains() // defined in revlog.c let cindex = self.cindex(py).borrow(); match item.extract::(py) { Ok(rev) => { Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision) } Err(_) => { cindex.inner().call_method( py, "has_node", PyTuple::new(py, &[item]), None)? .extract(py) } } } }); impl MixedIndex { /// forward a method call to the underlying C index fn call_cindex( &self, py: Python, name: &str, args: &PyTuple, kwargs: Option<&PyDict>, ) -> PyResult { self.cindex(py) .borrow() .inner() .call_method(py, name, args, kwargs) } pub fn clone_cindex(&self, py: Python) -> cindex::Index { self.cindex(py).borrow().clone_ref(py) } } /// Create the module, with __package__ given from parent pub fn init_module(py: Python, package: &str) -> PyResult { let dotted_name = &format!("{}.revlog", package); let m = PyModule::new(py, dotted_name)?; m.add(py, "__package__", package)?; m.add(py, "__doc__", "RevLog - Rust implementations")?; m.add_class::(py)?; 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) }