rust/hg-cpython/src/dagops.rs
author Georges Racinet <georges.racinet@cloudcrane.io>
Sat, 30 Nov 2024 20:57:02 +0100
changeset 52411 c2480ac4c5e2
parent 51252 24d3298189d7
permissions -rw-r--r--
rust-pyo3: retrieving the InnerRevlog of hg-cpython This allows PyO3-based code to use the InnerRevlog, access its shared data (core InnerRevlog), which will then allow, e.g., to retrieve references on the core Index. On the `hg-cpython` (`rusthg` crate, `rustext` Python extension module), we had to also build as a Rust library, and open up some accesses (see notably the public accessor for `inner`, the core `InnerRevlog`). Retrieving the Rust struct underlying a Python object defined by another extension module written in Rust is tricky because the Python type objects are duplicated in the extension modules, leading to failure of the normal type checking. See the doc-comment of `convert_cpython::extract_inner_revlog` for a complete explanation. To solve this, we import the Python type object of `rustext` (defined by `hg-cpython`) and perform a manual check. Checking the Python type is necessary, as PyO3 documentation clearly state that downcasting an object that has not the proper type is Undefined Behaviour. At this point, we do not have conversion facilities for exceptions (`PyErr` on both sides), hence the remaining unwraps).
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     1
// dagops.rs
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     2
//
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     3
// Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     4
//
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     5
// This software may be used and distributed according to the terms of the
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     6
// GNU General Public License version 2 or any later version.
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     7
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     8
//! Bindings for the `hg::dagops` module provided by the
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
     9
//! `hg-core` package.
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    10
//!
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    11
//! From Python, this will be seen as `mercurial.rustext.dagop`
50976
4c5f6e95df84 rust: make `Revision` a newtype
Rapha?l Gom?s <rgomes@octobus.net>
parents: 48854
diff changeset
    12
use crate::PyRevision;
43945
f98f0e3ddaa1 rust-index: add a function to convert PyObject index for hg-core
Pierre-Yves David <pierre-yves.david@octobus.net>
parents: 43269
diff changeset
    13
use crate::{conversion::rev_pyiter_collect, exceptions::GraphError};
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    14
use cpython::{PyDict, PyModule, PyObject, PyResult, Python};
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    15
use hg::dagops;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    16
use hg::Revision;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    17
use std::collections::HashSet;
51238
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    18
use vcsgraph::graph::Rank;
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    19
51238
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    20
use crate::revlog::py_rust_index_to_graph;
43945
f98f0e3ddaa1 rust-index: add a function to convert PyObject index for hg-core
Pierre-Yves David <pierre-yves.david@octobus.net>
parents: 43269
diff changeset
    21
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    22
/// Using the the `index`, return heads out of any Python iterable of Revisions
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    23
///
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    24
/// This is the Rust counterpart for `mercurial.dagop.headrevs`
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    25
pub fn headrevs(
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    26
    py: Python,
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    27
    index: PyObject,
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    28
    revs: PyObject,
50976
4c5f6e95df84 rust: make `Revision` a newtype
Rapha?l Gom?s <rgomes@octobus.net>
parents: 48854
diff changeset
    29
) -> PyResult<HashSet<PyRevision>> {
51238
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    30
    let py_leaked = py_rust_index_to_graph(py, index)?;
51252
24d3298189d7 rust-index: document safety invariants being upheld for every `unsafe` block
Rapha?l Gom?s <rgomes@octobus.net>
parents: 51238
diff changeset
    31
    // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
51238
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    32
    let index = &*unsafe { py_leaked.try_borrow(py)? };
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    33
    let mut as_set: HashSet<Revision> = rev_pyiter_collect(py, &revs, index)?;
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    34
    dagops::retain_heads(index, &mut as_set)
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    35
        .map_err(|e| GraphError::pynew(py, e))?;
50976
4c5f6e95df84 rust: make `Revision` a newtype
Rapha?l Gom?s <rgomes@octobus.net>
parents: 48854
diff changeset
    36
    Ok(as_set.into_iter().map(Into::into).collect())
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    37
}
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    38
48854
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    39
/// Computes the rank, i.e. the number of ancestors including itself,
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    40
/// of a node represented by its parents.
51238
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    41
///
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    42
/// Currently, the pure Rust index supports only the REVLOGV1 format, hence
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    43
/// the only possible return value is that the rank is unknown.
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    44
///
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    45
/// References:
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    46
/// - C implementation, function `index_fast_rank()`.
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    47
/// - `impl vcsgraph::graph::RankedGraph for Index` in `crate::cindex`.
48854
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    48
pub fn rank(
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    49
    py: Python,
51238
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    50
    _index: PyObject,
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    51
    _p1r: PyRevision,
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    52
    _p2r: PyRevision,
48854
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    53
) -> PyResult<Rank> {
51238
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    54
    Err(GraphError::pynew_from_vcsgraph(
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    55
        py,
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    56
        vcsgraph::graph::GraphReadError::InconsistentGraphData,
578c049f0408 rust-index: using `hg::index::Index` in `hg-cpython::dagops`
Georges Racinet <georges.racinet@octobus.net>
parents: 50976
diff changeset
    57
    ))
48854
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    58
}
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    59
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    60
/// Create the module, with `__package__` given from parent
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    61
pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    62
    let dotted_name = &format!("{}.dagop", package);
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    63
    let m = PyModule::new(py, dotted_name)?;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    64
    m.add(py, "__package__", package)?;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    65
    m.add(py, "__doc__", "DAG operations - Rust implementation")?;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    66
    m.add(
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    67
        py,
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    68
        "headrevs",
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    69
        py_fn!(py, headrevs(index: PyObject, revs: PyObject)),
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    70
    )?;
48854
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    71
    m.add(
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    72
        py,
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    73
        "rank",
50976
4c5f6e95df84 rust: make `Revision` a newtype
Rapha?l Gom?s <rgomes@octobus.net>
parents: 48854
diff changeset
    74
        py_fn!(py, rank(index: PyObject, p1r: PyRevision, p2r: PyRevision)),
48854
8b8054b8e5a7 rust: expose rank computation function to python
pacien <pacien.trangirard@pacien.net>
parents: 43945
diff changeset
    75
    )?;
41694
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    76
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    77
    let sys = PyModule::import(py, "sys")?;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    78
    let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    79
    sys_modules.set_item(py, dotted_name, &m)?;
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    80
    // Example C code (see pyexpat.c and import.c) will "give away the
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    81
    // reference", but we won't because it will be consumed once the
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    82
    // Rust PyObject is dropped.
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    83
    Ok(m)
0c7b353ce100 rust-cpython: binding for headrevs()
Georges Racinet <georges.racinet@octobus.net>
parents:
diff changeset
    84
}