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