comparison rust/hg-cpython/src/revlog.rs @ 51246:41e19e8a6133

rust-index: stop using C index We still keep its wrapper implementation in `hg-cpython::cindex`, because we might want to recreate ancestors handling objects using it for the case of REVLOGV2. Also, we still instantiate it (from Python code) and store it as attribute, for the likes of `get_cindex` and the caller that relies on it, but that is soon to be removed, too.
author Georges Racinet <georges.racinet@octobus.net>
date Fri, 20 Oct 2023 09:48:53 +0200
parents 0b81440e2a73
children 8dbd985733ff
comparison
equal deleted inserted replaced
51245:0b81440e2a73 51246:41e19e8a6133
12 PyRevision, 12 PyRevision,
13 }; 13 };
14 use cpython::{ 14 use cpython::{
15 buffer::{Element, PyBuffer}, 15 buffer::{Element, PyBuffer},
16 exc::{IndexError, ValueError}, 16 exc::{IndexError, ValueError},
17 ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyInt, PyList, 17 ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyList, PyModule,
18 PyModule, PyObject, PyResult, PySet, PyString, PyTuple, Python, 18 PyObject, PyResult, PySet, PyString, PyTuple, Python, PythonObject,
19 PythonObject, ToPyObject, UnsafePyLeaked, 19 ToPyObject, UnsafePyLeaked,
20 }; 20 };
21 use hg::{ 21 use hg::{
22 errors::HgError, 22 errors::HgError,
23 index::{ 23 index::{
24 IndexHeader, Phase, RevisionDataParams, SnapshotsCache, 24 IndexHeader, Phase, RevisionDataParams, SnapshotsCache,
121 /// Return Revision if found, raises a bare `error.RevlogError` 121 /// Return Revision if found, raises a bare `error.RevlogError`
122 /// in case of ambiguity, same as C version does 122 /// in case of ambiguity, same as C version does
123 def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> { 123 def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> {
124 let opt = self.get_nodetree(py)?.borrow(); 124 let opt = self.get_nodetree(py)?.borrow();
125 let nt = opt.as_ref().unwrap(); 125 let nt = opt.as_ref().unwrap();
126 let idx = &*self.cindex(py).borrow();
127 let ridx = &*self.index(py).borrow(); 126 let ridx = &*self.index(py).borrow();
128 let node = node_from_py_bytes(py, &node)?; 127 let node = node_from_py_bytes(py, &node)?;
129 let rust_rev = 128 let rust_rev =
130 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?; 129 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?;
131 let c_rev =
132 nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e))?;
133 assert_eq!(rust_rev, c_rev);
134 Ok(rust_rev.map(Into::into)) 130 Ok(rust_rev.map(Into::into))
135 131
136 } 132 }
137 133
138 /// same as `get_rev()` but raises a bare `error.RevlogError` if node 134 /// same as `get_rev()` but raises a bare `error.RevlogError` if node
200 } 196 }
201 let node_bytes = tup.get_item(py, 7).extract(py)?; 197 let node_bytes = tup.get_item(py, 7).extract(py)?;
202 let node = node_from_py_object(py, &node_bytes)?; 198 let node = node_from_py_object(py, &node_bytes)?;
203 199
204 let rev = self.len(py)? as BaseRevision; 200 let rev = self.len(py)? as BaseRevision;
205 let mut idx = self.cindex(py).borrow_mut();
206 201
207 // This is ok since we will just add the revision to the index 202 // This is ok since we will just add the revision to the index
208 let rev = Revision(rev); 203 let rev = Revision(rev);
209 idx.append(py, tup.clone_ref(py))?;
210 self.index(py) 204 self.index(py)
211 .borrow_mut() 205 .borrow_mut()
212 .append(py_tuple_to_revision_data_params(py, tup)?) 206 .append(py_tuple_to_revision_data_params(py, tup)?)
213 .unwrap(); 207 .unwrap();
208 let idx = &*self.index(py).borrow();
214 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap() 209 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
215 .insert(&*idx, &node, rev) 210 .insert(idx, &node, rev)
216 .map_err(|e| nodemap_error(py, e))?; 211 .map_err(|e| nodemap_error(py, e))?;
217 Ok(py.None()) 212 Ok(py.None())
218 } 213 }
219 214
220 def __delitem__(&self, key: PyObject) -> PyResult<()> { 215 def __delitem__(&self, key: PyObject) -> PyResult<()> {
221 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]` 216 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
222 self.cindex(py).borrow().inner().del_item(py, &key)?;
223 let start = key.getattr(py, "start")?; 217 let start = key.getattr(py, "start")?;
224 let start = UncheckedRevision(start.extract(py)?); 218 let start = UncheckedRevision(start.extract(py)?);
225 let start = self.index(py) 219 let start = self.index(py)
226 .borrow() 220 .borrow()
227 .check_revision(start) 221 .check_revision(start)
235 self.fill_nodemap(py, nt)?; 229 self.fill_nodemap(py, nt)?;
236 Ok(()) 230 Ok(())
237 } 231 }
238 232
239 // 233 //
240 // Reforwarded C index API 234 // Index methods previously reforwarded to C index (tp_methods)
235 // Same ordering as in revlog.c
241 // 236 //
242 237
243 // index_methods (tp_methods). Same ordering as in revlog.c
244
245 /// return the gca set of the given revs 238 /// return the gca set of the given revs
246 def ancestors(&self, *args, **kw) -> PyResult<PyObject> { 239 def ancestors(&self, *args, **_kw) -> PyResult<PyObject> {
247 let rust_res = self.inner_ancestors(py, args)?; 240 let rust_res = self.inner_ancestors(py, args)?;
248
249 let c_res = self.call_cindex(py, "ancestors", args, kw)?;
250 // the algorithm should always provide the results in reverse ordering
251 assert_py_eq(py, "ancestors", &rust_res, &c_res)?;
252
253 Ok(rust_res) 241 Ok(rust_res)
254 } 242 }
255 243
256 /// return the heads of the common ancestors of the given revs 244 /// return the heads of the common ancestors of the given revs
257 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> { 245 def commonancestorsheads(&self, *args, **_kw) -> PyResult<PyObject> {
258 let rust_res = self.inner_commonancestorsheads(py, args)?; 246 let rust_res = self.inner_commonancestorsheads(py, args)?;
259
260 let c_res = self.call_cindex(py, "commonancestorsheads", args, kw)?;
261 // the algorithm should always provide the results in reverse ordering
262 assert_py_eq(py, "commonancestorsheads", &rust_res, &c_res)?;
263
264 Ok(rust_res) 247 Ok(rust_res)
265 } 248 }
266 249
267 /// Clear the index caches and inner py_class data. 250 /// Clear the index caches and inner py_class data.
268 /// It is Python's responsibility to call `update_nodemap_data` again. 251 /// It is Python's responsibility to call `update_nodemap_data` again.
269 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> { 252 def clearcaches(&self) -> PyResult<PyObject> {
270 self.nt(py).borrow_mut().take(); 253 self.nt(py).borrow_mut().take();
271 self.docket(py).borrow_mut().take(); 254 self.docket(py).borrow_mut().take();
272 self.nodemap_mmap(py).borrow_mut().take(); 255 self.nodemap_mmap(py).borrow_mut().take();
273 self.index(py).borrow().clear_caches(); 256 self.index(py).borrow().clear_caches();
274 self.call_cindex(py, "clearcaches", args, kw) 257 Ok(py.None())
275 } 258 }
276 259
277 /// return the raw binary string representing a revision 260 /// return the raw binary string representing a revision
278 def entry_binary(&self, *args, **kw) -> PyResult<PyObject> { 261 def entry_binary(&self, *args, **_kw) -> PyResult<PyObject> {
279 let rindex = self.index(py).borrow(); 262 let rindex = self.index(py).borrow();
280 let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?); 263 let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?);
281 let rust_bytes = rindex.check_revision(rev).and_then( 264 let rust_bytes = rindex.check_revision(rev).and_then(
282 |r| rindex.entry_binary(r)) 265 |r| rindex.entry_binary(r))
283 .ok_or_else(|| rev_not_in_index(py, rev))?; 266 .ok_or_else(|| rev_not_in_index(py, rev))?;
284 let rust_res = PyBytes::new(py, rust_bytes).into_object(); 267 let rust_res = PyBytes::new(py, rust_bytes).into_object();
285
286 let c_res = self.call_cindex(py, "entry_binary", args, kw)?;
287 assert_py_eq(py, "entry_binary", &rust_res, &c_res)?;
288 Ok(rust_res) 268 Ok(rust_res)
289 } 269 }
290 270
291 /// return a binary packed version of the header 271 /// return a binary packed version of the header
292 def pack_header(&self, *args, **kw) -> PyResult<PyObject> { 272 def pack_header(&self, *args, **_kw) -> PyResult<PyObject> {
293 let rindex = self.index(py).borrow(); 273 let rindex = self.index(py).borrow();
294 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?); 274 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?);
295 let rust_res = PyBytes::new(py, &packed).into_object(); 275 let rust_res = PyBytes::new(py, &packed).into_object();
296
297 let c_res = self.call_cindex(py, "pack_header", args, kw)?;
298 assert_py_eq(py, "pack_header", &rust_res, &c_res)?;
299 Ok(rust_res) 276 Ok(rust_res)
300 } 277 }
301 278
302 /// compute phases 279 /// compute phases
303 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> { 280 def computephasesmapsets(&self, *args, **_kw) -> PyResult<PyObject> {
304 let py_roots = args.get_item(py, 0).extract::<PyDict>(py)?; 281 let py_roots = args.get_item(py, 0).extract::<PyDict>(py)?;
305 let rust_res = self.inner_computephasesmapsets(py, py_roots)?; 282 let rust_res = self.inner_computephasesmapsets(py, py_roots)?;
306
307 let c_res = self.call_cindex(py, "computephasesmapsets", args, kw)?;
308 assert_py_eq(py, "computephasesmapsets", &rust_res, &c_res)?;
309 Ok(rust_res) 283 Ok(rust_res)
310 } 284 }
311 285
312 /// reachableroots 286 /// reachableroots
313 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> { 287 def reachableroots2(&self, *args, **_kw) -> PyResult<PyObject> {
314 let rust_res = self.inner_reachableroots2( 288 let rust_res = self.inner_reachableroots2(
315 py, 289 py,
316 UncheckedRevision(args.get_item(py, 0).extract(py)?), 290 UncheckedRevision(args.get_item(py, 0).extract(py)?),
317 args.get_item(py, 1), 291 args.get_item(py, 1),
318 args.get_item(py, 2), 292 args.get_item(py, 2),
319 args.get_item(py, 3).extract(py)?, 293 args.get_item(py, 3).extract(py)?,
320 )?; 294 )?;
321
322 let c_res = self.call_cindex(py, "reachableroots2", args, kw)?;
323 // ordering of C result depends on how the computation went, and
324 // Rust result ordering is arbitrary. Hence we compare after
325 // sorting the results (in Python to avoid reconverting everything
326 // back to Rust structs).
327 assert_py_eq_normalized(py, "reachableroots2", &rust_res, &c_res,
328 |v| format!("sorted({})", v))?;
329
330 Ok(rust_res) 295 Ok(rust_res)
331 } 296 }
332 297
333 /// get head revisions 298 /// get head revisions
334 def headrevs(&self, *args, **kw) -> PyResult<PyObject> { 299 def headrevs(&self) -> PyResult<PyObject> {
335 let rust_res = self.inner_headrevs(py)?; 300 let rust_res = self.inner_headrevs(py)?;
336
337 let c_res = self.call_cindex(py, "headrevs", args, kw)?;
338 assert_py_eq(py, "headrevs", &rust_res, &c_res)?;
339 Ok(rust_res) 301 Ok(rust_res)
340 } 302 }
341 303
342 /// get filtered head revisions 304 /// get filtered head revisions
343 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> { 305 def headrevsfiltered(&self, *args, **_kw) -> PyResult<PyObject> {
344 let rust_res = self.inner_headrevsfiltered(py, &args.get_item(py, 0))?; 306 let rust_res = self.inner_headrevsfiltered(py, &args.get_item(py, 0))?;
345 let c_res = self.call_cindex(py, "headrevsfiltered", args, kw)?;
346
347 assert_py_eq(py, "headrevsfiltered", &rust_res, &c_res)?;
348 Ok(rust_res) 307 Ok(rust_res)
349 } 308 }
350 309
351 /// True if the object is a snapshot 310 /// True if the object is a snapshot
352 def issnapshot(&self, *args, **kw) -> PyResult<bool> { 311 def issnapshot(&self, *args, **_kw) -> PyResult<bool> {
353 let index = self.index(py).borrow(); 312 let index = self.index(py).borrow();
354 let result = index 313 let result = index
355 .is_snapshot(UncheckedRevision(args.get_item(py, 0).extract(py)?)) 314 .is_snapshot(UncheckedRevision(args.get_item(py, 0).extract(py)?))
356 .map_err(|e| { 315 .map_err(|e| {
357 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string()) 316 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
358 })?; 317 })?;
359 let cresult = self.call_cindex(py, "issnapshot", args, kw)?;
360 assert_eq!(result, cresult.extract(py)?);
361 Ok(result) 318 Ok(result)
362 } 319 }
363 320
364 /// Gather snapshot data in a cache dict 321 /// Gather snapshot data in a cache dict
365 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> { 322 def findsnapshots(&self, *args, **_kw) -> PyResult<PyObject> {
366 let index = self.index(py).borrow(); 323 let index = self.index(py).borrow();
367 let cache: PyDict = args.get_item(py, 0).extract(py)?; 324 let cache: PyDict = args.get_item(py, 0).extract(py)?;
368 // this methods operates by setting new values in the cache, 325 // this methods operates by setting new values in the cache,
369 // hence we will compare results by letting the C implementation 326 // hence we will compare results by letting the C implementation
370 // operate over a deepcopy of the cache, and finally compare both 327 // operate over a deepcopy of the cache, and finally compare both
380 index.find_snapshots( 337 index.find_snapshots(
381 start_rev, 338 start_rev,
382 end_rev, 339 end_rev,
383 &mut cache_wrapper, 340 &mut cache_wrapper,
384 ).map_err(|_| revlog_error(py))?; 341 ).map_err(|_| revlog_error(py))?;
385
386 let c_args = PyTuple::new(
387 py,
388 &[
389 c_cache.clone_ref(py).into_object(),
390 args.get_item(py, 1),
391 args.get_item(py, 2)
392 ]
393 );
394 self.call_cindex(py, "findsnapshots", &c_args, kw)?;
395 assert_py_eq(py, "findsnapshots cache",
396 &cache_wrapper.into_object(),
397 &c_cache.into_object())?;
398 Ok(py.None()) 342 Ok(py.None())
399 } 343 }
400 344
401 /// determine revisions with deltas to reconstruct fulltext 345 /// determine revisions with deltas to reconstruct fulltext
402 def deltachain(&self, *args, **kw) -> PyResult<PyObject> { 346 def deltachain(&self, *args, **_kw) -> PyResult<PyObject> {
403 let index = self.index(py).borrow(); 347 let index = self.index(py).borrow();
404 let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into(); 348 let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into();
405 let stop_rev = 349 let stop_rev =
406 args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?; 350 args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?;
407 let rev = index.check_revision(rev).ok_or_else(|| { 351 let rev = index.check_revision(rev).ok_or_else(|| {
420 rev, stop_rev, using_general_delta 364 rev, stop_rev, using_general_delta
421 ).map_err(|e| { 365 ).map_err(|e| {
422 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string()) 366 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string())
423 })?; 367 })?;
424 368
425 let cresult = self.call_cindex(py, "deltachain", args, kw)?;
426 let cchain: Vec<BaseRevision> =
427 cresult.get_item(py, 0)?.extract::<Vec<BaseRevision>>(py)?;
428 let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect(); 369 let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect();
429 assert_eq!(chain, cchain);
430 assert_eq!(stopped, cresult.get_item(py, 1)?.extract(py)?);
431
432 Ok( 370 Ok(
433 PyTuple::new( 371 PyTuple::new(
434 py, 372 py,
435 &[ 373 &[
436 chain.into_py_object(py).into_object(), 374 chain.into_py_object(py).into_object(),
440 ) 378 )
441 379
442 } 380 }
443 381
444 /// slice planned chunk read to reach a density threshold 382 /// slice planned chunk read to reach a density threshold
445 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> { 383 def slicechunktodensity(&self, *args, **_kw) -> PyResult<PyObject> {
446 let rust_res = self.inner_slicechunktodensity( 384 let rust_res = self.inner_slicechunktodensity(
447 py, 385 py,
448 args.get_item(py, 0), 386 args.get_item(py, 0),
449 args.get_item(py, 1).extract(py)?, 387 args.get_item(py, 1).extract(py)?,
450 args.get_item(py, 2).extract(py)? 388 args.get_item(py, 2).extract(py)?
451 )?; 389 )?;
452
453 let c_res = self.call_cindex(py, "slicechunktodensity", args, kw)?;
454 assert_py_eq(py, "slicechunktodensity", &rust_res, &c_res)?;
455 Ok(rust_res) 390 Ok(rust_res)
456 } 391 }
457 392
458 // index_sequence_methods and index_mapping_methods. 393 // index_sequence_methods and index_mapping_methods.
459 // 394 //
466 self.len(py) 401 self.len(py)
467 } 402 }
468 403
469 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> { 404 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
470 let rust_res = self.inner_getitem(py, key.clone_ref(py))?; 405 let rust_res = self.inner_getitem(py, key.clone_ref(py))?;
471
472 // this conversion seems needless, but that's actually because
473 // `index_getitem` does not handle conversion from PyLong,
474 // which expressions such as [e for e in index] internally use.
475 // Note that we don't seem to have a direct way to call
476 // PySequence_GetItem (does the job), which would possibly be better
477 // for performance
478 // gracinet 2023: the above comment can be removed when we use
479 // the pure Rust impl only. Note also that `key` can be a binary
480 // node id.
481 let c_key = match key.extract::<BaseRevision>(py) {
482 Ok(rev) => rev.to_py_object(py).into_object(),
483 Err(_) => key,
484 };
485 let c_res = self.cindex(py).borrow().inner().get_item(py, c_key)?;
486
487 assert_py_eq(py, "__getitem__", &rust_res, &c_res)?;
488 Ok(rust_res) 406 Ok(rust_res)
489 } 407 }
490 408
491 def __contains__(&self, item: PyObject) -> PyResult<bool> { 409 def __contains__(&self, item: PyObject) -> PyResult<bool> {
492 // ObjectProtocol does not seem to provide contains(), so 410 // ObjectProtocol does not seem to provide contains(), so
493 // this is an equivalent implementation of the index_contains() 411 // this is an equivalent implementation of the index_contains()
494 // defined in revlog.c 412 // defined in revlog.c
495 let cindex = self.cindex(py).borrow();
496 match item.extract::<i32>(py) { 413 match item.extract::<i32>(py) {
497 Ok(rev) => { 414 Ok(rev) => {
498 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision) 415 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision)
499 } 416 }
500 Err(_) => { 417 Err(_) => {
501 let item_bytes: PyBytes = item.extract(py)?; 418 let item_bytes: PyBytes = item.extract(py)?;
502 let rust_res = self.has_node(py, item_bytes)?; 419 let rust_res = self.has_node(py, item_bytes)?;
503
504 let c_res = cindex.inner().call_method(
505 py,
506 "has_node",
507 PyTuple::new(py, &[item.clone_ref(py)]),
508 None)?
509 .extract(py)?;
510
511 assert_eq!(rust_res, c_res);
512 Ok(rust_res) 420 Ok(rust_res)
513 } 421 }
514 } 422 }
515 } 423 }
516 424
530 } 438 }
531 439
532 @property 440 @property
533 def entry_size(&self) -> PyResult<PyInt> { 441 def entry_size(&self) -> PyResult<PyInt> {
534 let rust_res: PyInt = INDEX_ENTRY_SIZE.to_py_object(py); 442 let rust_res: PyInt = INDEX_ENTRY_SIZE.to_py_object(py);
535
536 let c_res = self.cindex(py).borrow().inner()
537 .getattr(py, "entry_size")?;
538 assert_py_eq(py, "entry_size", rust_res.as_object(), &c_res)?;
539
540 Ok(rust_res) 443 Ok(rust_res)
541 } 444 }
542 445
543 @property 446 @property
544 def rust_ext_compat(&self) -> PyResult<PyInt> { 447 def rust_ext_compat(&self) -> PyResult<PyInt> {
545 // will be entirely removed when the Rust index yet useful to 448 // will be entirely removed when the Rust index yet useful to
546 // implement in Rust to detangle things when removing `self.cindex` 449 // implement in Rust to detangle things when removing `self.cindex`
547 let rust_res: PyInt = 1.to_py_object(py); 450 let rust_res: PyInt = 1.to_py_object(py);
548
549 let c_res = self.cindex(py).borrow().inner()
550 .getattr(py, "rust_ext_compat")?;
551 assert_py_eq(py, "rust_ext_compat", rust_res.as_object(), &c_res)?;
552
553 Ok(rust_res) 451 Ok(rust_res)
554 } 452 }
555 453
556 }); 454 });
557 455
670 struct PySnapshotsCache<'p> { 568 struct PySnapshotsCache<'p> {
671 py: Python<'p>, 569 py: Python<'p>,
672 dict: PyDict, 570 dict: PyDict,
673 } 571 }
674 572
675 impl<'p> PySnapshotsCache<'p> {
676 fn into_object(self) -> PyObject {
677 self.dict.into_object()
678 }
679 }
680
681 impl<'p> SnapshotsCache for PySnapshotsCache<'p> { 573 impl<'p> SnapshotsCache for PySnapshotsCache<'p> {
682 fn insert_for( 574 fn insert_for(
683 &mut self, 575 &mut self,
684 rev: BaseRevision, 576 rev: BaseRevision,
685 value: BaseRevision, 577 value: BaseRevision,
729 ) 621 )
730 } 622 }
731 623
732 fn len(&self, py: Python) -> PyResult<usize> { 624 fn len(&self, py: Python) -> PyResult<usize> {
733 let rust_index_len = self.index(py).borrow().len(); 625 let rust_index_len = self.index(py).borrow().len();
734 let cindex_len = self.cindex(py).borrow().inner().len(py)?;
735 assert_eq!(rust_index_len, cindex_len);
736 Ok(rust_index_len) 626 Ok(rust_index_len)
737 } 627 }
738 628
739 /// This is scaffolding at this point, but it could also become 629 /// This is scaffolding at this point, but it could also become
740 /// a way to start a persistent nodemap or perform a 630 /// a way to start a persistent nodemap or perform a
763 let mut nt = NodeTree::load_bytes(readonly, 0); 653 let mut nt = NodeTree::load_bytes(readonly, 0);
764 self.fill_nodemap(py, &mut nt)?; 654 self.fill_nodemap(py, &mut nt)?;
765 self.nt(py).borrow_mut().replace(nt); 655 self.nt(py).borrow_mut().replace(nt);
766 } 656 }
767 Ok(self.nt(py)) 657 Ok(self.nt(py))
768 }
769
770 /// forward a method call to the underlying C index
771 fn call_cindex(
772 &self,
773 py: Python,
774 name: &str,
775 args: &PyTuple,
776 kwargs: Option<&PyDict>,
777 ) -> PyResult<PyObject> {
778 self.cindex(py)
779 .borrow()
780 .inner()
781 .call_method(py, name, args, kwargs)
782 } 658 }
783 659
784 pub fn clone_cindex(&self, py: Python) -> cindex::Index { 660 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
785 self.cindex(py).borrow().clone_ref(py) 661 self.cindex(py).borrow().clone_ref(py)
786 } 662 }
1142 NodeMapError::MultipleResults => revlog_error(py), 1018 NodeMapError::MultipleResults => revlog_error(py),
1143 NodeMapError::RevisionNotInIndex(r) => nodemap_rev_not_in_index(py, r), 1019 NodeMapError::RevisionNotInIndex(r) => nodemap_rev_not_in_index(py, r),
1144 } 1020 }
1145 } 1021 }
1146 1022
1147 /// assert two Python objects to be equal from a Python point of view
1148 ///
1149 /// `method` is a label for the assertion error message, intended to be the
1150 /// name of the caller.
1151 /// `normalizer` is a function that takes a Python variable name and returns
1152 /// an expression that the conparison will actually use.
1153 /// Foe example: `|v| format!("sorted({})", v)`
1154 fn assert_py_eq_normalized(
1155 py: Python,
1156 method: &str,
1157 rust: &PyObject,
1158 c: &PyObject,
1159 normalizer: impl FnOnce(&str) -> String + Copy,
1160 ) -> PyResult<()> {
1161 let locals = PyDict::new(py);
1162 locals.set_item(py, "rust".into_py_object(py).into_object(), rust)?;
1163 locals.set_item(py, "c".into_py_object(py).into_object(), c)?;
1164 // let lhs = format!(normalizer_fmt, "rust");
1165 // let rhs = format!(normalizer_fmt, "c");
1166 let is_eq: PyBool = py
1167 .eval(
1168 &format!("{} == {}", &normalizer("rust"), &normalizer("c")),
1169 None,
1170 Some(&locals),
1171 )?
1172 .extract(py)?;
1173 assert!(
1174 is_eq.is_true(),
1175 "{} results differ. Rust: {:?} C: {:?} (before any normalization)",
1176 method,
1177 rust,
1178 c
1179 );
1180 Ok(())
1181 }
1182
1183 fn assert_py_eq(
1184 py: Python,
1185 method: &str,
1186 rust: &PyObject,
1187 c: &PyObject,
1188 ) -> PyResult<()> {
1189 assert_py_eq_normalized(py, method, rust, c, |v| v.to_owned())
1190 }
1191
1192 /// Create the module, with __package__ given from parent 1023 /// Create the module, with __package__ given from parent
1193 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { 1024 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
1194 let dotted_name = &format!("{}.revlog", package); 1025 let dotted_name = &format!("{}.revlog", package);
1195 let m = PyModule::new(py, dotted_name)?; 1026 let m = PyModule::new(py, dotted_name)?;
1196 m.add(py, "__package__", package)?; 1027 m.add(py, "__package__", package)?;