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