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