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