##// END OF EJS Templates
rust-index: add a struct wrapping the C index...
Georges Racinet -
r44413:b69d5f3a default
parent child Browse files
Show More
@@ -0,0 +1,34 b''
1 from __future__ import absolute_import
2 import unittest
3
4 try:
5 from mercurial import rustext
6
7 rustext.__name__ # trigger immediate actual import
8 except ImportError:
9 rustext = None
10 else:
11 from mercurial.rustext import revlog
12
13 from mercurial.testing import revlog as revlogtesting
14
15
16 @unittest.skipIf(
17 rustext is None, "rustext module revlog relies on is not available",
18 )
19 class RustRevlogIndexTest(revlogtesting.RevlogBasedTestBase):
20 def test_heads(self):
21 idx = self.parseindex()
22 rustidx = revlog.MixedIndex(idx)
23 self.assertEqual(rustidx.headrevs(), idx.headrevs())
24
25 def test_len(self):
26 idx = self.parseindex()
27 rustidx = revlog.MixedIndex(idx)
28 self.assertEqual(len(rustidx), len(idx))
29
30
31 if __name__ == '__main__':
32 import silenttestrunner
33
34 silenttestrunner.main(__name__)
@@ -1,82 +1,83 b''
1 // lib.rs
1 // lib.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 //! Python bindings of `hg-core` objects using the `cpython` crate.
8 //! Python bindings of `hg-core` objects using the `cpython` crate.
9 //! Once compiled, the resulting single shared library object can be placed in
9 //! Once compiled, the resulting single shared library object can be placed in
10 //! the `mercurial` package directly as `rustext.so` or `rustext.dll`.
10 //! the `mercurial` package directly as `rustext.so` or `rustext.dll`.
11 //! It holds several modules, so that from the point of view of Python,
11 //! It holds several modules, so that from the point of view of Python,
12 //! it behaves as the `cext` package.
12 //! it behaves as the `cext` package.
13 //!
13 //!
14 //! Example:
14 //! Example:
15 //!
15 //!
16 //! ```text
16 //! ```text
17 //! >>> from mercurial.rustext import ancestor
17 //! >>> from mercurial.rustext import ancestor
18 //! >>> ancestor.__doc__
18 //! >>> ancestor.__doc__
19 //! 'Generic DAG ancestor algorithms - Rust implementation'
19 //! 'Generic DAG ancestor algorithms - Rust implementation'
20 //! ```
20 //! ```
21
21
22 /// This crate uses nested private macros, `extern crate` is still needed in
22 /// This crate uses nested private macros, `extern crate` is still needed in
23 /// 2018 edition.
23 /// 2018 edition.
24 #[macro_use]
24 #[macro_use]
25 extern crate cpython;
25 extern crate cpython;
26
26
27 pub mod ancestors;
27 pub mod ancestors;
28 mod cindex;
28 mod cindex;
29 mod conversion;
29 mod conversion;
30 #[macro_use]
30 #[macro_use]
31 pub mod ref_sharing;
31 pub mod ref_sharing;
32 pub mod dagops;
32 pub mod dagops;
33 pub mod dirstate;
33 pub mod dirstate;
34 pub mod discovery;
34 pub mod discovery;
35 pub mod exceptions;
35 pub mod exceptions;
36 pub mod filepatterns;
36 pub mod filepatterns;
37 pub mod parsers;
37 pub mod parsers;
38 pub mod revlog;
38 pub mod revlog;
39 pub mod utils;
39 pub mod utils;
40
40
41 py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| {
41 py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| {
42 m.add(
42 m.add(
43 py,
43 py,
44 "__doc__",
44 "__doc__",
45 "Mercurial core concepts - Rust implementation",
45 "Mercurial core concepts - Rust implementation",
46 )?;
46 )?;
47
47
48 let dotted_name: String = m.get(py, "__name__")?.extract(py)?;
48 let dotted_name: String = m.get(py, "__name__")?.extract(py)?;
49 m.add(py, "ancestor", ancestors::init_module(py, &dotted_name)?)?;
49 m.add(py, "ancestor", ancestors::init_module(py, &dotted_name)?)?;
50 m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?;
50 m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?;
51 m.add(py, "discovery", discovery::init_module(py, &dotted_name)?)?;
51 m.add(py, "discovery", discovery::init_module(py, &dotted_name)?)?;
52 m.add(py, "dirstate", dirstate::init_module(py, &dotted_name)?)?;
52 m.add(py, "dirstate", dirstate::init_module(py, &dotted_name)?)?;
53 m.add(py, "revlog", revlog::init_module(py, &dotted_name)?)?;
53 m.add(
54 m.add(
54 py,
55 py,
55 "filepatterns",
56 "filepatterns",
56 filepatterns::init_module(py, &dotted_name)?,
57 filepatterns::init_module(py, &dotted_name)?,
57 )?;
58 )?;
58 m.add(
59 m.add(
59 py,
60 py,
60 "parsers",
61 "parsers",
61 parsers::init_parsers_module(py, &dotted_name)?,
62 parsers::init_parsers_module(py, &dotted_name)?,
62 )?;
63 )?;
63 m.add(py, "GraphError", py.get_type::<exceptions::GraphError>())?;
64 m.add(py, "GraphError", py.get_type::<exceptions::GraphError>())?;
64 m.add(
65 m.add(
65 py,
66 py,
66 "PatternFileError",
67 "PatternFileError",
67 py.get_type::<exceptions::PatternFileError>(),
68 py.get_type::<exceptions::PatternFileError>(),
68 )?;
69 )?;
69 m.add(
70 m.add(
70 py,
71 py,
71 "PatternError",
72 "PatternError",
72 py.get_type::<exceptions::PatternError>(),
73 py.get_type::<exceptions::PatternError>(),
73 )?;
74 )?;
74 Ok(())
75 Ok(())
75 });
76 });
76
77
77 #[cfg(not(any(feature = "python27-bin", feature = "python3-bin")))]
78 #[cfg(not(any(feature = "python27-bin", feature = "python3-bin")))]
78 #[test]
79 #[test]
79 #[ignore]
80 #[ignore]
80 fn libpython_must_be_linked_to_run_tests() {
81 fn libpython_must_be_linked_to_run_tests() {
81 // stub function to tell that some tests wouldn't run
82 // stub function to tell that some tests wouldn't run
82 }
83 }
@@ -1,17 +1,217 b''
1 // revlog.rs
1 // revlog.rs
2 //
2 //
3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
3 // Copyright 2019 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
7
8 use crate::cindex;
8 use crate::cindex;
9 use cpython::{PyObject, PyResult, Python};
9 use cpython::{
10 ObjectProtocol, PyDict, PyModule, PyObject, PyResult, PyTuple, Python,
11 PythonObject, ToPyObject,
12 };
13 use hg::Revision;
14 use std::cell::RefCell;
10
15
11 /// Return a Struct implementing the Graph trait
16 /// Return a Struct implementing the Graph trait
12 pub(crate) fn pyindex_to_graph(
17 pub(crate) fn pyindex_to_graph(
13 py: Python,
18 py: Python,
14 index: PyObject,
19 index: PyObject,
15 ) -> PyResult<cindex::Index> {
20 ) -> PyResult<cindex::Index> {
16 cindex::Index::new(py, index)
21 cindex::Index::new(py, index)
17 }
22 }
23
24 py_class!(pub class MixedIndex |py| {
25 data cindex: RefCell<cindex::Index>;
26
27 def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
28 Self::create_instance(py, RefCell::new(
29 cindex::Index::new(py, cindex)?))
30 }
31
32
33 // Reforwarded C index API
34
35 // index_methods (tp_methods). Same ordering as in revlog.c
36
37 /// return the gca set of the given revs
38 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
39 self.call_cindex(py, "ancestors", args, kw)
40 }
41
42 /// return the heads of the common ancestors of the given revs
43 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
44 self.call_cindex(py, "commonancestorsheads", args, kw)
45 }
46
47 /// clear the index caches
48 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
49 self.call_cindex(py, "clearcaches", args, kw)
50 }
51
52 /// get an index entry
53 def get(&self, *args, **kw) -> PyResult<PyObject> {
54 self.call_cindex(py, "get", args, kw)
55 }
56
57 /// return `rev` associated with a node or None
58 def get_rev(&self, *args, **kw) -> PyResult<PyObject> {
59 self.call_cindex(py, "get_rev", args, kw)
60 }
61
62 /// return True if the node exist in the index
63 def has_node(&self, *args, **kw) -> PyResult<PyObject> {
64 self.call_cindex(py, "has_node", args, kw)
65 }
66
67 /// return `rev` associated with a node or raise RevlogError
68 def rev(&self, *args, **kw) -> PyResult<PyObject> {
69 self.call_cindex(py, "rev", args, kw)
70 }
71
72 /// compute phases
73 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
74 self.call_cindex(py, "computephasesmapsets", args, kw)
75 }
76
77 /// reachableroots
78 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
79 self.call_cindex(py, "reachableroots2", args, kw)
80 }
81
82 /// get head revisions
83 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
84 self.call_cindex(py, "headrevs", args, kw)
85 }
86
87 /// get filtered head revisions
88 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
89 self.call_cindex(py, "headrevsfiltered", args, kw)
90 }
91
92 /// True if the object is a snapshot
93 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
94 self.call_cindex(py, "issnapshot", args, kw)
95 }
96
97 /// Gather snapshot data in a cache dict
98 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
99 self.call_cindex(py, "findsnapshots", args, kw)
100 }
101
102 /// determine revisions with deltas to reconstruct fulltext
103 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
104 self.call_cindex(py, "deltachain", args, kw)
105 }
106
107 /// slice planned chunk read to reach a density threshold
108 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
109 self.call_cindex(py, "slicechunktodensity", args, kw)
110 }
111
112 /// append an index entry
113 def append(&self, *args, **kw) -> PyResult<PyObject> {
114 self.call_cindex(py, "append", args, kw)
115 }
116
117 /// match a potentially ambiguous node ID
118 def partialmatch(&self, *args, **kw) -> PyResult<PyObject> {
119 self.call_cindex(py, "partialmatch", args, kw)
120 }
121
122 /// find length of shortest hex nodeid of a binary ID
123 def shortest(&self, *args, **kw) -> PyResult<PyObject> {
124 self.call_cindex(py, "shortest", args, kw)
125 }
126
127 /// stats for the index
128 def stats(&self, *args, **kw) -> PyResult<PyObject> {
129 self.call_cindex(py, "stats", args, kw)
130 }
131
132 // index_sequence_methods and index_mapping_methods.
133 //
134 // Since we call back through the high level Python API,
135 // there's no point making a distinction between index_get
136 // and index_getitem.
137
138 def __len__(&self) -> PyResult<usize> {
139 self.cindex(py).borrow().inner().len(py)
140 }
141
142 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
143 // this conversion seems needless, but that's actually because
144 // `index_getitem` does not handle conversion from PyLong,
145 // which expressions such as [e for e in index] internally use.
146 // Note that we don't seem to have a direct way to call
147 // PySequence_GetItem (does the job), which would be better for
148 // for performance
149 let key = match key.extract::<Revision>(py) {
150 Ok(rev) => rev.to_py_object(py).into_object(),
151 Err(_) => key,
152 };
153 self.cindex(py).borrow().inner().get_item(py, key)
154 }
155
156 def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> {
157 self.cindex(py).borrow().inner().set_item(py, key, value)
158 }
159
160 def __delitem__(&self, key: PyObject) -> PyResult<()> {
161 self.cindex(py).borrow().inner().del_item(py, key)
162 }
163
164 def __contains__(&self, item: PyObject) -> PyResult<bool> {
165 // ObjectProtocol does not seem to provide contains(), so
166 // this is an equivalent implementation of the index_contains()
167 // defined in revlog.c
168 let cindex = self.cindex(py).borrow();
169 match item.extract::<Revision>(py) {
170 Ok(rev) => {
171 Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision)
172 }
173 Err(_) => {
174 cindex.inner().call_method(
175 py,
176 "has_node",
177 PyTuple::new(py, &[item]),
178 None)?
179 .extract(py)
180 }
181 }
182 }
183
184
185 });
186
187 impl MixedIndex {
188 /// forward a method call to the underlying C index
189 fn call_cindex(
190 &self,
191 py: Python,
192 name: &str,
193 args: &PyTuple,
194 kwargs: Option<&PyDict>,
195 ) -> PyResult<PyObject> {
196 self.cindex(py)
197 .borrow()
198 .inner()
199 .call_method(py, name, args, kwargs)
200 }
201 }
202
203 /// Create the module, with __package__ given from parent
204 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
205 let dotted_name = &format!("{}.revlog", package);
206 let m = PyModule::new(py, dotted_name)?;
207 m.add(py, "__package__", package)?;
208 m.add(py, "__doc__", "RevLog - Rust implementations")?;
209
210 m.add_class::<MixedIndex>(py)?;
211
212 let sys = PyModule::import(py, "sys")?;
213 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
214 sys_modules.set_item(py, dotted_name, &m)?;
215
216 Ok(m)
217 }
General Comments 0
You need to be logged in to leave comments. Login now