##// END OF EJS Templates
rust-index: headrevsfiltered() returning Rust result
Georges Racinet -
r52110:898674a4 default
parent child Browse files
Show More
@@ -1,934 +1,928 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 conversion::rev_pyiter_collect,
10 conversion::rev_pyiter_collect,
11 exceptions::GraphError,
12 utils::{node_from_py_bytes, node_from_py_object},
11 utils::{node_from_py_bytes, node_from_py_object},
13 PyRevision,
12 PyRevision,
14 };
13 };
15 use cpython::{
14 use cpython::{
16 buffer::{Element, PyBuffer},
15 buffer::{Element, PyBuffer},
17 exc::{IndexError, ValueError},
16 exc::{IndexError, ValueError},
18 ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyInt, PyList,
17 ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyInt, PyList,
19 PyModule, PyObject, PyResult, PySet, PyString, PyTuple, Python,
18 PyModule, PyObject, PyResult, PySet, PyString, PyTuple, Python,
20 PythonObject, ToPyObject,
19 PythonObject, ToPyObject,
21 };
20 };
22 use hg::{
21 use hg::{
23 errors::HgError,
22 errors::HgError,
24 index::{IndexHeader, RevisionDataParams, SnapshotsCache},
23 index::{IndexHeader, RevisionDataParams, SnapshotsCache},
25 nodemap::{Block, NodeMapError, NodeTree},
24 nodemap::{Block, NodeMapError, NodeTree},
26 revlog::{nodemap::NodeMap, NodePrefix, RevlogError, RevlogIndex},
25 revlog::{nodemap::NodeMap, NodePrefix, RevlogError, RevlogIndex},
27 BaseRevision, Revision, UncheckedRevision, NULL_REVISION,
26 BaseRevision, Revision, UncheckedRevision, NULL_REVISION,
28 };
27 };
29 use std::cell::RefCell;
28 use std::cell::RefCell;
30
29
31 /// Return a Struct implementing the Graph trait
30 /// Return a Struct implementing the Graph trait
32 pub(crate) fn pyindex_to_graph(
31 pub(crate) fn pyindex_to_graph(
33 py: Python,
32 py: Python,
34 index: PyObject,
33 index: PyObject,
35 ) -> PyResult<cindex::Index> {
34 ) -> PyResult<cindex::Index> {
36 match index.extract::<MixedIndex>(py) {
35 match index.extract::<MixedIndex>(py) {
37 Ok(midx) => Ok(midx.clone_cindex(py)),
36 Ok(midx) => Ok(midx.clone_cindex(py)),
38 Err(_) => cindex::Index::new(py, index),
37 Err(_) => cindex::Index::new(py, index),
39 }
38 }
40 }
39 }
41
40
42 py_class!(pub class MixedIndex |py| {
41 py_class!(pub class MixedIndex |py| {
43 data cindex: RefCell<cindex::Index>;
42 data cindex: RefCell<cindex::Index>;
44 data index: RefCell<hg::index::Index>;
43 data index: RefCell<hg::index::Index>;
45 data nt: RefCell<Option<NodeTree>>;
44 data nt: RefCell<Option<NodeTree>>;
46 data docket: RefCell<Option<PyObject>>;
45 data docket: RefCell<Option<PyObject>>;
47 // Holds a reference to the mmap'ed persistent nodemap data
46 // Holds a reference to the mmap'ed persistent nodemap data
48 data nodemap_mmap: RefCell<Option<PyBuffer>>;
47 data nodemap_mmap: RefCell<Option<PyBuffer>>;
49 // Holds a reference to the mmap'ed persistent index data
48 // Holds a reference to the mmap'ed persistent index data
50 data index_mmap: RefCell<Option<PyBuffer>>;
49 data index_mmap: RefCell<Option<PyBuffer>>;
51
50
52 def __new__(
51 def __new__(
53 _cls,
52 _cls,
54 cindex: PyObject,
53 cindex: PyObject,
55 data: PyObject,
54 data: PyObject,
56 default_header: u32,
55 default_header: u32,
57 ) -> PyResult<MixedIndex> {
56 ) -> PyResult<MixedIndex> {
58 Self::new(py, cindex, data, default_header)
57 Self::new(py, cindex, data, default_header)
59 }
58 }
60
59
61 /// Compatibility layer used for Python consumers needing access to the C index
60 /// Compatibility layer used for Python consumers needing access to the C index
62 ///
61 ///
63 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
62 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
64 /// that may need to build a custom `nodetree`, based on a specified revset.
63 /// that may need to build a custom `nodetree`, based on a specified revset.
65 /// With a Rust implementation of the nodemap, we will be able to get rid of
64 /// With a Rust implementation of the nodemap, we will be able to get rid of
66 /// this, by exposing our own standalone nodemap class,
65 /// this, by exposing our own standalone nodemap class,
67 /// ready to accept `MixedIndex`.
66 /// ready to accept `MixedIndex`.
68 def get_cindex(&self) -> PyResult<PyObject> {
67 def get_cindex(&self) -> PyResult<PyObject> {
69 Ok(self.cindex(py).borrow().inner().clone_ref(py))
68 Ok(self.cindex(py).borrow().inner().clone_ref(py))
70 }
69 }
71
70
72 // Index API involving nodemap, as defined in mercurial/pure/parsers.py
71 // Index API involving nodemap, as defined in mercurial/pure/parsers.py
73
72
74 /// Return Revision if found, raises a bare `error.RevlogError`
73 /// Return Revision if found, raises a bare `error.RevlogError`
75 /// in case of ambiguity, same as C version does
74 /// in case of ambiguity, same as C version does
76 def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> {
75 def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> {
77 let opt = self.get_nodetree(py)?.borrow();
76 let opt = self.get_nodetree(py)?.borrow();
78 let nt = opt.as_ref().unwrap();
77 let nt = opt.as_ref().unwrap();
79 let idx = &*self.cindex(py).borrow();
78 let idx = &*self.cindex(py).borrow();
80 let ridx = &*self.index(py).borrow();
79 let ridx = &*self.index(py).borrow();
81 let node = node_from_py_bytes(py, &node)?;
80 let node = node_from_py_bytes(py, &node)?;
82 let rust_rev =
81 let rust_rev =
83 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?;
82 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?;
84 let c_rev =
83 let c_rev =
85 nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e))?;
84 nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e))?;
86 assert_eq!(rust_rev, c_rev);
85 assert_eq!(rust_rev, c_rev);
87 Ok(rust_rev.map(Into::into))
86 Ok(rust_rev.map(Into::into))
88
87
89 }
88 }
90
89
91 /// same as `get_rev()` but raises a bare `error.RevlogError` if node
90 /// same as `get_rev()` but raises a bare `error.RevlogError` if node
92 /// is not found.
91 /// is not found.
93 ///
92 ///
94 /// No need to repeat `node` in the exception, `mercurial/revlog.py`
93 /// No need to repeat `node` in the exception, `mercurial/revlog.py`
95 /// will catch and rewrap with it
94 /// will catch and rewrap with it
96 def rev(&self, node: PyBytes) -> PyResult<PyRevision> {
95 def rev(&self, node: PyBytes) -> PyResult<PyRevision> {
97 self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
96 self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
98 }
97 }
99
98
100 /// return True if the node exist in the index
99 /// return True if the node exist in the index
101 def has_node(&self, node: PyBytes) -> PyResult<bool> {
100 def has_node(&self, node: PyBytes) -> PyResult<bool> {
102 // TODO OPTIM we could avoid a needless conversion here,
101 // TODO OPTIM we could avoid a needless conversion here,
103 // to do when scaffolding for pure Rust switch is removed,
102 // to do when scaffolding for pure Rust switch is removed,
104 // as `get_rev()` currently does the necessary assertions
103 // as `get_rev()` currently does the necessary assertions
105 self.get_rev(py, node).map(|opt| opt.is_some())
104 self.get_rev(py, node).map(|opt| opt.is_some())
106 }
105 }
107
106
108 /// find length of shortest hex nodeid of a binary ID
107 /// find length of shortest hex nodeid of a binary ID
109 def shortest(&self, node: PyBytes) -> PyResult<usize> {
108 def shortest(&self, node: PyBytes) -> PyResult<usize> {
110 let opt = self.get_nodetree(py)?.borrow();
109 let opt = self.get_nodetree(py)?.borrow();
111 let nt = opt.as_ref().unwrap();
110 let nt = opt.as_ref().unwrap();
112 let idx = &*self.index(py).borrow();
111 let idx = &*self.index(py).borrow();
113 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
112 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
114 {
113 {
115 Ok(Some(l)) => Ok(l),
114 Ok(Some(l)) => Ok(l),
116 Ok(None) => Err(revlog_error(py)),
115 Ok(None) => Err(revlog_error(py)),
117 Err(e) => Err(nodemap_error(py, e)),
116 Err(e) => Err(nodemap_error(py, e)),
118 }
117 }
119 }
118 }
120
119
121 def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> {
120 def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> {
122 let opt = self.get_nodetree(py)?.borrow();
121 let opt = self.get_nodetree(py)?.borrow();
123 let nt = opt.as_ref().unwrap();
122 let nt = opt.as_ref().unwrap();
124 let idx = &*self.index(py).borrow();
123 let idx = &*self.index(py).borrow();
125
124
126 let node_as_string = if cfg!(feature = "python3-sys") {
125 let node_as_string = if cfg!(feature = "python3-sys") {
127 node.cast_as::<PyString>(py)?.to_string(py)?.to_string()
126 node.cast_as::<PyString>(py)?.to_string(py)?.to_string()
128 }
127 }
129 else {
128 else {
130 let node = node.extract::<PyBytes>(py)?;
129 let node = node.extract::<PyBytes>(py)?;
131 String::from_utf8_lossy(node.data(py)).to_string()
130 String::from_utf8_lossy(node.data(py)).to_string()
132 };
131 };
133
132
134 let prefix = NodePrefix::from_hex(&node_as_string)
133 let prefix = NodePrefix::from_hex(&node_as_string)
135 .map_err(|_| PyErr::new::<ValueError, _>(
134 .map_err(|_| PyErr::new::<ValueError, _>(
136 py, format!("Invalid node or prefix '{}'", node_as_string))
135 py, format!("Invalid node or prefix '{}'", node_as_string))
137 )?;
136 )?;
138
137
139 nt.find_bin(idx, prefix)
138 nt.find_bin(idx, prefix)
140 // TODO make an inner API returning the node directly
139 // TODO make an inner API returning the node directly
141 .map(|opt| opt.map(
140 .map(|opt| opt.map(
142 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes())))
141 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes())))
143 .map_err(|e| nodemap_error(py, e))
142 .map_err(|e| nodemap_error(py, e))
144
143
145 }
144 }
146
145
147 /// append an index entry
146 /// append an index entry
148 def append(&self, tup: PyTuple) -> PyResult<PyObject> {
147 def append(&self, tup: PyTuple) -> PyResult<PyObject> {
149 if tup.len(py) < 8 {
148 if tup.len(py) < 8 {
150 // this is better than the panic promised by tup.get_item()
149 // this is better than the panic promised by tup.get_item()
151 return Err(
150 return Err(
152 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
151 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
153 }
152 }
154 let node_bytes = tup.get_item(py, 7).extract(py)?;
153 let node_bytes = tup.get_item(py, 7).extract(py)?;
155 let node = node_from_py_object(py, &node_bytes)?;
154 let node = node_from_py_object(py, &node_bytes)?;
156
155
157 let rev = self.len(py)? as BaseRevision;
156 let rev = self.len(py)? as BaseRevision;
158 let mut idx = self.cindex(py).borrow_mut();
157 let mut idx = self.cindex(py).borrow_mut();
159
158
160 // This is ok since we will just add the revision to the index
159 // This is ok since we will just add the revision to the index
161 let rev = Revision(rev);
160 let rev = Revision(rev);
162 idx.append(py, tup.clone_ref(py))?;
161 idx.append(py, tup.clone_ref(py))?;
163 self.index(py)
162 self.index(py)
164 .borrow_mut()
163 .borrow_mut()
165 .append(py_tuple_to_revision_data_params(py, tup)?)
164 .append(py_tuple_to_revision_data_params(py, tup)?)
166 .unwrap();
165 .unwrap();
167 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
166 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
168 .insert(&*idx, &node, rev)
167 .insert(&*idx, &node, rev)
169 .map_err(|e| nodemap_error(py, e))?;
168 .map_err(|e| nodemap_error(py, e))?;
170 Ok(py.None())
169 Ok(py.None())
171 }
170 }
172
171
173 def __delitem__(&self, key: PyObject) -> PyResult<()> {
172 def __delitem__(&self, key: PyObject) -> PyResult<()> {
174 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
173 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
175 self.cindex(py).borrow().inner().del_item(py, &key)?;
174 self.cindex(py).borrow().inner().del_item(py, &key)?;
176 let start = key.getattr(py, "start")?;
175 let start = key.getattr(py, "start")?;
177 let start = UncheckedRevision(start.extract(py)?);
176 let start = UncheckedRevision(start.extract(py)?);
178 let start = self.index(py)
177 let start = self.index(py)
179 .borrow()
178 .borrow()
180 .check_revision(start)
179 .check_revision(start)
181 .ok_or_else(|| {
180 .ok_or_else(|| {
182 nodemap_error(py, NodeMapError::RevisionNotInIndex(start))
181 nodemap_error(py, NodeMapError::RevisionNotInIndex(start))
183 })?;
182 })?;
184 self.index(py).borrow_mut().remove(start).unwrap();
183 self.index(py).borrow_mut().remove(start).unwrap();
185 let mut opt = self.get_nodetree(py)?.borrow_mut();
184 let mut opt = self.get_nodetree(py)?.borrow_mut();
186 let nt = opt.as_mut().unwrap();
185 let nt = opt.as_mut().unwrap();
187 nt.invalidate_all();
186 nt.invalidate_all();
188 self.fill_nodemap(py, nt)?;
187 self.fill_nodemap(py, nt)?;
189 Ok(())
188 Ok(())
190 }
189 }
191
190
192 //
191 //
193 // Reforwarded C index API
192 // Reforwarded C index API
194 //
193 //
195
194
196 // index_methods (tp_methods). Same ordering as in revlog.c
195 // index_methods (tp_methods). Same ordering as in revlog.c
197
196
198 /// return the gca set of the given revs
197 /// return the gca set of the given revs
199 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
198 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
200 self.call_cindex(py, "ancestors", args, kw)
199 self.call_cindex(py, "ancestors", args, kw)
201 }
200 }
202
201
203 /// return the heads of the common ancestors of the given revs
202 /// return the heads of the common ancestors of the given revs
204 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
203 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
205 self.call_cindex(py, "commonancestorsheads", args, kw)
204 self.call_cindex(py, "commonancestorsheads", args, kw)
206 }
205 }
207
206
208 /// Clear the index caches and inner py_class data.
207 /// Clear the index caches and inner py_class data.
209 /// It is Python's responsibility to call `update_nodemap_data` again.
208 /// It is Python's responsibility to call `update_nodemap_data` again.
210 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
209 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
211 self.nt(py).borrow_mut().take();
210 self.nt(py).borrow_mut().take();
212 self.docket(py).borrow_mut().take();
211 self.docket(py).borrow_mut().take();
213 self.nodemap_mmap(py).borrow_mut().take();
212 self.nodemap_mmap(py).borrow_mut().take();
214 self.index(py).borrow_mut().clear_caches();
213 self.index(py).borrow_mut().clear_caches();
215 self.call_cindex(py, "clearcaches", args, kw)
214 self.call_cindex(py, "clearcaches", args, kw)
216 }
215 }
217
216
218 /// return the raw binary string representing a revision
217 /// return the raw binary string representing a revision
219 def entry_binary(&self, *args, **kw) -> PyResult<PyObject> {
218 def entry_binary(&self, *args, **kw) -> PyResult<PyObject> {
220 let rindex = self.index(py).borrow();
219 let rindex = self.index(py).borrow();
221 let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?);
220 let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?);
222 let rust_bytes = rindex.check_revision(rev).and_then(
221 let rust_bytes = rindex.check_revision(rev).and_then(
223 |r| rindex.entry_binary(r))
222 |r| rindex.entry_binary(r))
224 .ok_or_else(|| rev_not_in_index(py, rev))?;
223 .ok_or_else(|| rev_not_in_index(py, rev))?;
225 let rust_res = PyBytes::new(py, rust_bytes).into_object();
224 let rust_res = PyBytes::new(py, rust_bytes).into_object();
226
225
227 let c_res = self.call_cindex(py, "entry_binary", args, kw)?;
226 let c_res = self.call_cindex(py, "entry_binary", args, kw)?;
228 assert_py_eq(py, "entry_binary", &rust_res, &c_res)?;
227 assert_py_eq(py, "entry_binary", &rust_res, &c_res)?;
229 Ok(rust_res)
228 Ok(rust_res)
230 }
229 }
231
230
232 /// return a binary packed version of the header
231 /// return a binary packed version of the header
233 def pack_header(&self, *args, **kw) -> PyResult<PyObject> {
232 def pack_header(&self, *args, **kw) -> PyResult<PyObject> {
234 let rindex = self.index(py).borrow();
233 let rindex = self.index(py).borrow();
235 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?);
234 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?);
236 let rust_res = PyBytes::new(py, &packed).into_object();
235 let rust_res = PyBytes::new(py, &packed).into_object();
237
236
238 let c_res = self.call_cindex(py, "pack_header", args, kw)?;
237 let c_res = self.call_cindex(py, "pack_header", args, kw)?;
239 assert_py_eq(py, "pack_header", &rust_res, &c_res)?;
238 assert_py_eq(py, "pack_header", &rust_res, &c_res)?;
240 Ok(rust_res)
239 Ok(rust_res)
241 }
240 }
242
241
243 /// compute phases
242 /// compute phases
244 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
243 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
245 self.call_cindex(py, "computephasesmapsets", args, kw)
244 self.call_cindex(py, "computephasesmapsets", args, kw)
246 }
245 }
247
246
248 /// reachableroots
247 /// reachableroots
249 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
248 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
250 self.call_cindex(py, "reachableroots2", args, kw)
249 self.call_cindex(py, "reachableroots2", args, kw)
251 }
250 }
252
251
253 /// get head revisions
252 /// get head revisions
254 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
253 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
255 let rust_res = self.inner_headrevs(py)?;
254 let rust_res = self.inner_headrevs(py)?;
256
255
257 let c_res = self.call_cindex(py, "headrevs", args, kw)?;
256 let c_res = self.call_cindex(py, "headrevs", args, kw)?;
258 assert_py_eq(py, "headrevs", &rust_res, &c_res)?;
257 assert_py_eq(py, "headrevs", &rust_res, &c_res)?;
259 Ok(rust_res)
258 Ok(rust_res)
260 }
259 }
261
260
262 /// get filtered head revisions
261 /// get filtered head revisions
263 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
262 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
264 let rust_res = self.inner_headrevsfiltered(py, &args.get_item(py, 0))?;
263 let rust_res = self.inner_headrevsfiltered(py, &args.get_item(py, 0))?;
265 let c_res = self.call_cindex(py, "headrevsfiltered", args, kw)?;
264 let c_res = self.call_cindex(py, "headrevsfiltered", args, kw)?;
266 assert_eq!(
265
267 rust_res.len(),
266 assert_py_eq(py, "headrevsfiltered", &rust_res, &c_res)?;
268 c_res.len(py)?,
267 Ok(rust_res)
269 "filtered heads differ {:?} {}",
270 rust_res,
271 c_res
272 );
273 for (index, rev) in rust_res.iter().enumerate() {
274 let c_rev: BaseRevision = c_res.get_item(py, index)?.extract(py)?;
275 assert_eq!(c_rev, rev.0);
276 }
277 Ok(c_res)
278 }
268 }
279
269
280 /// True if the object is a snapshot
270 /// True if the object is a snapshot
281 def issnapshot(&self, *args, **kw) -> PyResult<bool> {
271 def issnapshot(&self, *args, **kw) -> PyResult<bool> {
282 let index = self.index(py).borrow();
272 let index = self.index(py).borrow();
283 let result = index
273 let result = index
284 .is_snapshot(UncheckedRevision(args.get_item(py, 0).extract(py)?))
274 .is_snapshot(UncheckedRevision(args.get_item(py, 0).extract(py)?))
285 .map_err(|e| {
275 .map_err(|e| {
286 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
276 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
287 })?;
277 })?;
288 let cresult = self.call_cindex(py, "issnapshot", args, kw)?;
278 let cresult = self.call_cindex(py, "issnapshot", args, kw)?;
289 assert_eq!(result, cresult.extract(py)?);
279 assert_eq!(result, cresult.extract(py)?);
290 Ok(result)
280 Ok(result)
291 }
281 }
292
282
293 /// Gather snapshot data in a cache dict
283 /// Gather snapshot data in a cache dict
294 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
284 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
295 let index = self.index(py).borrow();
285 let index = self.index(py).borrow();
296 let cache: PyDict = args.get_item(py, 0).extract(py)?;
286 let cache: PyDict = args.get_item(py, 0).extract(py)?;
297 // this methods operates by setting new values in the cache,
287 // this methods operates by setting new values in the cache,
298 // hence we will compare results by letting the C implementation
288 // hence we will compare results by letting the C implementation
299 // operate over a deepcopy of the cache, and finally compare both
289 // operate over a deepcopy of the cache, and finally compare both
300 // caches.
290 // caches.
301 let c_cache = PyDict::new(py);
291 let c_cache = PyDict::new(py);
302 for (k, v) in cache.items(py) {
292 for (k, v) in cache.items(py) {
303 c_cache.set_item(py, k, PySet::new(py, v)?)?;
293 c_cache.set_item(py, k, PySet::new(py, v)?)?;
304 }
294 }
305
295
306 let start_rev = UncheckedRevision(args.get_item(py, 1).extract(py)?);
296 let start_rev = UncheckedRevision(args.get_item(py, 1).extract(py)?);
307 let end_rev = UncheckedRevision(args.get_item(py, 2).extract(py)?);
297 let end_rev = UncheckedRevision(args.get_item(py, 2).extract(py)?);
308 let mut cache_wrapper = PySnapshotsCache{ py, dict: cache };
298 let mut cache_wrapper = PySnapshotsCache{ py, dict: cache };
309 index.find_snapshots(
299 index.find_snapshots(
310 start_rev,
300 start_rev,
311 end_rev,
301 end_rev,
312 &mut cache_wrapper,
302 &mut cache_wrapper,
313 ).map_err(|_| revlog_error(py))?;
303 ).map_err(|_| revlog_error(py))?;
314
304
315 let c_args = PyTuple::new(
305 let c_args = PyTuple::new(
316 py,
306 py,
317 &[
307 &[
318 c_cache.clone_ref(py).into_object(),
308 c_cache.clone_ref(py).into_object(),
319 args.get_item(py, 1),
309 args.get_item(py, 1),
320 args.get_item(py, 2)
310 args.get_item(py, 2)
321 ]
311 ]
322 );
312 );
323 self.call_cindex(py, "findsnapshots", &c_args, kw)?;
313 self.call_cindex(py, "findsnapshots", &c_args, kw)?;
324 assert_py_eq(py, "findsnapshots cache",
314 assert_py_eq(py, "findsnapshots cache",
325 &cache_wrapper.into_object(),
315 &cache_wrapper.into_object(),
326 &c_cache.into_object())?;
316 &c_cache.into_object())?;
327 Ok(py.None())
317 Ok(py.None())
328 }
318 }
329
319
330 /// determine revisions with deltas to reconstruct fulltext
320 /// determine revisions with deltas to reconstruct fulltext
331 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
321 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
332 let index = self.index(py).borrow();
322 let index = self.index(py).borrow();
333 let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into();
323 let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into();
334 let stop_rev =
324 let stop_rev =
335 args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?;
325 args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?;
336 let rev = index.check_revision(rev).ok_or_else(|| {
326 let rev = index.check_revision(rev).ok_or_else(|| {
337 nodemap_error(py, NodeMapError::RevisionNotInIndex(rev))
327 nodemap_error(py, NodeMapError::RevisionNotInIndex(rev))
338 })?;
328 })?;
339 let stop_rev = if let Some(stop_rev) = stop_rev {
329 let stop_rev = if let Some(stop_rev) = stop_rev {
340 let stop_rev = UncheckedRevision(stop_rev);
330 let stop_rev = UncheckedRevision(stop_rev);
341 Some(index.check_revision(stop_rev).ok_or_else(|| {
331 Some(index.check_revision(stop_rev).ok_or_else(|| {
342 nodemap_error(py, NodeMapError::RevisionNotInIndex(stop_rev))
332 nodemap_error(py, NodeMapError::RevisionNotInIndex(stop_rev))
343 })?)
333 })?)
344 } else {None};
334 } else {None};
345 let (chain, stopped) = index.delta_chain(rev, stop_rev).map_err(|e| {
335 let (chain, stopped) = index.delta_chain(rev, stop_rev).map_err(|e| {
346 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
336 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
347 })?;
337 })?;
348
338
349 let cresult = self.call_cindex(py, "deltachain", args, kw)?;
339 let cresult = self.call_cindex(py, "deltachain", args, kw)?;
350 let cchain: Vec<BaseRevision> =
340 let cchain: Vec<BaseRevision> =
351 cresult.get_item(py, 0)?.extract::<Vec<BaseRevision>>(py)?;
341 cresult.get_item(py, 0)?.extract::<Vec<BaseRevision>>(py)?;
352 let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect();
342 let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect();
353 assert_eq!(chain, cchain);
343 assert_eq!(chain, cchain);
354 assert_eq!(stopped, cresult.get_item(py, 1)?.extract(py)?);
344 assert_eq!(stopped, cresult.get_item(py, 1)?.extract(py)?);
355
345
356 Ok(
346 Ok(
357 PyTuple::new(
347 PyTuple::new(
358 py,
348 py,
359 &[
349 &[
360 chain.into_py_object(py).into_object(),
350 chain.into_py_object(py).into_object(),
361 stopped.into_py_object(py).into_object()
351 stopped.into_py_object(py).into_object()
362 ]
352 ]
363 ).into_object()
353 ).into_object()
364 )
354 )
365
355
366 }
356 }
367
357
368 /// slice planned chunk read to reach a density threshold
358 /// slice planned chunk read to reach a density threshold
369 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
359 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
370 self.call_cindex(py, "slicechunktodensity", args, kw)
360 self.call_cindex(py, "slicechunktodensity", args, kw)
371 }
361 }
372
362
373 /// stats for the index
363 /// stats for the index
374 def stats(&self, *args, **kw) -> PyResult<PyObject> {
364 def stats(&self, *args, **kw) -> PyResult<PyObject> {
375 self.call_cindex(py, "stats", args, kw)
365 self.call_cindex(py, "stats", args, kw)
376 }
366 }
377
367
378 // index_sequence_methods and index_mapping_methods.
368 // index_sequence_methods and index_mapping_methods.
379 //
369 //
380 // Since we call back through the high level Python API,
370 // Since we call back through the high level Python API,
381 // there's no point making a distinction between index_get
371 // there's no point making a distinction between index_get
382 // and index_getitem.
372 // and index_getitem.
383 // gracinet 2023: this above is no longer true for the pure Rust impl
373 // gracinet 2023: this above is no longer true for the pure Rust impl
384
374
385 def __len__(&self) -> PyResult<usize> {
375 def __len__(&self) -> PyResult<usize> {
386 self.len(py)
376 self.len(py)
387 }
377 }
388
378
389 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
379 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
390 let rust_res = self.inner_getitem(py, key.clone_ref(py))?;
380 let rust_res = self.inner_getitem(py, key.clone_ref(py))?;
391
381
392 // this conversion seems needless, but that's actually because
382 // this conversion seems needless, but that's actually because
393 // `index_getitem` does not handle conversion from PyLong,
383 // `index_getitem` does not handle conversion from PyLong,
394 // which expressions such as [e for e in index] internally use.
384 // which expressions such as [e for e in index] internally use.
395 // Note that we don't seem to have a direct way to call
385 // Note that we don't seem to have a direct way to call
396 // PySequence_GetItem (does the job), which would possibly be better
386 // PySequence_GetItem (does the job), which would possibly be better
397 // for performance
387 // for performance
398 // gracinet 2023: the above comment can be removed when we use
388 // gracinet 2023: the above comment can be removed when we use
399 // the pure Rust impl only. Note also that `key` can be a binary
389 // the pure Rust impl only. Note also that `key` can be a binary
400 // node id.
390 // node id.
401 let c_key = match key.extract::<BaseRevision>(py) {
391 let c_key = match key.extract::<BaseRevision>(py) {
402 Ok(rev) => rev.to_py_object(py).into_object(),
392 Ok(rev) => rev.to_py_object(py).into_object(),
403 Err(_) => key,
393 Err(_) => key,
404 };
394 };
405 let c_res = self.cindex(py).borrow().inner().get_item(py, c_key)?;
395 let c_res = self.cindex(py).borrow().inner().get_item(py, c_key)?;
406
396
407 assert_py_eq(py, "__getitem__", &rust_res, &c_res)?;
397 assert_py_eq(py, "__getitem__", &rust_res, &c_res)?;
408 Ok(rust_res)
398 Ok(rust_res)
409 }
399 }
410
400
411 def __contains__(&self, item: PyObject) -> PyResult<bool> {
401 def __contains__(&self, item: PyObject) -> PyResult<bool> {
412 // ObjectProtocol does not seem to provide contains(), so
402 // ObjectProtocol does not seem to provide contains(), so
413 // this is an equivalent implementation of the index_contains()
403 // this is an equivalent implementation of the index_contains()
414 // defined in revlog.c
404 // defined in revlog.c
415 let cindex = self.cindex(py).borrow();
405 let cindex = self.cindex(py).borrow();
416 match item.extract::<i32>(py) {
406 match item.extract::<i32>(py) {
417 Ok(rev) => {
407 Ok(rev) => {
418 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision)
408 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision)
419 }
409 }
420 Err(_) => {
410 Err(_) => {
421 let item_bytes: PyBytes = item.extract(py)?;
411 let item_bytes: PyBytes = item.extract(py)?;
422 let rust_res = self.has_node(py, item_bytes)?;
412 let rust_res = self.has_node(py, item_bytes)?;
423
413
424 let c_res = cindex.inner().call_method(
414 let c_res = cindex.inner().call_method(
425 py,
415 py,
426 "has_node",
416 "has_node",
427 PyTuple::new(py, &[item.clone_ref(py)]),
417 PyTuple::new(py, &[item.clone_ref(py)]),
428 None)?
418 None)?
429 .extract(py)?;
419 .extract(py)?;
430
420
431 assert_eq!(rust_res, c_res);
421 assert_eq!(rust_res, c_res);
432 Ok(rust_res)
422 Ok(rust_res)
433 }
423 }
434 }
424 }
435 }
425 }
436
426
437 def nodemap_data_all(&self) -> PyResult<PyBytes> {
427 def nodemap_data_all(&self) -> PyResult<PyBytes> {
438 self.inner_nodemap_data_all(py)
428 self.inner_nodemap_data_all(py)
439 }
429 }
440
430
441 def nodemap_data_incremental(&self) -> PyResult<PyObject> {
431 def nodemap_data_incremental(&self) -> PyResult<PyObject> {
442 self.inner_nodemap_data_incremental(py)
432 self.inner_nodemap_data_incremental(py)
443 }
433 }
444 def update_nodemap_data(
434 def update_nodemap_data(
445 &self,
435 &self,
446 docket: PyObject,
436 docket: PyObject,
447 nm_data: PyObject
437 nm_data: PyObject
448 ) -> PyResult<PyObject> {
438 ) -> PyResult<PyObject> {
449 self.inner_update_nodemap_data(py, docket, nm_data)
439 self.inner_update_nodemap_data(py, docket, nm_data)
450 }
440 }
451
441
452 @property
442 @property
453 def entry_size(&self) -> PyResult<PyInt> {
443 def entry_size(&self) -> PyResult<PyInt> {
454 self.cindex(py).borrow().inner().getattr(py, "entry_size")?.extract::<PyInt>(py)
444 self.cindex(py).borrow().inner().getattr(py, "entry_size")?.extract::<PyInt>(py)
455 }
445 }
456
446
457 @property
447 @property
458 def rust_ext_compat(&self) -> PyResult<PyInt> {
448 def rust_ext_compat(&self) -> PyResult<PyInt> {
459 self.cindex(py).borrow().inner().getattr(py, "rust_ext_compat")?.extract::<PyInt>(py)
449 self.cindex(py).borrow().inner().getattr(py, "rust_ext_compat")?.extract::<PyInt>(py)
460 }
450 }
461
451
462 });
452 });
463
453
464 /// Take a (potentially) mmap'ed buffer, and return the underlying Python
454 /// Take a (potentially) mmap'ed buffer, and return the underlying Python
465 /// buffer along with the Rust slice into said buffer. We need to keep the
455 /// buffer along with the Rust slice into said buffer. We need to keep the
466 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer
456 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer
467 /// is freed from Python's side.
457 /// is freed from Python's side.
468 ///
458 ///
469 /// # Safety
459 /// # Safety
470 ///
460 ///
471 /// The caller must make sure that the buffer is kept around for at least as
461 /// The caller must make sure that the buffer is kept around for at least as
472 /// long as the slice.
462 /// long as the slice.
473 #[deny(unsafe_op_in_unsafe_fn)]
463 #[deny(unsafe_op_in_unsafe_fn)]
474 unsafe fn mmap_keeparound(
464 unsafe fn mmap_keeparound(
475 py: Python,
465 py: Python,
476 data: PyObject,
466 data: PyObject,
477 ) -> PyResult<(
467 ) -> PyResult<(
478 PyBuffer,
468 PyBuffer,
479 Box<dyn std::ops::Deref<Target = [u8]> + Send + 'static>,
469 Box<dyn std::ops::Deref<Target = [u8]> + Send + 'static>,
480 )> {
470 )> {
481 let buf = PyBuffer::get(py, &data)?;
471 let buf = PyBuffer::get(py, &data)?;
482 let len = buf.item_count();
472 let len = buf.item_count();
483
473
484 // Build a slice from the mmap'ed buffer data
474 // Build a slice from the mmap'ed buffer data
485 let cbuf = buf.buf_ptr();
475 let cbuf = buf.buf_ptr();
486 let bytes = if std::mem::size_of::<u8>() == buf.item_size()
476 let bytes = if std::mem::size_of::<u8>() == buf.item_size()
487 && buf.is_c_contiguous()
477 && buf.is_c_contiguous()
488 && u8::is_compatible_format(buf.format())
478 && u8::is_compatible_format(buf.format())
489 {
479 {
490 unsafe { std::slice::from_raw_parts(cbuf as *const u8, len) }
480 unsafe { std::slice::from_raw_parts(cbuf as *const u8, len) }
491 } else {
481 } else {
492 return Err(PyErr::new::<ValueError, _>(
482 return Err(PyErr::new::<ValueError, _>(
493 py,
483 py,
494 "Nodemap data buffer has an invalid memory representation"
484 "Nodemap data buffer has an invalid memory representation"
495 .to_string(),
485 .to_string(),
496 ));
486 ));
497 };
487 };
498
488
499 Ok((buf, Box::new(bytes)))
489 Ok((buf, Box::new(bytes)))
500 }
490 }
501
491
502 fn py_tuple_to_revision_data_params(
492 fn py_tuple_to_revision_data_params(
503 py: Python,
493 py: Python,
504 tuple: PyTuple,
494 tuple: PyTuple,
505 ) -> PyResult<RevisionDataParams> {
495 ) -> PyResult<RevisionDataParams> {
506 if tuple.len(py) < 8 {
496 if tuple.len(py) < 8 {
507 // this is better than the panic promised by tup.get_item()
497 // this is better than the panic promised by tup.get_item()
508 return Err(PyErr::new::<IndexError, _>(
498 return Err(PyErr::new::<IndexError, _>(
509 py,
499 py,
510 "tuple index out of range",
500 "tuple index out of range",
511 ));
501 ));
512 }
502 }
513 let offset_or_flags: u64 = tuple.get_item(py, 0).extract(py)?;
503 let offset_or_flags: u64 = tuple.get_item(py, 0).extract(py)?;
514 let node_id = tuple
504 let node_id = tuple
515 .get_item(py, 7)
505 .get_item(py, 7)
516 .extract::<PyBytes>(py)?
506 .extract::<PyBytes>(py)?
517 .data(py)
507 .data(py)
518 .try_into()
508 .try_into()
519 .unwrap();
509 .unwrap();
520 let flags = (offset_or_flags & 0xFFFF) as u16;
510 let flags = (offset_or_flags & 0xFFFF) as u16;
521 let data_offset = offset_or_flags >> 16;
511 let data_offset = offset_or_flags >> 16;
522 Ok(RevisionDataParams {
512 Ok(RevisionDataParams {
523 flags,
513 flags,
524 data_offset,
514 data_offset,
525 data_compressed_length: tuple.get_item(py, 1).extract(py)?,
515 data_compressed_length: tuple.get_item(py, 1).extract(py)?,
526 data_uncompressed_length: tuple.get_item(py, 2).extract(py)?,
516 data_uncompressed_length: tuple.get_item(py, 2).extract(py)?,
527 data_delta_base: tuple.get_item(py, 3).extract(py)?,
517 data_delta_base: tuple.get_item(py, 3).extract(py)?,
528 link_rev: tuple.get_item(py, 4).extract(py)?,
518 link_rev: tuple.get_item(py, 4).extract(py)?,
529 parent_rev_1: tuple.get_item(py, 5).extract(py)?,
519 parent_rev_1: tuple.get_item(py, 5).extract(py)?,
530 parent_rev_2: tuple.get_item(py, 6).extract(py)?,
520 parent_rev_2: tuple.get_item(py, 6).extract(py)?,
531 node_id,
521 node_id,
532 ..Default::default()
522 ..Default::default()
533 })
523 })
534 }
524 }
535 fn revision_data_params_to_py_tuple(
525 fn revision_data_params_to_py_tuple(
536 py: Python,
526 py: Python,
537 params: RevisionDataParams,
527 params: RevisionDataParams,
538 ) -> PyTuple {
528 ) -> PyTuple {
539 PyTuple::new(
529 PyTuple::new(
540 py,
530 py,
541 &[
531 &[
542 params.data_offset.into_py_object(py).into_object(),
532 params.data_offset.into_py_object(py).into_object(),
543 params
533 params
544 .data_compressed_length
534 .data_compressed_length
545 .into_py_object(py)
535 .into_py_object(py)
546 .into_object(),
536 .into_object(),
547 params
537 params
548 .data_uncompressed_length
538 .data_uncompressed_length
549 .into_py_object(py)
539 .into_py_object(py)
550 .into_object(),
540 .into_object(),
551 params.data_delta_base.into_py_object(py).into_object(),
541 params.data_delta_base.into_py_object(py).into_object(),
552 params.link_rev.into_py_object(py).into_object(),
542 params.link_rev.into_py_object(py).into_object(),
553 params.parent_rev_1.into_py_object(py).into_object(),
543 params.parent_rev_1.into_py_object(py).into_object(),
554 params.parent_rev_2.into_py_object(py).into_object(),
544 params.parent_rev_2.into_py_object(py).into_object(),
555 PyBytes::new(py, &params.node_id)
545 PyBytes::new(py, &params.node_id)
556 .into_py_object(py)
546 .into_py_object(py)
557 .into_object(),
547 .into_object(),
558 params._sidedata_offset.into_py_object(py).into_object(),
548 params._sidedata_offset.into_py_object(py).into_object(),
559 params
549 params
560 ._sidedata_compressed_length
550 ._sidedata_compressed_length
561 .into_py_object(py)
551 .into_py_object(py)
562 .into_object(),
552 .into_object(),
563 params
553 params
564 .data_compression_mode
554 .data_compression_mode
565 .into_py_object(py)
555 .into_py_object(py)
566 .into_object(),
556 .into_object(),
567 params
557 params
568 ._sidedata_compression_mode
558 ._sidedata_compression_mode
569 .into_py_object(py)
559 .into_py_object(py)
570 .into_object(),
560 .into_object(),
571 params._rank.into_py_object(py).into_object(),
561 params._rank.into_py_object(py).into_object(),
572 ],
562 ],
573 )
563 )
574 }
564 }
575
565
576 struct PySnapshotsCache<'p> {
566 struct PySnapshotsCache<'p> {
577 py: Python<'p>,
567 py: Python<'p>,
578 dict: PyDict,
568 dict: PyDict,
579 }
569 }
580
570
581 impl<'p> PySnapshotsCache<'p> {
571 impl<'p> PySnapshotsCache<'p> {
582 fn into_object(self) -> PyObject {
572 fn into_object(self) -> PyObject {
583 self.dict.into_object()
573 self.dict.into_object()
584 }
574 }
585 }
575 }
586
576
587 impl<'p> SnapshotsCache for PySnapshotsCache<'p> {
577 impl<'p> SnapshotsCache for PySnapshotsCache<'p> {
588 fn insert_for(
578 fn insert_for(
589 &mut self,
579 &mut self,
590 rev: BaseRevision,
580 rev: BaseRevision,
591 value: BaseRevision,
581 value: BaseRevision,
592 ) -> Result<(), RevlogError> {
582 ) -> Result<(), RevlogError> {
593 let pyvalue = value.into_py_object(self.py).into_object();
583 let pyvalue = value.into_py_object(self.py).into_object();
594 match self.dict.get_item(self.py, rev) {
584 match self.dict.get_item(self.py, rev) {
595 Some(obj) => obj
585 Some(obj) => obj
596 .extract::<PySet>(self.py)
586 .extract::<PySet>(self.py)
597 .and_then(|set| set.add(self.py, pyvalue)),
587 .and_then(|set| set.add(self.py, pyvalue)),
598 None => PySet::new(self.py, vec![pyvalue])
588 None => PySet::new(self.py, vec![pyvalue])
599 .and_then(|set| self.dict.set_item(self.py, rev, set)),
589 .and_then(|set| self.dict.set_item(self.py, rev, set)),
600 }
590 }
601 .map_err(|_| {
591 .map_err(|_| {
602 RevlogError::Other(HgError::unsupported(
592 RevlogError::Other(HgError::unsupported(
603 "Error in Python caches handling",
593 "Error in Python caches handling",
604 ))
594 ))
605 })
595 })
606 }
596 }
607 }
597 }
608
598
609 impl MixedIndex {
599 impl MixedIndex {
610 fn new(
600 fn new(
611 py: Python,
601 py: Python,
612 cindex: PyObject,
602 cindex: PyObject,
613 data: PyObject,
603 data: PyObject,
614 header: u32,
604 header: u32,
615 ) -> PyResult<MixedIndex> {
605 ) -> PyResult<MixedIndex> {
616 // Safety: we keep the buffer around inside the class as `index_mmap`
606 // Safety: we keep the buffer around inside the class as `index_mmap`
617 let (buf, bytes) = unsafe { mmap_keeparound(py, data)? };
607 let (buf, bytes) = unsafe { mmap_keeparound(py, data)? };
618
608
619 Self::create_instance(
609 Self::create_instance(
620 py,
610 py,
621 RefCell::new(cindex::Index::new(py, cindex)?),
611 RefCell::new(cindex::Index::new(py, cindex)?),
622 RefCell::new(
612 RefCell::new(
623 hg::index::Index::new(
613 hg::index::Index::new(
624 bytes,
614 bytes,
625 IndexHeader::parse(&header.to_be_bytes())
615 IndexHeader::parse(&header.to_be_bytes())
626 .expect("default header is broken")
616 .expect("default header is broken")
627 .unwrap(),
617 .unwrap(),
628 )
618 )
629 .unwrap(),
619 .unwrap(),
630 ),
620 ),
631 RefCell::new(None),
621 RefCell::new(None),
632 RefCell::new(None),
622 RefCell::new(None),
633 RefCell::new(None),
623 RefCell::new(None),
634 RefCell::new(Some(buf)),
624 RefCell::new(Some(buf)),
635 )
625 )
636 }
626 }
637
627
638 fn len(&self, py: Python) -> PyResult<usize> {
628 fn len(&self, py: Python) -> PyResult<usize> {
639 let rust_index_len = self.index(py).borrow().len();
629 let rust_index_len = self.index(py).borrow().len();
640 let cindex_len = self.cindex(py).borrow().inner().len(py)?;
630 let cindex_len = self.cindex(py).borrow().inner().len(py)?;
641 assert_eq!(rust_index_len, cindex_len);
631 assert_eq!(rust_index_len, cindex_len);
642 Ok(cindex_len)
632 Ok(cindex_len)
643 }
633 }
644
634
645 /// This is scaffolding at this point, but it could also become
635 /// This is scaffolding at this point, but it could also become
646 /// a way to start a persistent nodemap or perform a
636 /// a way to start a persistent nodemap or perform a
647 /// vacuum / repack operation
637 /// vacuum / repack operation
648 fn fill_nodemap(
638 fn fill_nodemap(
649 &self,
639 &self,
650 py: Python,
640 py: Python,
651 nt: &mut NodeTree,
641 nt: &mut NodeTree,
652 ) -> PyResult<PyObject> {
642 ) -> PyResult<PyObject> {
653 let index = self.index(py).borrow();
643 let index = self.index(py).borrow();
654 for r in 0..self.len(py)? {
644 for r in 0..self.len(py)? {
655 let rev = Revision(r as BaseRevision);
645 let rev = Revision(r as BaseRevision);
656 // in this case node() won't ever return None
646 // in this case node() won't ever return None
657 nt.insert(&*index, index.node(rev).unwrap(), rev)
647 nt.insert(&*index, index.node(rev).unwrap(), rev)
658 .map_err(|e| nodemap_error(py, e))?
648 .map_err(|e| nodemap_error(py, e))?
659 }
649 }
660 Ok(py.None())
650 Ok(py.None())
661 }
651 }
662
652
663 fn get_nodetree<'a>(
653 fn get_nodetree<'a>(
664 &'a self,
654 &'a self,
665 py: Python<'a>,
655 py: Python<'a>,
666 ) -> PyResult<&'a RefCell<Option<NodeTree>>> {
656 ) -> PyResult<&'a RefCell<Option<NodeTree>>> {
667 if self.nt(py).borrow().is_none() {
657 if self.nt(py).borrow().is_none() {
668 let readonly = Box::<Vec<_>>::default();
658 let readonly = Box::<Vec<_>>::default();
669 let mut nt = NodeTree::load_bytes(readonly, 0);
659 let mut nt = NodeTree::load_bytes(readonly, 0);
670 self.fill_nodemap(py, &mut nt)?;
660 self.fill_nodemap(py, &mut nt)?;
671 self.nt(py).borrow_mut().replace(nt);
661 self.nt(py).borrow_mut().replace(nt);
672 }
662 }
673 Ok(self.nt(py))
663 Ok(self.nt(py))
674 }
664 }
675
665
676 /// forward a method call to the underlying C index
666 /// forward a method call to the underlying C index
677 fn call_cindex(
667 fn call_cindex(
678 &self,
668 &self,
679 py: Python,
669 py: Python,
680 name: &str,
670 name: &str,
681 args: &PyTuple,
671 args: &PyTuple,
682 kwargs: Option<&PyDict>,
672 kwargs: Option<&PyDict>,
683 ) -> PyResult<PyObject> {
673 ) -> PyResult<PyObject> {
684 self.cindex(py)
674 self.cindex(py)
685 .borrow()
675 .borrow()
686 .inner()
676 .inner()
687 .call_method(py, name, args, kwargs)
677 .call_method(py, name, args, kwargs)
688 }
678 }
689
679
690 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
680 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
691 self.cindex(py).borrow().clone_ref(py)
681 self.cindex(py).borrow().clone_ref(py)
692 }
682 }
693
683
694 /// Returns the full nodemap bytes to be written as-is to disk
684 /// Returns the full nodemap bytes to be written as-is to disk
695 fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
685 fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
696 let nodemap = self.get_nodetree(py)?.borrow_mut().take().unwrap();
686 let nodemap = self.get_nodetree(py)?.borrow_mut().take().unwrap();
697 let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
687 let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
698
688
699 // If there's anything readonly, we need to build the data again from
689 // If there's anything readonly, we need to build the data again from
700 // scratch
690 // scratch
701 let bytes = if readonly.len() > 0 {
691 let bytes = if readonly.len() > 0 {
702 let mut nt = NodeTree::load_bytes(Box::<Vec<_>>::default(), 0);
692 let mut nt = NodeTree::load_bytes(Box::<Vec<_>>::default(), 0);
703 self.fill_nodemap(py, &mut nt)?;
693 self.fill_nodemap(py, &mut nt)?;
704
694
705 let (readonly, bytes) = nt.into_readonly_and_added_bytes();
695 let (readonly, bytes) = nt.into_readonly_and_added_bytes();
706 assert_eq!(readonly.len(), 0);
696 assert_eq!(readonly.len(), 0);
707
697
708 bytes
698 bytes
709 } else {
699 } else {
710 bytes
700 bytes
711 };
701 };
712
702
713 let bytes = PyBytes::new(py, &bytes);
703 let bytes = PyBytes::new(py, &bytes);
714 Ok(bytes)
704 Ok(bytes)
715 }
705 }
716
706
717 /// Returns the last saved docket along with the size of any changed data
707 /// Returns the last saved docket along with the size of any changed data
718 /// (in number of blocks), and said data as bytes.
708 /// (in number of blocks), and said data as bytes.
719 fn inner_nodemap_data_incremental(
709 fn inner_nodemap_data_incremental(
720 &self,
710 &self,
721 py: Python,
711 py: Python,
722 ) -> PyResult<PyObject> {
712 ) -> PyResult<PyObject> {
723 let docket = self.docket(py).borrow();
713 let docket = self.docket(py).borrow();
724 let docket = match docket.as_ref() {
714 let docket = match docket.as_ref() {
725 Some(d) => d,
715 Some(d) => d,
726 None => return Ok(py.None()),
716 None => return Ok(py.None()),
727 };
717 };
728
718
729 let node_tree = self.get_nodetree(py)?.borrow_mut().take().unwrap();
719 let node_tree = self.get_nodetree(py)?.borrow_mut().take().unwrap();
730 let masked_blocks = node_tree.masked_readonly_blocks();
720 let masked_blocks = node_tree.masked_readonly_blocks();
731 let (_, data) = node_tree.into_readonly_and_added_bytes();
721 let (_, data) = node_tree.into_readonly_and_added_bytes();
732 let changed = masked_blocks * std::mem::size_of::<Block>();
722 let changed = masked_blocks * std::mem::size_of::<Block>();
733
723
734 Ok((docket, changed, PyBytes::new(py, &data))
724 Ok((docket, changed, PyBytes::new(py, &data))
735 .to_py_object(py)
725 .to_py_object(py)
736 .into_object())
726 .into_object())
737 }
727 }
738
728
739 /// Update the nodemap from the new (mmaped) data.
729 /// Update the nodemap from the new (mmaped) data.
740 /// The docket is kept as a reference for later incremental calls.
730 /// The docket is kept as a reference for later incremental calls.
741 fn inner_update_nodemap_data(
731 fn inner_update_nodemap_data(
742 &self,
732 &self,
743 py: Python,
733 py: Python,
744 docket: PyObject,
734 docket: PyObject,
745 nm_data: PyObject,
735 nm_data: PyObject,
746 ) -> PyResult<PyObject> {
736 ) -> PyResult<PyObject> {
747 // Safety: we keep the buffer around inside the class as `nodemap_mmap`
737 // Safety: we keep the buffer around inside the class as `nodemap_mmap`
748 let (buf, bytes) = unsafe { mmap_keeparound(py, nm_data)? };
738 let (buf, bytes) = unsafe { mmap_keeparound(py, nm_data)? };
749 let len = buf.item_count();
739 let len = buf.item_count();
750 self.nodemap_mmap(py).borrow_mut().replace(buf);
740 self.nodemap_mmap(py).borrow_mut().replace(buf);
751
741
752 let mut nt = NodeTree::load_bytes(bytes, len);
742 let mut nt = NodeTree::load_bytes(bytes, len);
753
743
754 let data_tip = docket
744 let data_tip = docket
755 .getattr(py, "tip_rev")?
745 .getattr(py, "tip_rev")?
756 .extract::<BaseRevision>(py)?
746 .extract::<BaseRevision>(py)?
757 .into();
747 .into();
758 self.docket(py).borrow_mut().replace(docket.clone_ref(py));
748 self.docket(py).borrow_mut().replace(docket.clone_ref(py));
759 let idx = self.index(py).borrow();
749 let idx = self.index(py).borrow();
760 let data_tip = idx.check_revision(data_tip).ok_or_else(|| {
750 let data_tip = idx.check_revision(data_tip).ok_or_else(|| {
761 nodemap_error(py, NodeMapError::RevisionNotInIndex(data_tip))
751 nodemap_error(py, NodeMapError::RevisionNotInIndex(data_tip))
762 })?;
752 })?;
763 let current_tip = idx.len();
753 let current_tip = idx.len();
764
754
765 for r in (data_tip.0 + 1)..current_tip as BaseRevision {
755 for r in (data_tip.0 + 1)..current_tip as BaseRevision {
766 let rev = Revision(r);
756 let rev = Revision(r);
767 // in this case node() won't ever return None
757 // in this case node() won't ever return None
768 nt.insert(&*idx, idx.node(rev).unwrap(), rev)
758 nt.insert(&*idx, idx.node(rev).unwrap(), rev)
769 .map_err(|e| nodemap_error(py, e))?
759 .map_err(|e| nodemap_error(py, e))?
770 }
760 }
771
761
772 *self.nt(py).borrow_mut() = Some(nt);
762 *self.nt(py).borrow_mut() = Some(nt);
773
763
774 Ok(py.None())
764 Ok(py.None())
775 }
765 }
776
766
777 fn inner_getitem(&self, py: Python, key: PyObject) -> PyResult<PyObject> {
767 fn inner_getitem(&self, py: Python, key: PyObject) -> PyResult<PyObject> {
778 let idx = self.index(py).borrow();
768 let idx = self.index(py).borrow();
779 Ok(match key.extract::<BaseRevision>(py) {
769 Ok(match key.extract::<BaseRevision>(py) {
780 Ok(key_as_int) => {
770 Ok(key_as_int) => {
781 let entry_params = if key_as_int == NULL_REVISION.0 {
771 let entry_params = if key_as_int == NULL_REVISION.0 {
782 RevisionDataParams::default()
772 RevisionDataParams::default()
783 } else {
773 } else {
784 let rev = UncheckedRevision(key_as_int);
774 let rev = UncheckedRevision(key_as_int);
785 match idx.entry_as_params(rev) {
775 match idx.entry_as_params(rev) {
786 Some(e) => e,
776 Some(e) => e,
787 None => {
777 None => {
788 return Err(PyErr::new::<IndexError, _>(
778 return Err(PyErr::new::<IndexError, _>(
789 py,
779 py,
790 "revlog index out of range",
780 "revlog index out of range",
791 ));
781 ));
792 }
782 }
793 }
783 }
794 };
784 };
795 revision_data_params_to_py_tuple(py, entry_params)
785 revision_data_params_to_py_tuple(py, entry_params)
796 .into_object()
786 .into_object()
797 }
787 }
798 _ => self.get_rev(py, key.extract::<PyBytes>(py)?)?.map_or_else(
788 _ => self.get_rev(py, key.extract::<PyBytes>(py)?)?.map_or_else(
799 || py.None(),
789 || py.None(),
800 |py_rev| py_rev.into_py_object(py).into_object(),
790 |py_rev| py_rev.into_py_object(py).into_object(),
801 ),
791 ),
802 })
792 })
803 }
793 }
804
794
805 fn inner_headrevs(&self, py: Python) -> PyResult<PyObject> {
795 fn inner_headrevs(&self, py: Python) -> PyResult<PyObject> {
806 let index = &mut *self.index(py).borrow_mut();
796 let index = &mut *self.index(py).borrow_mut();
807 let as_vec: Vec<PyObject> = index
797 let as_vec: Vec<PyObject> = index
808 .head_revs()
798 .head_revs()
809 .map_err(|e| graph_error(py, e))?
799 .map_err(|e| graph_error(py, e))?
810 .iter()
800 .iter()
811 .map(|r| PyRevision::from(*r).into_py_object(py).into_object())
801 .map(|r| PyRevision::from(*r).into_py_object(py).into_object())
812 .collect();
802 .collect();
813 Ok(PyList::new(py, &as_vec).into_object())
803 Ok(PyList::new(py, &as_vec).into_object())
814 }
804 }
815
805
816 fn inner_headrevsfiltered(
806 fn inner_headrevsfiltered(
817 &self,
807 &self,
818 py: Python,
808 py: Python,
819 filtered_revs: &PyObject,
809 filtered_revs: &PyObject,
820 ) -> PyResult<Vec<Revision>> {
810 ) -> PyResult<PyObject> {
821 let index = &mut *self.index(py).borrow_mut();
811 let index = &mut *self.index(py).borrow_mut();
822 let filtered_revs = rev_pyiter_collect(py, filtered_revs, index)?;
812 let filtered_revs = rev_pyiter_collect(py, filtered_revs, index)?;
823
813
824 index
814 let as_vec: Vec<PyObject> = index
825 .head_revs_filtered(&filtered_revs)
815 .head_revs_filtered(&filtered_revs)
826 .map_err(|e| GraphError::pynew(py, e))
816 .map_err(|e| graph_error(py, e))?
817 .iter()
818 .map(|r| PyRevision::from(*r).into_py_object(py).into_object())
819 .collect();
820 Ok(PyList::new(py, &as_vec).into_object())
827 }
821 }
828 }
822 }
829
823
830 fn revlog_error(py: Python) -> PyErr {
824 fn revlog_error(py: Python) -> PyErr {
831 match py
825 match py
832 .import("mercurial.error")
826 .import("mercurial.error")
833 .and_then(|m| m.get(py, "RevlogError"))
827 .and_then(|m| m.get(py, "RevlogError"))
834 {
828 {
835 Err(e) => e,
829 Err(e) => e,
836 Ok(cls) => PyErr::from_instance(
830 Ok(cls) => PyErr::from_instance(
837 py,
831 py,
838 cls.call(py, (py.None(),), None).ok().into_py_object(py),
832 cls.call(py, (py.None(),), None).ok().into_py_object(py),
839 ),
833 ),
840 }
834 }
841 }
835 }
842
836
843 fn graph_error(py: Python, _err: hg::GraphError) -> PyErr {
837 fn graph_error(py: Python, _err: hg::GraphError) -> PyErr {
844 // ParentOutOfRange is currently the only alternative
838 // ParentOutOfRange is currently the only alternative
845 // in `hg::GraphError`. The C index always raises this simple ValueError.
839 // in `hg::GraphError`. The C index always raises this simple ValueError.
846 PyErr::new::<ValueError, _>(py, "parent out of range")
840 PyErr::new::<ValueError, _>(py, "parent out of range")
847 }
841 }
848
842
849 fn nodemap_rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
843 fn nodemap_rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
850 PyErr::new::<ValueError, _>(
844 PyErr::new::<ValueError, _>(
851 py,
845 py,
852 format!(
846 format!(
853 "Inconsistency: Revision {} found in nodemap \
847 "Inconsistency: Revision {} found in nodemap \
854 is not in revlog index",
848 is not in revlog index",
855 rev
849 rev
856 ),
850 ),
857 )
851 )
858 }
852 }
859
853
860 fn rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
854 fn rev_not_in_index(py: Python, rev: UncheckedRevision) -> PyErr {
861 PyErr::new::<ValueError, _>(
855 PyErr::new::<ValueError, _>(
862 py,
856 py,
863 format!("revlog index out of range: {}", rev),
857 format!("revlog index out of range: {}", rev),
864 )
858 )
865 }
859 }
866
860
867 /// Standard treatment of NodeMapError
861 /// Standard treatment of NodeMapError
868 fn nodemap_error(py: Python, err: NodeMapError) -> PyErr {
862 fn nodemap_error(py: Python, err: NodeMapError) -> PyErr {
869 match err {
863 match err {
870 NodeMapError::MultipleResults => revlog_error(py),
864 NodeMapError::MultipleResults => revlog_error(py),
871 NodeMapError::RevisionNotInIndex(r) => nodemap_rev_not_in_index(py, r),
865 NodeMapError::RevisionNotInIndex(r) => nodemap_rev_not_in_index(py, r),
872 }
866 }
873 }
867 }
874
868
875 /// assert two Python objects to be equal from a Python point of view
869 /// assert two Python objects to be equal from a Python point of view
876 ///
870 ///
877 /// `method` is a label for the assertion error message, intended to be the
871 /// `method` is a label for the assertion error message, intended to be the
878 /// name of the caller.
872 /// name of the caller.
879 /// `normalizer` is a function that takes a Python variable name and returns
873 /// `normalizer` is a function that takes a Python variable name and returns
880 /// an expression that the conparison will actually use.
874 /// an expression that the conparison will actually use.
881 /// Foe example: `|v| format!("sorted({})", v)`
875 /// Foe example: `|v| format!("sorted({})", v)`
882 fn assert_py_eq_normalized(
876 fn assert_py_eq_normalized(
883 py: Python,
877 py: Python,
884 method: &str,
878 method: &str,
885 rust: &PyObject,
879 rust: &PyObject,
886 c: &PyObject,
880 c: &PyObject,
887 normalizer: impl FnOnce(&str) -> String + Copy,
881 normalizer: impl FnOnce(&str) -> String + Copy,
888 ) -> PyResult<()> {
882 ) -> PyResult<()> {
889 let locals = PyDict::new(py);
883 let locals = PyDict::new(py);
890 locals.set_item(py, "rust".into_py_object(py).into_object(), rust)?;
884 locals.set_item(py, "rust".into_py_object(py).into_object(), rust)?;
891 locals.set_item(py, "c".into_py_object(py).into_object(), c)?;
885 locals.set_item(py, "c".into_py_object(py).into_object(), c)?;
892 // let lhs = format!(normalizer_fmt, "rust");
886 // let lhs = format!(normalizer_fmt, "rust");
893 // let rhs = format!(normalizer_fmt, "c");
887 // let rhs = format!(normalizer_fmt, "c");
894 let is_eq: PyBool = py
888 let is_eq: PyBool = py
895 .eval(
889 .eval(
896 &format!("{} == {}", &normalizer("rust"), &normalizer("c")),
890 &format!("{} == {}", &normalizer("rust"), &normalizer("c")),
897 None,
891 None,
898 Some(&locals),
892 Some(&locals),
899 )?
893 )?
900 .extract(py)?;
894 .extract(py)?;
901 assert!(
895 assert!(
902 is_eq.is_true(),
896 is_eq.is_true(),
903 "{} results differ. Rust: {:?} C: {:?} (before any normalization)",
897 "{} results differ. Rust: {:?} C: {:?} (before any normalization)",
904 method,
898 method,
905 rust,
899 rust,
906 c
900 c
907 );
901 );
908 Ok(())
902 Ok(())
909 }
903 }
910
904
911 fn assert_py_eq(
905 fn assert_py_eq(
912 py: Python,
906 py: Python,
913 method: &str,
907 method: &str,
914 rust: &PyObject,
908 rust: &PyObject,
915 c: &PyObject,
909 c: &PyObject,
916 ) -> PyResult<()> {
910 ) -> PyResult<()> {
917 assert_py_eq_normalized(py, method, rust, c, |v| v.to_owned())
911 assert_py_eq_normalized(py, method, rust, c, |v| v.to_owned())
918 }
912 }
919
913
920 /// Create the module, with __package__ given from parent
914 /// Create the module, with __package__ given from parent
921 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
915 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
922 let dotted_name = &format!("{}.revlog", package);
916 let dotted_name = &format!("{}.revlog", package);
923 let m = PyModule::new(py, dotted_name)?;
917 let m = PyModule::new(py, dotted_name)?;
924 m.add(py, "__package__", package)?;
918 m.add(py, "__package__", package)?;
925 m.add(py, "__doc__", "RevLog - Rust implementations")?;
919 m.add(py, "__doc__", "RevLog - Rust implementations")?;
926
920
927 m.add_class::<MixedIndex>(py)?;
921 m.add_class::<MixedIndex>(py)?;
928
922
929 let sys = PyModule::import(py, "sys")?;
923 let sys = PyModule::import(py, "sys")?;
930 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
924 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
931 sys_modules.set_item(py, dotted_name, &m)?;
925 sys_modules.set_item(py, dotted_name, &m)?;
932
926
933 Ok(m)
927 Ok(m)
934 }
928 }
General Comments 0
You need to be logged in to leave comments. Login now