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