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