##// END OF EJS Templates
rust-index: results comparison helper with details...
Georges Racinet -
r52094:52bbb57a default
parent child Browse files
Show More
@@ -1,647 +1,668 b''
1 // revlog.rs
1 // revlog.rs
2 //
2 //
3 // Copyright 2019-2020 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::{
8 use crate::{
9 cindex,
9 cindex,
10 utils::{node_from_py_bytes, node_from_py_object},
10 utils::{node_from_py_bytes, node_from_py_object},
11 PyRevision,
11 PyRevision,
12 };
12 };
13 use cpython::{
13 use cpython::{
14 buffer::{Element, PyBuffer},
14 buffer::{Element, PyBuffer},
15 exc::{IndexError, ValueError},
15 exc::{IndexError, ValueError},
16 ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyModule,
16 ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyInt, PyModule,
17 PyObject, PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject,
17 PyObject, PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject,
18 };
18 };
19 use hg::{
19 use hg::{
20 index::IndexHeader,
20 index::IndexHeader,
21 index::{RevisionDataParams, COMPRESSION_MODE_INLINE},
21 index::{RevisionDataParams, COMPRESSION_MODE_INLINE},
22 nodemap::{Block, NodeMapError, NodeTree},
22 nodemap::{Block, NodeMapError, NodeTree},
23 revlog::{nodemap::NodeMap, NodePrefix, RevlogIndex},
23 revlog::{nodemap::NodeMap, NodePrefix, RevlogIndex},
24 BaseRevision, Revision, UncheckedRevision,
24 BaseRevision, Revision, UncheckedRevision,
25 };
25 };
26 use std::cell::RefCell;
26 use std::cell::RefCell;
27
27
28 /// Return a Struct implementing the Graph trait
28 /// Return a Struct implementing the Graph trait
29 pub(crate) fn pyindex_to_graph(
29 pub(crate) fn pyindex_to_graph(
30 py: Python,
30 py: Python,
31 index: PyObject,
31 index: PyObject,
32 ) -> PyResult<cindex::Index> {
32 ) -> PyResult<cindex::Index> {
33 match index.extract::<MixedIndex>(py) {
33 match index.extract::<MixedIndex>(py) {
34 Ok(midx) => Ok(midx.clone_cindex(py)),
34 Ok(midx) => Ok(midx.clone_cindex(py)),
35 Err(_) => cindex::Index::new(py, index),
35 Err(_) => cindex::Index::new(py, index),
36 }
36 }
37 }
37 }
38
38
39 py_class!(pub class MixedIndex |py| {
39 py_class!(pub class MixedIndex |py| {
40 data cindex: RefCell<cindex::Index>;
40 data cindex: RefCell<cindex::Index>;
41 data index: RefCell<hg::index::Index>;
41 data index: RefCell<hg::index::Index>;
42 data nt: RefCell<Option<NodeTree>>;
42 data nt: RefCell<Option<NodeTree>>;
43 data docket: RefCell<Option<PyObject>>;
43 data docket: RefCell<Option<PyObject>>;
44 // Holds a reference to the mmap'ed persistent nodemap data
44 // Holds a reference to the mmap'ed persistent nodemap data
45 data nodemap_mmap: RefCell<Option<PyBuffer>>;
45 data nodemap_mmap: RefCell<Option<PyBuffer>>;
46 // Holds a reference to the mmap'ed persistent index data
46 // Holds a reference to the mmap'ed persistent index data
47 data index_mmap: RefCell<Option<PyBuffer>>;
47 data index_mmap: RefCell<Option<PyBuffer>>;
48
48
49 def __new__(
49 def __new__(
50 _cls,
50 _cls,
51 cindex: PyObject,
51 cindex: PyObject,
52 data: PyObject,
52 data: PyObject,
53 default_header: u32,
53 default_header: u32,
54 ) -> PyResult<MixedIndex> {
54 ) -> PyResult<MixedIndex> {
55 Self::new(py, cindex, data, default_header)
55 Self::new(py, cindex, data, default_header)
56 }
56 }
57
57
58 /// Compatibility layer used for Python consumers needing access to the C index
58 /// Compatibility layer used for Python consumers needing access to the C index
59 ///
59 ///
60 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
60 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
61 /// that may need to build a custom `nodetree`, based on a specified revset.
61 /// that may need to build a custom `nodetree`, based on a specified revset.
62 /// With a Rust implementation of the nodemap, we will be able to get rid of
62 /// With a Rust implementation of the nodemap, we will be able to get rid of
63 /// this, by exposing our own standalone nodemap class,
63 /// this, by exposing our own standalone nodemap class,
64 /// ready to accept `MixedIndex`.
64 /// ready to accept `MixedIndex`.
65 def get_cindex(&self) -> PyResult<PyObject> {
65 def get_cindex(&self) -> PyResult<PyObject> {
66 Ok(self.cindex(py).borrow().inner().clone_ref(py))
66 Ok(self.cindex(py).borrow().inner().clone_ref(py))
67 }
67 }
68
68
69 // Index API involving nodemap, as defined in mercurial/pure/parsers.py
69 // Index API involving nodemap, as defined in mercurial/pure/parsers.py
70
70
71 /// Return Revision if found, raises a bare `error.RevlogError`
71 /// Return Revision if found, raises a bare `error.RevlogError`
72 /// in case of ambiguity, same as C version does
72 /// in case of ambiguity, same as C version does
73 def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> {
73 def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> {
74 let opt = self.get_nodetree(py)?.borrow();
74 let opt = self.get_nodetree(py)?.borrow();
75 let nt = opt.as_ref().unwrap();
75 let nt = opt.as_ref().unwrap();
76 let idx = &*self.cindex(py).borrow();
76 let idx = &*self.cindex(py).borrow();
77 let ridx = &*self.index(py).borrow();
77 let ridx = &*self.index(py).borrow();
78 let node = node_from_py_bytes(py, &node)?;
78 let node = node_from_py_bytes(py, &node)?;
79 let rust_rev =
79 let rust_rev =
80 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?;
80 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?;
81 let c_rev =
81 let c_rev =
82 nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e))?;
82 nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e))?;
83 assert_eq!(rust_rev, c_rev);
83 assert_eq!(rust_rev, c_rev);
84 Ok(rust_rev.map(Into::into))
84 Ok(rust_rev.map(Into::into))
85
85
86 }
86 }
87
87
88 /// same as `get_rev()` but raises a bare `error.RevlogError` if node
88 /// same as `get_rev()` but raises a bare `error.RevlogError` if node
89 /// is not found.
89 /// is not found.
90 ///
90 ///
91 /// No need to repeat `node` in the exception, `mercurial/revlog.py`
91 /// No need to repeat `node` in the exception, `mercurial/revlog.py`
92 /// will catch and rewrap with it
92 /// will catch and rewrap with it
93 def rev(&self, node: PyBytes) -> PyResult<PyRevision> {
93 def rev(&self, node: PyBytes) -> PyResult<PyRevision> {
94 self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
94 self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
95 }
95 }
96
96
97 /// return True if the node exist in the index
97 /// return True if the node exist in the index
98 def has_node(&self, node: PyBytes) -> PyResult<bool> {
98 def has_node(&self, node: PyBytes) -> PyResult<bool> {
99 self.get_rev(py, node).map(|opt| opt.is_some())
99 self.get_rev(py, node).map(|opt| opt.is_some())
100 }
100 }
101
101
102 /// find length of shortest hex nodeid of a binary ID
102 /// find length of shortest hex nodeid of a binary ID
103 def shortest(&self, node: PyBytes) -> PyResult<usize> {
103 def shortest(&self, node: PyBytes) -> PyResult<usize> {
104 let opt = self.get_nodetree(py)?.borrow();
104 let opt = self.get_nodetree(py)?.borrow();
105 let nt = opt.as_ref().unwrap();
105 let nt = opt.as_ref().unwrap();
106 let idx = &*self.cindex(py).borrow();
106 let idx = &*self.cindex(py).borrow();
107 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
107 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
108 {
108 {
109 Ok(Some(l)) => Ok(l),
109 Ok(Some(l)) => Ok(l),
110 Ok(None) => Err(revlog_error(py)),
110 Ok(None) => Err(revlog_error(py)),
111 Err(e) => Err(nodemap_error(py, e)),
111 Err(e) => Err(nodemap_error(py, e)),
112 }
112 }
113 }
113 }
114
114
115 def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> {
115 def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> {
116 let opt = self.get_nodetree(py)?.borrow();
116 let opt = self.get_nodetree(py)?.borrow();
117 let nt = opt.as_ref().unwrap();
117 let nt = opt.as_ref().unwrap();
118 let idx = &*self.cindex(py).borrow();
118 let idx = &*self.cindex(py).borrow();
119
119
120 let node_as_string = if cfg!(feature = "python3-sys") {
120 let node_as_string = if cfg!(feature = "python3-sys") {
121 node.cast_as::<PyString>(py)?.to_string(py)?.to_string()
121 node.cast_as::<PyString>(py)?.to_string(py)?.to_string()
122 }
122 }
123 else {
123 else {
124 let node = node.extract::<PyBytes>(py)?;
124 let node = node.extract::<PyBytes>(py)?;
125 String::from_utf8_lossy(node.data(py)).to_string()
125 String::from_utf8_lossy(node.data(py)).to_string()
126 };
126 };
127
127
128 let prefix = NodePrefix::from_hex(&node_as_string)
128 let prefix = NodePrefix::from_hex(&node_as_string)
129 .map_err(|_| PyErr::new::<ValueError, _>(
129 .map_err(|_| PyErr::new::<ValueError, _>(
130 py, format!("Invalid node or prefix '{}'", node_as_string))
130 py, format!("Invalid node or prefix '{}'", node_as_string))
131 )?;
131 )?;
132
132
133 nt.find_bin(idx, prefix)
133 nt.find_bin(idx, prefix)
134 // TODO make an inner API returning the node directly
134 // TODO make an inner API returning the node directly
135 .map(|opt| opt.map(
135 .map(|opt| opt.map(
136 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes())))
136 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes())))
137 .map_err(|e| nodemap_error(py, e))
137 .map_err(|e| nodemap_error(py, e))
138
138
139 }
139 }
140
140
141 /// append an index entry
141 /// append an index entry
142 def append(&self, tup: PyTuple) -> PyResult<PyObject> {
142 def append(&self, tup: PyTuple) -> PyResult<PyObject> {
143 if tup.len(py) < 8 {
143 if tup.len(py) < 8 {
144 // this is better than the panic promised by tup.get_item()
144 // this is better than the panic promised by tup.get_item()
145 return Err(
145 return Err(
146 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
146 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
147 }
147 }
148 let node_bytes = tup.get_item(py, 7).extract(py)?;
148 let node_bytes = tup.get_item(py, 7).extract(py)?;
149 let node = node_from_py_object(py, &node_bytes)?;
149 let node = node_from_py_object(py, &node_bytes)?;
150
150
151 let rev = self.len(py)? as BaseRevision;
151 let rev = self.len(py)? as BaseRevision;
152 let mut idx = self.cindex(py).borrow_mut();
152 let mut idx = self.cindex(py).borrow_mut();
153
153
154 // This is ok since we will just add the revision to the index
154 // This is ok since we will just add the revision to the index
155 let rev = Revision(rev);
155 let rev = Revision(rev);
156 idx.append(py, tup.clone_ref(py))?;
156 idx.append(py, tup.clone_ref(py))?;
157 self.index(py)
157 self.index(py)
158 .borrow_mut()
158 .borrow_mut()
159 .append(py_tuple_to_revision_data_params(py, tup)?)
159 .append(py_tuple_to_revision_data_params(py, tup)?)
160 .unwrap();
160 .unwrap();
161 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
161 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
162 .insert(&*idx, &node, rev)
162 .insert(&*idx, &node, rev)
163 .map_err(|e| nodemap_error(py, e))?;
163 .map_err(|e| nodemap_error(py, e))?;
164 Ok(py.None())
164 Ok(py.None())
165 }
165 }
166
166
167 def __delitem__(&self, key: PyObject) -> PyResult<()> {
167 def __delitem__(&self, key: PyObject) -> PyResult<()> {
168 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
168 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
169 self.cindex(py).borrow().inner().del_item(py, &key)?;
169 self.cindex(py).borrow().inner().del_item(py, &key)?;
170 let start = key.getattr(py, "start")?;
170 let start = key.getattr(py, "start")?;
171 let start = UncheckedRevision(start.extract(py)?);
171 let start = UncheckedRevision(start.extract(py)?);
172 let start = self.index(py)
172 let start = self.index(py)
173 .borrow()
173 .borrow()
174 .check_revision(start)
174 .check_revision(start)
175 .ok_or_else(|| {
175 .ok_or_else(|| {
176 nodemap_error(py, NodeMapError::RevisionNotInIndex(start))
176 nodemap_error(py, NodeMapError::RevisionNotInIndex(start))
177 })?;
177 })?;
178 self.index(py).borrow_mut().remove(start).unwrap();
178 self.index(py).borrow_mut().remove(start).unwrap();
179 let mut opt = self.get_nodetree(py)?.borrow_mut();
179 let mut opt = self.get_nodetree(py)?.borrow_mut();
180 let nt = opt.as_mut().unwrap();
180 let nt = opt.as_mut().unwrap();
181 nt.invalidate_all();
181 nt.invalidate_all();
182 self.fill_nodemap(py, nt)?;
182 self.fill_nodemap(py, nt)?;
183 Ok(())
183 Ok(())
184 }
184 }
185
185
186 //
186 //
187 // Reforwarded C index API
187 // Reforwarded C index API
188 //
188 //
189
189
190 // index_methods (tp_methods). Same ordering as in revlog.c
190 // index_methods (tp_methods). Same ordering as in revlog.c
191
191
192 /// return the gca set of the given revs
192 /// return the gca set of the given revs
193 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
193 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
194 self.call_cindex(py, "ancestors", args, kw)
194 self.call_cindex(py, "ancestors", args, kw)
195 }
195 }
196
196
197 /// return the heads of the common ancestors of the given revs
197 /// return the heads of the common ancestors of the given revs
198 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
198 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
199 self.call_cindex(py, "commonancestorsheads", args, kw)
199 self.call_cindex(py, "commonancestorsheads", args, kw)
200 }
200 }
201
201
202 /// Clear the index caches and inner py_class data.
202 /// Clear the index caches and inner py_class data.
203 /// It is Python's responsibility to call `update_nodemap_data` again.
203 /// It is Python's responsibility to call `update_nodemap_data` again.
204 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
204 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
205 self.nt(py).borrow_mut().take();
205 self.nt(py).borrow_mut().take();
206 self.docket(py).borrow_mut().take();
206 self.docket(py).borrow_mut().take();
207 self.nodemap_mmap(py).borrow_mut().take();
207 self.nodemap_mmap(py).borrow_mut().take();
208 self.index(py).borrow_mut().clear_caches();
208 self.index(py).borrow_mut().clear_caches();
209 self.call_cindex(py, "clearcaches", args, kw)
209 self.call_cindex(py, "clearcaches", args, kw)
210 }
210 }
211
211
212 /// return the raw binary string representing a revision
212 /// return the raw binary string representing a revision
213 def entry_binary(&self, *args, **kw) -> PyResult<PyObject> {
213 def entry_binary(&self, *args, **kw) -> PyResult<PyObject> {
214 self.call_cindex(py, "entry_binary", args, kw)
214 self.call_cindex(py, "entry_binary", args, kw)
215 }
215 }
216
216
217 /// return a binary packed version of the header
217 /// return a binary packed version of the header
218 def pack_header(&self, *args, **kw) -> PyResult<PyObject> {
218 def pack_header(&self, *args, **kw) -> PyResult<PyObject> {
219 let rindex = self.index(py).borrow();
219 let rindex = self.index(py).borrow();
220 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?);
220 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?);
221 let packed = PyBytes::new(py, &packed);
221 let packed = PyBytes::new(py, &packed).into_object();
222 let cpacked = self.call_cindex(py, "pack_header", args, kw)?;
222 let cpacked = self.call_cindex(py, "pack_header", args, kw)?;
223 assert!(packed.as_object().compare(py, cpacked)?.is_eq());
223 assert_py_eq(py, "pack_header", &packed, &cpacked)?;
224 Ok(packed.into_object())
224 Ok(packed)
225 }
225 }
226
226
227 /// get an index entry
227 /// get an index entry
228 def get(&self, *args, **kw) -> PyResult<PyObject> {
228 def get(&self, *args, **kw) -> PyResult<PyObject> {
229 self.call_cindex(py, "get", args, kw)
229 self.call_cindex(py, "get", args, kw)
230 }
230 }
231
231
232 /// compute phases
232 /// compute phases
233 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
233 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
234 self.call_cindex(py, "computephasesmapsets", args, kw)
234 self.call_cindex(py, "computephasesmapsets", args, kw)
235 }
235 }
236
236
237 /// reachableroots
237 /// reachableroots
238 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
238 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
239 self.call_cindex(py, "reachableroots2", args, kw)
239 self.call_cindex(py, "reachableroots2", args, kw)
240 }
240 }
241
241
242 /// get head revisions
242 /// get head revisions
243 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
243 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
244 self.call_cindex(py, "headrevs", args, kw)
244 self.call_cindex(py, "headrevs", args, kw)
245 }
245 }
246
246
247 /// get filtered head revisions
247 /// get filtered head revisions
248 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
248 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
249 self.call_cindex(py, "headrevsfiltered", args, kw)
249 self.call_cindex(py, "headrevsfiltered", args, kw)
250 }
250 }
251
251
252 /// True if the object is a snapshot
252 /// True if the object is a snapshot
253 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
253 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
254 self.call_cindex(py, "issnapshot", args, kw)
254 self.call_cindex(py, "issnapshot", args, kw)
255 }
255 }
256
256
257 /// Gather snapshot data in a cache dict
257 /// Gather snapshot data in a cache dict
258 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
258 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
259 self.call_cindex(py, "findsnapshots", args, kw)
259 self.call_cindex(py, "findsnapshots", args, kw)
260 }
260 }
261
261
262 /// determine revisions with deltas to reconstruct fulltext
262 /// determine revisions with deltas to reconstruct fulltext
263 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
263 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
264 self.call_cindex(py, "deltachain", args, kw)
264 self.call_cindex(py, "deltachain", args, kw)
265 }
265 }
266
266
267 /// slice planned chunk read to reach a density threshold
267 /// slice planned chunk read to reach a density threshold
268 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
268 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
269 self.call_cindex(py, "slicechunktodensity", args, kw)
269 self.call_cindex(py, "slicechunktodensity", args, kw)
270 }
270 }
271
271
272 /// stats for the index
272 /// stats for the index
273 def stats(&self, *args, **kw) -> PyResult<PyObject> {
273 def stats(&self, *args, **kw) -> PyResult<PyObject> {
274 self.call_cindex(py, "stats", args, kw)
274 self.call_cindex(py, "stats", args, kw)
275 }
275 }
276
276
277 // index_sequence_methods and index_mapping_methods.
277 // index_sequence_methods and index_mapping_methods.
278 //
278 //
279 // Since we call back through the high level Python API,
279 // Since we call back through the high level Python API,
280 // there's no point making a distinction between index_get
280 // there's no point making a distinction between index_get
281 // and index_getitem.
281 // and index_getitem.
282
282
283 def __len__(&self) -> PyResult<usize> {
283 def __len__(&self) -> PyResult<usize> {
284 self.len(py)
284 self.len(py)
285 }
285 }
286
286
287 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
287 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
288 // this conversion seems needless, but that's actually because
288 // this conversion seems needless, but that's actually because
289 // `index_getitem` does not handle conversion from PyLong,
289 // `index_getitem` does not handle conversion from PyLong,
290 // which expressions such as [e for e in index] internally use.
290 // which expressions such as [e for e in index] internally use.
291 // Note that we don't seem to have a direct way to call
291 // Note that we don't seem to have a direct way to call
292 // PySequence_GetItem (does the job), which would possibly be better
292 // PySequence_GetItem (does the job), which would possibly be better
293 // for performance
293 // for performance
294 let key = match key.extract::<i32>(py) {
294 let key = match key.extract::<i32>(py) {
295 Ok(rev) => rev.to_py_object(py).into_object(),
295 Ok(rev) => rev.to_py_object(py).into_object(),
296 Err(_) => key,
296 Err(_) => key,
297 };
297 };
298 self.cindex(py).borrow().inner().get_item(py, key)
298 self.cindex(py).borrow().inner().get_item(py, key)
299 }
299 }
300
300
301 def __contains__(&self, item: PyObject) -> PyResult<bool> {
301 def __contains__(&self, item: PyObject) -> PyResult<bool> {
302 // ObjectProtocol does not seem to provide contains(), so
302 // ObjectProtocol does not seem to provide contains(), so
303 // this is an equivalent implementation of the index_contains()
303 // this is an equivalent implementation of the index_contains()
304 // defined in revlog.c
304 // defined in revlog.c
305 let cindex = self.cindex(py).borrow();
305 let cindex = self.cindex(py).borrow();
306 match item.extract::<i32>(py) {
306 match item.extract::<i32>(py) {
307 Ok(rev) => {
307 Ok(rev) => {
308 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision)
308 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision)
309 }
309 }
310 Err(_) => {
310 Err(_) => {
311 cindex.inner().call_method(
311 cindex.inner().call_method(
312 py,
312 py,
313 "has_node",
313 "has_node",
314 PyTuple::new(py, &[item]),
314 PyTuple::new(py, &[item]),
315 None)?
315 None)?
316 .extract(py)
316 .extract(py)
317 }
317 }
318 }
318 }
319 }
319 }
320
320
321 def nodemap_data_all(&self) -> PyResult<PyBytes> {
321 def nodemap_data_all(&self) -> PyResult<PyBytes> {
322 self.inner_nodemap_data_all(py)
322 self.inner_nodemap_data_all(py)
323 }
323 }
324
324
325 def nodemap_data_incremental(&self) -> PyResult<PyObject> {
325 def nodemap_data_incremental(&self) -> PyResult<PyObject> {
326 self.inner_nodemap_data_incremental(py)
326 self.inner_nodemap_data_incremental(py)
327 }
327 }
328 def update_nodemap_data(
328 def update_nodemap_data(
329 &self,
329 &self,
330 docket: PyObject,
330 docket: PyObject,
331 nm_data: PyObject
331 nm_data: PyObject
332 ) -> PyResult<PyObject> {
332 ) -> PyResult<PyObject> {
333 self.inner_update_nodemap_data(py, docket, nm_data)
333 self.inner_update_nodemap_data(py, docket, nm_data)
334 }
334 }
335
335
336 @property
336 @property
337 def entry_size(&self) -> PyResult<PyInt> {
337 def entry_size(&self) -> PyResult<PyInt> {
338 self.cindex(py).borrow().inner().getattr(py, "entry_size")?.extract::<PyInt>(py)
338 self.cindex(py).borrow().inner().getattr(py, "entry_size")?.extract::<PyInt>(py)
339 }
339 }
340
340
341 @property
341 @property
342 def rust_ext_compat(&self) -> PyResult<PyInt> {
342 def rust_ext_compat(&self) -> PyResult<PyInt> {
343 self.cindex(py).borrow().inner().getattr(py, "rust_ext_compat")?.extract::<PyInt>(py)
343 self.cindex(py).borrow().inner().getattr(py, "rust_ext_compat")?.extract::<PyInt>(py)
344 }
344 }
345
345
346 });
346 });
347
347
348 /// Take a (potentially) mmap'ed buffer, and return the underlying Python
348 /// Take a (potentially) mmap'ed buffer, and return the underlying Python
349 /// buffer along with the Rust slice into said buffer. We need to keep the
349 /// buffer along with the Rust slice into said buffer. We need to keep the
350 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer
350 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer
351 /// is freed from Python's side.
351 /// is freed from Python's side.
352 ///
352 ///
353 /// # Safety
353 /// # Safety
354 ///
354 ///
355 /// The caller must make sure that the buffer is kept around for at least as
355 /// The caller must make sure that the buffer is kept around for at least as
356 /// long as the slice.
356 /// long as the slice.
357 #[deny(unsafe_op_in_unsafe_fn)]
357 #[deny(unsafe_op_in_unsafe_fn)]
358 unsafe fn mmap_keeparound(
358 unsafe fn mmap_keeparound(
359 py: Python,
359 py: Python,
360 data: PyObject,
360 data: PyObject,
361 ) -> PyResult<(
361 ) -> PyResult<(
362 PyBuffer,
362 PyBuffer,
363 Box<dyn std::ops::Deref<Target = [u8]> + Send + 'static>,
363 Box<dyn std::ops::Deref<Target = [u8]> + Send + 'static>,
364 )> {
364 )> {
365 let buf = PyBuffer::get(py, &data)?;
365 let buf = PyBuffer::get(py, &data)?;
366 let len = buf.item_count();
366 let len = buf.item_count();
367
367
368 // Build a slice from the mmap'ed buffer data
368 // Build a slice from the mmap'ed buffer data
369 let cbuf = buf.buf_ptr();
369 let cbuf = buf.buf_ptr();
370 let bytes = if std::mem::size_of::<u8>() == buf.item_size()
370 let bytes = if std::mem::size_of::<u8>() == buf.item_size()
371 && buf.is_c_contiguous()
371 && buf.is_c_contiguous()
372 && u8::is_compatible_format(buf.format())
372 && u8::is_compatible_format(buf.format())
373 {
373 {
374 unsafe { std::slice::from_raw_parts(cbuf as *const u8, len) }
374 unsafe { std::slice::from_raw_parts(cbuf as *const u8, len) }
375 } else {
375 } else {
376 return Err(PyErr::new::<ValueError, _>(
376 return Err(PyErr::new::<ValueError, _>(
377 py,
377 py,
378 "Nodemap data buffer has an invalid memory representation"
378 "Nodemap data buffer has an invalid memory representation"
379 .to_string(),
379 .to_string(),
380 ));
380 ));
381 };
381 };
382
382
383 Ok((buf, Box::new(bytes)))
383 Ok((buf, Box::new(bytes)))
384 }
384 }
385
385
386 fn py_tuple_to_revision_data_params(
386 fn py_tuple_to_revision_data_params(
387 py: Python,
387 py: Python,
388 tuple: PyTuple,
388 tuple: PyTuple,
389 ) -> PyResult<RevisionDataParams> {
389 ) -> PyResult<RevisionDataParams> {
390 if tuple.len(py) < 8 {
390 if tuple.len(py) < 8 {
391 // this is better than the panic promised by tup.get_item()
391 // this is better than the panic promised by tup.get_item()
392 return Err(PyErr::new::<IndexError, _>(
392 return Err(PyErr::new::<IndexError, _>(
393 py,
393 py,
394 "tuple index out of range",
394 "tuple index out of range",
395 ));
395 ));
396 }
396 }
397 let offset_or_flags: u64 = tuple.get_item(py, 0).extract(py)?;
397 let offset_or_flags: u64 = tuple.get_item(py, 0).extract(py)?;
398 let node_id = tuple
398 let node_id = tuple
399 .get_item(py, 7)
399 .get_item(py, 7)
400 .extract::<PyBytes>(py)?
400 .extract::<PyBytes>(py)?
401 .data(py)
401 .data(py)
402 .try_into()
402 .try_into()
403 .unwrap();
403 .unwrap();
404 let flags = (offset_or_flags & 0xFFFF) as u16;
404 let flags = (offset_or_flags & 0xFFFF) as u16;
405 let data_offset = offset_or_flags >> 16;
405 let data_offset = offset_or_flags >> 16;
406 Ok(RevisionDataParams {
406 Ok(RevisionDataParams {
407 flags,
407 flags,
408 data_offset,
408 data_offset,
409 data_compressed_length: tuple.get_item(py, 1).extract(py)?,
409 data_compressed_length: tuple.get_item(py, 1).extract(py)?,
410 data_uncompressed_length: tuple.get_item(py, 2).extract(py)?,
410 data_uncompressed_length: tuple.get_item(py, 2).extract(py)?,
411 data_delta_base: tuple.get_item(py, 3).extract(py)?,
411 data_delta_base: tuple.get_item(py, 3).extract(py)?,
412 link_rev: tuple.get_item(py, 4).extract(py)?,
412 link_rev: tuple.get_item(py, 4).extract(py)?,
413 parent_rev_1: tuple.get_item(py, 5).extract(py)?,
413 parent_rev_1: tuple.get_item(py, 5).extract(py)?,
414 parent_rev_2: tuple.get_item(py, 6).extract(py)?,
414 parent_rev_2: tuple.get_item(py, 6).extract(py)?,
415 node_id,
415 node_id,
416 _sidedata_offset: 0,
416 _sidedata_offset: 0,
417 _sidedata_compressed_length: 0,
417 _sidedata_compressed_length: 0,
418 data_compression_mode: COMPRESSION_MODE_INLINE,
418 data_compression_mode: COMPRESSION_MODE_INLINE,
419 _sidedata_compression_mode: COMPRESSION_MODE_INLINE,
419 _sidedata_compression_mode: COMPRESSION_MODE_INLINE,
420 _rank: -1,
420 _rank: -1,
421 })
421 })
422 }
422 }
423
423
424 impl MixedIndex {
424 impl MixedIndex {
425 fn new(
425 fn new(
426 py: Python,
426 py: Python,
427 cindex: PyObject,
427 cindex: PyObject,
428 data: PyObject,
428 data: PyObject,
429 header: u32,
429 header: u32,
430 ) -> PyResult<MixedIndex> {
430 ) -> PyResult<MixedIndex> {
431 // Safety: we keep the buffer around inside the class as `index_mmap`
431 // Safety: we keep the buffer around inside the class as `index_mmap`
432 let (buf, bytes) = unsafe { mmap_keeparound(py, data)? };
432 let (buf, bytes) = unsafe { mmap_keeparound(py, data)? };
433
433
434 Self::create_instance(
434 Self::create_instance(
435 py,
435 py,
436 RefCell::new(cindex::Index::new(py, cindex)?),
436 RefCell::new(cindex::Index::new(py, cindex)?),
437 RefCell::new(
437 RefCell::new(
438 hg::index::Index::new(
438 hg::index::Index::new(
439 bytes,
439 bytes,
440 IndexHeader::parse(&header.to_be_bytes())
440 IndexHeader::parse(&header.to_be_bytes())
441 .expect("default header is broken")
441 .expect("default header is broken")
442 .unwrap(),
442 .unwrap(),
443 )
443 )
444 .unwrap(),
444 .unwrap(),
445 ),
445 ),
446 RefCell::new(None),
446 RefCell::new(None),
447 RefCell::new(None),
447 RefCell::new(None),
448 RefCell::new(None),
448 RefCell::new(None),
449 RefCell::new(Some(buf)),
449 RefCell::new(Some(buf)),
450 )
450 )
451 }
451 }
452
452
453 fn len(&self, py: Python) -> PyResult<usize> {
453 fn len(&self, py: Python) -> PyResult<usize> {
454 let rust_index_len = self.index(py).borrow().len();
454 let rust_index_len = self.index(py).borrow().len();
455 let cindex_len = self.cindex(py).borrow().inner().len(py)?;
455 let cindex_len = self.cindex(py).borrow().inner().len(py)?;
456 assert_eq!(rust_index_len, cindex_len);
456 assert_eq!(rust_index_len, cindex_len);
457 Ok(cindex_len)
457 Ok(cindex_len)
458 }
458 }
459
459
460 /// This is scaffolding at this point, but it could also become
460 /// This is scaffolding at this point, but it could also become
461 /// a way to start a persistent nodemap or perform a
461 /// a way to start a persistent nodemap or perform a
462 /// vacuum / repack operation
462 /// vacuum / repack operation
463 fn fill_nodemap(
463 fn fill_nodemap(
464 &self,
464 &self,
465 py: Python,
465 py: Python,
466 nt: &mut NodeTree,
466 nt: &mut NodeTree,
467 ) -> PyResult<PyObject> {
467 ) -> PyResult<PyObject> {
468 let index = self.cindex(py).borrow();
468 let index = self.cindex(py).borrow();
469 for r in 0..self.len(py)? {
469 for r in 0..self.len(py)? {
470 let rev = Revision(r as BaseRevision);
470 let rev = Revision(r as BaseRevision);
471 // in this case node() won't ever return None
471 // in this case node() won't ever return None
472 nt.insert(&*index, index.node(rev).unwrap(), rev)
472 nt.insert(&*index, index.node(rev).unwrap(), rev)
473 .map_err(|e| nodemap_error(py, e))?
473 .map_err(|e| nodemap_error(py, e))?
474 }
474 }
475 Ok(py.None())
475 Ok(py.None())
476 }
476 }
477
477
478 fn get_nodetree<'a>(
478 fn get_nodetree<'a>(
479 &'a self,
479 &'a self,
480 py: Python<'a>,
480 py: Python<'a>,
481 ) -> PyResult<&'a RefCell<Option<NodeTree>>> {
481 ) -> PyResult<&'a RefCell<Option<NodeTree>>> {
482 if self.nt(py).borrow().is_none() {
482 if self.nt(py).borrow().is_none() {
483 let readonly = Box::<Vec<_>>::default();
483 let readonly = Box::<Vec<_>>::default();
484 let mut nt = NodeTree::load_bytes(readonly, 0);
484 let mut nt = NodeTree::load_bytes(readonly, 0);
485 self.fill_nodemap(py, &mut nt)?;
485 self.fill_nodemap(py, &mut nt)?;
486 self.nt(py).borrow_mut().replace(nt);
486 self.nt(py).borrow_mut().replace(nt);
487 }
487 }
488 Ok(self.nt(py))
488 Ok(self.nt(py))
489 }
489 }
490
490
491 /// forward a method call to the underlying C index
491 /// forward a method call to the underlying C index
492 fn call_cindex(
492 fn call_cindex(
493 &self,
493 &self,
494 py: Python,
494 py: Python,
495 name: &str,
495 name: &str,
496 args: &PyTuple,
496 args: &PyTuple,
497 kwargs: Option<&PyDict>,
497 kwargs: Option<&PyDict>,
498 ) -> PyResult<PyObject> {
498 ) -> PyResult<PyObject> {
499 self.cindex(py)
499 self.cindex(py)
500 .borrow()
500 .borrow()
501 .inner()
501 .inner()
502 .call_method(py, name, args, kwargs)
502 .call_method(py, name, args, kwargs)
503 }
503 }
504
504
505 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
505 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
506 self.cindex(py).borrow().clone_ref(py)
506 self.cindex(py).borrow().clone_ref(py)
507 }
507 }
508
508
509 /// Returns the full nodemap bytes to be written as-is to disk
509 /// Returns the full nodemap bytes to be written as-is to disk
510 fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
510 fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
511 let nodemap = self.get_nodetree(py)?.borrow_mut().take().unwrap();
511 let nodemap = self.get_nodetree(py)?.borrow_mut().take().unwrap();
512 let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
512 let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
513
513
514 // If there's anything readonly, we need to build the data again from
514 // If there's anything readonly, we need to build the data again from
515 // scratch
515 // scratch
516 let bytes = if readonly.len() > 0 {
516 let bytes = if readonly.len() > 0 {
517 let mut nt = NodeTree::load_bytes(Box::<Vec<_>>::default(), 0);
517 let mut nt = NodeTree::load_bytes(Box::<Vec<_>>::default(), 0);
518 self.fill_nodemap(py, &mut nt)?;
518 self.fill_nodemap(py, &mut nt)?;
519
519
520 let (readonly, bytes) = nt.into_readonly_and_added_bytes();
520 let (readonly, bytes) = nt.into_readonly_and_added_bytes();
521 assert_eq!(readonly.len(), 0);
521 assert_eq!(readonly.len(), 0);
522
522
523 bytes
523 bytes
524 } else {
524 } else {
525 bytes
525 bytes
526 };
526 };
527
527
528 let bytes = PyBytes::new(py, &bytes);
528 let bytes = PyBytes::new(py, &bytes);
529 Ok(bytes)
529 Ok(bytes)
530 }
530 }
531
531
532 /// Returns the last saved docket along with the size of any changed data
532 /// Returns the last saved docket along with the size of any changed data
533 /// (in number of blocks), and said data as bytes.
533 /// (in number of blocks), and said data as bytes.
534 fn inner_nodemap_data_incremental(
534 fn inner_nodemap_data_incremental(
535 &self,
535 &self,
536 py: Python,
536 py: Python,
537 ) -> PyResult<PyObject> {
537 ) -> PyResult<PyObject> {
538 let docket = self.docket(py).borrow();
538 let docket = self.docket(py).borrow();
539 let docket = match docket.as_ref() {
539 let docket = match docket.as_ref() {
540 Some(d) => d,
540 Some(d) => d,
541 None => return Ok(py.None()),
541 None => return Ok(py.None()),
542 };
542 };
543
543
544 let node_tree = self.get_nodetree(py)?.borrow_mut().take().unwrap();
544 let node_tree = self.get_nodetree(py)?.borrow_mut().take().unwrap();
545 let masked_blocks = node_tree.masked_readonly_blocks();
545 let masked_blocks = node_tree.masked_readonly_blocks();
546 let (_, data) = node_tree.into_readonly_and_added_bytes();
546 let (_, data) = node_tree.into_readonly_and_added_bytes();
547 let changed = masked_blocks * std::mem::size_of::<Block>();
547 let changed = masked_blocks * std::mem::size_of::<Block>();
548
548
549 Ok((docket, changed, PyBytes::new(py, &data))
549 Ok((docket, changed, PyBytes::new(py, &data))
550 .to_py_object(py)
550 .to_py_object(py)
551 .into_object())
551 .into_object())
552 }
552 }
553
553
554 /// Update the nodemap from the new (mmaped) data.
554 /// Update the nodemap from the new (mmaped) data.
555 /// The docket is kept as a reference for later incremental calls.
555 /// The docket is kept as a reference for later incremental calls.
556 fn inner_update_nodemap_data(
556 fn inner_update_nodemap_data(
557 &self,
557 &self,
558 py: Python,
558 py: Python,
559 docket: PyObject,
559 docket: PyObject,
560 nm_data: PyObject,
560 nm_data: PyObject,
561 ) -> PyResult<PyObject> {
561 ) -> PyResult<PyObject> {
562 // Safety: we keep the buffer around inside the class as `nodemap_mmap`
562 // Safety: we keep the buffer around inside the class as `nodemap_mmap`
563 let (buf, bytes) = unsafe { mmap_keeparound(py, nm_data)? };
563 let (buf, bytes) = unsafe { mmap_keeparound(py, nm_data)? };
564 let len = buf.item_count();
564 let len = buf.item_count();
565 self.nodemap_mmap(py).borrow_mut().replace(buf);
565 self.nodemap_mmap(py).borrow_mut().replace(buf);
566
566
567 let mut nt = NodeTree::load_bytes(bytes, len);
567 let mut nt = NodeTree::load_bytes(bytes, len);
568
568
569 let data_tip = docket
569 let data_tip = docket
570 .getattr(py, "tip_rev")?
570 .getattr(py, "tip_rev")?
571 .extract::<BaseRevision>(py)?
571 .extract::<BaseRevision>(py)?
572 .into();
572 .into();
573 self.docket(py).borrow_mut().replace(docket.clone_ref(py));
573 self.docket(py).borrow_mut().replace(docket.clone_ref(py));
574 let idx = self.cindex(py).borrow();
574 let idx = self.cindex(py).borrow();
575 let data_tip = idx.check_revision(data_tip).ok_or_else(|| {
575 let data_tip = idx.check_revision(data_tip).ok_or_else(|| {
576 nodemap_error(py, NodeMapError::RevisionNotInIndex(data_tip))
576 nodemap_error(py, NodeMapError::RevisionNotInIndex(data_tip))
577 })?;
577 })?;
578 let current_tip = idx.len();
578 let current_tip = idx.len();
579
579
580 for r in (data_tip.0 + 1)..current_tip as BaseRevision {
580 for r in (data_tip.0 + 1)..current_tip as BaseRevision {
581 let rev = Revision(r);
581 let rev = Revision(r);
582 // in this case node() won't ever return None
582 // in this case node() won't ever return None
583 nt.insert(&*idx, idx.node(rev).unwrap(), rev)
583 nt.insert(&*idx, idx.node(rev).unwrap(), rev)
584 .map_err(|e| nodemap_error(py, e))?
584 .map_err(|e| nodemap_error(py, e))?
585 }
585 }
586
586
587 *self.nt(py).borrow_mut() = Some(nt);
587 *self.nt(py).borrow_mut() = Some(nt);
588
588
589 Ok(py.None())
589 Ok(py.None())
590 }
590 }
591 }
591 }
592
592
593 fn revlog_error(py: Python) -> PyErr {
593 fn revlog_error(py: Python) -> PyErr {
594 match py
594 match py
595 .import("mercurial.error")
595 .import("mercurial.error")
596 .and_then(|m| m.get(py, "RevlogError"))
596 .and_then(|m| m.get(py, "RevlogError"))
597 {
597 {
598 Err(e) => e,
598 Err(e) => e,
599 Ok(cls) => PyErr::from_instance(
599 Ok(cls) => PyErr::from_instance(
600 py,
600 py,
601 cls.call(py, (py.None(),), None).ok().into_py_object(py),
601 cls.call(py, (py.None(),), None).ok().into_py_object(py),
602 ),
602 ),
603 }
603 }
604 }
604 }
605
605
606 fn nodemap_rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
606 fn nodemap_rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
607 PyErr::new::<ValueError, _>(
607 PyErr::new::<ValueError, _>(
608 py,
608 py,
609 format!(
609 format!(
610 "Inconsistency: Revision {} found in nodemap \
610 "Inconsistency: Revision {} found in nodemap \
611 is not in revlog index",
611 is not in revlog index",
612 rev
612 rev
613 ),
613 ),
614 )
614 )
615 }
615 }
616
616
617 #[allow(dead_code)]
617 #[allow(dead_code)]
618 fn rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
618 fn rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
619 PyErr::new::<ValueError, _>(
619 PyErr::new::<ValueError, _>(
620 py,
620 py,
621 format!("revlog index out of range: {}", rev),
621 format!("revlog index out of range: {}", rev),
622 )
622 )
623 }
623 }
624
624
625 /// Standard treatment of NodeMapError
625 /// Standard treatment of NodeMapError
626 fn nodemap_error(py: Python, err: NodeMapError) -> PyErr {
626 fn nodemap_error(py: Python, err: NodeMapError) -> PyErr {
627 match err {
627 match err {
628 NodeMapError::MultipleResults => revlog_error(py),
628 NodeMapError::MultipleResults => revlog_error(py),
629 NodeMapError::RevisionNotInIndex(r) => nodemap_rev_not_in_index(py, r),
629 NodeMapError::RevisionNotInIndex(r) => nodemap_rev_not_in_index(py, r),
630 }
630 }
631 }
631 }
632
632
633 fn assert_py_eq(
634 py: Python,
635 method: &str,
636 rust: &PyObject,
637 c: &PyObject,
638 ) -> PyResult<()> {
639 let locals = PyDict::new(py);
640 locals.set_item(py, "rust".into_py_object(py).into_object(), rust)?;
641 locals.set_item(py, "c".into_py_object(py).into_object(), c)?;
642 let is_eq: PyBool =
643 py.eval("rust == c", None, Some(&locals))?.extract(py)?;
644 assert!(
645 is_eq.is_true(),
646 "{} results differ. Rust: {:?} C: {:?}",
647 method,
648 rust,
649 c
650 );
651 Ok(())
652 }
653
633 /// Create the module, with __package__ given from parent
654 /// Create the module, with __package__ given from parent
634 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
655 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
635 let dotted_name = &format!("{}.revlog", package);
656 let dotted_name = &format!("{}.revlog", package);
636 let m = PyModule::new(py, dotted_name)?;
657 let m = PyModule::new(py, dotted_name)?;
637 m.add(py, "__package__", package)?;
658 m.add(py, "__package__", package)?;
638 m.add(py, "__doc__", "RevLog - Rust implementations")?;
659 m.add(py, "__doc__", "RevLog - Rust implementations")?;
639
660
640 m.add_class::<MixedIndex>(py)?;
661 m.add_class::<MixedIndex>(py)?;
641
662
642 let sys = PyModule::import(py, "sys")?;
663 let sys = PyModule::import(py, "sys")?;
643 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
664 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
644 sys_modules.set_item(py, dotted_name, &m)?;
665 sys_modules.set_item(py, dotted_name, &m)?;
645
666
646 Ok(m)
667 Ok(m)
647 }
668 }
General Comments 0
You need to be logged in to leave comments. Login now