##// END OF EJS Templates
hg-cpython: use ancestor iterator impls from vcsgraph...
pacien -
r49350:35ebe6f8 default
parent child Browse files
Show More
@@ -1,227 +1,231 b''
1 // ancestors.rs
1 // ancestors.rs
2 //
2 //
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
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::ancestors` module provided by the
8 //! Bindings for the `hg::ancestors` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor`
9 //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor`
10 //! and can be used as replacement for the the pure `ancestor` Python module.
10 //! and can be used as replacement for the the pure `ancestor` Python module.
11 //!
11 //!
12 //! # Classes visible from Python:
12 //! # Classes visible from Python:
13 //! - [`LazyAncestors`] is the Rust implementation of
13 //! - [`LazyAncestors`] is the Rust implementation of
14 //! `mercurial.ancestor.lazyancestors`. The only difference is that it is
14 //! `mercurial.ancestor.lazyancestors`. The only difference is that it is
15 //! instantiated with a C `parsers.index` instance instead of a parents
15 //! instantiated with a C `parsers.index` instance instead of a parents
16 //! function.
16 //! function.
17 //!
17 //!
18 //! - [`MissingAncestors`] is the Rust implementation of
18 //! - [`MissingAncestors`] is the Rust implementation of
19 //! `mercurial.ancestor.incrementalmissingancestors`.
19 //! `mercurial.ancestor.incrementalmissingancestors`.
20 //!
20 //!
21 //! API differences:
21 //! API differences:
22 //! + it is instantiated with a C `parsers.index`
22 //! + it is instantiated with a C `parsers.index`
23 //! instance instead of a parents function.
23 //! instance instead of a parents function.
24 //! + `MissingAncestors.bases` is a method returning a tuple instead of
24 //! + `MissingAncestors.bases` is a method returning a tuple instead of
25 //! a set-valued attribute. We could return a Python set easily if our
25 //! a set-valued attribute. We could return a Python set easily if our
26 //! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165)
26 //! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165)
27 //! is accepted.
27 //! is accepted.
28 //!
28 //!
29 //! - [`AncestorsIterator`] is the Rust counterpart of the
29 //! - [`AncestorsIterator`] is the Rust counterpart of the
30 //! `ancestor._lazyancestorsiter` Python generator. From Python, instances of
30 //! `ancestor._lazyancestorsiter` Python generator. From Python, instances of
31 //! this should be mainly obtained by calling `iter()` on a [`LazyAncestors`]
31 //! this should be mainly obtained by calling `iter()` on a [`LazyAncestors`]
32 //! instance.
32 //! instance.
33 //!
33 //!
34 //! [`LazyAncestors`]: struct.LazyAncestors.html
34 //! [`LazyAncestors`]: struct.LazyAncestors.html
35 //! [`MissingAncestors`]: struct.MissingAncestors.html
35 //! [`MissingAncestors`]: struct.MissingAncestors.html
36 //! [`AncestorsIterator`]: struct.AncestorsIterator.html
36 //! [`AncestorsIterator`]: struct.AncestorsIterator.html
37 use crate::revlog::pyindex_to_graph;
37 use crate::revlog::pyindex_to_graph;
38 use crate::{
38 use crate::{
39 cindex::Index, conversion::rev_pyiter_collect, exceptions::GraphError,
39 cindex::Index, conversion::rev_pyiter_collect, exceptions::GraphError,
40 };
40 };
41 use cpython::{
41 use cpython::{
42 ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult,
42 ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult,
43 Python, PythonObject, ToPyObject,
43 Python, PythonObject, ToPyObject,
44 };
44 };
45 use hg::MissingAncestors as CoreMissing;
45 use hg::Revision;
46 use hg::Revision;
46 use hg::{
47 AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy,
48 MissingAncestors as CoreMissing,
49 };
50 use std::cell::RefCell;
47 use std::cell::RefCell;
51 use std::collections::HashSet;
48 use std::collections::HashSet;
49 use vcsgraph::lazy_ancestors::{
50 AncestorsIterator as VCGAncestorsIterator,
51 LazyAncestors as VCGLazyAncestors,
52 };
52
53
53 py_class!(pub class AncestorsIterator |py| {
54 py_class!(pub class AncestorsIterator |py| {
54 data inner: RefCell<Box<CoreIterator<Index>>>;
55 data inner: RefCell<Box<VCGAncestorsIterator<Index>>>;
55
56
56 def __next__(&self) -> PyResult<Option<Revision>> {
57 def __next__(&self) -> PyResult<Option<Revision>> {
57 match self.inner(py).borrow_mut().next() {
58 match self.inner(py).borrow_mut().next() {
58 Some(Err(e)) => Err(GraphError::pynew(py, e)),
59 Some(Err(e)) => Err(GraphError::pynew_from_vcsgraph(py, e)),
59 None => Ok(None),
60 None => Ok(None),
60 Some(Ok(r)) => Ok(Some(r)),
61 Some(Ok(r)) => Ok(Some(r)),
61 }
62 }
62 }
63 }
63
64
64 def __contains__(&self, rev: Revision) -> PyResult<bool> {
65 def __contains__(&self, rev: Revision) -> PyResult<bool> {
65 self.inner(py).borrow_mut().contains(rev)
66 self.inner(py).borrow_mut().contains(rev)
66 .map_err(|e| GraphError::pynew(py, e))
67 .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))
67 }
68 }
68
69
69 def __iter__(&self) -> PyResult<Self> {
70 def __iter__(&self) -> PyResult<Self> {
70 Ok(self.clone_ref(py))
71 Ok(self.clone_ref(py))
71 }
72 }
72
73
73 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
74 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
74 inclusive: bool) -> PyResult<AncestorsIterator> {
75 inclusive: bool) -> PyResult<AncestorsIterator> {
75 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
76 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
76 let ait = CoreIterator::new(
77 let ait = VCGAncestorsIterator::new(
77 pyindex_to_graph(py, index)?,
78 pyindex_to_graph(py, index)?,
78 initvec,
79 initvec,
79 stoprev,
80 stoprev,
80 inclusive,
81 inclusive,
81 )
82 )
82 .map_err(|e| GraphError::pynew(py, e))?;
83 .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))?;
83 AncestorsIterator::from_inner(py, ait)
84 AncestorsIterator::from_inner(py, ait)
84 }
85 }
85
86
86 });
87 });
87
88
88 impl AncestorsIterator {
89 impl AncestorsIterator {
89 pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> {
90 pub fn from_inner(
91 py: Python,
92 ait: VCGAncestorsIterator<Index>,
93 ) -> PyResult<Self> {
90 Self::create_instance(py, RefCell::new(Box::new(ait)))
94 Self::create_instance(py, RefCell::new(Box::new(ait)))
91 }
95 }
92 }
96 }
93
97
94 py_class!(pub class LazyAncestors |py| {
98 py_class!(pub class LazyAncestors |py| {
95 data inner: RefCell<Box<CoreLazy<Index>>>;
99 data inner: RefCell<Box<VCGLazyAncestors<Index>>>;
96
100
97 def __contains__(&self, rev: Revision) -> PyResult<bool> {
101 def __contains__(&self, rev: Revision) -> PyResult<bool> {
98 self.inner(py)
102 self.inner(py)
99 .borrow_mut()
103 .borrow_mut()
100 .contains(rev)
104 .contains(rev)
101 .map_err(|e| GraphError::pynew(py, e))
105 .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))
102 }
106 }
103
107
104 def __iter__(&self) -> PyResult<AncestorsIterator> {
108 def __iter__(&self) -> PyResult<AncestorsIterator> {
105 AncestorsIterator::from_inner(py, self.inner(py).borrow().iter())
109 AncestorsIterator::from_inner(py, self.inner(py).borrow().iter())
106 }
110 }
107
111
108 def __bool__(&self) -> PyResult<bool> {
112 def __bool__(&self) -> PyResult<bool> {
109 Ok(!self.inner(py).borrow().is_empty())
113 Ok(!self.inner(py).borrow().is_empty())
110 }
114 }
111
115
112 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
116 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
113 inclusive: bool) -> PyResult<Self> {
117 inclusive: bool) -> PyResult<Self> {
114 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
118 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
115
119
116 let lazy =
120 let lazy =
117 CoreLazy::new(pyindex_to_graph(py, index)?,
121 VCGLazyAncestors::new(pyindex_to_graph(py, index)?,
118 initvec, stoprev, inclusive)
122 initvec, stoprev, inclusive)
119 .map_err(|e| GraphError::pynew(py, e))?;
123 .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))?;
120
124
121 Self::create_instance(py, RefCell::new(Box::new(lazy)))
125 Self::create_instance(py, RefCell::new(Box::new(lazy)))
122 }
126 }
123
127
124 });
128 });
125
129
126 py_class!(pub class MissingAncestors |py| {
130 py_class!(pub class MissingAncestors |py| {
127 data inner: RefCell<Box<CoreMissing<Index>>>;
131 data inner: RefCell<Box<CoreMissing<Index>>>;
128
132
129 def __new__(
133 def __new__(
130 _cls,
134 _cls,
131 index: PyObject,
135 index: PyObject,
132 bases: PyObject
136 bases: PyObject
133 )
137 )
134 -> PyResult<MissingAncestors> {
138 -> PyResult<MissingAncestors> {
135 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
139 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
136 let inner = CoreMissing::new(pyindex_to_graph(py, index)?, bases_vec);
140 let inner = CoreMissing::new(pyindex_to_graph(py, index)?, bases_vec);
137 MissingAncestors::create_instance(py, RefCell::new(Box::new(inner)))
141 MissingAncestors::create_instance(py, RefCell::new(Box::new(inner)))
138 }
142 }
139
143
140 def hasbases(&self) -> PyResult<bool> {
144 def hasbases(&self) -> PyResult<bool> {
141 Ok(self.inner(py).borrow().has_bases())
145 Ok(self.inner(py).borrow().has_bases())
142 }
146 }
143
147
144 def addbases(&self, bases: PyObject) -> PyResult<PyObject> {
148 def addbases(&self, bases: PyObject) -> PyResult<PyObject> {
145 let mut inner = self.inner(py).borrow_mut();
149 let mut inner = self.inner(py).borrow_mut();
146 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
150 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
147 inner.add_bases(bases_vec);
151 inner.add_bases(bases_vec);
148 // cpython doc has examples with PyResult<()> but this gives me
152 // cpython doc has examples with PyResult<()> but this gives me
149 // the trait `cpython::ToPyObject` is not implemented for `()`
153 // the trait `cpython::ToPyObject` is not implemented for `()`
150 // so let's return an explicit None
154 // so let's return an explicit None
151 Ok(py.None())
155 Ok(py.None())
152 }
156 }
153
157
154 def bases(&self) -> PyResult<HashSet<Revision>> {
158 def bases(&self) -> PyResult<HashSet<Revision>> {
155 Ok(self.inner(py).borrow().get_bases().clone())
159 Ok(self.inner(py).borrow().get_bases().clone())
156 }
160 }
157
161
158 def basesheads(&self) -> PyResult<HashSet<Revision>> {
162 def basesheads(&self) -> PyResult<HashSet<Revision>> {
159 let inner = self.inner(py).borrow();
163 let inner = self.inner(py).borrow();
160 inner.bases_heads().map_err(|e| GraphError::pynew(py, e))
164 inner.bases_heads().map_err(|e| GraphError::pynew(py, e))
161 }
165 }
162
166
163 def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> {
167 def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> {
164 let mut inner = self.inner(py).borrow_mut();
168 let mut inner = self.inner(py).borrow_mut();
165 // this is very lame: we convert to a Rust set, update it in place
169 // this is very lame: we convert to a Rust set, update it in place
166 // and then convert back to Python, only to have Python remove the
170 // and then convert back to Python, only to have Python remove the
167 // excess (thankfully, Python is happy with a list or even an iterator)
171 // excess (thankfully, Python is happy with a list or even an iterator)
168 // Leads to improve this:
172 // Leads to improve this:
169 // - have the CoreMissing instead do something emit revisions to
173 // - have the CoreMissing instead do something emit revisions to
170 // discard
174 // discard
171 // - define a trait for sets of revisions in the core and implement
175 // - define a trait for sets of revisions in the core and implement
172 // it for a Python set rewrapped with the GIL marker
176 // it for a Python set rewrapped with the GIL marker
173 let mut revs_pyset: HashSet<Revision> = rev_pyiter_collect(py, &revs)?;
177 let mut revs_pyset: HashSet<Revision> = rev_pyiter_collect(py, &revs)?;
174 inner.remove_ancestors_from(&mut revs_pyset)
178 inner.remove_ancestors_from(&mut revs_pyset)
175 .map_err(|e| GraphError::pynew(py, e))?;
179 .map_err(|e| GraphError::pynew(py, e))?;
176
180
177 // convert as Python list
181 // convert as Python list
178 let mut remaining_pyint_vec: Vec<PyObject> = Vec::with_capacity(
182 let mut remaining_pyint_vec: Vec<PyObject> = Vec::with_capacity(
179 revs_pyset.len());
183 revs_pyset.len());
180 for rev in revs_pyset {
184 for rev in revs_pyset {
181 remaining_pyint_vec.push(rev.to_py_object(py).into_object());
185 remaining_pyint_vec.push(rev.to_py_object(py).into_object());
182 }
186 }
183 let remaining_pylist = PyList::new(py, remaining_pyint_vec.as_slice());
187 let remaining_pylist = PyList::new(py, remaining_pyint_vec.as_slice());
184 revs.call_method(py, "intersection_update", (remaining_pylist, ), None)
188 revs.call_method(py, "intersection_update", (remaining_pylist, ), None)
185 }
189 }
186
190
187 def missingancestors(&self, revs: PyObject) -> PyResult<PyList> {
191 def missingancestors(&self, revs: PyObject) -> PyResult<PyList> {
188 let mut inner = self.inner(py).borrow_mut();
192 let mut inner = self.inner(py).borrow_mut();
189 let revs_vec: Vec<Revision> = rev_pyiter_collect(py, &revs)?;
193 let revs_vec: Vec<Revision> = rev_pyiter_collect(py, &revs)?;
190 let missing_vec = match inner.missing_ancestors(revs_vec) {
194 let missing_vec = match inner.missing_ancestors(revs_vec) {
191 Ok(missing) => missing,
195 Ok(missing) => missing,
192 Err(e) => {
196 Err(e) => {
193 return Err(GraphError::pynew(py, e));
197 return Err(GraphError::pynew(py, e));
194 }
198 }
195 };
199 };
196 // convert as Python list
200 // convert as Python list
197 let mut missing_pyint_vec: Vec<PyObject> = Vec::with_capacity(
201 let mut missing_pyint_vec: Vec<PyObject> = Vec::with_capacity(
198 missing_vec.len());
202 missing_vec.len());
199 for rev in missing_vec {
203 for rev in missing_vec {
200 missing_pyint_vec.push(rev.to_py_object(py).into_object());
204 missing_pyint_vec.push(rev.to_py_object(py).into_object());
201 }
205 }
202 Ok(PyList::new(py, missing_pyint_vec.as_slice()))
206 Ok(PyList::new(py, missing_pyint_vec.as_slice()))
203 }
207 }
204 });
208 });
205
209
206 /// Create the module, with __package__ given from parent
210 /// Create the module, with __package__ given from parent
207 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
211 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
208 let dotted_name = &format!("{}.ancestor", package);
212 let dotted_name = &format!("{}.ancestor", package);
209 let m = PyModule::new(py, dotted_name)?;
213 let m = PyModule::new(py, dotted_name)?;
210 m.add(py, "__package__", package)?;
214 m.add(py, "__package__", package)?;
211 m.add(
215 m.add(
212 py,
216 py,
213 "__doc__",
217 "__doc__",
214 "Generic DAG ancestor algorithms - Rust implementation",
218 "Generic DAG ancestor algorithms - Rust implementation",
215 )?;
219 )?;
216 m.add_class::<AncestorsIterator>(py)?;
220 m.add_class::<AncestorsIterator>(py)?;
217 m.add_class::<LazyAncestors>(py)?;
221 m.add_class::<LazyAncestors>(py)?;
218 m.add_class::<MissingAncestors>(py)?;
222 m.add_class::<MissingAncestors>(py)?;
219
223
220 let sys = PyModule::import(py, "sys")?;
224 let sys = PyModule::import(py, "sys")?;
221 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
225 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
222 sys_modules.set_item(py, dotted_name, &m)?;
226 sys_modules.set_item(py, dotted_name, &m)?;
223 // Example C code (see pyexpat.c and import.c) will "give away the
227 // Example C code (see pyexpat.c and import.c) will "give away the
224 // reference", but we won't because it will be consumed once the
228 // reference", but we won't because it will be consumed once the
225 // Rust PyObject is dropped.
229 // Rust PyObject is dropped.
226 Ok(m)
230 Ok(m)
227 }
231 }
@@ -1,44 +1,70 b''
1 // ancestors.rs
1 // ancestors.rs
2 //
2 //
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
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 Rust errors
8 //! Bindings for Rust errors
9 //!
9 //!
10 //! [`GraphError`] exposes `hg::GraphError` as a subclass of `ValueError`
10 //! [`GraphError`] exposes `hg::GraphError` as a subclass of `ValueError`
11 //! but some variants of `hg::GraphError` can be converted directly to other
11 //! but some variants of `hg::GraphError` can be converted directly to other
12 //! existing Python exceptions if appropriate.
12 //! existing Python exceptions if appropriate.
13 //!
13 //!
14 //! [`GraphError`]: struct.GraphError.html
14 //! [`GraphError`]: struct.GraphError.html
15 use cpython::{
15 use cpython::{
16 exc::{RuntimeError, ValueError},
16 exc::{RuntimeError, ValueError},
17 py_exception, PyErr, Python,
17 py_exception, PyErr, Python,
18 };
18 };
19 use hg;
19 use hg;
20
20
21 py_exception!(rustext, GraphError, ValueError);
21 py_exception!(rustext, GraphError, ValueError);
22
22
23 impl GraphError {
23 impl GraphError {
24 pub fn pynew(py: Python, inner: hg::GraphError) -> PyErr {
24 pub fn pynew(py: Python, inner: hg::GraphError) -> PyErr {
25 match inner {
25 match inner {
26 hg::GraphError::ParentOutOfRange(r) => {
26 hg::GraphError::ParentOutOfRange(r) => {
27 GraphError::new(py, ("ParentOutOfRange", r))
27 GraphError::new(py, ("ParentOutOfRange", r))
28 }
28 }
29 hg::GraphError::WorkingDirectoryUnsupported => {
29 hg::GraphError::WorkingDirectoryUnsupported => {
30 match py
30 match py
31 .import("mercurial.error")
31 .import("mercurial.error")
32 .and_then(|m| m.get(py, "WdirUnsupported"))
32 .and_then(|m| m.get(py, "WdirUnsupported"))
33 {
33 {
34 Err(e) => e,
34 Err(e) => e,
35 Ok(cls) => PyErr::from_instance(py, cls),
35 Ok(cls) => PyErr::from_instance(py, cls),
36 }
36 }
37 }
37 }
38 }
38 }
39 }
39 }
40
41 pub fn pynew_from_vcsgraph(
42 py: Python,
43 inner: vcsgraph::graph::GraphReadError,
44 ) -> PyErr {
45 match inner {
46 vcsgraph::graph::GraphReadError::InconsistentGraphData => {
47 GraphError::new(py, "InconsistentGraphData")
48 }
49 vcsgraph::graph::GraphReadError::InvalidKey => {
50 GraphError::new(py, "ParentOutOfRange")
51 }
52 vcsgraph::graph::GraphReadError::KeyedInvalidKey(r) => {
53 GraphError::new(py, ("ParentOutOfRange", r))
54 }
55 vcsgraph::graph::GraphReadError::WorkingDirectoryUnsupported => {
56 match py
57 .import("mercurial.error")
58 .and_then(|m| m.get(py, "WdirUnsupported"))
59 {
60 Err(e) => e,
61 Ok(cls) => PyErr::from_instance(py, cls),
62 }
63 }
64 }
65 }
40 }
66 }
41
67
42 py_exception!(rustext, HgPathPyError, RuntimeError);
68 py_exception!(rustext, HgPathPyError, RuntimeError);
43 py_exception!(rustext, FallbackError, RuntimeError);
69 py_exception!(rustext, FallbackError, RuntimeError);
44 py_exception!(shared_ref, AlreadyBorrowed, RuntimeError);
70 py_exception!(shared_ref, AlreadyBorrowed, RuntimeError);
General Comments 0
You need to be logged in to leave comments. Login now