rust/hg-cpython/src/revlog.rs
changeset 52163 7346f93be7a4
parent 52158 f2eab4967bfc
child 52172 72bc29f01570
equal deleted inserted replaced
52162:13815c9decd4 52163:7346f93be7a4
     2 //
     2 //
     3 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
     3 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
     4 //
     4 //
     5 // This software may be used and distributed according to the terms of the
     5 // This software may be used and distributed according to the terms of the
     6 // GNU General Public License version 2 or any later version.
     6 // GNU General Public License version 2 or any later version.
       
     7 #![allow(non_snake_case)]
     7 
     8 
     8 use crate::{
     9 use crate::{
     9     conversion::{rev_pyiter_collect, rev_pyiter_collect_or_else},
    10     conversion::{rev_pyiter_collect, rev_pyiter_collect_or_else},
       
    11     pybytes_deref::{PyBufferDeref, PyBytesDeref},
    10     utils::{node_from_py_bytes, node_from_py_object},
    12     utils::{node_from_py_bytes, node_from_py_object},
       
    13     vfs::PyVfs,
    11     PyRevision,
    14     PyRevision,
    12 };
    15 };
    13 use cpython::{
    16 use cpython::{
    14     buffer::{Element, PyBuffer},
    17     buffer::{Element, PyBuffer},
    15     exc::{IndexError, ValueError},
    18     exc::{IndexError, ValueError},
    16     ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyInt, PyList,
    19     ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyInt, PyList,
    17     PyModule, PyObject, PyResult, PySet, PyString, PyTuple, Python,
    20     PyModule, PyObject, PyResult, PySet, PyTuple, PyType, Python,
    18     PythonObject, ToPyObject, UnsafePyLeaked,
    21     PythonObject, ToPyObject, UnsafePyLeaked,
    19 };
    22 };
    20 use hg::{
    23 use hg::{
    21     errors::HgError,
    24     errors::HgError,
    22     index::{
    25     index::{Phase, RevisionDataParams, SnapshotsCache, INDEX_ENTRY_SIZE},
    23         IndexHeader, Phase, RevisionDataParams, SnapshotsCache,
    26     nodemap::{Block, NodeMapError, NodeTree as CoreNodeTree},
    24         INDEX_ENTRY_SIZE,
    27     revlog::compression::CompressionConfig,
       
    28     revlog::inner_revlog::InnerRevlog as CoreInnerRevlog,
       
    29     revlog::inner_revlog::RevisionBuffer,
       
    30     revlog::options::{
       
    31         RevlogDataConfig, RevlogDeltaConfig, RevlogFeatureConfig,
       
    32         RevlogOpenOptions,
    25     },
    33     },
    26     nodemap::{Block, NodeMapError, NodeTree as CoreNodeTree},
       
    27     revlog::{nodemap::NodeMap, Graph, NodePrefix, RevlogError, RevlogIndex},
    34     revlog::{nodemap::NodeMap, Graph, NodePrefix, RevlogError, RevlogIndex},
    28     BaseRevision, Node, Revision, UncheckedRevision, NULL_REVISION,
    35     transaction::Transaction,
       
    36     utils::files::{get_bytes_from_path, get_path_from_bytes},
       
    37     BaseRevision, Node, Revision, RevlogType, UncheckedRevision,
       
    38     NULL_REVISION,
    29 };
    39 };
    30 use std::{
    40 use std::{
    31     cell::RefCell,
    41     cell::{Cell, RefCell},
    32     collections::{HashMap, HashSet},
    42     collections::{HashMap, HashSet},
       
    43     sync::OnceLock,
    33 };
    44 };
    34 use vcsgraph::graph::Graph as VCSGraph;
    45 use vcsgraph::graph::Graph as VCSGraph;
    35 
    46 
    36 pub struct PySharedIndex {
    47 pub struct PySharedIndex {
    37     /// The underlying hg-core index
    48     /// The underlying hg-core index
    39 }
    50 }
    40 
    51 
    41 /// Return a Struct implementing the Graph trait
    52 /// Return a Struct implementing the Graph trait
    42 pub(crate) fn py_rust_index_to_graph(
    53 pub(crate) fn py_rust_index_to_graph(
    43     py: Python,
    54     py: Python,
    44     index: PyObject,
    55     index_proxy: PyObject,
    45 ) -> PyResult<UnsafePyLeaked<PySharedIndex>> {
    56 ) -> PyResult<UnsafePyLeaked<PySharedIndex>> {
    46     let midx = index.extract::<Index>(py)?;
    57     let inner_revlog = index_proxy.getattr(py, "inner")?;
    47     let leaked = midx.index(py).leak_immutable();
    58     let inner_revlog = inner_revlog.extract::<InnerRevlog>(py)?;
       
    59     let leaked = inner_revlog.inner(py).leak_immutable();
    48     // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked`
    60     // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked`
    49     Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: idx }) })
    61     Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) })
    50 }
    62 }
    51 
    63 
    52 impl Clone for PySharedIndex {
    64 impl Clone for PySharedIndex {
    53     fn clone(&self) -> Self {
    65     fn clone(&self) -> Self {
    54         Self { inner: self.inner }
    66         Self { inner: self.inner }
    88     }
   100     }
    89     fn node(&self, rev: Revision) -> Option<&Node> {
   101     fn node(&self, rev: Revision) -> Option<&Node> {
    90         self.inner.node(rev)
   102         self.inner.node(rev)
    91     }
   103     }
    92 }
   104 }
    93 
       
    94 py_class!(pub class Index |py| {
       
    95     @shared data index: hg::index::Index;
       
    96     data nt: RefCell<Option<CoreNodeTree>>;
       
    97     data docket: RefCell<Option<PyObject>>;
       
    98     // Holds a reference to the mmap'ed persistent nodemap data
       
    99     data nodemap_mmap: RefCell<Option<PyBuffer>>;
       
   100     // Holds a reference to the mmap'ed persistent index data
       
   101     data index_mmap: RefCell<Option<PyBuffer>>;
       
   102     data head_revs_py_list: RefCell<Option<PyList>>;
       
   103     data head_node_ids_py_list: RefCell<Option<PyList>>;
       
   104 
       
   105     def __new__(
       
   106         _cls,
       
   107         data: PyObject,
       
   108         default_header: u32,
       
   109     ) -> PyResult<Self> {
       
   110         Self::new(py, data, default_header)
       
   111     }
       
   112 
       
   113     /// Compatibility layer used for Python consumers needing access to the C index
       
   114     ///
       
   115     /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
       
   116     /// that may need to build a custom `nodetree`, based on a specified revset.
       
   117     /// With a Rust implementation of the nodemap, we will be able to get rid of
       
   118     /// this, by exposing our own standalone nodemap class,
       
   119     /// ready to accept `Index`.
       
   120 /*    def get_cindex(&self) -> PyResult<PyObject> {
       
   121         Ok(self.cindex(py).borrow().inner().clone_ref(py))
       
   122     }
       
   123 */
       
   124     // Index API involving nodemap, as defined in mercurial/pure/parsers.py
       
   125 
       
   126     /// Return Revision if found, raises a bare `error.RevlogError`
       
   127     /// in case of ambiguity, same as C version does
       
   128     def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> {
       
   129         let opt = self.get_nodetree(py)?.borrow();
       
   130         let nt = opt.as_ref().unwrap();
       
   131         let ridx = &*self.index(py).borrow();
       
   132         let node = node_from_py_bytes(py, &node)?;
       
   133         let rust_rev =
       
   134             nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?;
       
   135         Ok(rust_rev.map(Into::into))
       
   136 
       
   137     }
       
   138 
       
   139     /// same as `get_rev()` but raises a bare `error.RevlogError` if node
       
   140     /// is not found.
       
   141     ///
       
   142     /// No need to repeat `node` in the exception, `mercurial/revlog.py`
       
   143     /// will catch and rewrap with it
       
   144     def rev(&self, node: PyBytes) -> PyResult<PyRevision> {
       
   145         self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
       
   146     }
       
   147 
       
   148     /// return True if the node exist in the index
       
   149     def has_node(&self, node: PyBytes) -> PyResult<bool> {
       
   150         // TODO OPTIM we could avoid a needless conversion here,
       
   151         // to do when scaffolding for pure Rust switch is removed,
       
   152         // as `get_rev()` currently does the necessary assertions
       
   153         self.get_rev(py, node).map(|opt| opt.is_some())
       
   154     }
       
   155 
       
   156     /// find length of shortest hex nodeid of a binary ID
       
   157     def shortest(&self, node: PyBytes) -> PyResult<usize> {
       
   158         let opt = self.get_nodetree(py)?.borrow();
       
   159         let nt = opt.as_ref().unwrap();
       
   160         let idx = &*self.index(py).borrow();
       
   161         match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
       
   162         {
       
   163             Ok(Some(l)) => Ok(l),
       
   164             Ok(None) => Err(revlog_error(py)),
       
   165             Err(e) => Err(nodemap_error(py, e)),
       
   166         }
       
   167     }
       
   168 
       
   169     def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> {
       
   170         let opt = self.get_nodetree(py)?.borrow();
       
   171         let nt = opt.as_ref().unwrap();
       
   172         let idx = &*self.index(py).borrow();
       
   173 
       
   174         let node_as_string = if cfg!(feature = "python3-sys") {
       
   175             node.cast_as::<PyString>(py)?.to_string(py)?.to_string()
       
   176         }
       
   177         else {
       
   178             let node = node.extract::<PyBytes>(py)?;
       
   179             String::from_utf8_lossy(node.data(py)).to_string()
       
   180         };
       
   181 
       
   182         let prefix = NodePrefix::from_hex(&node_as_string)
       
   183             .map_err(|_| PyErr::new::<ValueError, _>(
       
   184                 py, format!("Invalid node or prefix '{}'", node_as_string))
       
   185             )?;
       
   186 
       
   187         nt.find_bin(idx, prefix)
       
   188             // TODO make an inner API returning the node directly
       
   189             .map(|opt| opt.map(
       
   190                 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes())))
       
   191             .map_err(|e| nodemap_error(py, e))
       
   192 
       
   193     }
       
   194 
       
   195     /// append an index entry
       
   196     def append(&self, tup: PyTuple) -> PyResult<PyObject> {
       
   197         if tup.len(py) < 8 {
       
   198             // this is better than the panic promised by tup.get_item()
       
   199             return Err(
       
   200                 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
       
   201         }
       
   202         let node_bytes = tup.get_item(py, 7).extract(py)?;
       
   203         let node = node_from_py_object(py, &node_bytes)?;
       
   204 
       
   205         let rev = self.len(py)? as BaseRevision;
       
   206 
       
   207         // This is ok since we will just add the revision to the index
       
   208         let rev = Revision(rev);
       
   209         self.index(py)
       
   210             .borrow_mut()
       
   211             .append(py_tuple_to_revision_data_params(py, tup)?)
       
   212             .unwrap();
       
   213         let idx = &*self.index(py).borrow();
       
   214         self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
       
   215             .insert(idx, &node, rev)
       
   216             .map_err(|e| nodemap_error(py, e))?;
       
   217         Ok(py.None())
       
   218     }
       
   219 
       
   220     def __delitem__(&self, key: PyObject) -> PyResult<()> {
       
   221         // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
       
   222         let start = if let Ok(rev) = key.extract(py) {
       
   223             UncheckedRevision(rev)
       
   224         } else {
       
   225             let start = key.getattr(py, "start")?;
       
   226             UncheckedRevision(start.extract(py)?)
       
   227         };
       
   228         let start = self.index(py)
       
   229             .borrow()
       
   230             .check_revision(start)
       
   231             .ok_or_else(|| {
       
   232                 nodemap_error(py, NodeMapError::RevisionNotInIndex(start))
       
   233             })?;
       
   234         self.index(py).borrow_mut().remove(start).unwrap();
       
   235         let mut opt = self.get_nodetree(py)?.borrow_mut();
       
   236         let nt = opt.as_mut().unwrap();
       
   237         nt.invalidate_all();
       
   238         self.fill_nodemap(py, nt)?;
       
   239         Ok(())
       
   240     }
       
   241 
       
   242     //
       
   243     // Index methods previously reforwarded to C index (tp_methods)
       
   244     // Same ordering as in revlog.c
       
   245     //
       
   246 
       
   247     /// return the gca set of the given revs
       
   248     def ancestors(&self, *args, **_kw) -> PyResult<PyObject> {
       
   249         let rust_res = self.inner_ancestors(py, args)?;
       
   250         Ok(rust_res)
       
   251     }
       
   252 
       
   253     /// return the heads of the common ancestors of the given revs
       
   254     def commonancestorsheads(&self, *args, **_kw) -> PyResult<PyObject> {
       
   255         let rust_res = self.inner_commonancestorsheads(py, args)?;
       
   256         Ok(rust_res)
       
   257     }
       
   258 
       
   259     /// Clear the index caches and inner py_class data.
       
   260     /// It is Python's responsibility to call `update_nodemap_data` again.
       
   261     def clearcaches(&self) -> PyResult<PyObject> {
       
   262         self.nt(py).borrow_mut().take();
       
   263         self.docket(py).borrow_mut().take();
       
   264         self.nodemap_mmap(py).borrow_mut().take();
       
   265         self.head_revs_py_list(py).borrow_mut().take();
       
   266         self.head_node_ids_py_list(py).borrow_mut().take();
       
   267         self.index(py).borrow().clear_caches();
       
   268         Ok(py.None())
       
   269     }
       
   270 
       
   271     /// return the raw binary string representing a revision
       
   272     def entry_binary(&self, *args, **_kw) -> PyResult<PyObject> {
       
   273         let rindex = self.index(py).borrow();
       
   274         let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?);
       
   275         let rust_bytes = rindex.check_revision(rev).and_then(
       
   276             |r| rindex.entry_binary(r))
       
   277             .ok_or_else(|| rev_not_in_index(py, rev))?;
       
   278         let rust_res = PyBytes::new(py, rust_bytes).into_object();
       
   279         Ok(rust_res)
       
   280     }
       
   281 
       
   282     /// return a binary packed version of the header
       
   283     def pack_header(&self, *args, **_kw) -> PyResult<PyObject> {
       
   284         let rindex = self.index(py).borrow();
       
   285         let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?);
       
   286         let rust_res = PyBytes::new(py, &packed).into_object();
       
   287         Ok(rust_res)
       
   288     }
       
   289 
       
   290     /// compute phases
       
   291     def computephasesmapsets(&self, *args, **_kw) -> PyResult<PyObject> {
       
   292         let py_roots = args.get_item(py, 0).extract::<PyDict>(py)?;
       
   293         let rust_res = self.inner_computephasesmapsets(py, py_roots)?;
       
   294         Ok(rust_res)
       
   295     }
       
   296 
       
   297     /// reachableroots
       
   298     def reachableroots2(&self, *args, **_kw) -> PyResult<PyObject> {
       
   299         let rust_res = self.inner_reachableroots2(
       
   300             py,
       
   301             UncheckedRevision(args.get_item(py, 0).extract(py)?),
       
   302             args.get_item(py, 1),
       
   303             args.get_item(py, 2),
       
   304             args.get_item(py, 3).extract(py)?,
       
   305         )?;
       
   306         Ok(rust_res)
       
   307     }
       
   308 
       
   309     /// get head revisions
       
   310     def headrevs(&self, *args, **_kw) -> PyResult<PyObject> {
       
   311         let (filtered_revs, stop_rev) = match &args.len(py) {
       
   312              0 => Ok((py.None(), py.None())),
       
   313              1 => Ok((args.get_item(py, 0), py.None())),
       
   314              2 => Ok((args.get_item(py, 0), args.get_item(py, 1))),
       
   315              _ => Err(PyErr::new::<cpython::exc::TypeError, _>(py, "too many arguments")),
       
   316         }?;
       
   317         self.inner_headrevs(py, &filtered_revs, &stop_rev)
       
   318     }
       
   319 
       
   320     /// get head nodeids
       
   321     def head_node_ids(&self) -> PyResult<PyObject> {
       
   322         let rust_res = self.inner_head_node_ids(py)?;
       
   323         Ok(rust_res)
       
   324     }
       
   325 
       
   326     /// get diff in head revisions
       
   327     def headrevsdiff(&self, *args, **_kw) -> PyResult<PyObject> {
       
   328         let rust_res = self.inner_headrevsdiff(
       
   329           py,
       
   330           &args.get_item(py, 0),
       
   331           &args.get_item(py, 1))?;
       
   332         Ok(rust_res)
       
   333     }
       
   334 
       
   335     /// True if the object is a snapshot
       
   336     def issnapshot(&self, *args, **_kw) -> PyResult<bool> {
       
   337         let index = self.index(py).borrow();
       
   338         let result = index
       
   339             .is_snapshot(UncheckedRevision(args.get_item(py, 0).extract(py)?))
       
   340             .map_err(|e| {
       
   341                 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
       
   342             })?;
       
   343         Ok(result)
       
   344     }
       
   345 
       
   346     /// Gather snapshot data in a cache dict
       
   347     def findsnapshots(&self, *args, **_kw) -> PyResult<PyObject> {
       
   348         let index = self.index(py).borrow();
       
   349         let cache: PyDict = args.get_item(py, 0).extract(py)?;
       
   350         // this methods operates by setting new values in the cache,
       
   351         // hence we will compare results by letting the C implementation
       
   352         // operate over a deepcopy of the cache, and finally compare both
       
   353         // caches.
       
   354         let c_cache = PyDict::new(py);
       
   355         for (k, v) in cache.items(py) {
       
   356             c_cache.set_item(py, k, PySet::new(py, v)?)?;
       
   357         }
       
   358 
       
   359         let start_rev = UncheckedRevision(args.get_item(py, 1).extract(py)?);
       
   360         let end_rev = UncheckedRevision(args.get_item(py, 2).extract(py)?);
       
   361         let mut cache_wrapper = PySnapshotsCache{ py, dict: cache };
       
   362         index.find_snapshots(
       
   363             start_rev,
       
   364             end_rev,
       
   365             &mut cache_wrapper,
       
   366         ).map_err(|_| revlog_error(py))?;
       
   367         Ok(py.None())
       
   368     }
       
   369 
       
   370     /// determine revisions with deltas to reconstruct fulltext
       
   371     def deltachain(&self, *args, **_kw) -> PyResult<PyObject> {
       
   372         let index = self.index(py).borrow();
       
   373         let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into();
       
   374         let stop_rev =
       
   375             args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?;
       
   376         let rev = index.check_revision(rev).ok_or_else(|| {
       
   377             nodemap_error(py, NodeMapError::RevisionNotInIndex(rev))
       
   378         })?;
       
   379         let stop_rev = if let Some(stop_rev) = stop_rev {
       
   380             let stop_rev = UncheckedRevision(stop_rev);
       
   381             Some(index.check_revision(stop_rev).ok_or_else(|| {
       
   382                 nodemap_error(py, NodeMapError::RevisionNotInIndex(stop_rev))
       
   383             })?)
       
   384         } else {None};
       
   385         let using_general_delta = args.get_item(py, 2)
       
   386             .extract::<Option<u32>>(py)?
       
   387             .map(|i| i != 0);
       
   388         let (chain, stopped) = index.delta_chain(
       
   389             rev, stop_rev, using_general_delta
       
   390         ).map_err(|e| {
       
   391             PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
       
   392         })?;
       
   393 
       
   394         let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect();
       
   395         Ok(
       
   396             PyTuple::new(
       
   397                 py,
       
   398                 &[
       
   399                     chain.into_py_object(py).into_object(),
       
   400                     stopped.into_py_object(py).into_object()
       
   401                 ]
       
   402             ).into_object()
       
   403         )
       
   404 
       
   405     }
       
   406 
       
   407     /// slice planned chunk read to reach a density threshold
       
   408     def slicechunktodensity(&self, *args, **_kw) -> PyResult<PyObject> {
       
   409         let rust_res = self.inner_slicechunktodensity(
       
   410             py,
       
   411             args.get_item(py, 0),
       
   412             args.get_item(py, 1).extract(py)?,
       
   413             args.get_item(py, 2).extract(py)?
       
   414         )?;
       
   415         Ok(rust_res)
       
   416     }
       
   417 
       
   418     // index_sequence_methods and index_mapping_methods.
       
   419     //
       
   420     // Since we call back through the high level Python API,
       
   421     // there's no point making a distinction between index_get
       
   422     // and index_getitem.
       
   423     // gracinet 2023: this above is no longer true for the pure Rust impl
       
   424 
       
   425     def __len__(&self) -> PyResult<usize> {
       
   426         self.len(py)
       
   427     }
       
   428 
       
   429     def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
       
   430         let rust_res = self.inner_getitem(py, key.clone_ref(py))?;
       
   431         Ok(rust_res)
       
   432     }
       
   433 
       
   434     def __contains__(&self, item: PyObject) -> PyResult<bool> {
       
   435         // ObjectProtocol does not seem to provide contains(), so
       
   436         // this is an equivalent implementation of the index_contains()
       
   437         // defined in revlog.c
       
   438         match item.extract::<i32>(py) {
       
   439             Ok(rev) => {
       
   440                 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision)
       
   441             }
       
   442             Err(_) => {
       
   443                 let item_bytes: PyBytes = item.extract(py)?;
       
   444                 let rust_res = self.has_node(py, item_bytes)?;
       
   445                 Ok(rust_res)
       
   446             }
       
   447         }
       
   448     }
       
   449 
       
   450     def nodemap_data_all(&self) -> PyResult<PyBytes> {
       
   451         self.inner_nodemap_data_all(py)
       
   452     }
       
   453 
       
   454     def nodemap_data_incremental(&self) -> PyResult<PyObject> {
       
   455         self.inner_nodemap_data_incremental(py)
       
   456     }
       
   457     def update_nodemap_data(
       
   458         &self,
       
   459         docket: PyObject,
       
   460         nm_data: PyObject
       
   461     ) -> PyResult<PyObject> {
       
   462         self.inner_update_nodemap_data(py, docket, nm_data)
       
   463     }
       
   464 
       
   465     @property
       
   466     def entry_size(&self) -> PyResult<PyInt> {
       
   467         let rust_res: PyInt = INDEX_ENTRY_SIZE.to_py_object(py);
       
   468         Ok(rust_res)
       
   469     }
       
   470 
       
   471     @property
       
   472     def rust_ext_compat(&self) -> PyResult<PyInt> {
       
   473         // will be entirely removed when the Rust index yet useful to
       
   474         // implement in Rust to detangle things when removing `self.cindex`
       
   475         let rust_res: PyInt = 1.to_py_object(py);
       
   476         Ok(rust_res)
       
   477     }
       
   478 
       
   479     @property
       
   480     def is_rust(&self) -> PyResult<PyBool> {
       
   481         Ok(false.to_py_object(py))
       
   482     }
       
   483 
       
   484 });
       
   485 
   105 
   486 /// Take a (potentially) mmap'ed buffer, and return the underlying Python
   106 /// Take a (potentially) mmap'ed buffer, and return the underlying Python
   487 /// buffer along with the Rust slice into said buffer. We need to keep the
   107 /// buffer along with the Rust slice into said buffer. We need to keep the
   488 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer
   108 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer
   489 /// is freed from Python's side.
   109 /// is freed from Python's side.
   536     let node_id = tuple
   156     let node_id = tuple
   537         .get_item(py, 7)
   157         .get_item(py, 7)
   538         .extract::<PyBytes>(py)?
   158         .extract::<PyBytes>(py)?
   539         .data(py)
   159         .data(py)
   540         .try_into()
   160         .try_into()
   541         .unwrap();
   161         .expect("nodeid should be set");
   542     let flags = (offset_or_flags & 0xFFFF) as u16;
   162     let flags = (offset_or_flags & 0xFFFF) as u16;
   543     let data_offset = offset_or_flags >> 16;
   163     let data_offset = offset_or_flags >> 16;
   544     Ok(RevisionDataParams {
   164     Ok(RevisionDataParams {
   545         flags,
   165         flags,
   546         data_offset,
   166         data_offset,
   620             ))
   240             ))
   621         })
   241         })
   622     }
   242     }
   623 }
   243 }
   624 
   244 
   625 impl Index {
   245 // There are no static generics in Rust (because their implementation is hard,
   626     fn new(py: Python, data: PyObject, header: u32) -> PyResult<Self> {
   246 // I'm guessing it's due to different compilation stages, etc.).
   627         // Safety: we keep the buffer around inside the class as `index_mmap`
   247 // So manually generate all three caches and use them in `with_filelog_cache`.
   628         let (buf, bytes) = unsafe { mmap_keeparound(py, data)? };
   248 static DELTA_CONFIG_CACHE: OnceLock<(PyObject, RevlogDeltaConfig)> =
   629 
   249     OnceLock::new();
   630         Self::create_instance(
   250 static DATA_CONFIG_CACHE: OnceLock<(PyObject, RevlogDataConfig)> =
       
   251     OnceLock::new();
       
   252 static FEATURE_CONFIG_CACHE: OnceLock<(PyObject, RevlogFeatureConfig)> =
       
   253     OnceLock::new();
       
   254 
       
   255 /// Cache the first conversion from Python -> Rust config for all filelogs to
       
   256 /// save on conversion time when called in a loop.
       
   257 fn with_filelog_cache<T: Copy>(
       
   258     py: Python,
       
   259     py_config: &PyObject,
       
   260     revlog_type: RevlogType,
       
   261     cache: &OnceLock<(PyObject, T)>,
       
   262     callback: impl Fn() -> PyResult<T>,
       
   263 ) -> PyResult<T> {
       
   264     let mut was_cached = false;
       
   265     if revlog_type == RevlogType::Filelog {
       
   266         if let Some((cached_py_config, rust_config)) = cache.get() {
       
   267             was_cached = true;
       
   268             // All filelogs in a given repository *most likely* have the
       
   269             // exact same config, but it's not impossible that some extensions
       
   270             // do some magic with configs or that this code will be used
       
   271             // for longer-running processes. So compare the source `PyObject`
       
   272             // in case the source changed, at the cost of some overhead.
       
   273             // We can't use `py_config.eq(cached_py_config)` because all config
       
   274             // objects are different in Python and `a is b` is false.
       
   275             if py_config.compare(py, cached_py_config)?.is_eq() {
       
   276                 return Ok(*rust_config);
       
   277             }
       
   278         }
       
   279     }
       
   280     let config = callback()?;
       
   281     // Do not call the lock unnecessarily if it's already been set.
       
   282     if !was_cached && revlog_type == RevlogType::Filelog {
       
   283         cache.set((py_config.clone_ref(py), config)).ok();
       
   284     }
       
   285     Ok(config)
       
   286 }
       
   287 
       
   288 fn extract_delta_config(
       
   289     py: Python,
       
   290     py_config: PyObject,
       
   291     revlog_type: RevlogType,
       
   292 ) -> PyResult<RevlogDeltaConfig> {
       
   293     let get_delta_config = || {
       
   294         let max_deltachain_span = py_config
       
   295             .getattr(py, "max_deltachain_span")?
       
   296             .extract::<i64>(py)?;
       
   297 
       
   298         let revlog_delta_config = RevlogDeltaConfig {
       
   299             general_delta: py_config
       
   300                 .getattr(py, "general_delta")?
       
   301                 .extract(py)?,
       
   302             sparse_revlog: py_config
       
   303                 .getattr(py, "sparse_revlog")?
       
   304                 .extract(py)?,
       
   305             max_chain_len: py_config
       
   306                 .getattr(py, "max_chain_len")?
       
   307                 .extract(py)?,
       
   308             max_deltachain_span: if max_deltachain_span < 0 {
       
   309                 None
       
   310             } else {
       
   311                 Some(max_deltachain_span as u64)
       
   312             },
       
   313             upper_bound_comp: py_config
       
   314                 .getattr(py, "upper_bound_comp")?
       
   315                 .extract(py)?,
       
   316             delta_both_parents: py_config
       
   317                 .getattr(py, "delta_both_parents")?
       
   318                 .extract(py)?,
       
   319             candidate_group_chunk_size: py_config
       
   320                 .getattr(py, "candidate_group_chunk_size")?
       
   321                 .extract(py)?,
       
   322             debug_delta: py_config.getattr(py, "debug_delta")?.extract(py)?,
       
   323             lazy_delta: py_config.getattr(py, "lazy_delta")?.extract(py)?,
       
   324             lazy_delta_base: py_config
       
   325                 .getattr(py, "lazy_delta_base")?
       
   326                 .extract(py)?,
       
   327         };
       
   328         Ok(revlog_delta_config)
       
   329     };
       
   330     with_filelog_cache(
       
   331         py,
       
   332         &py_config,
       
   333         revlog_type,
       
   334         &DELTA_CONFIG_CACHE,
       
   335         get_delta_config,
       
   336     )
       
   337 }
       
   338 
       
   339 fn extract_data_config(
       
   340     py: Python,
       
   341     py_config: PyObject,
       
   342     revlog_type: RevlogType,
       
   343 ) -> PyResult<RevlogDataConfig> {
       
   344     let get_data_config = || {
       
   345         Ok(RevlogDataConfig {
       
   346             try_pending: py_config.getattr(py, "try_pending")?.extract(py)?,
       
   347             try_split: py_config.getattr(py, "try_split")?.extract(py)?,
       
   348             check_ambig: py_config.getattr(py, "check_ambig")?.extract(py)?,
       
   349             mmap_large_index: py_config
       
   350                 .getattr(py, "mmap_large_index")?
       
   351                 .extract(py)?,
       
   352             mmap_index_threshold: py_config
       
   353                 .getattr(py, "mmap_index_threshold")?
       
   354                 .extract(py)?,
       
   355             chunk_cache_size: py_config
       
   356                 .getattr(py, "chunk_cache_size")?
       
   357                 .extract(py)?,
       
   358             uncompressed_cache_factor: py_config
       
   359                 .getattr(py, "uncompressed_cache_factor")?
       
   360                 .extract(py)?,
       
   361             uncompressed_cache_count: py_config
       
   362                 .getattr(py, "uncompressed_cache_count")?
       
   363                 .extract(py)?,
       
   364             with_sparse_read: py_config
       
   365                 .getattr(py, "with_sparse_read")?
       
   366                 .extract(py)?,
       
   367             sr_density_threshold: py_config
       
   368                 .getattr(py, "sr_density_threshold")?
       
   369                 .extract(py)?,
       
   370             sr_min_gap_size: py_config
       
   371                 .getattr(py, "sr_min_gap_size")?
       
   372                 .extract(py)?,
       
   373             general_delta: py_config
       
   374                 .getattr(py, "generaldelta")?
       
   375                 .extract(py)?,
       
   376         })
       
   377     };
       
   378 
       
   379     with_filelog_cache(
       
   380         py,
       
   381         &py_config,
       
   382         revlog_type,
       
   383         &DATA_CONFIG_CACHE,
       
   384         get_data_config,
       
   385     )
       
   386 }
       
   387 
       
   388 fn extract_feature_config(
       
   389     py: Python,
       
   390     py_config: PyObject,
       
   391     revlog_type: RevlogType,
       
   392 ) -> PyResult<RevlogFeatureConfig> {
       
   393     let get_feature_config = || {
       
   394         let engine_bytes = &py_config
       
   395             .getattr(py, "compression_engine")?
       
   396             .extract::<PyBytes>(py)?;
       
   397         let compression_engine = engine_bytes.data(py);
       
   398         let compression_engine = match compression_engine {
       
   399             b"zlib" => {
       
   400                 let compression_options = &py_config
       
   401                     .getattr(py, "compression_engine_options")?
       
   402                     .extract::<PyDict>(py)?;
       
   403                 let zlib_level = compression_options
       
   404                     .get_item(py, PyBytes::new(py, &b"zlib.level"[..]));
       
   405                 let level = if let Some(level) = zlib_level {
       
   406                     if level.is_none(py) {
       
   407                         None
       
   408                     } else {
       
   409                         Some(level.extract(py)?)
       
   410                     }
       
   411                 } else {
       
   412                     None
       
   413                 };
       
   414                 let mut engine = CompressionConfig::default();
       
   415                 if let Some(level) = level {
       
   416                     engine
       
   417                         .set_level(level)
       
   418                         .expect("invalid compression level from Python");
       
   419                 }
       
   420                 engine
       
   421             }
       
   422             b"zstd" => {
       
   423                 let compression_options = &py_config
       
   424                     .getattr(py, "compression_engine_options")?
       
   425                     .extract::<PyDict>(py)?;
       
   426                 let zstd_level = compression_options
       
   427                     .get_item(py, PyBytes::new(py, &b"zstd.level"[..]));
       
   428                 let level = if let Some(level) = zstd_level {
       
   429                     if level.is_none(py) {
       
   430                         None
       
   431                     } else {
       
   432                         Some(level.extract(py)?)
       
   433                     }
       
   434                 } else {
       
   435                     let level = compression_options
       
   436                         .get_item(py, PyBytes::new(py, &b"level"[..]));
       
   437                     if let Some(level) = level {
       
   438                         if level.is_none(py) {
       
   439                             None
       
   440                         } else {
       
   441                             Some(level.extract(py)?)
       
   442                         }
       
   443                     } else {
       
   444                         None
       
   445                     }
       
   446                 };
       
   447                 CompressionConfig::zstd(level)
       
   448                     .expect("invalid compression level from Python")
       
   449             }
       
   450             b"none" => CompressionConfig::None,
       
   451             e => {
       
   452                 return Err(PyErr::new::<ValueError, _>(
       
   453                     py,
       
   454                     format!(
       
   455                         "invalid compression engine {}",
       
   456                         String::from_utf8_lossy(e)
       
   457                     ),
       
   458                 ))
       
   459             }
       
   460         };
       
   461         let revlog_feature_config = RevlogFeatureConfig {
       
   462             compression_engine,
       
   463             censorable: py_config.getattr(py, "censorable")?.extract(py)?,
       
   464             has_side_data: py_config
       
   465                 .getattr(py, "has_side_data")?
       
   466                 .extract(py)?,
       
   467             compute_rank: py_config
       
   468                 .getattr(py, "compute_rank")?
       
   469                 .extract(py)?,
       
   470             canonical_parent_order: py_config
       
   471                 .getattr(py, "canonical_parent_order")?
       
   472                 .extract(py)?,
       
   473             enable_ellipsis: py_config
       
   474                 .getattr(py, "enable_ellipsis")?
       
   475                 .extract(py)?,
       
   476         };
       
   477         Ok(revlog_feature_config)
       
   478     };
       
   479     with_filelog_cache(
       
   480         py,
       
   481         &py_config,
       
   482         revlog_type,
       
   483         &FEATURE_CONFIG_CACHE,
       
   484         get_feature_config,
       
   485     )
       
   486 }
       
   487 
       
   488 fn revlog_error_from_msg(py: Python, e: impl ToString) -> PyErr {
       
   489     let msg = e.to_string();
       
   490 
       
   491     match py
       
   492         .import("mercurial.error")
       
   493         .and_then(|m| m.get(py, "RevlogError"))
       
   494     {
       
   495         Err(e) => e,
       
   496         Ok(cls) => {
       
   497             let msg = PyBytes::new(py, msg.as_bytes());
       
   498             PyErr::from_instance(
       
   499                 py,
       
   500                 cls.call(py, (msg,), None).ok().into_py_object(py),
       
   501             )
       
   502         }
       
   503     }
       
   504 }
       
   505 
       
   506 py_class!(pub class ReadingContextManager |py| {
       
   507     data inner_revlog: RefCell<InnerRevlog>;
       
   508 
       
   509     def __enter__(&self) -> PyResult<PyObject> {
       
   510         let res = self.inner_revlog(py)
       
   511             .borrow()
       
   512             .inner(py)
       
   513             .borrow()
       
   514             .enter_reading_context()
       
   515             .map_err(|e| revlog_error_from_msg(py, e));
       
   516         if let Err(e) = res {
       
   517             // `__exit__` is not called from Python if `__enter__` fails
       
   518             self.inner_revlog(py)
       
   519                 .borrow()
       
   520                 .inner(py)
       
   521                 .borrow()
       
   522                 .exit_reading_context();
       
   523             return Err(e)
       
   524         }
       
   525         Ok(py.None())
       
   526     }
       
   527 
       
   528     def __exit__(
       
   529         &self,
       
   530         ty: Option<PyType>,
       
   531         value: PyObject,
       
   532         traceback: PyObject
       
   533     ) -> PyResult<PyObject> {
       
   534         // unused arguments, keep clippy from complaining without adding
       
   535         // a general rule
       
   536         let _ = ty;
       
   537         let _ = value;
       
   538         let _ = traceback;
       
   539 
       
   540         self.inner_revlog(py)
       
   541             .borrow()
       
   542             .inner(py)
       
   543             .borrow()
       
   544             .exit_reading_context();
       
   545         Ok(py.None())
       
   546     }
       
   547 });
       
   548 
       
   549 // Only used from Python *tests*
       
   550 py_class!(pub class PyFileHandle |py| {
       
   551     data inner_file: RefCell<std::os::fd::RawFd>;
       
   552 
       
   553     def tell(&self) -> PyResult<PyObject> {
       
   554         let locals = PyDict::new(py);
       
   555         locals.set_item(py, "os", py.import("os")?)?;
       
   556         locals.set_item(py, "fd", *self.inner_file(py).borrow())?;
       
   557         let f = py.eval("os.fdopen(fd)", None, Some(&locals))?;
       
   558 
       
   559         // Prevent Python from closing the file after garbage collecting.
       
   560         // This is fine since Rust is still holding on to the actual File.
       
   561         // (and also because it's only used in tests).
       
   562         std::mem::forget(f.clone_ref(py));
       
   563 
       
   564         locals.set_item(py, "f", f)?;
       
   565         let res = py.eval("f.tell()", None, Some(&locals))?;
       
   566         Ok(res)
       
   567     }
       
   568 });
       
   569 
       
   570 /// Wrapper around a Python transaction object, to keep `hg-core` oblivious
       
   571 /// of the fact it's being called from Python.
       
   572 pub struct PyTransaction {
       
   573     inner: PyObject,
       
   574 }
       
   575 
       
   576 impl PyTransaction {
       
   577     pub fn new(inner: PyObject) -> Self {
       
   578         Self { inner }
       
   579     }
       
   580 }
       
   581 
       
   582 impl Clone for PyTransaction {
       
   583     fn clone(&self) -> Self {
       
   584         let gil = &Python::acquire_gil();
       
   585         let py = gil.python();
       
   586         Self {
       
   587             inner: self.inner.clone_ref(py),
       
   588         }
       
   589     }
       
   590 }
       
   591 
       
   592 impl Transaction for PyTransaction {
       
   593     fn add(&mut self, file: impl AsRef<std::path::Path>, offset: usize) {
       
   594         let gil = &Python::acquire_gil();
       
   595         let py = gil.python();
       
   596         let file = PyBytes::new(py, &get_bytes_from_path(file.as_ref()));
       
   597         self.inner
       
   598             .call_method(py, "add", (file, offset), None)
       
   599             .expect("transaction add failed");
       
   600     }
       
   601 }
       
   602 
       
   603 py_class!(pub class WritingContextManager |py| {
       
   604     data inner_revlog: RefCell<InnerRevlog>;
       
   605     data transaction: RefCell<PyTransaction>;
       
   606     data data_end: Cell<Option<usize>>;
       
   607 
       
   608     def __enter__(&self) -> PyResult<PyObject> {
       
   609         let res = self.inner_revlog(py)
       
   610             .borrow_mut()
       
   611             .inner(py)
       
   612             .borrow_mut()
       
   613             .enter_writing_context(
       
   614                 self.data_end(py).get(),
       
   615                 &mut *self.transaction(py).borrow_mut()
       
   616             ).map_err(|e| revlog_error_from_msg(py, e));
       
   617         if let Err(e) = res {
       
   618             // `__exit__` is not called from Python if `__enter__` fails
       
   619             self.inner_revlog(py)
       
   620                 .borrow_mut()
       
   621                 .inner(py)
       
   622                 .borrow_mut()
       
   623                 .exit_writing_context();
       
   624             return Err(e)
       
   625         }
       
   626         Ok(py.None())
       
   627     }
       
   628 
       
   629     def __exit__(
       
   630         &self,
       
   631         ty: Option<PyType>,
       
   632         value: PyObject,
       
   633         traceback: PyObject
       
   634     ) -> PyResult<PyObject> {
       
   635         // unused arguments, keep clippy from complaining without adding
       
   636         // a general rule
       
   637         let _ = ty;
       
   638         let _ = value;
       
   639         let _ = traceback;
       
   640 
       
   641         self.inner_revlog(py)
       
   642             .borrow_mut()
       
   643             .inner(py)
       
   644             .borrow_mut()
       
   645             .exit_writing_context();
       
   646         Ok(py.None())
       
   647     }
       
   648 });
       
   649 
       
   650 py_class!(pub class InnerRevlog |py| {
       
   651     @shared data inner: CoreInnerRevlog;
       
   652     data nt: RefCell<Option<CoreNodeTree>>;
       
   653     data docket: RefCell<Option<PyObject>>;
       
   654     // Holds a reference to the mmap'ed persistent nodemap data
       
   655     data nodemap_mmap: RefCell<Option<PyBuffer>>;
       
   656     // Holds a reference to the mmap'ed persistent index data
       
   657     data index_mmap: RefCell<PyBuffer>;
       
   658     data head_revs_py_list: RefCell<Option<PyList>>;
       
   659     data head_node_ids_py_list: RefCell<Option<PyList>>;
       
   660     data revision_cache: RefCell<Option<PyObject>>;
       
   661 
       
   662     def __new__(
       
   663         _cls,
       
   664         opener: PyObject,
       
   665         index_data: PyObject,
       
   666         index_file: PyObject,
       
   667         data_file: PyObject,
       
   668         sidedata_file: PyObject,
       
   669         inline: bool,
       
   670         data_config: PyObject,
       
   671         delta_config: PyObject,
       
   672         feature_config: PyObject,
       
   673         chunk_cache: PyObject,
       
   674         default_compression_header: PyObject,
       
   675         revlog_type: usize,
       
   676     ) -> PyResult<Self> {
       
   677         Self::inner_new(
   631             py,
   678             py,
   632             hg::index::Index::new(
   679             opener,
   633                 bytes,
   680             index_data,
   634                 IndexHeader::parse(&header.to_be_bytes())
   681             index_file,
   635                     .expect("default header is broken"),
   682             data_file,
       
   683             sidedata_file,
       
   684             inline,
       
   685             data_config,
       
   686             delta_config,
       
   687             feature_config,
       
   688             chunk_cache,
       
   689             default_compression_header,
       
   690             revlog_type
       
   691         )
       
   692     }
       
   693 
       
   694     def clear_cache(&self) -> PyResult<PyObject> {
       
   695         assert!(!self.is_delaying(py)?);
       
   696         self.revision_cache(py).borrow_mut().take();
       
   697         self.inner(py).borrow_mut().clear_cache();
       
   698         Ok(py.None())
       
   699     }
       
   700 
       
   701     @property def canonical_index_file(&self) -> PyResult<PyBytes> {
       
   702         let path = self.inner(py).borrow().canonical_index_file();
       
   703         Ok(PyBytes::new(py, &get_bytes_from_path(path)))
       
   704     }
       
   705 
       
   706     @property def is_delaying(&self) -> PyResult<bool> {
       
   707         Ok(self.inner(py).borrow().is_delaying())
       
   708     }
       
   709 
       
   710     @property def _revisioncache(&self) -> PyResult<PyObject> {
       
   711         let cache = &*self.revision_cache(py).borrow();
       
   712         match cache {
       
   713             None => Ok(py.None()),
       
   714             Some(cache) => {
       
   715                 Ok(cache.clone_ref(py))
       
   716             }
       
   717         }
       
   718 
       
   719     }
       
   720 
       
   721     @property def _writinghandles(&self) -> PyResult<PyObject> {
       
   722         use std::os::fd::AsRawFd;
       
   723 
       
   724         let inner = self.inner(py).borrow();
       
   725         let handles = inner.python_writing_handles();
       
   726 
       
   727         match handles.as_ref() {
       
   728             None => Ok(py.None()),
       
   729             Some(handles) => {
       
   730                 let d_handle = if let Some(d_handle) = &handles.data_handle {
       
   731                     let handle = RefCell::new(d_handle.file.as_raw_fd());
       
   732                     Some(PyFileHandle::create_instance(py, handle)?)
       
   733                 } else {
       
   734                     None
       
   735                 };
       
   736                 let handle =
       
   737                     RefCell::new(handles.index_handle.file.as_raw_fd());
       
   738                 Ok(
       
   739                     (
       
   740                         PyFileHandle::create_instance(py, handle)?,
       
   741                         d_handle,
       
   742                         py.None(),  // Sidedata handle
       
   743 
       
   744                     ).to_py_object(py).into_object()
       
   745                 )
       
   746             }
       
   747         }
       
   748 
       
   749     }
       
   750 
       
   751     @_revisioncache.setter def set_revision_cache(
       
   752         &self,
       
   753         value: Option<PyObject>
       
   754     ) -> PyResult<()> {
       
   755         *self.revision_cache(py).borrow_mut() = value.clone_ref(py);
       
   756         match value {
       
   757             None => {
       
   758                 // This means the property has been deleted, *not* that the
       
   759                 // property has been set to `None`. Whatever happens is up
       
   760                 // to the implementation. Here we just set it to `None`.
       
   761                 self
       
   762                     .inner(py)
       
   763                     .borrow()
       
   764                     .last_revision_cache
       
   765                     .lock()
       
   766                     .expect("lock should not be held")
       
   767                     .take();
       
   768             },
       
   769             Some(tuple) => {
       
   770                 if tuple.is_none(py) {
       
   771                     self
       
   772                         .inner(py)
       
   773                         .borrow()
       
   774                         .last_revision_cache
       
   775                         .lock()
       
   776                         .expect("lock should not be held")
       
   777                         .take();
       
   778                     return Ok(())
       
   779                 }
       
   780                 let node = tuple.get_item(py, 0)?.extract::<PyBytes>(py)?;
       
   781                 let node = node_from_py_bytes(py, &node)?;
       
   782                 let rev = tuple.get_item(py, 1)?.extract::<BaseRevision>(py)?;
       
   783                 // Ok because Python only sets this if the revision has been
       
   784                 // checked
       
   785                 let rev = Revision(rev);
       
   786                 let data = tuple.get_item(py, 2)?.extract::<PyBytes>(py)?;
       
   787                 let inner = self.inner(py).borrow();
       
   788                 let mut last_revision_cache = inner
       
   789                     .last_revision_cache
       
   790                     .lock()
       
   791                     .expect("lock should not be held");
       
   792                 *last_revision_cache =
       
   793                     Some((node, rev, Box::new(PyBytesDeref::new(py, data))));
       
   794             }
       
   795         }
       
   796         Ok(())
       
   797     }
       
   798 
       
   799     @property def inline(&self) -> PyResult<bool> {
       
   800         Ok(self.inner(py).borrow().is_inline())
       
   801     }
       
   802 
       
   803     @inline.setter def set_inline(
       
   804         &self,
       
   805         value: Option<PyObject>
       
   806     ) -> PyResult<()> {
       
   807         if let Some(v) = value {
       
   808             self.inner(py).borrow_mut().inline = v.extract(py)?;
       
   809         };
       
   810         Ok(())
       
   811     }
       
   812 
       
   813     @property def index_file(&self) -> PyResult<PyBytes> {
       
   814         Ok(
       
   815             PyBytes::new(
       
   816                 py,
       
   817                 &get_bytes_from_path(&self.inner(py).borrow().index_file)
   636             )
   818             )
   637             .map_err(|e| {
       
   638                 revlog_error_with_msg(py, e.to_string().as_bytes())
       
   639             })?,
       
   640             RefCell::new(None),
       
   641             RefCell::new(None),
       
   642             RefCell::new(None),
       
   643             RefCell::new(Some(buf)),
       
   644             RefCell::new(None),
       
   645             RefCell::new(None),
       
   646         )
   819         )
   647     }
   820     }
   648 
   821 
       
   822     @index_file.setter def set_index_file(
       
   823         &self,
       
   824         value: Option<PyObject>
       
   825     ) -> PyResult<()> {
       
   826         let path = get_path_from_bytes(
       
   827             value
       
   828                 .expect("don't delete the index path")
       
   829                 .extract::<PyBytes>(py)?
       
   830                 .data(py)
       
   831         ).to_owned();
       
   832         self.inner(py).borrow_mut().index_file = path;
       
   833         Ok(())
       
   834     }
       
   835 
       
   836     @property def is_writing(&self) -> PyResult<bool> {
       
   837         Ok(self.inner(py).borrow().is_writing())
       
   838     }
       
   839 
       
   840     @property def is_open(&self) -> PyResult<bool> {
       
   841         Ok(self.inner(py).borrow().is_open())
       
   842     }
       
   843 
       
   844     def issnapshot(&self, rev: PyRevision) -> PyResult<bool> {
       
   845         self.inner_issnapshot(py, UncheckedRevision(rev.0))
       
   846     }
       
   847 
       
   848     def _deltachain(&self, *args, **kw) -> PyResult<PyObject> {
       
   849         let inner = self.inner(py).borrow();
       
   850         let general_delta = inner.index.uses_generaldelta();
       
   851         let args = PyTuple::new(
       
   852             py,
       
   853             &[
       
   854                 args.get_item(py, 0),
       
   855                 kw.and_then(|d| d.get_item(py, "stoprev")).to_py_object(py),
       
   856                 general_delta.to_py_object(py).into_object(),
       
   857             ]
       
   858         );
       
   859         self._index_deltachain(py, &args, kw)
       
   860     }
       
   861 
       
   862     def compress(&self, data: PyObject) -> PyResult<PyTuple> {
       
   863         let inner = self.inner(py).borrow();
       
   864         let py_buffer = PyBuffer::get(py, &data)?;
       
   865         let deref = PyBufferDeref::new(py, py_buffer)?;
       
   866         let compressed = inner.compress(&deref)
       
   867         .map_err(|e| revlog_error_from_msg(py, e))?;
       
   868         let compressed = compressed.as_deref();
       
   869         let header = if compressed.is_some() {
       
   870             PyBytes::new(py, &b""[..])
       
   871         } else {
       
   872             PyBytes::new(py, &b"u"[..])
       
   873         };
       
   874         Ok(
       
   875             (
       
   876                 header,
       
   877                 PyBytes::new(py, compressed.unwrap_or(&deref))
       
   878             ).to_py_object(py)
       
   879         )
       
   880     }
       
   881 
       
   882     def reading(&self) -> PyResult<ReadingContextManager> {
       
   883         ReadingContextManager::create_instance(
       
   884             py,
       
   885             RefCell::new(self.clone_ref(py)),
       
   886         )
       
   887     }
       
   888 
       
   889     def writing(
       
   890         &self,
       
   891         transaction: PyObject,
       
   892         data_end: Option<usize>,
       
   893         sidedata_end: Option<usize>,
       
   894     ) -> PyResult<WritingContextManager> {
       
   895         // Silence unused argument (only relevant for changelog v2)
       
   896         let _ = sidedata_end;
       
   897         WritingContextManager::create_instance(
       
   898             py,
       
   899             RefCell::new(self.clone_ref(py)),
       
   900             RefCell::new(PyTransaction::new(transaction)),
       
   901             Cell::new(data_end)
       
   902         )
       
   903     }
       
   904 
       
   905     def split_inline(
       
   906         &self,
       
   907         _tr: PyObject,
       
   908         header: i32,
       
   909         new_index_file_path: Option<PyObject>
       
   910     ) -> PyResult<PyBytes> {
       
   911         let mut inner = self.inner(py).borrow_mut();
       
   912         let new_index_file_path = match new_index_file_path {
       
   913             Some(path) => {
       
   914                 let path = path.extract::<PyBytes>(py)?;
       
   915                 Some(get_path_from_bytes(path.data(py)).to_owned())
       
   916             },
       
   917             None => None,
       
   918         };
       
   919         let header = hg::index::IndexHeader::parse(&header.to_be_bytes());
       
   920         let header = header.expect("invalid header bytes");
       
   921         let path = inner
       
   922             .split_inline(header, new_index_file_path)
       
   923             .map_err(|e| revlog_error_from_msg(py, e))?;
       
   924         Ok(PyBytes::new(py, &get_bytes_from_path(path)))
       
   925     }
       
   926 
       
   927     def get_segment_for_revs(
       
   928         &self,
       
   929         startrev: PyRevision,
       
   930         endrev: PyRevision,
       
   931     ) -> PyResult<PyTuple> {
       
   932         let inner = self.inner(py).borrow();
       
   933         let (offset, data) = inner
       
   934             .get_segment_for_revs(Revision(startrev.0), Revision(endrev.0))
       
   935             .map_err(|e| revlog_error_from_msg(py, e))?;
       
   936         let data = PyBytes::new(py, &data);
       
   937         Ok((offset, data).to_py_object(py))
       
   938     }
       
   939 
       
   940     def raw_text(
       
   941         &self,
       
   942         _node: PyObject,
       
   943         rev: PyRevision
       
   944     ) -> PyResult<PyBytes> {
       
   945         let inner = self.inner(py).borrow();
       
   946         let mut py_bytes = PyBytes::new(py, &[]);
       
   947         inner
       
   948             .raw_text(Revision(rev.0), |size, f| {
       
   949                 py_bytes = with_pybytes_buffer(py, size, f)?;
       
   950                 Ok(())
       
   951             }).map_err(|e| revlog_error_from_msg(py, e))?;
       
   952         Ok(py_bytes)
       
   953     }
       
   954 
       
   955     def _chunk(
       
   956         &self,
       
   957         rev: PyRevision,
       
   958     ) -> PyResult<PyBytes> {
       
   959         let inner = self.inner(py).borrow();
       
   960         let chunk = inner
       
   961             .chunk_for_rev(Revision(rev.0))
       
   962             .map_err(|e| revlog_error_from_msg(py, e))?;
       
   963         let chunk = PyBytes::new(py, &chunk);
       
   964         Ok(chunk)
       
   965     }
       
   966 
       
   967     def write_entry(
       
   968         &self,
       
   969         transaction: PyObject,
       
   970         entry: PyObject,
       
   971         data: PyTuple,
       
   972         _link: PyObject,
       
   973         offset: usize,
       
   974         _sidedata: PyObject,
       
   975         _sidedata_offset: PyInt,
       
   976         index_end: Option<u64>,
       
   977         data_end: Option<u64>,
       
   978         _sidedata_end: Option<PyInt>,
       
   979     ) -> PyResult<PyTuple> {
       
   980         let mut inner = self.inner(py).borrow_mut();
       
   981         let transaction = PyTransaction::new(transaction);
       
   982         let py_bytes = entry.extract(py)?;
       
   983         let entry = PyBytesDeref::new(py, py_bytes);
       
   984         let header = data.get_item(py, 0).extract::<PyBytes>(py)?;
       
   985         let header = header.data(py);
       
   986         let data = data.get_item(py, 1);
       
   987         let py_bytes = data.extract(py)?;
       
   988         let data = PyBytesDeref::new(py, py_bytes);
       
   989         Ok(
       
   990             inner.write_entry(
       
   991                 transaction,
       
   992                 &entry,
       
   993                 (header, &data),
       
   994                 offset,
       
   995                 index_end,
       
   996                 data_end
       
   997             ).map_err(|e| revlog_error_from_msg(py, e))?
       
   998              .to_py_object(py)
       
   999         )
       
  1000     }
       
  1001 
       
  1002     def delay(&self) -> PyResult<Option<PyBytes>> {
       
  1003         let path = self.inner(py)
       
  1004             .borrow_mut()
       
  1005             .delay()
       
  1006             .map_err(|e| revlog_error_from_msg(py, e))?;
       
  1007         Ok(path.map(|p| PyBytes::new(py, &get_bytes_from_path(p))))
       
  1008     }
       
  1009 
       
  1010     def write_pending(&self) -> PyResult<PyTuple> {
       
  1011         let (path, any_pending) = self.inner(py)
       
  1012             .borrow_mut()
       
  1013             .write_pending()
       
  1014             .map_err(|e| revlog_error_from_msg(py, e))?;
       
  1015         let maybe_path = match path {
       
  1016             Some(path) => {
       
  1017                 PyBytes::new(py, &get_bytes_from_path(path)).into_object()
       
  1018             },
       
  1019             None => {
       
  1020                 py.None()
       
  1021             }
       
  1022         };
       
  1023         Ok(
       
  1024             (
       
  1025                 maybe_path,
       
  1026                 any_pending
       
  1027             ).to_py_object(py)
       
  1028         )
       
  1029     }
       
  1030 
       
  1031     def finalize_pending(&self) -> PyResult<PyBytes> {
       
  1032         let path = self.inner(py)
       
  1033             .borrow_mut()
       
  1034             .finalize_pending()
       
  1035             .map_err(|e| revlog_error_from_msg(py, e))?;
       
  1036         Ok(PyBytes::new(py, &get_bytes_from_path(path)))
       
  1037     }
       
  1038 
       
  1039     // -- forwarded index methods --
       
  1040 
       
  1041     def _index_get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> {
       
  1042         let opt = self.get_nodetree(py)?.borrow();
       
  1043         let nt = opt.as_ref().expect("nodetree should be set");
       
  1044         let ridx = &self.inner(py).borrow().index;
       
  1045         let node = node_from_py_bytes(py, &node)?;
       
  1046         let rust_rev =
       
  1047             nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?;
       
  1048         Ok(rust_rev.map(Into::into))
       
  1049     }
       
  1050 
       
  1051     /// same as `_index_get_rev()` but raises a bare `error.RevlogError` if node
       
  1052     /// is not found.
       
  1053     ///
       
  1054     /// No need to repeat `node` in the exception, `mercurial/revlog.py`
       
  1055     /// will catch and rewrap with it
       
  1056     def _index_rev(&self, node: PyBytes) -> PyResult<PyRevision> {
       
  1057         self._index_get_rev(py, node)?.ok_or_else(|| revlog_error(py))
       
  1058     }
       
  1059 
       
  1060     /// return True if the node exist in the index
       
  1061     def _index_has_node(&self, node: PyBytes) -> PyResult<bool> {
       
  1062         // TODO OPTIM we could avoid a needless conversion here,
       
  1063         // to do when scaffolding for pure Rust switch is removed,
       
  1064         // as `_index_get_rev()` currently does the necessary assertions
       
  1065         self._index_get_rev(py, node).map(|opt| opt.is_some())
       
  1066     }
       
  1067 
       
  1068     /// find length of shortest hex nodeid of a binary ID
       
  1069     def _index_shortest(&self, node: PyBytes) -> PyResult<usize> {
       
  1070         let opt = self.get_nodetree(py)?.borrow();
       
  1071         let nt = opt.as_ref().expect("nodetree should be set");
       
  1072         let idx = &self.inner(py).borrow().index;
       
  1073         match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
       
  1074         {
       
  1075             Ok(Some(l)) => Ok(l),
       
  1076             Ok(None) => Err(revlog_error(py)),
       
  1077             Err(e) => Err(nodemap_error(py, e)),
       
  1078         }
       
  1079     }
       
  1080 
       
  1081     def _index_partialmatch(
       
  1082         &self,
       
  1083         node: PyObject
       
  1084     ) -> PyResult<Option<PyBytes>> {
       
  1085         let opt = self.get_nodetree(py)?.borrow();
       
  1086         let nt = opt.as_ref().expect("nodetree should be set");
       
  1087         let idx = &self.inner(py).borrow().index;
       
  1088 
       
  1089         let node = node.extract::<PyBytes>(py)?;
       
  1090         let node_as_string = String::from_utf8_lossy(node.data(py));
       
  1091 
       
  1092         let prefix = NodePrefix::from_hex(node_as_string.to_string())
       
  1093             .map_err(|_| PyErr::new::<ValueError, _>(
       
  1094                 py, format!("Invalid node or prefix '{}'", node_as_string))
       
  1095             )?;
       
  1096 
       
  1097         nt.find_bin(idx, prefix)
       
  1098             // TODO make an inner API returning the node directly
       
  1099             .map(|opt| opt.map(|rev| {
       
  1100                     PyBytes::new(
       
  1101                         py,
       
  1102                         idx.node(rev).expect("node should exist").as_bytes()
       
  1103                     )
       
  1104             }))
       
  1105             .map_err(|e| nodemap_error(py, e))
       
  1106 
       
  1107     }
       
  1108 
       
  1109     /// append an index entry
       
  1110     def _index_append(&self, tup: PyTuple) -> PyResult<PyObject> {
       
  1111         if tup.len(py) < 8 {
       
  1112             // this is better than the panic promised by tup.get_item()
       
  1113             return Err(
       
  1114                 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
       
  1115         }
       
  1116         let node_bytes = tup.get_item(py, 7).extract(py)?;
       
  1117         let node = node_from_py_object(py, &node_bytes)?;
       
  1118 
       
  1119         let rev = self.len(py)? as BaseRevision;
       
  1120 
       
  1121         // This is ok since we will just add the revision to the index
       
  1122         let rev = Revision(rev);
       
  1123         self.inner(py)
       
  1124             .borrow_mut()
       
  1125             .index
       
  1126             .append(py_tuple_to_revision_data_params(py, tup)?)
       
  1127             .map_err(|e| revlog_error_from_msg(py, e))?;
       
  1128         let idx = &self.inner(py).borrow().index;
       
  1129         self.get_nodetree(py)?
       
  1130             .borrow_mut()
       
  1131             .as_mut()
       
  1132             .expect("nodetree should be set")
       
  1133             .insert(idx, &node, rev)
       
  1134             .map_err(|e| nodemap_error(py, e))?;
       
  1135         Ok(py.None())
       
  1136     }
       
  1137 
       
  1138     def _index___delitem__(&self, key: PyObject) -> PyResult<PyObject> {
       
  1139         // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
       
  1140         let start = if let Ok(rev) = key.extract(py) {
       
  1141             UncheckedRevision(rev)
       
  1142         } else {
       
  1143             let start = key.getattr(py, "start")?;
       
  1144             UncheckedRevision(start.extract(py)?)
       
  1145         };
       
  1146         let mut borrow = self.inner(py).borrow_mut();
       
  1147         let start = borrow
       
  1148             .index
       
  1149             .check_revision(start)
       
  1150             .ok_or_else(|| {
       
  1151                 nodemap_error(py, NodeMapError::RevisionNotInIndex(start))
       
  1152             })?;
       
  1153         borrow.index
       
  1154             .remove(start)
       
  1155             .map_err(|e| revlog_error_from_msg(py, e))?;
       
  1156         drop(borrow);
       
  1157         let mut opt = self.get_nodetree(py)?.borrow_mut();
       
  1158         let nt = opt.as_mut().expect("nodetree should be set");
       
  1159         nt.invalidate_all();
       
  1160         self.fill_nodemap(py, nt)?;
       
  1161         Ok(py.None())
       
  1162     }
       
  1163 
       
  1164     /// return the gca set of the given revs
       
  1165     def _index_ancestors(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1166         let rust_res = self.inner_ancestors(py, args)?;
       
  1167         Ok(rust_res)
       
  1168     }
       
  1169 
       
  1170     /// return the heads of the common ancestors of the given revs
       
  1171     def _index_commonancestorsheads(
       
  1172         &self,
       
  1173         *args,
       
  1174         **_kw
       
  1175     ) -> PyResult<PyObject> {
       
  1176         let rust_res = self.inner_commonancestorsheads(py, args)?;
       
  1177         Ok(rust_res)
       
  1178     }
       
  1179 
       
  1180     /// Clear the index caches and inner py_class data.
       
  1181     /// It is Python's responsibility to call `update_nodemap_data` again.
       
  1182     def _index_clearcaches(&self) -> PyResult<PyObject> {
       
  1183         self.nt(py).borrow_mut().take();
       
  1184         self.docket(py).borrow_mut().take();
       
  1185         self.nodemap_mmap(py).borrow_mut().take();
       
  1186         self.head_revs_py_list(py).borrow_mut().take();
       
  1187         self.head_node_ids_py_list(py).borrow_mut().take();
       
  1188         self.inner(py).borrow_mut().index.clear_caches();
       
  1189         Ok(py.None())
       
  1190     }
       
  1191 
       
  1192     /// return the raw binary string representing a revision
       
  1193     def _index_entry_binary(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1194         let rindex = &self.inner(py).borrow().index;
       
  1195         let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?);
       
  1196         let rust_bytes = rindex.check_revision(rev).and_then(
       
  1197             |r| rindex.entry_binary(r)).ok_or_else(|| rev_not_in_index(py, rev)
       
  1198         )?;
       
  1199         let rust_res = PyBytes::new(py, rust_bytes).into_object();
       
  1200         Ok(rust_res)
       
  1201     }
       
  1202 
       
  1203 
       
  1204     /// return a binary packed version of the header
       
  1205     def _index_pack_header(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1206         let rindex = &self.inner(py).borrow().index;
       
  1207         let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?);
       
  1208         let rust_res = PyBytes::new(py, &packed).into_object();
       
  1209         Ok(rust_res)
       
  1210     }
       
  1211 
       
  1212     /// compute phases
       
  1213     def _index_computephasesmapsets(
       
  1214         &self,
       
  1215         *args,
       
  1216         **_kw
       
  1217     ) -> PyResult<PyObject> {
       
  1218         let py_roots = args.get_item(py, 0).extract::<PyDict>(py)?;
       
  1219         let rust_res = self.inner_computephasesmapsets(py, py_roots)?;
       
  1220         Ok(rust_res)
       
  1221     }
       
  1222 
       
  1223     /// reachableroots
       
  1224     def _index_reachableroots2(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1225         let rust_res = self.inner_reachableroots2(
       
  1226             py,
       
  1227             UncheckedRevision(args.get_item(py, 0).extract(py)?),
       
  1228             args.get_item(py, 1),
       
  1229             args.get_item(py, 2),
       
  1230             args.get_item(py, 3).extract(py)?,
       
  1231         )?;
       
  1232         Ok(rust_res)
       
  1233     }
       
  1234 
       
  1235     /// get head revisions
       
  1236     def _index_headrevs(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1237         let (filtered_revs, stop_rev) = match &args.len(py) {
       
  1238              0 => Ok((py.None(), py.None())),
       
  1239              1 => Ok((args.get_item(py, 0), py.None())),
       
  1240              2 => Ok((args.get_item(py, 0), args.get_item(py, 1))),
       
  1241              _ => Err(PyErr::new::<cpython::exc::TypeError, _>(py, "too many arguments")),
       
  1242         }?;
       
  1243         self.inner_headrevs(py, &filtered_revs, &stop_rev)
       
  1244     }
       
  1245 
       
  1246     /// get head nodeids
       
  1247     def _index_head_node_ids(&self) -> PyResult<PyObject> {
       
  1248         let rust_res = self.inner_head_node_ids(py)?;
       
  1249         Ok(rust_res)
       
  1250     }
       
  1251 
       
  1252     /// get diff in head revisions
       
  1253     def _index_headrevsdiff(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1254         let rust_res = self.inner_headrevsdiff(
       
  1255           py,
       
  1256           &args.get_item(py, 0),
       
  1257           &args.get_item(py, 1))?;
       
  1258         Ok(rust_res)
       
  1259     }
       
  1260 
       
  1261     /// True if the object is a snapshot
       
  1262     def _index_issnapshot(&self, *args, **_kw) -> PyResult<bool> {
       
  1263         let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?);
       
  1264         self.inner_issnapshot(py, rev)
       
  1265     }
       
  1266 
       
  1267     /// Gather snapshot data in a cache dict
       
  1268     def _index_findsnapshots(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1269         let index = &self.inner(py).borrow().index;
       
  1270         let cache: PyDict = args.get_item(py, 0).extract(py)?;
       
  1271         // this methods operates by setting new values in the cache,
       
  1272         // hence we will compare results by letting the C implementation
       
  1273         // operate over a deepcopy of the cache, and finally compare both
       
  1274         // caches.
       
  1275         let c_cache = PyDict::new(py);
       
  1276         for (k, v) in cache.items(py) {
       
  1277             c_cache.set_item(py, k, PySet::new(py, v)?)?;
       
  1278         }
       
  1279 
       
  1280         let start_rev = UncheckedRevision(args.get_item(py, 1).extract(py)?);
       
  1281         let end_rev = UncheckedRevision(args.get_item(py, 2).extract(py)?);
       
  1282         let mut cache_wrapper = PySnapshotsCache{ py, dict: cache };
       
  1283         index.find_snapshots(
       
  1284             start_rev,
       
  1285             end_rev,
       
  1286             &mut cache_wrapper,
       
  1287         ).map_err(|_| revlog_error(py))?;
       
  1288         Ok(py.None())
       
  1289     }
       
  1290 
       
  1291     /// determine revisions with deltas to reconstruct fulltext
       
  1292     def _index_deltachain(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1293         let index = &self.inner(py).borrow().index;
       
  1294         let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into();
       
  1295         let stop_rev =
       
  1296             args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?;
       
  1297         let rev = index.check_revision(rev).ok_or_else(|| {
       
  1298             nodemap_error(py, NodeMapError::RevisionNotInIndex(rev))
       
  1299         })?;
       
  1300         let stop_rev = if let Some(stop_rev) = stop_rev {
       
  1301             let stop_rev = UncheckedRevision(stop_rev);
       
  1302             Some(index.check_revision(stop_rev).ok_or_else(|| {
       
  1303                 nodemap_error(py, NodeMapError::RevisionNotInIndex(stop_rev))
       
  1304             })?)
       
  1305         } else {None};
       
  1306         let using_general_delta = args.get_item(py, 2)
       
  1307             .extract::<Option<u32>>(py)?
       
  1308             .map(|i| i != 0);
       
  1309         let (chain, stopped) = index.delta_chain(
       
  1310             rev, stop_rev, using_general_delta
       
  1311         ).map_err(|e| {
       
  1312             PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
       
  1313         })?;
       
  1314 
       
  1315         let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect();
       
  1316         Ok(
       
  1317             PyTuple::new(
       
  1318                 py,
       
  1319                 &[
       
  1320                     chain.into_py_object(py).into_object(),
       
  1321                     stopped.into_py_object(py).into_object()
       
  1322                 ]
       
  1323             ).into_object()
       
  1324         )
       
  1325     }
       
  1326 
       
  1327     /// slice planned chunk read to reach a density threshold
       
  1328     def _index_slicechunktodensity(&self, *args, **_kw) -> PyResult<PyObject> {
       
  1329         let rust_res = self.inner_slicechunktodensity(
       
  1330             py,
       
  1331             args.get_item(py, 0),
       
  1332             args.get_item(py, 1).extract(py)?,
       
  1333             args.get_item(py, 2).extract(py)?
       
  1334         )?;
       
  1335         Ok(rust_res)
       
  1336     }
       
  1337 
       
  1338     def _index___len__(&self) -> PyResult<usize> {
       
  1339         self.len(py)
       
  1340     }
       
  1341 
       
  1342     def _index___getitem__(&self, key: PyObject) -> PyResult<PyObject> {
       
  1343         let rust_res = self.inner_getitem(py, key.clone_ref(py))?;
       
  1344         Ok(rust_res)
       
  1345     }
       
  1346 
       
  1347     def _index___contains__(&self, item: PyObject) -> PyResult<bool> {
       
  1348         // ObjectProtocol does not seem to provide contains(), so
       
  1349         // this is an equivalent implementation of the index_contains()
       
  1350         // defined in revlog.c
       
  1351         match item.extract::<i32>(py) {
       
  1352             Ok(rev) => {
       
  1353                 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision)
       
  1354             }
       
  1355             Err(_) => {
       
  1356                 let item_bytes: PyBytes = item.extract(py)?;
       
  1357                 let rust_res = self._index_has_node(py, item_bytes)?;
       
  1358                 Ok(rust_res)
       
  1359             }
       
  1360         }
       
  1361     }
       
  1362 
       
  1363     def _index_nodemap_data_all(&self) -> PyResult<PyBytes> {
       
  1364         self.inner_nodemap_data_all(py)
       
  1365     }
       
  1366 
       
  1367     def _index_nodemap_data_incremental(&self) -> PyResult<PyObject> {
       
  1368         self.inner_nodemap_data_incremental(py)
       
  1369     }
       
  1370 
       
  1371     def _index_update_nodemap_data(
       
  1372         &self,
       
  1373         docket: PyObject,
       
  1374         nm_data: PyObject
       
  1375     ) -> PyResult<PyObject> {
       
  1376         self.inner_update_nodemap_data(py, docket, nm_data)
       
  1377     }
       
  1378 
       
  1379     @property
       
  1380     def _index_entry_size(&self) -> PyResult<PyInt> {
       
  1381         let rust_res: PyInt = INDEX_ENTRY_SIZE.to_py_object(py);
       
  1382         Ok(rust_res)
       
  1383     }
       
  1384 
       
  1385     @property
       
  1386     def _index_rust_ext_compat(&self) -> PyResult<PyInt> {
       
  1387         // will be entirely removed when the Rust index yet useful to
       
  1388         // implement in Rust to detangle things when removing `self.cindex`
       
  1389         let rust_res: PyInt = 1.to_py_object(py);
       
  1390         Ok(rust_res)
       
  1391     }
       
  1392 
       
  1393     @property
       
  1394     def _index_is_rust(&self) -> PyResult<PyBool> {
       
  1395         Ok(false.to_py_object(py))
       
  1396     }
       
  1397 
       
  1398 
       
  1399 });
       
  1400 
       
  1401 /// Forwarded index methods?
       
  1402 impl InnerRevlog {
   649     fn len(&self, py: Python) -> PyResult<usize> {
  1403     fn len(&self, py: Python) -> PyResult<usize> {
   650         let rust_index_len = self.index(py).borrow().len();
  1404         let rust_index_len = self.inner(py).borrow().index.len();
   651         Ok(rust_index_len)
  1405         Ok(rust_index_len)
   652     }
  1406     }
   653 
       
   654     /// This is scaffolding at this point, but it could also become
  1407     /// This is scaffolding at this point, but it could also become
   655     /// a way to start a persistent nodemap or perform a
  1408     /// a way to start a persistent nodemap or perform a
   656     /// vacuum / repack operation
  1409     /// vacuum / repack operation
   657     fn fill_nodemap(
  1410     fn fill_nodemap(
   658         &self,
  1411         &self,
   659         py: Python,
  1412         py: Python,
   660         nt: &mut CoreNodeTree,
  1413         nt: &mut CoreNodeTree,
   661     ) -> PyResult<PyObject> {
  1414     ) -> PyResult<PyObject> {
   662         let index = self.index(py).borrow();
  1415         let index = &self.inner(py).borrow().index;
   663         for r in 0..self.len(py)? {
  1416         for r in 0..self.len(py)? {
   664             let rev = Revision(r as BaseRevision);
  1417             let rev = Revision(r as BaseRevision);
   665             // in this case node() won't ever return None
  1418             // in this case node() won't ever return None
   666             nt.insert(&*index, index.node(rev).unwrap(), rev)
  1419             nt.insert(index, index.node(rev).expect("node should exist"), rev)
   667                 .map_err(|e| nodemap_error(py, e))?
  1420                 .map_err(|e| nodemap_error(py, e))?
   668         }
  1421         }
   669         Ok(py.None())
  1422         Ok(py.None())
   670     }
  1423     }
   671 
  1424 
   682         Ok(self.nt(py))
  1435         Ok(self.nt(py))
   683     }
  1436     }
   684 
  1437 
   685     /// Returns the full nodemap bytes to be written as-is to disk
  1438     /// Returns the full nodemap bytes to be written as-is to disk
   686     fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
  1439     fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
   687         let nodemap = self.get_nodetree(py)?.borrow_mut().take().unwrap();
  1440         let nodemap = self
       
  1441             .get_nodetree(py)?
       
  1442             .borrow_mut()
       
  1443             .take()
       
  1444             .expect("nodetree should exist");
   688         let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
  1445         let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
   689 
  1446 
   690         // If there's anything readonly, we need to build the data again from
  1447         // If there's anything readonly, we need to build the data again from
   691         // scratch
  1448         // scratch
   692         let bytes = if readonly.len() > 0 {
  1449         let bytes = if readonly.len() > 0 {
   715         let docket = match docket.as_ref() {
  1472         let docket = match docket.as_ref() {
   716             Some(d) => d,
  1473             Some(d) => d,
   717             None => return Ok(py.None()),
  1474             None => return Ok(py.None()),
   718         };
  1475         };
   719 
  1476 
   720         let node_tree = self.get_nodetree(py)?.borrow_mut().take().unwrap();
  1477         let node_tree = self
       
  1478             .get_nodetree(py)?
       
  1479             .borrow_mut()
       
  1480             .take()
       
  1481             .expect("nodetree should exist");
   721         let masked_blocks = node_tree.masked_readonly_blocks();
  1482         let masked_blocks = node_tree.masked_readonly_blocks();
   722         let (_, data) = node_tree.into_readonly_and_added_bytes();
  1483         let (_, data) = node_tree.into_readonly_and_added_bytes();
   723         let changed = masked_blocks * std::mem::size_of::<Block>();
  1484         let changed = masked_blocks * std::mem::size_of::<Block>();
   724 
  1485 
   725         Ok((docket, changed, PyBytes::new(py, &data))
  1486         Ok((docket, changed, PyBytes::new(py, &data))
   745         let data_tip = docket
  1506         let data_tip = docket
   746             .getattr(py, "tip_rev")?
  1507             .getattr(py, "tip_rev")?
   747             .extract::<BaseRevision>(py)?
  1508             .extract::<BaseRevision>(py)?
   748             .into();
  1509             .into();
   749         self.docket(py).borrow_mut().replace(docket.clone_ref(py));
  1510         self.docket(py).borrow_mut().replace(docket.clone_ref(py));
   750         let idx = self.index(py).borrow();
  1511         let idx = &self.inner(py).borrow().index;
   751         let data_tip = idx.check_revision(data_tip).ok_or_else(|| {
  1512         let data_tip = idx.check_revision(data_tip).ok_or_else(|| {
   752             nodemap_error(py, NodeMapError::RevisionNotInIndex(data_tip))
  1513             nodemap_error(py, NodeMapError::RevisionNotInIndex(data_tip))
   753         })?;
  1514         })?;
   754         let current_tip = idx.len();
  1515         let current_tip = idx.len();
   755 
  1516 
   756         for r in (data_tip.0 + 1)..current_tip as BaseRevision {
  1517         for r in (data_tip.0 + 1)..current_tip as BaseRevision {
   757             let rev = Revision(r);
  1518             let rev = Revision(r);
   758             // in this case node() won't ever return None
  1519             // in this case node() won't ever return None
   759             nt.insert(&*idx, idx.node(rev).unwrap(), rev)
  1520             nt.insert(idx, idx.node(rev).expect("node should exist"), rev)
   760                 .map_err(|e| nodemap_error(py, e))?
  1521                 .map_err(|e| nodemap_error(py, e))?
   761         }
  1522         }
   762 
  1523 
   763         *self.nt(py).borrow_mut() = Some(nt);
  1524         *self.nt(py).borrow_mut() = Some(nt);
   764 
  1525 
   765         Ok(py.None())
  1526         Ok(py.None())
   766     }
  1527     }
   767 
  1528 
   768     fn inner_getitem(&self, py: Python, key: PyObject) -> PyResult<PyObject> {
  1529     fn inner_getitem(&self, py: Python, key: PyObject) -> PyResult<PyObject> {
   769         let idx = self.index(py).borrow();
  1530         let idx = &self.inner(py).borrow().index;
   770         Ok(match key.extract::<BaseRevision>(py) {
  1531         Ok(match key.extract::<BaseRevision>(py) {
   771             Ok(key_as_int) => {
  1532             Ok(key_as_int) => {
   772                 let entry_params = if key_as_int == NULL_REVISION.0 {
  1533                 let entry_params = if key_as_int == NULL_REVISION.0 {
   773                     RevisionDataParams::default()
  1534                     RevisionDataParams::default()
   774                 } else {
  1535                 } else {
   784                     }
  1545                     }
   785                 };
  1546                 };
   786                 revision_data_params_to_py_tuple(py, entry_params)
  1547                 revision_data_params_to_py_tuple(py, entry_params)
   787                     .into_object()
  1548                     .into_object()
   788             }
  1549             }
   789             _ => self.get_rev(py, key.extract::<PyBytes>(py)?)?.map_or_else(
  1550             _ => self
   790                 || py.None(),
  1551                 ._index_get_rev(py, key.extract::<PyBytes>(py)?)?
   791                 |py_rev| py_rev.into_py_object(py).into_object(),
  1552                 .map_or_else(
   792             ),
  1553                     || py.None(),
       
  1554                     |py_rev| py_rev.into_py_object(py).into_object(),
       
  1555                 ),
   793         })
  1556         })
   794     }
  1557     }
   795 
  1558 
   796     fn inner_head_node_ids(&self, py: Python) -> PyResult<PyObject> {
  1559     fn inner_head_node_ids(&self, py: Python) -> PyResult<PyObject> {
   797         let index = &*self.index(py).borrow();
  1560         let index = &self.inner(py).borrow().index;
   798 
  1561 
   799         // We don't use the shortcut here, as it's actually slower to loop
  1562         // We don't use the shortcut here, as it's actually slower to loop
   800         // through the cached `PyList` than to re-do the whole computation for
  1563         // through the cached `PyList` than to re-do the whole computation for
   801         // large lists, which are the performance sensitive ones anyway.
  1564         // large lists, which are the performance sensitive ones anyway.
   802         let head_revs = index.head_revs().map_err(|e| graph_error(py, e))?;
  1565         let head_revs = index.head_revs().map_err(|e| graph_error(py, e))?;
   824         &self,
  1587         &self,
   825         py: Python,
  1588         py: Python,
   826         filtered_revs: &PyObject,
  1589         filtered_revs: &PyObject,
   827         stop_rev: &PyObject,
  1590         stop_rev: &PyObject,
   828     ) -> PyResult<PyObject> {
  1591     ) -> PyResult<PyObject> {
   829         let index = &*self.index(py).borrow();
  1592         let index = &self.inner(py).borrow().index;
   830         let stop_rev = if stop_rev.is_none(py) {
  1593         let stop_rev = if stop_rev.is_none(py) {
   831             None
  1594             None
   832         } else {
  1595         } else {
   833             let rev = stop_rev.extract::<i32>(py)?;
  1596             let rev = stop_rev.extract::<i32>(py)?;
   834             if 0 <= rev && rev < index.len() as BaseRevision {
  1597             if 0 <= rev && rev < index.len() as BaseRevision {
   897         begin: &PyObject,
  1660         begin: &PyObject,
   898         end: &PyObject,
  1661         end: &PyObject,
   899     ) -> PyResult<PyObject> {
  1662     ) -> PyResult<PyObject> {
   900         let begin = begin.extract::<BaseRevision>(py)?;
  1663         let begin = begin.extract::<BaseRevision>(py)?;
   901         let end = end.extract::<BaseRevision>(py)?;
  1664         let end = end.extract::<BaseRevision>(py)?;
   902         let index = &*self.index(py).borrow();
  1665         let index = &self.inner(py).borrow().index;
   903         let begin =
  1666         let begin =
   904             Self::check_revision(index, UncheckedRevision(begin - 1), py)?;
  1667             Self::check_revision(index, UncheckedRevision(begin - 1), py)?;
   905         let end = Self::check_revision(index, UncheckedRevision(end - 1), py)?;
  1668         let end = Self::check_revision(index, UncheckedRevision(end - 1), py)?;
   906         let (removed, added) = index
  1669         let (removed, added) = index
   907             .head_revs_diff(begin, end)
  1670             .head_revs_diff(begin, end)
   916     fn cache_new_heads_node_ids_py_list(
  1679     fn cache_new_heads_node_ids_py_list(
   917         &self,
  1680         &self,
   918         new_heads: &[Revision],
  1681         new_heads: &[Revision],
   919         py: Python<'_>,
  1682         py: Python<'_>,
   920     ) -> PyList {
  1683     ) -> PyList {
   921         let index = self.index(py).borrow();
  1684         let index = &self.inner(py).borrow().index;
   922         let as_vec: Vec<PyObject> = new_heads
  1685         let as_vec: Vec<PyObject> = new_heads
   923             .iter()
  1686             .iter()
   924             .map(|r| {
  1687             .map(|r| {
   925                 PyBytes::new(
  1688                 PyBytes::new(
   926                     py,
  1689                     py,
   956     fn inner_ancestors(
  1719     fn inner_ancestors(
   957         &self,
  1720         &self,
   958         py: Python,
  1721         py: Python,
   959         py_revs: &PyTuple,
  1722         py_revs: &PyTuple,
   960     ) -> PyResult<PyObject> {
  1723     ) -> PyResult<PyObject> {
   961         let index = &*self.index(py).borrow();
  1724         let index = &self.inner(py).borrow().index;
   962         let revs: Vec<_> = rev_pyiter_collect(py, py_revs.as_object(), index)?;
  1725         let revs: Vec<_> = rev_pyiter_collect(py, py_revs.as_object(), index)?;
   963         let as_vec: Vec<_> = index
  1726         let as_vec: Vec<_> = index
   964             .ancestors(&revs)
  1727             .ancestors(&revs)
   965             .map_err(|e| graph_error(py, e))?
  1728             .map_err(|e| graph_error(py, e))?
   966             .iter()
  1729             .iter()
   972     fn inner_commonancestorsheads(
  1735     fn inner_commonancestorsheads(
   973         &self,
  1736         &self,
   974         py: Python,
  1737         py: Python,
   975         py_revs: &PyTuple,
  1738         py_revs: &PyTuple,
   976     ) -> PyResult<PyObject> {
  1739     ) -> PyResult<PyObject> {
   977         let index = &*self.index(py).borrow();
  1740         let index = &self.inner(py).borrow().index;
   978         let revs: Vec<_> = rev_pyiter_collect(py, py_revs.as_object(), index)?;
  1741         let revs: Vec<_> = rev_pyiter_collect(py, py_revs.as_object(), index)?;
   979         let as_vec: Vec<_> = index
  1742         let as_vec: Vec<_> = index
   980             .common_ancestor_heads(&revs)
  1743             .common_ancestor_heads(&revs)
   981             .map_err(|e| graph_error(py, e))?
  1744             .map_err(|e| graph_error(py, e))?
   982             .iter()
  1745             .iter()
   988     fn inner_computephasesmapsets(
  1751     fn inner_computephasesmapsets(
   989         &self,
  1752         &self,
   990         py: Python,
  1753         py: Python,
   991         py_roots: PyDict,
  1754         py_roots: PyDict,
   992     ) -> PyResult<PyObject> {
  1755     ) -> PyResult<PyObject> {
   993         let index = &*self.index(py).borrow();
  1756         let index = &self.inner(py).borrow().index;
   994         let roots: Result<HashMap<Phase, Vec<Revision>>, PyErr> = py_roots
  1757         let roots: Result<HashMap<Phase, Vec<Revision>>, PyErr> = py_roots
   995             .items_list(py)
  1758             .items_list(py)
   996             .iter(py)
  1759             .iter(py)
   997             .map(|r| {
  1760             .map(|r| {
   998                 let phase = r.get_item(py, 0)?;
  1761                 let phase = r.get_item(py, 0)?;
  1035         py: Python,
  1798         py: Python,
  1036         revs: PyObject,
  1799         revs: PyObject,
  1037         target_density: f64,
  1800         target_density: f64,
  1038         min_gap_size: usize,
  1801         min_gap_size: usize,
  1039     ) -> PyResult<PyObject> {
  1802     ) -> PyResult<PyObject> {
  1040         let index = &*self.index(py).borrow();
  1803         let index = &self.inner(py).borrow().index;
  1041         let revs: Vec<_> = rev_pyiter_collect(py, &revs, index)?;
  1804         let revs: Vec<_> = rev_pyiter_collect(py, &revs, index)?;
  1042         let as_nested_vec =
  1805         let as_nested_vec =
  1043             index.slice_chunk_to_density(&revs, target_density, min_gap_size);
  1806             index.slice_chunk_to_density(&revs, target_density, min_gap_size);
  1044         let mut res = Vec::with_capacity(as_nested_vec.len());
  1807         let mut res = Vec::with_capacity(as_nested_vec.len());
  1045         let mut py_chunk = Vec::new();
  1808         let mut py_chunk = Vec::new();
  1067         min_root: UncheckedRevision,
  1830         min_root: UncheckedRevision,
  1068         heads: PyObject,
  1831         heads: PyObject,
  1069         roots: PyObject,
  1832         roots: PyObject,
  1070         include_path: bool,
  1833         include_path: bool,
  1071     ) -> PyResult<PyObject> {
  1834     ) -> PyResult<PyObject> {
  1072         let index = &*self.index(py).borrow();
  1835         let index = &self.inner(py).borrow().index;
  1073         let heads = rev_pyiter_collect_or_else(py, &heads, index, |_rev| {
  1836         let heads = rev_pyiter_collect_or_else(py, &heads, index, |_rev| {
  1074             PyErr::new::<IndexError, _>(py, "head out of range")
  1837             PyErr::new::<IndexError, _>(py, "head out of range")
  1075         })?;
  1838         })?;
  1076         let roots: Result<_, _> = roots
  1839         let roots: Result<_, _> = roots
  1077             .iter(py)?
  1840             .iter(py)?
  1089             .iter()
  1852             .iter()
  1090             .map(|r| PyRevision::from(*r).into_py_object(py).into_object())
  1853             .map(|r| PyRevision::from(*r).into_py_object(py).into_object())
  1091             .collect();
  1854             .collect();
  1092         Ok(PyList::new(py, &as_vec).into_object())
  1855         Ok(PyList::new(py, &as_vec).into_object())
  1093     }
  1856     }
       
  1857     fn inner_issnapshot(
       
  1858         &self,
       
  1859         py: Python,
       
  1860         rev: UncheckedRevision,
       
  1861     ) -> PyResult<bool> {
       
  1862         let inner = &self.inner(py).borrow();
       
  1863         let index = &self.inner(py).borrow().index;
       
  1864         let rev = index
       
  1865             .check_revision(rev)
       
  1866             .ok_or_else(|| rev_not_in_index(py, rev))?;
       
  1867         let result = inner.is_snapshot(rev).map_err(|e| {
       
  1868             PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
       
  1869         })?;
       
  1870         Ok(result)
       
  1871     }
       
  1872 }
       
  1873 
       
  1874 impl InnerRevlog {
       
  1875     pub fn inner_new(
       
  1876         py: Python,
       
  1877         opener: PyObject,
       
  1878         index_data: PyObject,
       
  1879         index_file: PyObject,
       
  1880         data_file: PyObject,
       
  1881         _sidedata_file: PyObject,
       
  1882         inline: bool,
       
  1883         data_config: PyObject,
       
  1884         delta_config: PyObject,
       
  1885         feature_config: PyObject,
       
  1886         _chunk_cache: PyObject,
       
  1887         _default_compression_header: PyObject,
       
  1888         revlog_type: usize,
       
  1889     ) -> PyResult<Self> {
       
  1890         let vfs = Box::new(PyVfs::new(py, opener)?);
       
  1891         let index_file =
       
  1892             get_path_from_bytes(index_file.extract::<PyBytes>(py)?.data(py))
       
  1893                 .to_owned();
       
  1894         let data_file =
       
  1895             get_path_from_bytes(data_file.extract::<PyBytes>(py)?.data(py))
       
  1896                 .to_owned();
       
  1897         let revlog_type = RevlogType::try_from(revlog_type)
       
  1898             .map_err(|e| revlog_error_from_msg(py, e))?;
       
  1899         let data_config = extract_data_config(py, data_config, revlog_type)?;
       
  1900         let delta_config =
       
  1901             extract_delta_config(py, delta_config, revlog_type)?;
       
  1902         let feature_config =
       
  1903             extract_feature_config(py, feature_config, revlog_type)?;
       
  1904         let options = RevlogOpenOptions::new(
       
  1905             inline,
       
  1906             data_config,
       
  1907             delta_config,
       
  1908             feature_config,
       
  1909         );
       
  1910         // Safety: we keep the buffer around inside the class as `index_mmap`
       
  1911         let (buf, bytes) = unsafe { mmap_keeparound(py, index_data)? };
       
  1912         let index = hg::index::Index::new(bytes, options.index_header())
       
  1913             .map_err(|e| revlog_error_from_msg(py, e))?;
       
  1914         let core = CoreInnerRevlog::new(
       
  1915             vfs,
       
  1916             index,
       
  1917             index_file,
       
  1918             data_file,
       
  1919             data_config,
       
  1920             delta_config,
       
  1921             feature_config,
       
  1922         );
       
  1923         Self::create_instance(
       
  1924             py,
       
  1925             core,
       
  1926             RefCell::new(None),
       
  1927             RefCell::new(None),
       
  1928             RefCell::new(None),
       
  1929             RefCell::new(buf),
       
  1930             RefCell::new(None),
       
  1931             RefCell::new(None),
       
  1932             RefCell::new(None),
       
  1933         )
       
  1934     }
  1094 }
  1935 }
  1095 
  1936 
  1096 py_class!(pub class NodeTree |py| {
  1937 py_class!(pub class NodeTree |py| {
  1097     data nt: RefCell<CoreNodeTree>;
  1938     data nt: RefCell<CoreNodeTree>;
  1098     data index: RefCell<UnsafePyLeaked<PySharedIndex>>;
  1939     data index: RefCell<UnsafePyLeaked<PySharedIndex>>;
  1109     /// guaranteed to be correct, and in fact, the methods borrowing
  1950     /// guaranteed to be correct, and in fact, the methods borrowing
  1110     /// the inner index would fail because of `PySharedRef` poisoning
  1951     /// the inner index would fail because of `PySharedRef` poisoning
  1111     /// (generation-based guard), same as iterating on a `dict` that has
  1952     /// (generation-based guard), same as iterating on a `dict` that has
  1112     /// been meanwhile mutated.
  1953     /// been meanwhile mutated.
  1113     def is_invalidated(&self) -> PyResult<bool> {
  1954     def is_invalidated(&self) -> PyResult<bool> {
  1114         let leaked = self.index(py).borrow();
  1955         let leaked = &self.index(py).borrow();
  1115         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  1956         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  1116         let result = unsafe { leaked.try_borrow(py) };
  1957         let result = unsafe { leaked.try_borrow(py) };
  1117         // two cases for result to be an error:
  1958         // two cases for result to be an error:
  1118         // - the index has previously been mutably borrowed
  1959         // - the index has previously been mutably borrowed
  1119         // - there is currently a mutable borrow
  1960         // - there is currently a mutable borrow
  1121         // the index to still be valid.
  1962         // the index to still be valid.
  1122         Ok(result.is_err())
  1963         Ok(result.is_err())
  1123     }
  1964     }
  1124 
  1965 
  1125     def insert(&self, rev: PyRevision) -> PyResult<PyObject> {
  1966     def insert(&self, rev: PyRevision) -> PyResult<PyObject> {
  1126         let leaked = self.index(py).borrow();
  1967         let leaked = &self.index(py).borrow();
  1127         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  1968         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  1128         let index = &*unsafe { leaked.try_borrow(py)? };
  1969         let index = &*unsafe { leaked.try_borrow(py)? };
  1129 
  1970 
  1130         let rev = UncheckedRevision(rev.0);
  1971         let rev = UncheckedRevision(rev.0);
  1131         let rev = index
  1972         let rev = index
  1133             .ok_or_else(|| rev_not_in_index(py, rev))?;
  1974             .ok_or_else(|| rev_not_in_index(py, rev))?;
  1134         if rev == NULL_REVISION {
  1975         if rev == NULL_REVISION {
  1135             return Err(rev_not_in_index(py, rev.into()))
  1976             return Err(rev_not_in_index(py, rev.into()))
  1136         }
  1977         }
  1137 
  1978 
  1138         let entry = index.inner.get_entry(rev).unwrap();
  1979         let entry = index.inner.get_entry(rev).expect("entry should exist");
  1139         let mut nt = self.nt(py).borrow_mut();
  1980         let mut nt = self.nt(py).borrow_mut();
  1140         nt.insert(index, entry.hash(), rev).map_err(|e| nodemap_error(py, e))?;
  1981         nt.insert(index, entry.hash(), rev).map_err(|e| nodemap_error(py, e))?;
  1141 
  1982 
  1142         Ok(py.None())
  1983         Ok(py.None())
  1143     }
  1984     }
  1156                 format!("Invalid node or prefix {:?}",
  1997                 format!("Invalid node or prefix {:?}",
  1157                         node_prefix.as_object()))
  1998                         node_prefix.as_object()))
  1158             )?;
  1999             )?;
  1159 
  2000 
  1160         let nt = self.nt(py).borrow();
  2001         let nt = self.nt(py).borrow();
  1161         let leaked = self.index(py).borrow();
  2002         let leaked = &self.index(py).borrow();
  1162         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  2003         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  1163         let index = &*unsafe { leaked.try_borrow(py)? };
  2004         let index = &*unsafe { leaked.try_borrow(py)? };
  1164 
  2005 
  1165         Ok(nt.find_bin(index, prefix)
  2006         Ok(nt.find_bin(index, prefix)
  1166                .map_err(|e| nodemap_error(py, e))?
  2007                .map_err(|e| nodemap_error(py, e))?
  1168         )
  2009         )
  1169     }
  2010     }
  1170 
  2011 
  1171     def shortest(&self, node: PyBytes) -> PyResult<usize> {
  2012     def shortest(&self, node: PyBytes) -> PyResult<usize> {
  1172         let nt = self.nt(py).borrow();
  2013         let nt = self.nt(py).borrow();
  1173         let leaked = self.index(py).borrow();
  2014         let leaked = &self.index(py).borrow();
  1174         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  2015         // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
  1175         let idx = &*unsafe { leaked.try_borrow(py)? };
  2016         let idx = &*unsafe { leaked.try_borrow(py)? };
  1176         match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
  2017         match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
  1177         {
  2018         {
  1178             Ok(Some(l)) => Ok(l),
  2019             Ok(Some(l)) => Ok(l),
  1179             Ok(None) => Err(revlog_error(py)),
  2020             Ok(None) => Err(revlog_error(py)),
  1180             Err(e) => Err(nodemap_error(py, e)),
  2021             Err(e) => Err(nodemap_error(py, e)),
  1181         }
  2022         }
  1182     }
  2023     }
  1183 });
  2024 });
       
  2025 
       
  2026 fn panic_after_error(_py: Python) -> ! {
       
  2027     unsafe {
       
  2028         python3_sys::PyErr_Print();
       
  2029     }
       
  2030     panic!("Python API called failed");
       
  2031 }
       
  2032 
       
  2033 /// # Safety
       
  2034 ///
       
  2035 /// Don't call this. Its only caller is taken from `PyO3`.
       
  2036 unsafe fn cast_from_owned_ptr_or_panic<T>(
       
  2037     py: Python,
       
  2038     p: *mut python3_sys::PyObject,
       
  2039 ) -> T
       
  2040 where
       
  2041     T: cpython::PythonObjectWithCheckedDowncast,
       
  2042 {
       
  2043     if p.is_null() {
       
  2044         panic_after_error(py);
       
  2045     } else {
       
  2046         PyObject::from_owned_ptr(py, p).cast_into(py).unwrap()
       
  2047     }
       
  2048 }
       
  2049 
       
  2050 fn with_pybytes_buffer<F>(
       
  2051     py: Python,
       
  2052     len: usize,
       
  2053     init: F,
       
  2054 ) -> Result<PyBytes, RevlogError>
       
  2055 where
       
  2056     F: FnOnce(
       
  2057         &mut dyn RevisionBuffer<Target = PyBytes>,
       
  2058     ) -> Result<(), RevlogError>,
       
  2059 {
       
  2060     // Largely inspired by code in PyO3
       
  2061     // https://pyo3.rs/main/doc/pyo3/types/struct.pybytes#method.new_bound_with
       
  2062     unsafe {
       
  2063         let pyptr = python3_sys::PyBytes_FromStringAndSize(
       
  2064             std::ptr::null(),
       
  2065             len as python3_sys::Py_ssize_t,
       
  2066         );
       
  2067         let pybytes = cast_from_owned_ptr_or_panic::<PyBytes>(py, pyptr);
       
  2068         let buffer: *mut u8 = python3_sys::PyBytes_AsString(pyptr).cast();
       
  2069         debug_assert!(!buffer.is_null());
       
  2070         let mut rev_buf = PyRevisionBuffer::new(pybytes, buffer, len);
       
  2071         // Initialise the bytestring in init
       
  2072         // If init returns an Err, the buffer is deallocated by `pybytes`
       
  2073         init(&mut rev_buf).map(|_| rev_buf.finish())
       
  2074     }
       
  2075 }
       
  2076 
       
  2077 /// Wrapper around a Python-provided buffer into which the revision contents
       
  2078 /// will be written. Done for speed in order to save a large allocation + copy.
       
  2079 struct PyRevisionBuffer {
       
  2080     py_bytes: PyBytes,
       
  2081     _buf: *mut u8,
       
  2082     len: usize,
       
  2083     current_buf: *mut u8,
       
  2084     current_len: usize,
       
  2085 }
       
  2086 
       
  2087 impl PyRevisionBuffer {
       
  2088     /// # Safety
       
  2089     ///
       
  2090     /// `buf` should be the start of the allocated bytes of `bytes`, and `len`
       
  2091     /// exactly the length of said allocated bytes.
       
  2092     #[inline]
       
  2093     unsafe fn new(bytes: PyBytes, buf: *mut u8, len: usize) -> Self {
       
  2094         Self {
       
  2095             py_bytes: bytes,
       
  2096             _buf: buf,
       
  2097             len,
       
  2098             current_len: 0,
       
  2099             current_buf: buf,
       
  2100         }
       
  2101     }
       
  2102 
       
  2103     /// Number of bytes that have been copied to. Will be different to the
       
  2104     /// total allocated length of the buffer unless the revision is done being
       
  2105     /// written.
       
  2106     #[inline]
       
  2107     fn current_len(&self) -> usize {
       
  2108         self.current_len
       
  2109     }
       
  2110 }
       
  2111 
       
  2112 impl RevisionBuffer for PyRevisionBuffer {
       
  2113     type Target = PyBytes;
       
  2114 
       
  2115     #[inline]
       
  2116     fn extend_from_slice(&mut self, slice: &[u8]) {
       
  2117         assert!(self.current_len + slice.len() <= self.len);
       
  2118         unsafe {
       
  2119             // We cannot use `copy_from_nonoverlapping` since it's *possible*
       
  2120             // to create a slice from the same Python memory region using
       
  2121             // [`PyBytesDeref`]. Probable that LLVM has an optimization anyway?
       
  2122             self.current_buf.copy_from(slice.as_ptr(), slice.len());
       
  2123             self.current_buf = self.current_buf.add(slice.len());
       
  2124         }
       
  2125         self.current_len += slice.len()
       
  2126     }
       
  2127 
       
  2128     #[inline]
       
  2129     fn finish(self) -> Self::Target {
       
  2130         // catch unzeroed bytes before it becomes undefined behavior
       
  2131         assert_eq!(
       
  2132             self.current_len(),
       
  2133             self.len,
       
  2134             "not enough bytes read for revision"
       
  2135         );
       
  2136         self.py_bytes
       
  2137     }
       
  2138 }
  1184 
  2139 
  1185 fn revlog_error(py: Python) -> PyErr {
  2140 fn revlog_error(py: Python) -> PyErr {
  1186     match py
  2141     match py
  1187         .import("mercurial.error")
  2142         .import("mercurial.error")
  1188         .and_then(|m| m.get(py, "RevlogError"))
  2143         .and_then(|m| m.get(py, "RevlogError"))
  1193             cls.call(py, (py.None(),), None).ok().into_py_object(py),
  2148             cls.call(py, (py.None(),), None).ok().into_py_object(py),
  1194         ),
  2149         ),
  1195     }
  2150     }
  1196 }
  2151 }
  1197 
  2152 
  1198 fn revlog_error_with_msg(py: Python, msg: &[u8]) -> PyErr {
       
  1199     match py
       
  1200         .import("mercurial.error")
       
  1201         .and_then(|m| m.get(py, "RevlogError"))
       
  1202     {
       
  1203         Err(e) => e,
       
  1204         Ok(cls) => PyErr::from_instance(
       
  1205             py,
       
  1206             cls.call(py, (PyBytes::new(py, msg),), None)
       
  1207                 .ok()
       
  1208                 .into_py_object(py),
       
  1209         ),
       
  1210     }
       
  1211 }
       
  1212 
       
  1213 fn graph_error(py: Python, _err: hg::GraphError) -> PyErr {
  2153 fn graph_error(py: Python, _err: hg::GraphError) -> PyErr {
  1214     // ParentOutOfRange is currently the only alternative
  2154     // ParentOutOfRange is currently the only alternative
  1215     // in `hg::GraphError`. The C index always raises this simple ValueError.
  2155     // in `hg::GraphError`. The C index always raises this simple ValueError.
  1216     PyErr::new::<ValueError, _>(py, "parent out of range")
  2156     PyErr::new::<ValueError, _>(py, "parent out of range")
  1217 }
  2157 }
  1247     let dotted_name = &format!("{}.revlog", package);
  2187     let dotted_name = &format!("{}.revlog", package);
  1248     let m = PyModule::new(py, dotted_name)?;
  2188     let m = PyModule::new(py, dotted_name)?;
  1249     m.add(py, "__package__", package)?;
  2189     m.add(py, "__package__", package)?;
  1250     m.add(py, "__doc__", "RevLog - Rust implementations")?;
  2190     m.add(py, "__doc__", "RevLog - Rust implementations")?;
  1251 
  2191 
  1252     m.add_class::<Index>(py)?;
       
  1253     m.add_class::<NodeTree>(py)?;
  2192     m.add_class::<NodeTree>(py)?;
       
  2193     m.add_class::<InnerRevlog>(py)?;
  1254 
  2194 
  1255     let sys = PyModule::import(py, "sys")?;
  2195     let sys = PyModule::import(py, "sys")?;
  1256     let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
  2196     let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
  1257     sys_modules.set_item(py, dotted_name, &m)?;
  2197     sys_modules.set_item(py, dotted_name, &m)?;
  1258 
  2198