88 } |
100 } |
89 fn node(&self, rev: Revision) -> Option<&Node> { |
101 fn node(&self, rev: Revision) -> Option<&Node> { |
90 self.inner.node(rev) |
102 self.inner.node(rev) |
91 } |
103 } |
92 } |
104 } |
93 |
|
94 py_class!(pub class Index |py| { |
|
95 @shared data index: hg::index::Index; |
|
96 data nt: RefCell<Option<CoreNodeTree>>; |
|
97 data docket: RefCell<Option<PyObject>>; |
|
98 // Holds a reference to the mmap'ed persistent nodemap data |
|
99 data nodemap_mmap: RefCell<Option<PyBuffer>>; |
|
100 // Holds a reference to the mmap'ed persistent index data |
|
101 data index_mmap: RefCell<Option<PyBuffer>>; |
|
102 data head_revs_py_list: RefCell<Option<PyList>>; |
|
103 data head_node_ids_py_list: RefCell<Option<PyList>>; |
|
104 |
|
105 def __new__( |
|
106 _cls, |
|
107 data: PyObject, |
|
108 default_header: u32, |
|
109 ) -> PyResult<Self> { |
|
110 Self::new(py, data, default_header) |
|
111 } |
|
112 |
|
113 /// Compatibility layer used for Python consumers needing access to the C index |
|
114 /// |
|
115 /// Only use case so far is `scmutil.shortesthexnodeidprefix`, |
|
116 /// that may need to build a custom `nodetree`, based on a specified revset. |
|
117 /// With a Rust implementation of the nodemap, we will be able to get rid of |
|
118 /// this, by exposing our own standalone nodemap class, |
|
119 /// ready to accept `Index`. |
|
120 /* def get_cindex(&self) -> PyResult<PyObject> { |
|
121 Ok(self.cindex(py).borrow().inner().clone_ref(py)) |
|
122 } |
|
123 */ |
|
124 // Index API involving nodemap, as defined in mercurial/pure/parsers.py |
|
125 |
|
126 /// Return Revision if found, raises a bare `error.RevlogError` |
|
127 /// in case of ambiguity, same as C version does |
|
128 def get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> { |
|
129 let opt = self.get_nodetree(py)?.borrow(); |
|
130 let nt = opt.as_ref().unwrap(); |
|
131 let ridx = &*self.index(py).borrow(); |
|
132 let node = node_from_py_bytes(py, &node)?; |
|
133 let rust_rev = |
|
134 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?; |
|
135 Ok(rust_rev.map(Into::into)) |
|
136 |
|
137 } |
|
138 |
|
139 /// same as `get_rev()` but raises a bare `error.RevlogError` if node |
|
140 /// is not found. |
|
141 /// |
|
142 /// No need to repeat `node` in the exception, `mercurial/revlog.py` |
|
143 /// will catch and rewrap with it |
|
144 def rev(&self, node: PyBytes) -> PyResult<PyRevision> { |
|
145 self.get_rev(py, node)?.ok_or_else(|| revlog_error(py)) |
|
146 } |
|
147 |
|
148 /// return True if the node exist in the index |
|
149 def has_node(&self, node: PyBytes) -> PyResult<bool> { |
|
150 // TODO OPTIM we could avoid a needless conversion here, |
|
151 // to do when scaffolding for pure Rust switch is removed, |
|
152 // as `get_rev()` currently does the necessary assertions |
|
153 self.get_rev(py, node).map(|opt| opt.is_some()) |
|
154 } |
|
155 |
|
156 /// find length of shortest hex nodeid of a binary ID |
|
157 def shortest(&self, node: PyBytes) -> PyResult<usize> { |
|
158 let opt = self.get_nodetree(py)?.borrow(); |
|
159 let nt = opt.as_ref().unwrap(); |
|
160 let idx = &*self.index(py).borrow(); |
|
161 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?) |
|
162 { |
|
163 Ok(Some(l)) => Ok(l), |
|
164 Ok(None) => Err(revlog_error(py)), |
|
165 Err(e) => Err(nodemap_error(py, e)), |
|
166 } |
|
167 } |
|
168 |
|
169 def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> { |
|
170 let opt = self.get_nodetree(py)?.borrow(); |
|
171 let nt = opt.as_ref().unwrap(); |
|
172 let idx = &*self.index(py).borrow(); |
|
173 |
|
174 let node_as_string = if cfg!(feature = "python3-sys") { |
|
175 node.cast_as::<PyString>(py)?.to_string(py)?.to_string() |
|
176 } |
|
177 else { |
|
178 let node = node.extract::<PyBytes>(py)?; |
|
179 String::from_utf8_lossy(node.data(py)).to_string() |
|
180 }; |
|
181 |
|
182 let prefix = NodePrefix::from_hex(&node_as_string) |
|
183 .map_err(|_| PyErr::new::<ValueError, _>( |
|
184 py, format!("Invalid node or prefix '{}'", node_as_string)) |
|
185 )?; |
|
186 |
|
187 nt.find_bin(idx, prefix) |
|
188 // TODO make an inner API returning the node directly |
|
189 .map(|opt| opt.map( |
|
190 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes()))) |
|
191 .map_err(|e| nodemap_error(py, e)) |
|
192 |
|
193 } |
|
194 |
|
195 /// append an index entry |
|
196 def append(&self, tup: PyTuple) -> PyResult<PyObject> { |
|
197 if tup.len(py) < 8 { |
|
198 // this is better than the panic promised by tup.get_item() |
|
199 return Err( |
|
200 PyErr::new::<IndexError, _>(py, "tuple index out of range")) |
|
201 } |
|
202 let node_bytes = tup.get_item(py, 7).extract(py)?; |
|
203 let node = node_from_py_object(py, &node_bytes)?; |
|
204 |
|
205 let rev = self.len(py)? as BaseRevision; |
|
206 |
|
207 // This is ok since we will just add the revision to the index |
|
208 let rev = Revision(rev); |
|
209 self.index(py) |
|
210 .borrow_mut() |
|
211 .append(py_tuple_to_revision_data_params(py, tup)?) |
|
212 .unwrap(); |
|
213 let idx = &*self.index(py).borrow(); |
|
214 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap() |
|
215 .insert(idx, &node, rev) |
|
216 .map_err(|e| nodemap_error(py, e))?; |
|
217 Ok(py.None()) |
|
218 } |
|
219 |
|
220 def __delitem__(&self, key: PyObject) -> PyResult<()> { |
|
221 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]` |
|
222 let start = if let Ok(rev) = key.extract(py) { |
|
223 UncheckedRevision(rev) |
|
224 } else { |
|
225 let start = key.getattr(py, "start")?; |
|
226 UncheckedRevision(start.extract(py)?) |
|
227 }; |
|
228 let start = self.index(py) |
|
229 .borrow() |
|
230 .check_revision(start) |
|
231 .ok_or_else(|| { |
|
232 nodemap_error(py, NodeMapError::RevisionNotInIndex(start)) |
|
233 })?; |
|
234 self.index(py).borrow_mut().remove(start).unwrap(); |
|
235 let mut opt = self.get_nodetree(py)?.borrow_mut(); |
|
236 let nt = opt.as_mut().unwrap(); |
|
237 nt.invalidate_all(); |
|
238 self.fill_nodemap(py, nt)?; |
|
239 Ok(()) |
|
240 } |
|
241 |
|
242 // |
|
243 // Index methods previously reforwarded to C index (tp_methods) |
|
244 // Same ordering as in revlog.c |
|
245 // |
|
246 |
|
247 /// return the gca set of the given revs |
|
248 def ancestors(&self, *args, **_kw) -> PyResult<PyObject> { |
|
249 let rust_res = self.inner_ancestors(py, args)?; |
|
250 Ok(rust_res) |
|
251 } |
|
252 |
|
253 /// return the heads of the common ancestors of the given revs |
|
254 def commonancestorsheads(&self, *args, **_kw) -> PyResult<PyObject> { |
|
255 let rust_res = self.inner_commonancestorsheads(py, args)?; |
|
256 Ok(rust_res) |
|
257 } |
|
258 |
|
259 /// Clear the index caches and inner py_class data. |
|
260 /// It is Python's responsibility to call `update_nodemap_data` again. |
|
261 def clearcaches(&self) -> PyResult<PyObject> { |
|
262 self.nt(py).borrow_mut().take(); |
|
263 self.docket(py).borrow_mut().take(); |
|
264 self.nodemap_mmap(py).borrow_mut().take(); |
|
265 self.head_revs_py_list(py).borrow_mut().take(); |
|
266 self.head_node_ids_py_list(py).borrow_mut().take(); |
|
267 self.index(py).borrow().clear_caches(); |
|
268 Ok(py.None()) |
|
269 } |
|
270 |
|
271 /// return the raw binary string representing a revision |
|
272 def entry_binary(&self, *args, **_kw) -> PyResult<PyObject> { |
|
273 let rindex = self.index(py).borrow(); |
|
274 let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?); |
|
275 let rust_bytes = rindex.check_revision(rev).and_then( |
|
276 |r| rindex.entry_binary(r)) |
|
277 .ok_or_else(|| rev_not_in_index(py, rev))?; |
|
278 let rust_res = PyBytes::new(py, rust_bytes).into_object(); |
|
279 Ok(rust_res) |
|
280 } |
|
281 |
|
282 /// return a binary packed version of the header |
|
283 def pack_header(&self, *args, **_kw) -> PyResult<PyObject> { |
|
284 let rindex = self.index(py).borrow(); |
|
285 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?); |
|
286 let rust_res = PyBytes::new(py, &packed).into_object(); |
|
287 Ok(rust_res) |
|
288 } |
|
289 |
|
290 /// compute phases |
|
291 def computephasesmapsets(&self, *args, **_kw) -> PyResult<PyObject> { |
|
292 let py_roots = args.get_item(py, 0).extract::<PyDict>(py)?; |
|
293 let rust_res = self.inner_computephasesmapsets(py, py_roots)?; |
|
294 Ok(rust_res) |
|
295 } |
|
296 |
|
297 /// reachableroots |
|
298 def reachableroots2(&self, *args, **_kw) -> PyResult<PyObject> { |
|
299 let rust_res = self.inner_reachableroots2( |
|
300 py, |
|
301 UncheckedRevision(args.get_item(py, 0).extract(py)?), |
|
302 args.get_item(py, 1), |
|
303 args.get_item(py, 2), |
|
304 args.get_item(py, 3).extract(py)?, |
|
305 )?; |
|
306 Ok(rust_res) |
|
307 } |
|
308 |
|
309 /// get head revisions |
|
310 def headrevs(&self, *args, **_kw) -> PyResult<PyObject> { |
|
311 let (filtered_revs, stop_rev) = match &args.len(py) { |
|
312 0 => Ok((py.None(), py.None())), |
|
313 1 => Ok((args.get_item(py, 0), py.None())), |
|
314 2 => Ok((args.get_item(py, 0), args.get_item(py, 1))), |
|
315 _ => Err(PyErr::new::<cpython::exc::TypeError, _>(py, "too many arguments")), |
|
316 }?; |
|
317 self.inner_headrevs(py, &filtered_revs, &stop_rev) |
|
318 } |
|
319 |
|
320 /// get head nodeids |
|
321 def head_node_ids(&self) -> PyResult<PyObject> { |
|
322 let rust_res = self.inner_head_node_ids(py)?; |
|
323 Ok(rust_res) |
|
324 } |
|
325 |
|
326 /// get diff in head revisions |
|
327 def headrevsdiff(&self, *args, **_kw) -> PyResult<PyObject> { |
|
328 let rust_res = self.inner_headrevsdiff( |
|
329 py, |
|
330 &args.get_item(py, 0), |
|
331 &args.get_item(py, 1))?; |
|
332 Ok(rust_res) |
|
333 } |
|
334 |
|
335 /// True if the object is a snapshot |
|
336 def issnapshot(&self, *args, **_kw) -> PyResult<bool> { |
|
337 let index = self.index(py).borrow(); |
|
338 let result = index |
|
339 .is_snapshot(UncheckedRevision(args.get_item(py, 0).extract(py)?)) |
|
340 .map_err(|e| { |
|
341 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string()) |
|
342 })?; |
|
343 Ok(result) |
|
344 } |
|
345 |
|
346 /// Gather snapshot data in a cache dict |
|
347 def findsnapshots(&self, *args, **_kw) -> PyResult<PyObject> { |
|
348 let index = self.index(py).borrow(); |
|
349 let cache: PyDict = args.get_item(py, 0).extract(py)?; |
|
350 // this methods operates by setting new values in the cache, |
|
351 // hence we will compare results by letting the C implementation |
|
352 // operate over a deepcopy of the cache, and finally compare both |
|
353 // caches. |
|
354 let c_cache = PyDict::new(py); |
|
355 for (k, v) in cache.items(py) { |
|
356 c_cache.set_item(py, k, PySet::new(py, v)?)?; |
|
357 } |
|
358 |
|
359 let start_rev = UncheckedRevision(args.get_item(py, 1).extract(py)?); |
|
360 let end_rev = UncheckedRevision(args.get_item(py, 2).extract(py)?); |
|
361 let mut cache_wrapper = PySnapshotsCache{ py, dict: cache }; |
|
362 index.find_snapshots( |
|
363 start_rev, |
|
364 end_rev, |
|
365 &mut cache_wrapper, |
|
366 ).map_err(|_| revlog_error(py))?; |
|
367 Ok(py.None()) |
|
368 } |
|
369 |
|
370 /// determine revisions with deltas to reconstruct fulltext |
|
371 def deltachain(&self, *args, **_kw) -> PyResult<PyObject> { |
|
372 let index = self.index(py).borrow(); |
|
373 let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into(); |
|
374 let stop_rev = |
|
375 args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?; |
|
376 let rev = index.check_revision(rev).ok_or_else(|| { |
|
377 nodemap_error(py, NodeMapError::RevisionNotInIndex(rev)) |
|
378 })?; |
|
379 let stop_rev = if let Some(stop_rev) = stop_rev { |
|
380 let stop_rev = UncheckedRevision(stop_rev); |
|
381 Some(index.check_revision(stop_rev).ok_or_else(|| { |
|
382 nodemap_error(py, NodeMapError::RevisionNotInIndex(stop_rev)) |
|
383 })?) |
|
384 } else {None}; |
|
385 let using_general_delta = args.get_item(py, 2) |
|
386 .extract::<Option<u32>>(py)? |
|
387 .map(|i| i != 0); |
|
388 let (chain, stopped) = index.delta_chain( |
|
389 rev, stop_rev, using_general_delta |
|
390 ).map_err(|e| { |
|
391 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string()) |
|
392 })?; |
|
393 |
|
394 let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect(); |
|
395 Ok( |
|
396 PyTuple::new( |
|
397 py, |
|
398 &[ |
|
399 chain.into_py_object(py).into_object(), |
|
400 stopped.into_py_object(py).into_object() |
|
401 ] |
|
402 ).into_object() |
|
403 ) |
|
404 |
|
405 } |
|
406 |
|
407 /// slice planned chunk read to reach a density threshold |
|
408 def slicechunktodensity(&self, *args, **_kw) -> PyResult<PyObject> { |
|
409 let rust_res = self.inner_slicechunktodensity( |
|
410 py, |
|
411 args.get_item(py, 0), |
|
412 args.get_item(py, 1).extract(py)?, |
|
413 args.get_item(py, 2).extract(py)? |
|
414 )?; |
|
415 Ok(rust_res) |
|
416 } |
|
417 |
|
418 // index_sequence_methods and index_mapping_methods. |
|
419 // |
|
420 // Since we call back through the high level Python API, |
|
421 // there's no point making a distinction between index_get |
|
422 // and index_getitem. |
|
423 // gracinet 2023: this above is no longer true for the pure Rust impl |
|
424 |
|
425 def __len__(&self) -> PyResult<usize> { |
|
426 self.len(py) |
|
427 } |
|
428 |
|
429 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> { |
|
430 let rust_res = self.inner_getitem(py, key.clone_ref(py))?; |
|
431 Ok(rust_res) |
|
432 } |
|
433 |
|
434 def __contains__(&self, item: PyObject) -> PyResult<bool> { |
|
435 // ObjectProtocol does not seem to provide contains(), so |
|
436 // this is an equivalent implementation of the index_contains() |
|
437 // defined in revlog.c |
|
438 match item.extract::<i32>(py) { |
|
439 Ok(rev) => { |
|
440 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision) |
|
441 } |
|
442 Err(_) => { |
|
443 let item_bytes: PyBytes = item.extract(py)?; |
|
444 let rust_res = self.has_node(py, item_bytes)?; |
|
445 Ok(rust_res) |
|
446 } |
|
447 } |
|
448 } |
|
449 |
|
450 def nodemap_data_all(&self) -> PyResult<PyBytes> { |
|
451 self.inner_nodemap_data_all(py) |
|
452 } |
|
453 |
|
454 def nodemap_data_incremental(&self) -> PyResult<PyObject> { |
|
455 self.inner_nodemap_data_incremental(py) |
|
456 } |
|
457 def update_nodemap_data( |
|
458 &self, |
|
459 docket: PyObject, |
|
460 nm_data: PyObject |
|
461 ) -> PyResult<PyObject> { |
|
462 self.inner_update_nodemap_data(py, docket, nm_data) |
|
463 } |
|
464 |
|
465 @property |
|
466 def entry_size(&self) -> PyResult<PyInt> { |
|
467 let rust_res: PyInt = INDEX_ENTRY_SIZE.to_py_object(py); |
|
468 Ok(rust_res) |
|
469 } |
|
470 |
|
471 @property |
|
472 def rust_ext_compat(&self) -> PyResult<PyInt> { |
|
473 // will be entirely removed when the Rust index yet useful to |
|
474 // implement in Rust to detangle things when removing `self.cindex` |
|
475 let rust_res: PyInt = 1.to_py_object(py); |
|
476 Ok(rust_res) |
|
477 } |
|
478 |
|
479 @property |
|
480 def is_rust(&self) -> PyResult<PyBool> { |
|
481 Ok(false.to_py_object(py)) |
|
482 } |
|
483 |
|
484 }); |
|
485 |
105 |
486 /// Take a (potentially) mmap'ed buffer, and return the underlying Python |
106 /// Take a (potentially) mmap'ed buffer, and return the underlying Python |
487 /// buffer along with the Rust slice into said buffer. We need to keep the |
107 /// buffer along with the Rust slice into said buffer. We need to keep the |
488 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer |
108 /// Python buffer around, otherwise we'd get a dangling pointer once the buffer |
489 /// is freed from Python's side. |
109 /// is freed from Python's side. |
620 )) |
240 )) |
621 }) |
241 }) |
622 } |
242 } |
623 } |
243 } |
624 |
244 |
625 impl Index { |
245 // There are no static generics in Rust (because their implementation is hard, |
626 fn new(py: Python, data: PyObject, header: u32) -> PyResult<Self> { |
246 // I'm guessing it's due to different compilation stages, etc.). |
627 // Safety: we keep the buffer around inside the class as `index_mmap` |
247 // So manually generate all three caches and use them in `with_filelog_cache`. |
628 let (buf, bytes) = unsafe { mmap_keeparound(py, data)? }; |
248 static DELTA_CONFIG_CACHE: OnceLock<(PyObject, RevlogDeltaConfig)> = |
629 |
249 OnceLock::new(); |
630 Self::create_instance( |
250 static DATA_CONFIG_CACHE: OnceLock<(PyObject, RevlogDataConfig)> = |
|
251 OnceLock::new(); |
|
252 static FEATURE_CONFIG_CACHE: OnceLock<(PyObject, RevlogFeatureConfig)> = |
|
253 OnceLock::new(); |
|
254 |
|
255 /// Cache the first conversion from Python -> Rust config for all filelogs to |
|
256 /// save on conversion time when called in a loop. |
|
257 fn with_filelog_cache<T: Copy>( |
|
258 py: Python, |
|
259 py_config: &PyObject, |
|
260 revlog_type: RevlogType, |
|
261 cache: &OnceLock<(PyObject, T)>, |
|
262 callback: impl Fn() -> PyResult<T>, |
|
263 ) -> PyResult<T> { |
|
264 let mut was_cached = false; |
|
265 if revlog_type == RevlogType::Filelog { |
|
266 if let Some((cached_py_config, rust_config)) = cache.get() { |
|
267 was_cached = true; |
|
268 // All filelogs in a given repository *most likely* have the |
|
269 // exact same config, but it's not impossible that some extensions |
|
270 // do some magic with configs or that this code will be used |
|
271 // for longer-running processes. So compare the source `PyObject` |
|
272 // in case the source changed, at the cost of some overhead. |
|
273 // We can't use `py_config.eq(cached_py_config)` because all config |
|
274 // objects are different in Python and `a is b` is false. |
|
275 if py_config.compare(py, cached_py_config)?.is_eq() { |
|
276 return Ok(*rust_config); |
|
277 } |
|
278 } |
|
279 } |
|
280 let config = callback()?; |
|
281 // Do not call the lock unnecessarily if it's already been set. |
|
282 if !was_cached && revlog_type == RevlogType::Filelog { |
|
283 cache.set((py_config.clone_ref(py), config)).ok(); |
|
284 } |
|
285 Ok(config) |
|
286 } |
|
287 |
|
288 fn extract_delta_config( |
|
289 py: Python, |
|
290 py_config: PyObject, |
|
291 revlog_type: RevlogType, |
|
292 ) -> PyResult<RevlogDeltaConfig> { |
|
293 let get_delta_config = || { |
|
294 let max_deltachain_span = py_config |
|
295 .getattr(py, "max_deltachain_span")? |
|
296 .extract::<i64>(py)?; |
|
297 |
|
298 let revlog_delta_config = RevlogDeltaConfig { |
|
299 general_delta: py_config |
|
300 .getattr(py, "general_delta")? |
|
301 .extract(py)?, |
|
302 sparse_revlog: py_config |
|
303 .getattr(py, "sparse_revlog")? |
|
304 .extract(py)?, |
|
305 max_chain_len: py_config |
|
306 .getattr(py, "max_chain_len")? |
|
307 .extract(py)?, |
|
308 max_deltachain_span: if max_deltachain_span < 0 { |
|
309 None |
|
310 } else { |
|
311 Some(max_deltachain_span as u64) |
|
312 }, |
|
313 upper_bound_comp: py_config |
|
314 .getattr(py, "upper_bound_comp")? |
|
315 .extract(py)?, |
|
316 delta_both_parents: py_config |
|
317 .getattr(py, "delta_both_parents")? |
|
318 .extract(py)?, |
|
319 candidate_group_chunk_size: py_config |
|
320 .getattr(py, "candidate_group_chunk_size")? |
|
321 .extract(py)?, |
|
322 debug_delta: py_config.getattr(py, "debug_delta")?.extract(py)?, |
|
323 lazy_delta: py_config.getattr(py, "lazy_delta")?.extract(py)?, |
|
324 lazy_delta_base: py_config |
|
325 .getattr(py, "lazy_delta_base")? |
|
326 .extract(py)?, |
|
327 }; |
|
328 Ok(revlog_delta_config) |
|
329 }; |
|
330 with_filelog_cache( |
|
331 py, |
|
332 &py_config, |
|
333 revlog_type, |
|
334 &DELTA_CONFIG_CACHE, |
|
335 get_delta_config, |
|
336 ) |
|
337 } |
|
338 |
|
339 fn extract_data_config( |
|
340 py: Python, |
|
341 py_config: PyObject, |
|
342 revlog_type: RevlogType, |
|
343 ) -> PyResult<RevlogDataConfig> { |
|
344 let get_data_config = || { |
|
345 Ok(RevlogDataConfig { |
|
346 try_pending: py_config.getattr(py, "try_pending")?.extract(py)?, |
|
347 try_split: py_config.getattr(py, "try_split")?.extract(py)?, |
|
348 check_ambig: py_config.getattr(py, "check_ambig")?.extract(py)?, |
|
349 mmap_large_index: py_config |
|
350 .getattr(py, "mmap_large_index")? |
|
351 .extract(py)?, |
|
352 mmap_index_threshold: py_config |
|
353 .getattr(py, "mmap_index_threshold")? |
|
354 .extract(py)?, |
|
355 chunk_cache_size: py_config |
|
356 .getattr(py, "chunk_cache_size")? |
|
357 .extract(py)?, |
|
358 uncompressed_cache_factor: py_config |
|
359 .getattr(py, "uncompressed_cache_factor")? |
|
360 .extract(py)?, |
|
361 uncompressed_cache_count: py_config |
|
362 .getattr(py, "uncompressed_cache_count")? |
|
363 .extract(py)?, |
|
364 with_sparse_read: py_config |
|
365 .getattr(py, "with_sparse_read")? |
|
366 .extract(py)?, |
|
367 sr_density_threshold: py_config |
|
368 .getattr(py, "sr_density_threshold")? |
|
369 .extract(py)?, |
|
370 sr_min_gap_size: py_config |
|
371 .getattr(py, "sr_min_gap_size")? |
|
372 .extract(py)?, |
|
373 general_delta: py_config |
|
374 .getattr(py, "generaldelta")? |
|
375 .extract(py)?, |
|
376 }) |
|
377 }; |
|
378 |
|
379 with_filelog_cache( |
|
380 py, |
|
381 &py_config, |
|
382 revlog_type, |
|
383 &DATA_CONFIG_CACHE, |
|
384 get_data_config, |
|
385 ) |
|
386 } |
|
387 |
|
388 fn extract_feature_config( |
|
389 py: Python, |
|
390 py_config: PyObject, |
|
391 revlog_type: RevlogType, |
|
392 ) -> PyResult<RevlogFeatureConfig> { |
|
393 let get_feature_config = || { |
|
394 let engine_bytes = &py_config |
|
395 .getattr(py, "compression_engine")? |
|
396 .extract::<PyBytes>(py)?; |
|
397 let compression_engine = engine_bytes.data(py); |
|
398 let compression_engine = match compression_engine { |
|
399 b"zlib" => { |
|
400 let compression_options = &py_config |
|
401 .getattr(py, "compression_engine_options")? |
|
402 .extract::<PyDict>(py)?; |
|
403 let zlib_level = compression_options |
|
404 .get_item(py, PyBytes::new(py, &b"zlib.level"[..])); |
|
405 let level = if let Some(level) = zlib_level { |
|
406 if level.is_none(py) { |
|
407 None |
|
408 } else { |
|
409 Some(level.extract(py)?) |
|
410 } |
|
411 } else { |
|
412 None |
|
413 }; |
|
414 let mut engine = CompressionConfig::default(); |
|
415 if let Some(level) = level { |
|
416 engine |
|
417 .set_level(level) |
|
418 .expect("invalid compression level from Python"); |
|
419 } |
|
420 engine |
|
421 } |
|
422 b"zstd" => { |
|
423 let compression_options = &py_config |
|
424 .getattr(py, "compression_engine_options")? |
|
425 .extract::<PyDict>(py)?; |
|
426 let zstd_level = compression_options |
|
427 .get_item(py, PyBytes::new(py, &b"zstd.level"[..])); |
|
428 let level = if let Some(level) = zstd_level { |
|
429 if level.is_none(py) { |
|
430 None |
|
431 } else { |
|
432 Some(level.extract(py)?) |
|
433 } |
|
434 } else { |
|
435 let level = compression_options |
|
436 .get_item(py, PyBytes::new(py, &b"level"[..])); |
|
437 if let Some(level) = level { |
|
438 if level.is_none(py) { |
|
439 None |
|
440 } else { |
|
441 Some(level.extract(py)?) |
|
442 } |
|
443 } else { |
|
444 None |
|
445 } |
|
446 }; |
|
447 CompressionConfig::zstd(level) |
|
448 .expect("invalid compression level from Python") |
|
449 } |
|
450 b"none" => CompressionConfig::None, |
|
451 e => { |
|
452 return Err(PyErr::new::<ValueError, _>( |
|
453 py, |
|
454 format!( |
|
455 "invalid compression engine {}", |
|
456 String::from_utf8_lossy(e) |
|
457 ), |
|
458 )) |
|
459 } |
|
460 }; |
|
461 let revlog_feature_config = RevlogFeatureConfig { |
|
462 compression_engine, |
|
463 censorable: py_config.getattr(py, "censorable")?.extract(py)?, |
|
464 has_side_data: py_config |
|
465 .getattr(py, "has_side_data")? |
|
466 .extract(py)?, |
|
467 compute_rank: py_config |
|
468 .getattr(py, "compute_rank")? |
|
469 .extract(py)?, |
|
470 canonical_parent_order: py_config |
|
471 .getattr(py, "canonical_parent_order")? |
|
472 .extract(py)?, |
|
473 enable_ellipsis: py_config |
|
474 .getattr(py, "enable_ellipsis")? |
|
475 .extract(py)?, |
|
476 }; |
|
477 Ok(revlog_feature_config) |
|
478 }; |
|
479 with_filelog_cache( |
|
480 py, |
|
481 &py_config, |
|
482 revlog_type, |
|
483 &FEATURE_CONFIG_CACHE, |
|
484 get_feature_config, |
|
485 ) |
|
486 } |
|
487 |
|
488 fn revlog_error_from_msg(py: Python, e: impl ToString) -> PyErr { |
|
489 let msg = e.to_string(); |
|
490 |
|
491 match py |
|
492 .import("mercurial.error") |
|
493 .and_then(|m| m.get(py, "RevlogError")) |
|
494 { |
|
495 Err(e) => e, |
|
496 Ok(cls) => { |
|
497 let msg = PyBytes::new(py, msg.as_bytes()); |
|
498 PyErr::from_instance( |
|
499 py, |
|
500 cls.call(py, (msg,), None).ok().into_py_object(py), |
|
501 ) |
|
502 } |
|
503 } |
|
504 } |
|
505 |
|
506 py_class!(pub class ReadingContextManager |py| { |
|
507 data inner_revlog: RefCell<InnerRevlog>; |
|
508 |
|
509 def __enter__(&self) -> PyResult<PyObject> { |
|
510 let res = self.inner_revlog(py) |
|
511 .borrow() |
|
512 .inner(py) |
|
513 .borrow() |
|
514 .enter_reading_context() |
|
515 .map_err(|e| revlog_error_from_msg(py, e)); |
|
516 if let Err(e) = res { |
|
517 // `__exit__` is not called from Python if `__enter__` fails |
|
518 self.inner_revlog(py) |
|
519 .borrow() |
|
520 .inner(py) |
|
521 .borrow() |
|
522 .exit_reading_context(); |
|
523 return Err(e) |
|
524 } |
|
525 Ok(py.None()) |
|
526 } |
|
527 |
|
528 def __exit__( |
|
529 &self, |
|
530 ty: Option<PyType>, |
|
531 value: PyObject, |
|
532 traceback: PyObject |
|
533 ) -> PyResult<PyObject> { |
|
534 // unused arguments, keep clippy from complaining without adding |
|
535 // a general rule |
|
536 let _ = ty; |
|
537 let _ = value; |
|
538 let _ = traceback; |
|
539 |
|
540 self.inner_revlog(py) |
|
541 .borrow() |
|
542 .inner(py) |
|
543 .borrow() |
|
544 .exit_reading_context(); |
|
545 Ok(py.None()) |
|
546 } |
|
547 }); |
|
548 |
|
549 // Only used from Python *tests* |
|
550 py_class!(pub class PyFileHandle |py| { |
|
551 data inner_file: RefCell<std::os::fd::RawFd>; |
|
552 |
|
553 def tell(&self) -> PyResult<PyObject> { |
|
554 let locals = PyDict::new(py); |
|
555 locals.set_item(py, "os", py.import("os")?)?; |
|
556 locals.set_item(py, "fd", *self.inner_file(py).borrow())?; |
|
557 let f = py.eval("os.fdopen(fd)", None, Some(&locals))?; |
|
558 |
|
559 // Prevent Python from closing the file after garbage collecting. |
|
560 // This is fine since Rust is still holding on to the actual File. |
|
561 // (and also because it's only used in tests). |
|
562 std::mem::forget(f.clone_ref(py)); |
|
563 |
|
564 locals.set_item(py, "f", f)?; |
|
565 let res = py.eval("f.tell()", None, Some(&locals))?; |
|
566 Ok(res) |
|
567 } |
|
568 }); |
|
569 |
|
570 /// Wrapper around a Python transaction object, to keep `hg-core` oblivious |
|
571 /// of the fact it's being called from Python. |
|
572 pub struct PyTransaction { |
|
573 inner: PyObject, |
|
574 } |
|
575 |
|
576 impl PyTransaction { |
|
577 pub fn new(inner: PyObject) -> Self { |
|
578 Self { inner } |
|
579 } |
|
580 } |
|
581 |
|
582 impl Clone for PyTransaction { |
|
583 fn clone(&self) -> Self { |
|
584 let gil = &Python::acquire_gil(); |
|
585 let py = gil.python(); |
|
586 Self { |
|
587 inner: self.inner.clone_ref(py), |
|
588 } |
|
589 } |
|
590 } |
|
591 |
|
592 impl Transaction for PyTransaction { |
|
593 fn add(&mut self, file: impl AsRef<std::path::Path>, offset: usize) { |
|
594 let gil = &Python::acquire_gil(); |
|
595 let py = gil.python(); |
|
596 let file = PyBytes::new(py, &get_bytes_from_path(file.as_ref())); |
|
597 self.inner |
|
598 .call_method(py, "add", (file, offset), None) |
|
599 .expect("transaction add failed"); |
|
600 } |
|
601 } |
|
602 |
|
603 py_class!(pub class WritingContextManager |py| { |
|
604 data inner_revlog: RefCell<InnerRevlog>; |
|
605 data transaction: RefCell<PyTransaction>; |
|
606 data data_end: Cell<Option<usize>>; |
|
607 |
|
608 def __enter__(&self) -> PyResult<PyObject> { |
|
609 let res = self.inner_revlog(py) |
|
610 .borrow_mut() |
|
611 .inner(py) |
|
612 .borrow_mut() |
|
613 .enter_writing_context( |
|
614 self.data_end(py).get(), |
|
615 &mut *self.transaction(py).borrow_mut() |
|
616 ).map_err(|e| revlog_error_from_msg(py, e)); |
|
617 if let Err(e) = res { |
|
618 // `__exit__` is not called from Python if `__enter__` fails |
|
619 self.inner_revlog(py) |
|
620 .borrow_mut() |
|
621 .inner(py) |
|
622 .borrow_mut() |
|
623 .exit_writing_context(); |
|
624 return Err(e) |
|
625 } |
|
626 Ok(py.None()) |
|
627 } |
|
628 |
|
629 def __exit__( |
|
630 &self, |
|
631 ty: Option<PyType>, |
|
632 value: PyObject, |
|
633 traceback: PyObject |
|
634 ) -> PyResult<PyObject> { |
|
635 // unused arguments, keep clippy from complaining without adding |
|
636 // a general rule |
|
637 let _ = ty; |
|
638 let _ = value; |
|
639 let _ = traceback; |
|
640 |
|
641 self.inner_revlog(py) |
|
642 .borrow_mut() |
|
643 .inner(py) |
|
644 .borrow_mut() |
|
645 .exit_writing_context(); |
|
646 Ok(py.None()) |
|
647 } |
|
648 }); |
|
649 |
|
650 py_class!(pub class InnerRevlog |py| { |
|
651 @shared data inner: CoreInnerRevlog; |
|
652 data nt: RefCell<Option<CoreNodeTree>>; |
|
653 data docket: RefCell<Option<PyObject>>; |
|
654 // Holds a reference to the mmap'ed persistent nodemap data |
|
655 data nodemap_mmap: RefCell<Option<PyBuffer>>; |
|
656 // Holds a reference to the mmap'ed persistent index data |
|
657 data index_mmap: RefCell<PyBuffer>; |
|
658 data head_revs_py_list: RefCell<Option<PyList>>; |
|
659 data head_node_ids_py_list: RefCell<Option<PyList>>; |
|
660 data revision_cache: RefCell<Option<PyObject>>; |
|
661 |
|
662 def __new__( |
|
663 _cls, |
|
664 opener: PyObject, |
|
665 index_data: PyObject, |
|
666 index_file: PyObject, |
|
667 data_file: PyObject, |
|
668 sidedata_file: PyObject, |
|
669 inline: bool, |
|
670 data_config: PyObject, |
|
671 delta_config: PyObject, |
|
672 feature_config: PyObject, |
|
673 chunk_cache: PyObject, |
|
674 default_compression_header: PyObject, |
|
675 revlog_type: usize, |
|
676 ) -> PyResult<Self> { |
|
677 Self::inner_new( |
631 py, |
678 py, |
632 hg::index::Index::new( |
679 opener, |
633 bytes, |
680 index_data, |
634 IndexHeader::parse(&header.to_be_bytes()) |
681 index_file, |
635 .expect("default header is broken"), |
682 data_file, |
|
683 sidedata_file, |
|
684 inline, |
|
685 data_config, |
|
686 delta_config, |
|
687 feature_config, |
|
688 chunk_cache, |
|
689 default_compression_header, |
|
690 revlog_type |
|
691 ) |
|
692 } |
|
693 |
|
694 def clear_cache(&self) -> PyResult<PyObject> { |
|
695 assert!(!self.is_delaying(py)?); |
|
696 self.revision_cache(py).borrow_mut().take(); |
|
697 self.inner(py).borrow_mut().clear_cache(); |
|
698 Ok(py.None()) |
|
699 } |
|
700 |
|
701 @property def canonical_index_file(&self) -> PyResult<PyBytes> { |
|
702 let path = self.inner(py).borrow().canonical_index_file(); |
|
703 Ok(PyBytes::new(py, &get_bytes_from_path(path))) |
|
704 } |
|
705 |
|
706 @property def is_delaying(&self) -> PyResult<bool> { |
|
707 Ok(self.inner(py).borrow().is_delaying()) |
|
708 } |
|
709 |
|
710 @property def _revisioncache(&self) -> PyResult<PyObject> { |
|
711 let cache = &*self.revision_cache(py).borrow(); |
|
712 match cache { |
|
713 None => Ok(py.None()), |
|
714 Some(cache) => { |
|
715 Ok(cache.clone_ref(py)) |
|
716 } |
|
717 } |
|
718 |
|
719 } |
|
720 |
|
721 @property def _writinghandles(&self) -> PyResult<PyObject> { |
|
722 use std::os::fd::AsRawFd; |
|
723 |
|
724 let inner = self.inner(py).borrow(); |
|
725 let handles = inner.python_writing_handles(); |
|
726 |
|
727 match handles.as_ref() { |
|
728 None => Ok(py.None()), |
|
729 Some(handles) => { |
|
730 let d_handle = if let Some(d_handle) = &handles.data_handle { |
|
731 let handle = RefCell::new(d_handle.file.as_raw_fd()); |
|
732 Some(PyFileHandle::create_instance(py, handle)?) |
|
733 } else { |
|
734 None |
|
735 }; |
|
736 let handle = |
|
737 RefCell::new(handles.index_handle.file.as_raw_fd()); |
|
738 Ok( |
|
739 ( |
|
740 PyFileHandle::create_instance(py, handle)?, |
|
741 d_handle, |
|
742 py.None(), // Sidedata handle |
|
743 |
|
744 ).to_py_object(py).into_object() |
|
745 ) |
|
746 } |
|
747 } |
|
748 |
|
749 } |
|
750 |
|
751 @_revisioncache.setter def set_revision_cache( |
|
752 &self, |
|
753 value: Option<PyObject> |
|
754 ) -> PyResult<()> { |
|
755 *self.revision_cache(py).borrow_mut() = value.clone_ref(py); |
|
756 match value { |
|
757 None => { |
|
758 // This means the property has been deleted, *not* that the |
|
759 // property has been set to `None`. Whatever happens is up |
|
760 // to the implementation. Here we just set it to `None`. |
|
761 self |
|
762 .inner(py) |
|
763 .borrow() |
|
764 .last_revision_cache |
|
765 .lock() |
|
766 .expect("lock should not be held") |
|
767 .take(); |
|
768 }, |
|
769 Some(tuple) => { |
|
770 if tuple.is_none(py) { |
|
771 self |
|
772 .inner(py) |
|
773 .borrow() |
|
774 .last_revision_cache |
|
775 .lock() |
|
776 .expect("lock should not be held") |
|
777 .take(); |
|
778 return Ok(()) |
|
779 } |
|
780 let node = tuple.get_item(py, 0)?.extract::<PyBytes>(py)?; |
|
781 let node = node_from_py_bytes(py, &node)?; |
|
782 let rev = tuple.get_item(py, 1)?.extract::<BaseRevision>(py)?; |
|
783 // Ok because Python only sets this if the revision has been |
|
784 // checked |
|
785 let rev = Revision(rev); |
|
786 let data = tuple.get_item(py, 2)?.extract::<PyBytes>(py)?; |
|
787 let inner = self.inner(py).borrow(); |
|
788 let mut last_revision_cache = inner |
|
789 .last_revision_cache |
|
790 .lock() |
|
791 .expect("lock should not be held"); |
|
792 *last_revision_cache = |
|
793 Some((node, rev, Box::new(PyBytesDeref::new(py, data)))); |
|
794 } |
|
795 } |
|
796 Ok(()) |
|
797 } |
|
798 |
|
799 @property def inline(&self) -> PyResult<bool> { |
|
800 Ok(self.inner(py).borrow().is_inline()) |
|
801 } |
|
802 |
|
803 @inline.setter def set_inline( |
|
804 &self, |
|
805 value: Option<PyObject> |
|
806 ) -> PyResult<()> { |
|
807 if let Some(v) = value { |
|
808 self.inner(py).borrow_mut().inline = v.extract(py)?; |
|
809 }; |
|
810 Ok(()) |
|
811 } |
|
812 |
|
813 @property def index_file(&self) -> PyResult<PyBytes> { |
|
814 Ok( |
|
815 PyBytes::new( |
|
816 py, |
|
817 &get_bytes_from_path(&self.inner(py).borrow().index_file) |
636 ) |
818 ) |
637 .map_err(|e| { |
|
638 revlog_error_with_msg(py, e.to_string().as_bytes()) |
|
639 })?, |
|
640 RefCell::new(None), |
|
641 RefCell::new(None), |
|
642 RefCell::new(None), |
|
643 RefCell::new(Some(buf)), |
|
644 RefCell::new(None), |
|
645 RefCell::new(None), |
|
646 ) |
819 ) |
647 } |
820 } |
648 |
821 |
|
822 @index_file.setter def set_index_file( |
|
823 &self, |
|
824 value: Option<PyObject> |
|
825 ) -> PyResult<()> { |
|
826 let path = get_path_from_bytes( |
|
827 value |
|
828 .expect("don't delete the index path") |
|
829 .extract::<PyBytes>(py)? |
|
830 .data(py) |
|
831 ).to_owned(); |
|
832 self.inner(py).borrow_mut().index_file = path; |
|
833 Ok(()) |
|
834 } |
|
835 |
|
836 @property def is_writing(&self) -> PyResult<bool> { |
|
837 Ok(self.inner(py).borrow().is_writing()) |
|
838 } |
|
839 |
|
840 @property def is_open(&self) -> PyResult<bool> { |
|
841 Ok(self.inner(py).borrow().is_open()) |
|
842 } |
|
843 |
|
844 def issnapshot(&self, rev: PyRevision) -> PyResult<bool> { |
|
845 self.inner_issnapshot(py, UncheckedRevision(rev.0)) |
|
846 } |
|
847 |
|
848 def _deltachain(&self, *args, **kw) -> PyResult<PyObject> { |
|
849 let inner = self.inner(py).borrow(); |
|
850 let general_delta = inner.index.uses_generaldelta(); |
|
851 let args = PyTuple::new( |
|
852 py, |
|
853 &[ |
|
854 args.get_item(py, 0), |
|
855 kw.and_then(|d| d.get_item(py, "stoprev")).to_py_object(py), |
|
856 general_delta.to_py_object(py).into_object(), |
|
857 ] |
|
858 ); |
|
859 self._index_deltachain(py, &args, kw) |
|
860 } |
|
861 |
|
862 def compress(&self, data: PyObject) -> PyResult<PyTuple> { |
|
863 let inner = self.inner(py).borrow(); |
|
864 let py_buffer = PyBuffer::get(py, &data)?; |
|
865 let deref = PyBufferDeref::new(py, py_buffer)?; |
|
866 let compressed = inner.compress(&deref) |
|
867 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
868 let compressed = compressed.as_deref(); |
|
869 let header = if compressed.is_some() { |
|
870 PyBytes::new(py, &b""[..]) |
|
871 } else { |
|
872 PyBytes::new(py, &b"u"[..]) |
|
873 }; |
|
874 Ok( |
|
875 ( |
|
876 header, |
|
877 PyBytes::new(py, compressed.unwrap_or(&deref)) |
|
878 ).to_py_object(py) |
|
879 ) |
|
880 } |
|
881 |
|
882 def reading(&self) -> PyResult<ReadingContextManager> { |
|
883 ReadingContextManager::create_instance( |
|
884 py, |
|
885 RefCell::new(self.clone_ref(py)), |
|
886 ) |
|
887 } |
|
888 |
|
889 def writing( |
|
890 &self, |
|
891 transaction: PyObject, |
|
892 data_end: Option<usize>, |
|
893 sidedata_end: Option<usize>, |
|
894 ) -> PyResult<WritingContextManager> { |
|
895 // Silence unused argument (only relevant for changelog v2) |
|
896 let _ = sidedata_end; |
|
897 WritingContextManager::create_instance( |
|
898 py, |
|
899 RefCell::new(self.clone_ref(py)), |
|
900 RefCell::new(PyTransaction::new(transaction)), |
|
901 Cell::new(data_end) |
|
902 ) |
|
903 } |
|
904 |
|
905 def split_inline( |
|
906 &self, |
|
907 _tr: PyObject, |
|
908 header: i32, |
|
909 new_index_file_path: Option<PyObject> |
|
910 ) -> PyResult<PyBytes> { |
|
911 let mut inner = self.inner(py).borrow_mut(); |
|
912 let new_index_file_path = match new_index_file_path { |
|
913 Some(path) => { |
|
914 let path = path.extract::<PyBytes>(py)?; |
|
915 Some(get_path_from_bytes(path.data(py)).to_owned()) |
|
916 }, |
|
917 None => None, |
|
918 }; |
|
919 let header = hg::index::IndexHeader::parse(&header.to_be_bytes()); |
|
920 let header = header.expect("invalid header bytes"); |
|
921 let path = inner |
|
922 .split_inline(header, new_index_file_path) |
|
923 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
924 Ok(PyBytes::new(py, &get_bytes_from_path(path))) |
|
925 } |
|
926 |
|
927 def get_segment_for_revs( |
|
928 &self, |
|
929 startrev: PyRevision, |
|
930 endrev: PyRevision, |
|
931 ) -> PyResult<PyTuple> { |
|
932 let inner = self.inner(py).borrow(); |
|
933 let (offset, data) = inner |
|
934 .get_segment_for_revs(Revision(startrev.0), Revision(endrev.0)) |
|
935 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
936 let data = PyBytes::new(py, &data); |
|
937 Ok((offset, data).to_py_object(py)) |
|
938 } |
|
939 |
|
940 def raw_text( |
|
941 &self, |
|
942 _node: PyObject, |
|
943 rev: PyRevision |
|
944 ) -> PyResult<PyBytes> { |
|
945 let inner = self.inner(py).borrow(); |
|
946 let mut py_bytes = PyBytes::new(py, &[]); |
|
947 inner |
|
948 .raw_text(Revision(rev.0), |size, f| { |
|
949 py_bytes = with_pybytes_buffer(py, size, f)?; |
|
950 Ok(()) |
|
951 }).map_err(|e| revlog_error_from_msg(py, e))?; |
|
952 Ok(py_bytes) |
|
953 } |
|
954 |
|
955 def _chunk( |
|
956 &self, |
|
957 rev: PyRevision, |
|
958 ) -> PyResult<PyBytes> { |
|
959 let inner = self.inner(py).borrow(); |
|
960 let chunk = inner |
|
961 .chunk_for_rev(Revision(rev.0)) |
|
962 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
963 let chunk = PyBytes::new(py, &chunk); |
|
964 Ok(chunk) |
|
965 } |
|
966 |
|
967 def write_entry( |
|
968 &self, |
|
969 transaction: PyObject, |
|
970 entry: PyObject, |
|
971 data: PyTuple, |
|
972 _link: PyObject, |
|
973 offset: usize, |
|
974 _sidedata: PyObject, |
|
975 _sidedata_offset: PyInt, |
|
976 index_end: Option<u64>, |
|
977 data_end: Option<u64>, |
|
978 _sidedata_end: Option<PyInt>, |
|
979 ) -> PyResult<PyTuple> { |
|
980 let mut inner = self.inner(py).borrow_mut(); |
|
981 let transaction = PyTransaction::new(transaction); |
|
982 let py_bytes = entry.extract(py)?; |
|
983 let entry = PyBytesDeref::new(py, py_bytes); |
|
984 let header = data.get_item(py, 0).extract::<PyBytes>(py)?; |
|
985 let header = header.data(py); |
|
986 let data = data.get_item(py, 1); |
|
987 let py_bytes = data.extract(py)?; |
|
988 let data = PyBytesDeref::new(py, py_bytes); |
|
989 Ok( |
|
990 inner.write_entry( |
|
991 transaction, |
|
992 &entry, |
|
993 (header, &data), |
|
994 offset, |
|
995 index_end, |
|
996 data_end |
|
997 ).map_err(|e| revlog_error_from_msg(py, e))? |
|
998 .to_py_object(py) |
|
999 ) |
|
1000 } |
|
1001 |
|
1002 def delay(&self) -> PyResult<Option<PyBytes>> { |
|
1003 let path = self.inner(py) |
|
1004 .borrow_mut() |
|
1005 .delay() |
|
1006 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
1007 Ok(path.map(|p| PyBytes::new(py, &get_bytes_from_path(p)))) |
|
1008 } |
|
1009 |
|
1010 def write_pending(&self) -> PyResult<PyTuple> { |
|
1011 let (path, any_pending) = self.inner(py) |
|
1012 .borrow_mut() |
|
1013 .write_pending() |
|
1014 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
1015 let maybe_path = match path { |
|
1016 Some(path) => { |
|
1017 PyBytes::new(py, &get_bytes_from_path(path)).into_object() |
|
1018 }, |
|
1019 None => { |
|
1020 py.None() |
|
1021 } |
|
1022 }; |
|
1023 Ok( |
|
1024 ( |
|
1025 maybe_path, |
|
1026 any_pending |
|
1027 ).to_py_object(py) |
|
1028 ) |
|
1029 } |
|
1030 |
|
1031 def finalize_pending(&self) -> PyResult<PyBytes> { |
|
1032 let path = self.inner(py) |
|
1033 .borrow_mut() |
|
1034 .finalize_pending() |
|
1035 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
1036 Ok(PyBytes::new(py, &get_bytes_from_path(path))) |
|
1037 } |
|
1038 |
|
1039 // -- forwarded index methods -- |
|
1040 |
|
1041 def _index_get_rev(&self, node: PyBytes) -> PyResult<Option<PyRevision>> { |
|
1042 let opt = self.get_nodetree(py)?.borrow(); |
|
1043 let nt = opt.as_ref().expect("nodetree should be set"); |
|
1044 let ridx = &self.inner(py).borrow().index; |
|
1045 let node = node_from_py_bytes(py, &node)?; |
|
1046 let rust_rev = |
|
1047 nt.find_bin(ridx, node.into()).map_err(|e| nodemap_error(py, e))?; |
|
1048 Ok(rust_rev.map(Into::into)) |
|
1049 } |
|
1050 |
|
1051 /// same as `_index_get_rev()` but raises a bare `error.RevlogError` if node |
|
1052 /// is not found. |
|
1053 /// |
|
1054 /// No need to repeat `node` in the exception, `mercurial/revlog.py` |
|
1055 /// will catch and rewrap with it |
|
1056 def _index_rev(&self, node: PyBytes) -> PyResult<PyRevision> { |
|
1057 self._index_get_rev(py, node)?.ok_or_else(|| revlog_error(py)) |
|
1058 } |
|
1059 |
|
1060 /// return True if the node exist in the index |
|
1061 def _index_has_node(&self, node: PyBytes) -> PyResult<bool> { |
|
1062 // TODO OPTIM we could avoid a needless conversion here, |
|
1063 // to do when scaffolding for pure Rust switch is removed, |
|
1064 // as `_index_get_rev()` currently does the necessary assertions |
|
1065 self._index_get_rev(py, node).map(|opt| opt.is_some()) |
|
1066 } |
|
1067 |
|
1068 /// find length of shortest hex nodeid of a binary ID |
|
1069 def _index_shortest(&self, node: PyBytes) -> PyResult<usize> { |
|
1070 let opt = self.get_nodetree(py)?.borrow(); |
|
1071 let nt = opt.as_ref().expect("nodetree should be set"); |
|
1072 let idx = &self.inner(py).borrow().index; |
|
1073 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?) |
|
1074 { |
|
1075 Ok(Some(l)) => Ok(l), |
|
1076 Ok(None) => Err(revlog_error(py)), |
|
1077 Err(e) => Err(nodemap_error(py, e)), |
|
1078 } |
|
1079 } |
|
1080 |
|
1081 def _index_partialmatch( |
|
1082 &self, |
|
1083 node: PyObject |
|
1084 ) -> PyResult<Option<PyBytes>> { |
|
1085 let opt = self.get_nodetree(py)?.borrow(); |
|
1086 let nt = opt.as_ref().expect("nodetree should be set"); |
|
1087 let idx = &self.inner(py).borrow().index; |
|
1088 |
|
1089 let node = node.extract::<PyBytes>(py)?; |
|
1090 let node_as_string = String::from_utf8_lossy(node.data(py)); |
|
1091 |
|
1092 let prefix = NodePrefix::from_hex(node_as_string.to_string()) |
|
1093 .map_err(|_| PyErr::new::<ValueError, _>( |
|
1094 py, format!("Invalid node or prefix '{}'", node_as_string)) |
|
1095 )?; |
|
1096 |
|
1097 nt.find_bin(idx, prefix) |
|
1098 // TODO make an inner API returning the node directly |
|
1099 .map(|opt| opt.map(|rev| { |
|
1100 PyBytes::new( |
|
1101 py, |
|
1102 idx.node(rev).expect("node should exist").as_bytes() |
|
1103 ) |
|
1104 })) |
|
1105 .map_err(|e| nodemap_error(py, e)) |
|
1106 |
|
1107 } |
|
1108 |
|
1109 /// append an index entry |
|
1110 def _index_append(&self, tup: PyTuple) -> PyResult<PyObject> { |
|
1111 if tup.len(py) < 8 { |
|
1112 // this is better than the panic promised by tup.get_item() |
|
1113 return Err( |
|
1114 PyErr::new::<IndexError, _>(py, "tuple index out of range")) |
|
1115 } |
|
1116 let node_bytes = tup.get_item(py, 7).extract(py)?; |
|
1117 let node = node_from_py_object(py, &node_bytes)?; |
|
1118 |
|
1119 let rev = self.len(py)? as BaseRevision; |
|
1120 |
|
1121 // This is ok since we will just add the revision to the index |
|
1122 let rev = Revision(rev); |
|
1123 self.inner(py) |
|
1124 .borrow_mut() |
|
1125 .index |
|
1126 .append(py_tuple_to_revision_data_params(py, tup)?) |
|
1127 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
1128 let idx = &self.inner(py).borrow().index; |
|
1129 self.get_nodetree(py)? |
|
1130 .borrow_mut() |
|
1131 .as_mut() |
|
1132 .expect("nodetree should be set") |
|
1133 .insert(idx, &node, rev) |
|
1134 .map_err(|e| nodemap_error(py, e))?; |
|
1135 Ok(py.None()) |
|
1136 } |
|
1137 |
|
1138 def _index___delitem__(&self, key: PyObject) -> PyResult<PyObject> { |
|
1139 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]` |
|
1140 let start = if let Ok(rev) = key.extract(py) { |
|
1141 UncheckedRevision(rev) |
|
1142 } else { |
|
1143 let start = key.getattr(py, "start")?; |
|
1144 UncheckedRevision(start.extract(py)?) |
|
1145 }; |
|
1146 let mut borrow = self.inner(py).borrow_mut(); |
|
1147 let start = borrow |
|
1148 .index |
|
1149 .check_revision(start) |
|
1150 .ok_or_else(|| { |
|
1151 nodemap_error(py, NodeMapError::RevisionNotInIndex(start)) |
|
1152 })?; |
|
1153 borrow.index |
|
1154 .remove(start) |
|
1155 .map_err(|e| revlog_error_from_msg(py, e))?; |
|
1156 drop(borrow); |
|
1157 let mut opt = self.get_nodetree(py)?.borrow_mut(); |
|
1158 let nt = opt.as_mut().expect("nodetree should be set"); |
|
1159 nt.invalidate_all(); |
|
1160 self.fill_nodemap(py, nt)?; |
|
1161 Ok(py.None()) |
|
1162 } |
|
1163 |
|
1164 /// return the gca set of the given revs |
|
1165 def _index_ancestors(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1166 let rust_res = self.inner_ancestors(py, args)?; |
|
1167 Ok(rust_res) |
|
1168 } |
|
1169 |
|
1170 /// return the heads of the common ancestors of the given revs |
|
1171 def _index_commonancestorsheads( |
|
1172 &self, |
|
1173 *args, |
|
1174 **_kw |
|
1175 ) -> PyResult<PyObject> { |
|
1176 let rust_res = self.inner_commonancestorsheads(py, args)?; |
|
1177 Ok(rust_res) |
|
1178 } |
|
1179 |
|
1180 /// Clear the index caches and inner py_class data. |
|
1181 /// It is Python's responsibility to call `update_nodemap_data` again. |
|
1182 def _index_clearcaches(&self) -> PyResult<PyObject> { |
|
1183 self.nt(py).borrow_mut().take(); |
|
1184 self.docket(py).borrow_mut().take(); |
|
1185 self.nodemap_mmap(py).borrow_mut().take(); |
|
1186 self.head_revs_py_list(py).borrow_mut().take(); |
|
1187 self.head_node_ids_py_list(py).borrow_mut().take(); |
|
1188 self.inner(py).borrow_mut().index.clear_caches(); |
|
1189 Ok(py.None()) |
|
1190 } |
|
1191 |
|
1192 /// return the raw binary string representing a revision |
|
1193 def _index_entry_binary(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1194 let rindex = &self.inner(py).borrow().index; |
|
1195 let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?); |
|
1196 let rust_bytes = rindex.check_revision(rev).and_then( |
|
1197 |r| rindex.entry_binary(r)).ok_or_else(|| rev_not_in_index(py, rev) |
|
1198 )?; |
|
1199 let rust_res = PyBytes::new(py, rust_bytes).into_object(); |
|
1200 Ok(rust_res) |
|
1201 } |
|
1202 |
|
1203 |
|
1204 /// return a binary packed version of the header |
|
1205 def _index_pack_header(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1206 let rindex = &self.inner(py).borrow().index; |
|
1207 let packed = rindex.pack_header(args.get_item(py, 0).extract(py)?); |
|
1208 let rust_res = PyBytes::new(py, &packed).into_object(); |
|
1209 Ok(rust_res) |
|
1210 } |
|
1211 |
|
1212 /// compute phases |
|
1213 def _index_computephasesmapsets( |
|
1214 &self, |
|
1215 *args, |
|
1216 **_kw |
|
1217 ) -> PyResult<PyObject> { |
|
1218 let py_roots = args.get_item(py, 0).extract::<PyDict>(py)?; |
|
1219 let rust_res = self.inner_computephasesmapsets(py, py_roots)?; |
|
1220 Ok(rust_res) |
|
1221 } |
|
1222 |
|
1223 /// reachableroots |
|
1224 def _index_reachableroots2(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1225 let rust_res = self.inner_reachableroots2( |
|
1226 py, |
|
1227 UncheckedRevision(args.get_item(py, 0).extract(py)?), |
|
1228 args.get_item(py, 1), |
|
1229 args.get_item(py, 2), |
|
1230 args.get_item(py, 3).extract(py)?, |
|
1231 )?; |
|
1232 Ok(rust_res) |
|
1233 } |
|
1234 |
|
1235 /// get head revisions |
|
1236 def _index_headrevs(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1237 let (filtered_revs, stop_rev) = match &args.len(py) { |
|
1238 0 => Ok((py.None(), py.None())), |
|
1239 1 => Ok((args.get_item(py, 0), py.None())), |
|
1240 2 => Ok((args.get_item(py, 0), args.get_item(py, 1))), |
|
1241 _ => Err(PyErr::new::<cpython::exc::TypeError, _>(py, "too many arguments")), |
|
1242 }?; |
|
1243 self.inner_headrevs(py, &filtered_revs, &stop_rev) |
|
1244 } |
|
1245 |
|
1246 /// get head nodeids |
|
1247 def _index_head_node_ids(&self) -> PyResult<PyObject> { |
|
1248 let rust_res = self.inner_head_node_ids(py)?; |
|
1249 Ok(rust_res) |
|
1250 } |
|
1251 |
|
1252 /// get diff in head revisions |
|
1253 def _index_headrevsdiff(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1254 let rust_res = self.inner_headrevsdiff( |
|
1255 py, |
|
1256 &args.get_item(py, 0), |
|
1257 &args.get_item(py, 1))?; |
|
1258 Ok(rust_res) |
|
1259 } |
|
1260 |
|
1261 /// True if the object is a snapshot |
|
1262 def _index_issnapshot(&self, *args, **_kw) -> PyResult<bool> { |
|
1263 let rev = UncheckedRevision(args.get_item(py, 0).extract(py)?); |
|
1264 self.inner_issnapshot(py, rev) |
|
1265 } |
|
1266 |
|
1267 /// Gather snapshot data in a cache dict |
|
1268 def _index_findsnapshots(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1269 let index = &self.inner(py).borrow().index; |
|
1270 let cache: PyDict = args.get_item(py, 0).extract(py)?; |
|
1271 // this methods operates by setting new values in the cache, |
|
1272 // hence we will compare results by letting the C implementation |
|
1273 // operate over a deepcopy of the cache, and finally compare both |
|
1274 // caches. |
|
1275 let c_cache = PyDict::new(py); |
|
1276 for (k, v) in cache.items(py) { |
|
1277 c_cache.set_item(py, k, PySet::new(py, v)?)?; |
|
1278 } |
|
1279 |
|
1280 let start_rev = UncheckedRevision(args.get_item(py, 1).extract(py)?); |
|
1281 let end_rev = UncheckedRevision(args.get_item(py, 2).extract(py)?); |
|
1282 let mut cache_wrapper = PySnapshotsCache{ py, dict: cache }; |
|
1283 index.find_snapshots( |
|
1284 start_rev, |
|
1285 end_rev, |
|
1286 &mut cache_wrapper, |
|
1287 ).map_err(|_| revlog_error(py))?; |
|
1288 Ok(py.None()) |
|
1289 } |
|
1290 |
|
1291 /// determine revisions with deltas to reconstruct fulltext |
|
1292 def _index_deltachain(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1293 let index = &self.inner(py).borrow().index; |
|
1294 let rev = args.get_item(py, 0).extract::<BaseRevision>(py)?.into(); |
|
1295 let stop_rev = |
|
1296 args.get_item(py, 1).extract::<Option<BaseRevision>>(py)?; |
|
1297 let rev = index.check_revision(rev).ok_or_else(|| { |
|
1298 nodemap_error(py, NodeMapError::RevisionNotInIndex(rev)) |
|
1299 })?; |
|
1300 let stop_rev = if let Some(stop_rev) = stop_rev { |
|
1301 let stop_rev = UncheckedRevision(stop_rev); |
|
1302 Some(index.check_revision(stop_rev).ok_or_else(|| { |
|
1303 nodemap_error(py, NodeMapError::RevisionNotInIndex(stop_rev)) |
|
1304 })?) |
|
1305 } else {None}; |
|
1306 let using_general_delta = args.get_item(py, 2) |
|
1307 .extract::<Option<u32>>(py)? |
|
1308 .map(|i| i != 0); |
|
1309 let (chain, stopped) = index.delta_chain( |
|
1310 rev, stop_rev, using_general_delta |
|
1311 ).map_err(|e| { |
|
1312 PyErr::new::<cpython::exc::ValueError, _>(py, e.to_string()) |
|
1313 })?; |
|
1314 |
|
1315 let chain: Vec<_> = chain.into_iter().map(|r| r.0).collect(); |
|
1316 Ok( |
|
1317 PyTuple::new( |
|
1318 py, |
|
1319 &[ |
|
1320 chain.into_py_object(py).into_object(), |
|
1321 stopped.into_py_object(py).into_object() |
|
1322 ] |
|
1323 ).into_object() |
|
1324 ) |
|
1325 } |
|
1326 |
|
1327 /// slice planned chunk read to reach a density threshold |
|
1328 def _index_slicechunktodensity(&self, *args, **_kw) -> PyResult<PyObject> { |
|
1329 let rust_res = self.inner_slicechunktodensity( |
|
1330 py, |
|
1331 args.get_item(py, 0), |
|
1332 args.get_item(py, 1).extract(py)?, |
|
1333 args.get_item(py, 2).extract(py)? |
|
1334 )?; |
|
1335 Ok(rust_res) |
|
1336 } |
|
1337 |
|
1338 def _index___len__(&self) -> PyResult<usize> { |
|
1339 self.len(py) |
|
1340 } |
|
1341 |
|
1342 def _index___getitem__(&self, key: PyObject) -> PyResult<PyObject> { |
|
1343 let rust_res = self.inner_getitem(py, key.clone_ref(py))?; |
|
1344 Ok(rust_res) |
|
1345 } |
|
1346 |
|
1347 def _index___contains__(&self, item: PyObject) -> PyResult<bool> { |
|
1348 // ObjectProtocol does not seem to provide contains(), so |
|
1349 // this is an equivalent implementation of the index_contains() |
|
1350 // defined in revlog.c |
|
1351 match item.extract::<i32>(py) { |
|
1352 Ok(rev) => { |
|
1353 Ok(rev >= -1 && rev < self.len(py)? as BaseRevision) |
|
1354 } |
|
1355 Err(_) => { |
|
1356 let item_bytes: PyBytes = item.extract(py)?; |
|
1357 let rust_res = self._index_has_node(py, item_bytes)?; |
|
1358 Ok(rust_res) |
|
1359 } |
|
1360 } |
|
1361 } |
|
1362 |
|
1363 def _index_nodemap_data_all(&self) -> PyResult<PyBytes> { |
|
1364 self.inner_nodemap_data_all(py) |
|
1365 } |
|
1366 |
|
1367 def _index_nodemap_data_incremental(&self) -> PyResult<PyObject> { |
|
1368 self.inner_nodemap_data_incremental(py) |
|
1369 } |
|
1370 |
|
1371 def _index_update_nodemap_data( |
|
1372 &self, |
|
1373 docket: PyObject, |
|
1374 nm_data: PyObject |
|
1375 ) -> PyResult<PyObject> { |
|
1376 self.inner_update_nodemap_data(py, docket, nm_data) |
|
1377 } |
|
1378 |
|
1379 @property |
|
1380 def _index_entry_size(&self) -> PyResult<PyInt> { |
|
1381 let rust_res: PyInt = INDEX_ENTRY_SIZE.to_py_object(py); |
|
1382 Ok(rust_res) |
|
1383 } |
|
1384 |
|
1385 @property |
|
1386 def _index_rust_ext_compat(&self) -> PyResult<PyInt> { |
|
1387 // will be entirely removed when the Rust index yet useful to |
|
1388 // implement in Rust to detangle things when removing `self.cindex` |
|
1389 let rust_res: PyInt = 1.to_py_object(py); |
|
1390 Ok(rust_res) |
|
1391 } |
|
1392 |
|
1393 @property |
|
1394 def _index_is_rust(&self) -> PyResult<PyBool> { |
|
1395 Ok(false.to_py_object(py)) |
|
1396 } |
|
1397 |
|
1398 |
|
1399 }); |
|
1400 |
|
1401 /// Forwarded index methods? |
|
1402 impl InnerRevlog { |
649 fn len(&self, py: Python) -> PyResult<usize> { |
1403 fn len(&self, py: Python) -> PyResult<usize> { |
650 let rust_index_len = self.index(py).borrow().len(); |
1404 let rust_index_len = self.inner(py).borrow().index.len(); |
651 Ok(rust_index_len) |
1405 Ok(rust_index_len) |
652 } |
1406 } |
653 |
|
654 /// This is scaffolding at this point, but it could also become |
1407 /// This is scaffolding at this point, but it could also become |
655 /// a way to start a persistent nodemap or perform a |
1408 /// a way to start a persistent nodemap or perform a |
656 /// vacuum / repack operation |
1409 /// vacuum / repack operation |
657 fn fill_nodemap( |
1410 fn fill_nodemap( |
658 &self, |
1411 &self, |
659 py: Python, |
1412 py: Python, |
660 nt: &mut CoreNodeTree, |
1413 nt: &mut CoreNodeTree, |
661 ) -> PyResult<PyObject> { |
1414 ) -> PyResult<PyObject> { |
662 let index = self.index(py).borrow(); |
1415 let index = &self.inner(py).borrow().index; |
663 for r in 0..self.len(py)? { |
1416 for r in 0..self.len(py)? { |
664 let rev = Revision(r as BaseRevision); |
1417 let rev = Revision(r as BaseRevision); |
665 // in this case node() won't ever return None |
1418 // in this case node() won't ever return None |
666 nt.insert(&*index, index.node(rev).unwrap(), rev) |
1419 nt.insert(index, index.node(rev).expect("node should exist"), rev) |
667 .map_err(|e| nodemap_error(py, e))? |
1420 .map_err(|e| nodemap_error(py, e))? |
668 } |
1421 } |
669 Ok(py.None()) |
1422 Ok(py.None()) |
670 } |
1423 } |
671 |
1424 |