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