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