Show More
@@ -1,173 +1,186 | |||||
1 | //! This module takes care of all conversions involving `rusthg` (hg-cpython) |
|
1 | //! This module takes care of all conversions involving `rusthg` (hg-cpython) | |
2 | //! objects in the PyO3 call context. |
|
2 | //! objects in the PyO3 call context. | |
3 | //! |
|
3 | //! | |
4 | //! For source code clarity, we only import (`use`) [`cpython`] traits and not |
|
4 | //! For source code clarity, we only import (`use`) [`cpython`] traits and not | |
5 | //! any of its data objects. We are instead using full qualifiers, such as |
|
5 | //! any of its data objects. We are instead using full qualifiers, such as | |
6 | //! `cpython::PyObject`, and believe that the added heaviness is an acceptatble |
|
6 | //! `cpython::PyObject`, and believe that the added heaviness is an acceptatble | |
7 | //! price to pay to avoid confusion. |
|
7 | //! price to pay to avoid confusion. | |
8 | //! |
|
8 | //! | |
9 | //! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`, |
|
9 | //! Also it, is customary in [`cpython`] to label the GIL lifetime as `'p`, | |
10 | //! whereas it is `'py` in PyO3 context. We keep both these conventions in |
|
10 | //! whereas it is `'py` in PyO3 context. We keep both these conventions in | |
11 | //! the arguments side of function signatures when they are not simply elided. |
|
11 | //! the arguments side of function signatures when they are not simply elided. | |
12 | use pyo3::exceptions::PyTypeError; |
|
12 | use pyo3::exceptions::PyTypeError; | |
13 | use pyo3::prelude::*; |
|
13 | use pyo3::prelude::*; | |
14 |
|
14 | |||
15 | use cpython::ObjectProtocol; |
|
15 | use cpython::ObjectProtocol; | |
16 | use cpython::PythonObject; |
|
16 | use cpython::PythonObject; | |
17 | use lazy_static::lazy_static; |
|
17 | use lazy_static::lazy_static; | |
18 |
|
18 | |||
|
19 | use hg::revlog::index::Index as CoreIndex; | |||
19 | use rusthg::revlog::{InnerRevlog, PySharedIndex}; |
|
20 | use rusthg::revlog::{InnerRevlog, PySharedIndex}; | |
20 |
|
21 | |||
21 | /// Force cpython's GIL handle with the appropriate lifetime |
|
22 | /// Force cpython's GIL handle with the appropriate lifetime | |
22 | /// |
|
23 | /// | |
23 | /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of |
|
24 | /// In `pyo3`, the fact that we have the GIL is expressed by the lifetime of | |
24 | /// the incoming [`Bound`] smart pointer. We therefore simply instantiate |
|
25 | /// the incoming [`Bound`] smart pointer. We therefore simply instantiate | |
25 | /// the `cpython` handle and coerce its lifetime by the function signature. |
|
26 | /// the `cpython` handle and coerce its lifetime by the function signature. | |
26 | /// |
|
27 | /// | |
27 | /// Reacquiring the GIL is also a possible alternative, as the CPython |
|
28 | /// Reacquiring the GIL is also a possible alternative, as the CPython | |
28 | /// documentation explicitely states that "recursive calls are allowed" |
|
29 | /// documentation explicitely states that "recursive calls are allowed" | |
29 | /// (we interpret that as saying that acquiring the GIL within a thread that |
|
30 | /// (we interpret that as saying that acquiring the GIL within a thread that | |
30 | /// already has it works) *as long as it is properly released* |
|
31 | /// already has it works) *as long as it is properly released* | |
31 | /// reference: |
|
32 | /// reference: | |
32 | /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure> |
|
33 | /// <https://docs.python.org/3.8/c-api/init.html#c.PyGILState_Ensure> | |
33 | pub(crate) fn cpython_handle<'py, T>( |
|
34 | pub(crate) fn cpython_handle<'py, T>( | |
34 | _bound: &Bound<'py, T>, |
|
35 | _bound: &Bound<'py, T>, | |
35 | ) -> cpython::Python<'py> { |
|
36 | ) -> cpython::Python<'py> { | |
36 | // safety: this is safe because the returned object has the 'py lifetime |
|
37 | // safety: this is safe because the returned object has the 'py lifetime | |
37 | unsafe { cpython::Python::assume_gil_acquired() } |
|
38 | unsafe { cpython::Python::assume_gil_acquired() } | |
38 | } |
|
39 | } | |
39 |
|
40 | |||
40 | /// Force PyO3 GIL handle from cpython's. |
|
41 | /// Force PyO3 GIL handle from cpython's. | |
41 | /// |
|
42 | /// | |
42 | /// Very similar to [`cpython_handle`] |
|
43 | /// Very similar to [`cpython_handle`] | |
43 | pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> { |
|
44 | pub fn pyo3_handle(_py: cpython::Python<'_>) -> Python<'_> { | |
44 | // safety: this is safe because the returned object has the same lifetime |
|
45 | // safety: this is safe because the returned object has the same lifetime | |
45 | // as the incoming object. |
|
46 | // as the incoming object. | |
46 | unsafe { Python::assume_gil_acquired() } |
|
47 | unsafe { Python::assume_gil_acquired() } | |
47 | } |
|
48 | } | |
48 |
|
49 | |||
49 | /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`] |
|
50 | /// Convert a PyO3 [`PyObject`] into a [`cpython::PyObject`] | |
50 | /// |
|
51 | /// | |
51 | /// During this process, the reference count is increased, then decreased. |
|
52 | /// During this process, the reference count is increased, then decreased. | |
52 | /// This means that the GIL (symbolized by the lifetime on the `obj` |
|
53 | /// This means that the GIL (symbolized by the lifetime on the `obj` | |
53 | /// argument) is needed. |
|
54 | /// argument) is needed. | |
54 | /// |
|
55 | /// | |
55 | /// We could make something perhaps more handy by simply stealing the |
|
56 | /// We could make something perhaps more handy by simply stealing the | |
56 | /// pointer, forgetting the incoming and then implement `From` with "newtype". |
|
57 | /// pointer, forgetting the incoming and then implement `From` with "newtype". | |
57 | /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps |
|
58 | /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps | |
58 | /// not for the current endeavour. |
|
59 | /// not for the current endeavour. | |
59 | pub(crate) fn to_cpython_py_object<'py>( |
|
60 | pub(crate) fn to_cpython_py_object<'py>( | |
60 | obj: &Bound<'py, PyAny>, |
|
61 | obj: &Bound<'py, PyAny>, | |
61 | ) -> (cpython::Python<'py>, cpython::PyObject) { |
|
62 | ) -> (cpython::Python<'py>, cpython::PyObject) { | |
62 | let py = cpython_handle(obj); |
|
63 | let py = cpython_handle(obj); | |
63 | // public alias of the private cpython::fii::PyObject (!) |
|
64 | // public alias of the private cpython::fii::PyObject (!) | |
64 | let raw = obj.as_ptr() as *mut python3_sys::PyObject; |
|
65 | let raw = obj.as_ptr() as *mut python3_sys::PyObject; | |
65 | // both pyo3 and rust-cpython will decrement the refcount on drop. |
|
66 | // both pyo3 and rust-cpython will decrement the refcount on drop. | |
66 | // If we use from_owned_ptr, that's a segfault. |
|
67 | // If we use from_owned_ptr, that's a segfault. | |
67 | (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) }) |
|
68 | (py, unsafe { cpython::PyObject::from_borrowed_ptr(py, raw) }) | |
68 | } |
|
69 | } | |
69 |
|
70 | |||
70 | /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`] |
|
71 | /// Convert a [`cpython::PyObject`] into a PyO3 [`PyObject`] | |
71 | /// |
|
72 | /// | |
72 | /// During this process, the reference count is increased, then decreased. |
|
73 | /// During this process, the reference count is increased, then decreased. | |
73 | /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is |
|
74 | /// This means that the GIL (symbolized by the PyO3 [`Python`] handle is | |
74 | /// needed. |
|
75 | /// needed. | |
75 | /// |
|
76 | /// | |
76 | /// We could make something perhaps more handy by simply stealing the |
|
77 | /// We could make something perhaps more handy by simply stealing the | |
77 | /// pointer, forgetting the incoming and then implement `From` with "newtype". |
|
78 | /// pointer, forgetting the incoming and then implement `From` with "newtype". | |
78 | /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps |
|
79 | /// It would be worth the effort for a generic cpython-to-pyo3 crate, perhaps | |
79 | /// not for the current endeavour. |
|
80 | /// not for the current endeavour. | |
80 | pub(crate) fn from_cpython_py_object( |
|
81 | pub(crate) fn from_cpython_py_object( | |
81 | py: Python<'_>, |
|
82 | py: Python<'_>, | |
82 | obj: cpython::PyObject, |
|
83 | obj: cpython::PyObject, | |
83 | ) -> PyObject { |
|
84 | ) -> PyObject { | |
84 | let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject; |
|
85 | let raw = obj.as_ptr() as *mut pyo3::ffi::PyObject; | |
85 | unsafe { Py::from_borrowed_ptr(py, raw) } |
|
86 | unsafe { Py::from_borrowed_ptr(py, raw) } | |
86 | } |
|
87 | } | |
87 |
|
88 | |||
88 | /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`] |
|
89 | /// Convert [`cpython::PyErr`] into [`pyo3::PyErr`] | |
89 | /// |
|
90 | /// | |
90 | /// The exception class remains the same as the original exception, |
|
91 | /// The exception class remains the same as the original exception, | |
91 | /// hence if it is also defined in another dylib based on `cpython` crate, |
|
92 | /// hence if it is also defined in another dylib based on `cpython` crate, | |
92 | /// it will need to be converted to be downcasted in this crate. |
|
93 | /// it will need to be converted to be downcasted in this crate. | |
93 | pub(crate) fn from_cpython_pyerr( |
|
94 | pub(crate) fn from_cpython_pyerr( | |
94 | py: cpython::Python<'_>, |
|
95 | py: cpython::Python<'_>, | |
95 | mut e: cpython::PyErr, |
|
96 | mut e: cpython::PyErr, | |
96 | ) -> PyErr { |
|
97 | ) -> PyErr { | |
97 | let pyo3_py = pyo3_handle(py); |
|
98 | let pyo3_py = pyo3_handle(py); | |
98 | let cpython_exc_obj = e.instance(py); |
|
99 | let cpython_exc_obj = e.instance(py); | |
99 | let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj); |
|
100 | let pyo3_exc_obj = from_cpython_py_object(pyo3_py, cpython_exc_obj); | |
100 | PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py)) |
|
101 | PyErr::from_value(pyo3_exc_obj.into_bound(pyo3_py)) | |
101 | } |
|
102 | } | |
102 |
|
103 | |||
103 | /// Retrieve the PyType for objects from the `mercurial.rustext` crate. |
|
104 | /// Retrieve the PyType for objects from the `mercurial.rustext` crate. | |
104 | fn retrieve_cpython_py_type( |
|
105 | fn retrieve_cpython_py_type( | |
105 | submodule_name: &str, |
|
106 | submodule_name: &str, | |
106 | type_name: &str, |
|
107 | type_name: &str, | |
107 | ) -> cpython::PyResult<cpython::PyType> { |
|
108 | ) -> cpython::PyResult<cpython::PyType> { | |
108 | let guard = cpython::Python::acquire_gil(); |
|
109 | let guard = cpython::Python::acquire_gil(); | |
109 | let py = guard.python(); |
|
110 | let py = guard.python(); | |
110 | let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?; |
|
111 | let module = py.import(&format!("mercurial.rustext.{submodule_name}"))?; | |
111 | module.get(py, type_name)?.extract::<cpython::PyType>(py) |
|
112 | module.get(py, type_name)?.extract::<cpython::PyType>(py) | |
112 | } |
|
113 | } | |
113 |
|
114 | |||
114 | lazy_static! { |
|
115 | lazy_static! { | |
115 | static ref INNER_REVLOG_PY_TYPE: cpython::PyType = { |
|
116 | static ref INNER_REVLOG_PY_TYPE: cpython::PyType = { | |
116 | retrieve_cpython_py_type("revlog", "InnerRevlog") |
|
117 | retrieve_cpython_py_type("revlog", "InnerRevlog") | |
117 | .expect("Could not import InnerRevlog in Python") |
|
118 | .expect("Could not import InnerRevlog in Python") | |
118 | }; |
|
119 | }; | |
119 | } |
|
120 | } | |
120 |
|
121 | |||
121 | /// Downcast [`InnerRevlog`], with the appropriate Python type checking. |
|
122 | /// Downcast [`InnerRevlog`], with the appropriate Python type checking. | |
122 | /// |
|
123 | /// | |
123 | /// The PyType object representing the `InnerRevlog` Python class is not the |
|
124 | /// The PyType object representing the `InnerRevlog` Python class is not the | |
124 | /// the same in this dylib as it is in the `mercurial.rustext` module. |
|
125 | /// the same in this dylib as it is in the `mercurial.rustext` module. | |
125 | /// This is because the code created with the [`cpython::py_class!`] |
|
126 | /// This is because the code created with the [`cpython::py_class!`] | |
126 | /// macro is itself duplicated in both dylibs. In the case of this crate, this |
|
127 | /// macro is itself duplicated in both dylibs. In the case of this crate, this | |
127 | /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog` |
|
128 | /// happens by linking to the [`rusthg`] crate and provides the `InnerRevlog` | |
128 | /// that is visible from this crate. The `InnerRevlog::get_type` associated |
|
129 | /// that is visible from this crate. The `InnerRevlog::get_type` associated | |
129 | /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in |
|
130 | /// function turns out to return a `static mut` (look for `TYPE_OBJECT` in | |
130 | /// `py_class_impl3.rs`), which obviously is different in both dylibs. |
|
131 | /// `py_class_impl3.rs`), which obviously is different in both dylibs. | |
131 | /// |
|
132 | /// | |
132 | /// The consequence of that is that downcasting an `InnerRevlog` originally |
|
133 | /// The consequence of that is that downcasting an `InnerRevlog` originally | |
133 | /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with |
|
134 | /// from the `mecurial.rustext` module to our `InnerRevlog` cannot be done with | |
134 | /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type |
|
135 | /// the usual `extract::<InnerRevlog>(py)`, as it would perform the type | |
135 | /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`. |
|
136 | /// checking with the `PyType` that is embedded in `mercurial.pyo3_rustext`. | |
136 | /// We must check the `PyType` that is within `mercurial.rustext` instead. |
|
137 | /// We must check the `PyType` that is within `mercurial.rustext` instead. | |
137 | /// This is what this function does. |
|
138 | /// This is what this function does. | |
138 | fn extract_inner_revlog( |
|
139 | fn extract_inner_revlog( | |
139 | py: cpython::Python, |
|
140 | py: cpython::Python, | |
140 | inner_revlog: cpython::PyObject, |
|
141 | inner_revlog: cpython::PyObject, | |
141 | ) -> PyResult<InnerRevlog> { |
|
142 | ) -> PyResult<InnerRevlog> { | |
142 | if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) { |
|
143 | if !(*INNER_REVLOG_PY_TYPE).is_instance(py, &inner_revlog) { | |
143 | return Err(PyTypeError::new_err("Not an InnerRevlog instance")); |
|
144 | return Err(PyTypeError::new_err("Not an InnerRevlog instance")); | |
144 | } |
|
145 | } | |
145 | // Safety: this is safe because we checked the PyType already, with the |
|
146 | // Safety: this is safe because we checked the PyType already, with the | |
146 | // value embedded in `mercurial.rustext`. |
|
147 | // value embedded in `mercurial.rustext`. | |
147 | Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) }) |
|
148 | Ok(unsafe { InnerRevlog::unchecked_downcast_from(inner_revlog) }) | |
148 | } |
|
149 | } | |
149 |
|
150 | |||
150 | /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in |
|
151 | /// This is similar to [`rusthg.py_rust_index_to_graph`], with difference in | |
151 | /// how we retrieve the [`InnerRevlog`]. |
|
152 | /// how we retrieve the [`InnerRevlog`]. | |
152 | pub fn py_rust_index_to_graph( |
|
153 | pub fn py_rust_index_to_graph( | |
153 | py: cpython::Python, |
|
154 | py: cpython::Python, | |
154 | index_proxy: cpython::PyObject, |
|
155 | index_proxy: cpython::PyObject, | |
155 | ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> { |
|
156 | ) -> PyResult<cpython::UnsafePyLeaked<PySharedIndex>> { | |
156 | let inner_revlog = extract_inner_revlog( |
|
157 | let inner_revlog = extract_inner_revlog( | |
157 | py, |
|
158 | py, | |
158 | index_proxy |
|
159 | index_proxy | |
159 | .getattr(py, "inner") |
|
160 | .getattr(py, "inner") | |
160 | .map_err(|e| from_cpython_pyerr(py, e))?, |
|
161 | .map_err(|e| from_cpython_pyerr(py, e))?, | |
161 | )?; |
|
162 | )?; | |
162 |
|
163 | |||
163 | let leaked = inner_revlog.pub_inner(py).leak_immutable(); |
|
164 | let leaked = inner_revlog.pub_inner(py).leak_immutable(); | |
164 | // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked` |
|
165 | // Safety: we don't leak the "faked" reference out of the `UnsafePyLeaked` | |
165 | Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) }) |
|
166 | Ok(unsafe { leaked.map(py, |idx| PySharedIndex { inner: &idx.index }) }) | |
166 | } |
|
167 | } | |
167 |
|
168 | |||
168 | pub(crate) fn proxy_index_extract<'py>( |
|
169 | /// Full extraction of the proxy index object as received in PyO3 to a | |
|
170 | /// [`CoreIndex`] reference. | |||
|
171 | /// | |||
|
172 | /// The safety invariants to maintain are those of the underlying | |||
|
173 | /// [`UnsafePyLeaked::try_borrow`]: the caller must not leak the inner | |||
|
174 | /// reference. | |||
|
175 | pub(crate) unsafe fn proxy_index_extract<'py>( | |||
169 | index_proxy: &Bound<'py, PyAny>, |
|
176 | index_proxy: &Bound<'py, PyAny>, | |
170 | ) -> PyResult<(cpython::Python<'py>, cpython::UnsafePyLeaked<PySharedIndex>)> { |
|
177 | ) -> PyResult<&'py CoreIndex> { | |
171 | let (py, idx_proxy) = to_cpython_py_object(index_proxy); |
|
178 | let (py, idx_proxy) = to_cpython_py_object(index_proxy); | |
172 |
|
|
179 | let py_leaked = py_rust_index_to_graph(py, idx_proxy)?; | |
|
180 | let py_shared = &*unsafe { | |||
|
181 | py_leaked | |||
|
182 | .try_borrow(py) | |||
|
183 | .map_err(|e| from_cpython_pyerr(py, e))? | |||
|
184 | }; | |||
|
185 | Ok(py_shared.inner) | |||
173 | } |
|
186 | } |
@@ -1,73 +1,67 | |||||
1 | // dagops.rs |
|
1 | // dagops.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2024 Georges Racinet <georges.racinet@cloudcrane.io> |
|
3 | // Copyright 2024 Georges Racinet <georges.racinet@cloudcrane.io> | |
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 |
|
7 | |||
8 | //! Bindings for the `hg::dagops` module provided by the |
|
8 | //! Bindings for the `hg::dagops` module provided by the | |
9 | //! `hg-core` package. |
|
9 | //! `hg-core` package. | |
10 | //! |
|
10 | //! | |
11 | //! From Python, this will be seen as `mercurial.pyo3-rustext.dagop` |
|
11 | //! From Python, this will be seen as `mercurial.pyo3-rustext.dagop` | |
12 | use pyo3::prelude::*; |
|
12 | use pyo3::prelude::*; | |
13 |
|
13 | |||
14 | use std::collections::HashSet; |
|
14 | use std::collections::HashSet; | |
15 |
|
15 | |||
16 | use hg::{dagops, Revision}; |
|
16 | use hg::{dagops, Revision}; | |
17 |
|
17 | |||
18 |
use crate::convert_cpython:: |
|
18 | use crate::convert_cpython::proxy_index_extract; | |
19 | use crate::exceptions::GraphError; |
|
19 | use crate::exceptions::GraphError; | |
20 | use crate::revision::{rev_pyiter_collect, PyRevision}; |
|
20 | use crate::revision::{rev_pyiter_collect, PyRevision}; | |
21 | use crate::util::new_submodule; |
|
21 | use crate::util::new_submodule; | |
22 |
|
22 | |||
23 | /// Using the the `index_proxy`, return heads out of any Python iterable of |
|
23 | /// Using the the `index_proxy`, return heads out of any Python iterable of | |
24 | /// Revisions |
|
24 | /// Revisions | |
25 | /// |
|
25 | /// | |
26 | /// This is the Rust counterpart for `mercurial.dagop.headrevs` |
|
26 | /// This is the Rust counterpart for `mercurial.dagop.headrevs` | |
27 | #[pyfunction] |
|
27 | #[pyfunction] | |
28 | pub fn headrevs( |
|
28 | pub fn headrevs( | |
29 | index_proxy: &Bound<'_, PyAny>, |
|
29 | index_proxy: &Bound<'_, PyAny>, | |
30 | revs: &Bound<'_, PyAny>, |
|
30 | revs: &Bound<'_, PyAny>, | |
31 | ) -> PyResult<HashSet<PyRevision>> { |
|
31 | ) -> PyResult<HashSet<PyRevision>> { | |
32 | let (py, py_leaked) = proxy_index_extract(index_proxy)?; |
|
|||
33 | // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` |
|
32 | // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked` | |
34 | let index = &*unsafe { |
|
33 | let index = unsafe { proxy_index_extract(index_proxy)? }; | |
35 | py_leaked |
|
|||
36 | .try_borrow(py) |
|
|||
37 | .map_err(|e| from_cpython_pyerr(py, e))? |
|
|||
38 | }; |
|
|||
39 |
|
||||
40 | let mut as_set: HashSet<Revision> = rev_pyiter_collect(revs, index)?; |
|
34 | let mut as_set: HashSet<Revision> = rev_pyiter_collect(revs, index)?; | |
41 | dagops::retain_heads(index, &mut as_set).map_err(GraphError::from_hg)?; |
|
35 | dagops::retain_heads(index, &mut as_set).map_err(GraphError::from_hg)?; | |
42 | Ok(as_set.into_iter().map(Into::into).collect()) |
|
36 | Ok(as_set.into_iter().map(Into::into).collect()) | |
43 | } |
|
37 | } | |
44 |
|
38 | |||
45 | /// Computes the rank, i.e. the number of ancestors including itself, |
|
39 | /// Computes the rank, i.e. the number of ancestors including itself, | |
46 | /// of a node represented by its parents. |
|
40 | /// of a node represented by its parents. | |
47 | /// |
|
41 | /// | |
48 | /// Currently, the pure Rust index supports only the REVLOGV1 format, hence |
|
42 | /// Currently, the pure Rust index supports only the REVLOGV1 format, hence | |
49 | /// the only possible return value is that the rank is unknown. |
|
43 | /// the only possible return value is that the rank is unknown. | |
50 | /// |
|
44 | /// | |
51 | /// References: |
|
45 | /// References: | |
52 | /// - C implementation, function `index_fast_rank()`. |
|
46 | /// - C implementation, function `index_fast_rank()`. | |
53 | /// - `impl vcsgraph::graph::RankedGraph for Index` in `crate::cindex`. |
|
47 | /// - `impl vcsgraph::graph::RankedGraph for Index` in `crate::cindex`. | |
54 | #[pyfunction] |
|
48 | #[pyfunction] | |
55 | pub fn rank( |
|
49 | pub fn rank( | |
56 | _index: &Bound<'_, PyAny>, |
|
50 | _index: &Bound<'_, PyAny>, | |
57 | _p1r: PyRevision, |
|
51 | _p1r: PyRevision, | |
58 | _p2r: PyRevision, |
|
52 | _p2r: PyRevision, | |
59 | ) -> PyResult<()> { |
|
53 | ) -> PyResult<()> { | |
60 | Err(GraphError::from_vcsgraph( |
|
54 | Err(GraphError::from_vcsgraph( | |
61 | vcsgraph::graph::GraphReadError::InconsistentGraphData, |
|
55 | vcsgraph::graph::GraphReadError::InconsistentGraphData, | |
62 | )) |
|
56 | )) | |
63 | } |
|
57 | } | |
64 |
|
58 | |||
65 | pub fn init_module<'py>( |
|
59 | pub fn init_module<'py>( | |
66 | py: Python<'py>, |
|
60 | py: Python<'py>, | |
67 | package: &str, |
|
61 | package: &str, | |
68 | ) -> PyResult<Bound<'py, PyModule>> { |
|
62 | ) -> PyResult<Bound<'py, PyModule>> { | |
69 | let m = new_submodule(py, package, "dagop")?; |
|
63 | let m = new_submodule(py, package, "dagop")?; | |
70 | m.add_function(wrap_pyfunction!(headrevs, &m)?)?; |
|
64 | m.add_function(wrap_pyfunction!(headrevs, &m)?)?; | |
71 | m.add_function(wrap_pyfunction!(rank, &m)?)?; |
|
65 | m.add_function(wrap_pyfunction!(rank, &m)?)?; | |
72 | Ok(m) |
|
66 | Ok(m) | |
73 | } |
|
67 | } |
General Comments 0
You need to be logged in to leave comments.
Login now