##// END OF EJS Templates
rust-nodemap: add utils for propagating errors...
Georges Racinet -
r44993:26dd35ac default
parent child Browse files
Show More
@@ -1,241 +1,278 b''
1 // revlog.rs
1 // revlog.rs
2 //
2 //
3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
3 // Copyright 2019-2020 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,
10 exc::ValueError, ObjectProtocol, PyClone, PyDict, PyErr, PyModule,
11 Python, PythonObject, ToPyObject,
11 PyObject, PyResult, PyTuple, Python, PythonObject, ToPyObject,
12 };
12 };
13 use hg::Revision;
13 use hg::{nodemap::NodeMapError, NodeError, 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(
17 pub(crate) fn pyindex_to_graph(
18 py: Python,
18 py: Python,
19 index: PyObject,
19 index: PyObject,
20 ) -> PyResult<cindex::Index> {
20 ) -> PyResult<cindex::Index> {
21 match index.extract::<MixedIndex>(py) {
21 match index.extract::<MixedIndex>(py) {
22 Ok(midx) => Ok(midx.clone_cindex(py)),
22 Ok(midx) => Ok(midx.clone_cindex(py)),
23 Err(_) => cindex::Index::new(py, index),
23 Err(_) => cindex::Index::new(py, index),
24 }
24 }
25 }
25 }
26
26
27 py_class!(pub class MixedIndex |py| {
27 py_class!(pub class MixedIndex |py| {
28 data cindex: RefCell<cindex::Index>;
28 data cindex: RefCell<cindex::Index>;
29
29
30 def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
30 def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
31 Self::new(py, cindex)
31 Self::new(py, cindex)
32 }
32 }
33
33
34 /// Compatibility layer used for Python consumers needing access to the C index
34 /// Compatibility layer used for Python consumers needing access to the C index
35 ///
35 ///
36 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
36 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
37 /// that may need to build a custom `nodetree`, based on a specified revset.
37 /// that may need to build a custom `nodetree`, based on a specified revset.
38 /// With a Rust implementation of the nodemap, we will be able to get rid of
38 /// With a Rust implementation of the nodemap, we will be able to get rid of
39 /// this, by exposing our own standalone nodemap class,
39 /// this, by exposing our own standalone nodemap class,
40 /// ready to accept `MixedIndex`.
40 /// ready to accept `MixedIndex`.
41 def get_cindex(&self) -> PyResult<PyObject> {
41 def get_cindex(&self) -> PyResult<PyObject> {
42 Ok(self.cindex(py).borrow().inner().clone_ref(py))
42 Ok(self.cindex(py).borrow().inner().clone_ref(py))
43 }
43 }
44
44
45
45
46 // Reforwarded C index API
46 // Reforwarded C index API
47
47
48 // index_methods (tp_methods). Same ordering as in revlog.c
48 // index_methods (tp_methods). Same ordering as in revlog.c
49
49
50 /// return the gca set of the given revs
50 /// return the gca set of the given revs
51 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
51 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
52 self.call_cindex(py, "ancestors", args, kw)
52 self.call_cindex(py, "ancestors", args, kw)
53 }
53 }
54
54
55 /// return the heads of the common ancestors of the given revs
55 /// return the heads of the common ancestors of the given revs
56 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
56 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
57 self.call_cindex(py, "commonancestorsheads", args, kw)
57 self.call_cindex(py, "commonancestorsheads", args, kw)
58 }
58 }
59
59
60 /// clear the index caches
60 /// clear the index caches
61 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
61 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
62 self.call_cindex(py, "clearcaches", args, kw)
62 self.call_cindex(py, "clearcaches", args, kw)
63 }
63 }
64
64
65 /// get an index entry
65 /// get an index entry
66 def get(&self, *args, **kw) -> PyResult<PyObject> {
66 def get(&self, *args, **kw) -> PyResult<PyObject> {
67 self.call_cindex(py, "get", args, kw)
67 self.call_cindex(py, "get", args, kw)
68 }
68 }
69
69
70 /// return `rev` associated with a node or None
70 /// return `rev` associated with a node or None
71 def get_rev(&self, *args, **kw) -> PyResult<PyObject> {
71 def get_rev(&self, *args, **kw) -> PyResult<PyObject> {
72 self.call_cindex(py, "get_rev", args, kw)
72 self.call_cindex(py, "get_rev", args, kw)
73 }
73 }
74
74
75 /// return True if the node exist in the index
75 /// return True if the node exist in the index
76 def has_node(&self, *args, **kw) -> PyResult<PyObject> {
76 def has_node(&self, *args, **kw) -> PyResult<PyObject> {
77 self.call_cindex(py, "has_node", args, kw)
77 self.call_cindex(py, "has_node", args, kw)
78 }
78 }
79
79
80 /// return `rev` associated with a node or raise RevlogError
80 /// return `rev` associated with a node or raise RevlogError
81 def rev(&self, *args, **kw) -> PyResult<PyObject> {
81 def rev(&self, *args, **kw) -> PyResult<PyObject> {
82 self.call_cindex(py, "rev", args, kw)
82 self.call_cindex(py, "rev", args, kw)
83 }
83 }
84
84
85 /// compute phases
85 /// compute phases
86 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
86 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
87 self.call_cindex(py, "computephasesmapsets", args, kw)
87 self.call_cindex(py, "computephasesmapsets", args, kw)
88 }
88 }
89
89
90 /// reachableroots
90 /// reachableroots
91 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
91 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
92 self.call_cindex(py, "reachableroots2", args, kw)
92 self.call_cindex(py, "reachableroots2", args, kw)
93 }
93 }
94
94
95 /// get head revisions
95 /// get head revisions
96 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
96 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
97 self.call_cindex(py, "headrevs", args, kw)
97 self.call_cindex(py, "headrevs", args, kw)
98 }
98 }
99
99
100 /// get filtered head revisions
100 /// get filtered head revisions
101 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
101 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
102 self.call_cindex(py, "headrevsfiltered", args, kw)
102 self.call_cindex(py, "headrevsfiltered", args, kw)
103 }
103 }
104
104
105 /// True if the object is a snapshot
105 /// True if the object is a snapshot
106 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
106 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
107 self.call_cindex(py, "issnapshot", args, kw)
107 self.call_cindex(py, "issnapshot", args, kw)
108 }
108 }
109
109
110 /// Gather snapshot data in a cache dict
110 /// Gather snapshot data in a cache dict
111 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
111 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
112 self.call_cindex(py, "findsnapshots", args, kw)
112 self.call_cindex(py, "findsnapshots", args, kw)
113 }
113 }
114
114
115 /// determine revisions with deltas to reconstruct fulltext
115 /// determine revisions with deltas to reconstruct fulltext
116 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
116 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
117 self.call_cindex(py, "deltachain", args, kw)
117 self.call_cindex(py, "deltachain", args, kw)
118 }
118 }
119
119
120 /// slice planned chunk read to reach a density threshold
120 /// slice planned chunk read to reach a density threshold
121 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
121 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
122 self.call_cindex(py, "slicechunktodensity", args, kw)
122 self.call_cindex(py, "slicechunktodensity", args, kw)
123 }
123 }
124
124
125 /// append an index entry
125 /// append an index entry
126 def append(&self, *args, **kw) -> PyResult<PyObject> {
126 def append(&self, *args, **kw) -> PyResult<PyObject> {
127 self.call_cindex(py, "append", args, kw)
127 self.call_cindex(py, "append", args, kw)
128 }
128 }
129
129
130 /// match a potentially ambiguous node ID
130 /// match a potentially ambiguous node ID
131 def partialmatch(&self, *args, **kw) -> PyResult<PyObject> {
131 def partialmatch(&self, *args, **kw) -> PyResult<PyObject> {
132 self.call_cindex(py, "partialmatch", args, kw)
132 self.call_cindex(py, "partialmatch", args, kw)
133 }
133 }
134
134
135 /// find length of shortest hex nodeid of a binary ID
135 /// find length of shortest hex nodeid of a binary ID
136 def shortest(&self, *args, **kw) -> PyResult<PyObject> {
136 def shortest(&self, *args, **kw) -> PyResult<PyObject> {
137 self.call_cindex(py, "shortest", args, kw)
137 self.call_cindex(py, "shortest", args, kw)
138 }
138 }
139
139
140 /// stats for the index
140 /// stats for the index
141 def stats(&self, *args, **kw) -> PyResult<PyObject> {
141 def stats(&self, *args, **kw) -> PyResult<PyObject> {
142 self.call_cindex(py, "stats", args, kw)
142 self.call_cindex(py, "stats", args, kw)
143 }
143 }
144
144
145 // index_sequence_methods and index_mapping_methods.
145 // index_sequence_methods and index_mapping_methods.
146 //
146 //
147 // Since we call back through the high level Python API,
147 // Since we call back through the high level Python API,
148 // there's no point making a distinction between index_get
148 // there's no point making a distinction between index_get
149 // and index_getitem.
149 // and index_getitem.
150
150
151 def __len__(&self) -> PyResult<usize> {
151 def __len__(&self) -> PyResult<usize> {
152 self.cindex(py).borrow().inner().len(py)
152 self.cindex(py).borrow().inner().len(py)
153 }
153 }
154
154
155 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
155 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
156 // this conversion seems needless, but that's actually because
156 // this conversion seems needless, but that's actually because
157 // `index_getitem` does not handle conversion from PyLong,
157 // `index_getitem` does not handle conversion from PyLong,
158 // which expressions such as [e for e in index] internally use.
158 // which expressions such as [e for e in index] internally use.
159 // Note that we don't seem to have a direct way to call
159 // Note that we don't seem to have a direct way to call
160 // PySequence_GetItem (does the job), which would be better for
160 // PySequence_GetItem (does the job), which would be better for
161 // for performance
161 // for performance
162 let key = match key.extract::<Revision>(py) {
162 let key = match key.extract::<Revision>(py) {
163 Ok(rev) => rev.to_py_object(py).into_object(),
163 Ok(rev) => rev.to_py_object(py).into_object(),
164 Err(_) => key,
164 Err(_) => key,
165 };
165 };
166 self.cindex(py).borrow().inner().get_item(py, key)
166 self.cindex(py).borrow().inner().get_item(py, key)
167 }
167 }
168
168
169 def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> {
169 def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> {
170 self.cindex(py).borrow().inner().set_item(py, key, value)
170 self.cindex(py).borrow().inner().set_item(py, key, value)
171 }
171 }
172
172
173 def __delitem__(&self, key: PyObject) -> PyResult<()> {
173 def __delitem__(&self, key: PyObject) -> PyResult<()> {
174 self.cindex(py).borrow().inner().del_item(py, key)
174 self.cindex(py).borrow().inner().del_item(py, key)
175 }
175 }
176
176
177 def __contains__(&self, item: PyObject) -> PyResult<bool> {
177 def __contains__(&self, item: PyObject) -> PyResult<bool> {
178 // ObjectProtocol does not seem to provide contains(), so
178 // ObjectProtocol does not seem to provide contains(), so
179 // this is an equivalent implementation of the index_contains()
179 // this is an equivalent implementation of the index_contains()
180 // defined in revlog.c
180 // defined in revlog.c
181 let cindex = self.cindex(py).borrow();
181 let cindex = self.cindex(py).borrow();
182 match item.extract::<Revision>(py) {
182 match item.extract::<Revision>(py) {
183 Ok(rev) => {
183 Ok(rev) => {
184 Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision)
184 Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision)
185 }
185 }
186 Err(_) => {
186 Err(_) => {
187 cindex.inner().call_method(
187 cindex.inner().call_method(
188 py,
188 py,
189 "has_node",
189 "has_node",
190 PyTuple::new(py, &[item]),
190 PyTuple::new(py, &[item]),
191 None)?
191 None)?
192 .extract(py)
192 .extract(py)
193 }
193 }
194 }
194 }
195 }
195 }
196
196
197
197
198 });
198 });
199
199
200 impl MixedIndex {
200 impl MixedIndex {
201 fn new(py: Python, cindex: PyObject) -> PyResult<MixedIndex> {
201 fn new(py: Python, cindex: PyObject) -> PyResult<MixedIndex> {
202 Self::create_instance(
202 Self::create_instance(
203 py,
203 py,
204 RefCell::new(cindex::Index::new(py, cindex)?),
204 RefCell::new(cindex::Index::new(py, cindex)?),
205 )
205 )
206 }
206 }
207
207
208 /// forward a method call to the underlying C index
208 /// forward a method call to the underlying C index
209 fn call_cindex(
209 fn call_cindex(
210 &self,
210 &self,
211 py: Python,
211 py: Python,
212 name: &str,
212 name: &str,
213 args: &PyTuple,
213 args: &PyTuple,
214 kwargs: Option<&PyDict>,
214 kwargs: Option<&PyDict>,
215 ) -> PyResult<PyObject> {
215 ) -> PyResult<PyObject> {
216 self.cindex(py)
216 self.cindex(py)
217 .borrow()
217 .borrow()
218 .inner()
218 .inner()
219 .call_method(py, name, args, kwargs)
219 .call_method(py, name, args, kwargs)
220 }
220 }
221
221
222 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
222 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
223 self.cindex(py).borrow().clone_ref(py)
223 self.cindex(py).borrow().clone_ref(py)
224 }
224 }
225 }
225 }
226
226
227 fn revlog_error(py: Python) -> PyErr {
228 match py
229 .import("mercurial.error")
230 .and_then(|m| m.get(py, "RevlogError"))
231 {
232 Err(e) => e,
233 Ok(cls) => PyErr::from_instance(py, cls),
234 }
235 }
236
237 fn rev_not_in_index(py: Python, rev: Revision) -> PyErr {
238 PyErr::new::<ValueError, _>(
239 py,
240 format!(
241 "Inconsistency: Revision {} found in nodemap \
242 is not in revlog index",
243 rev
244 ),
245 )
246 }
247
248 /// Standard treatment of NodeMapError
249 fn nodemap_error(py: Python, err: NodeMapError) -> PyErr {
250 match err {
251 NodeMapError::MultipleResults => revlog_error(py),
252 NodeMapError::RevisionNotInIndex(r) => rev_not_in_index(py, r),
253 NodeMapError::InvalidNodePrefix(s) => invalid_node_prefix(py, &s),
254 }
255 }
256
257 fn invalid_node_prefix(py: Python, ne: &NodeError) -> PyErr {
258 PyErr::new::<ValueError, _>(
259 py,
260 format!("Invalid node or prefix: {:?}", ne),
261 )
262 }
263
227 /// Create the module, with __package__ given from parent
264 /// Create the module, with __package__ given from parent
228 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
265 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
229 let dotted_name = &format!("{}.revlog", package);
266 let dotted_name = &format!("{}.revlog", package);
230 let m = PyModule::new(py, dotted_name)?;
267 let m = PyModule::new(py, dotted_name)?;
231 m.add(py, "__package__", package)?;
268 m.add(py, "__package__", package)?;
232 m.add(py, "__doc__", "RevLog - Rust implementations")?;
269 m.add(py, "__doc__", "RevLog - Rust implementations")?;
233
270
234 m.add_class::<MixedIndex>(py)?;
271 m.add_class::<MixedIndex>(py)?;
235
272
236 let sys = PyModule::import(py, "sys")?;
273 let sys = PyModule::import(py, "sys")?;
237 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
274 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
238 sys_modules.set_item(py, dotted_name, &m)?;
275 sys_modules.set_item(py, dotted_name, &m)?;
239
276
240 Ok(m)
277 Ok(m)
241 }
278 }
General Comments 0
You need to be logged in to leave comments. Login now