use cpython::ObjectProtocol; use cpython::PyBool; use cpython::PyBytes; use cpython::PyDict; use cpython::PyList; use cpython::PyModule; use cpython::PyObject; use cpython::PyResult; use cpython::PyTuple; use cpython::Python; use hg::copy_tracing::combine_changeset_copies; use hg::copy_tracing::ChangedFiles; use hg::copy_tracing::DataHolder; use hg::copy_tracing::RevInfo; use hg::copy_tracing::RevInfoMaker; use hg::Revision; /// Combines copies information contained into revision `revs` to build a copy /// map. /// /// See mercurial/copies.py for details pub fn combine_changeset_copies_wrapper( py: Python, revs: PyList, children_count: PyDict, target_rev: Revision, rev_info: PyObject, is_ancestor: PyObject, ) -> PyResult { let revs: PyResult<_> = revs.iter(py).map(|r| Ok(r.extract(py)?)).collect(); // Wrap the `is_ancestor` python callback as a Rust closure // // No errors are expected from the Python side, and they will should only // happens in case of programing error or severe data corruption. Such // errors will raise panic and the rust-cpython harness will turn them into // Python exception. let is_ancestor_wrap = |anc: Revision, desc: Revision| -> bool { is_ancestor .call(py, (anc, desc), None) .expect( "rust-copy-tracing: python call to `is_ancestor` \ failed", ) .cast_into::(py) .expect( "rust-copy-tracing: python call to `is_ancestor` \ returned unexpected non-Bool value", ) .is_true() }; // Wrap the `rev_info_maker` python callback as a Rust closure // // No errors are expected from the Python side, and they will should only // happens in case of programing error or severe data corruption. Such // errors will raise panic and the rust-cpython harness will turn them into // Python exception. let rev_info_maker: RevInfoMaker = Box::new(|rev: Revision, d: &mut DataHolder| -> RevInfo { let res: PyTuple = rev_info .call(py, (rev,), None) .expect("rust-copy-tracing: python call to `rev_info` failed") .cast_into(py) .expect( "rust-copy_tracing: python call to `rev_info` returned \ unexpected non-Tuple value", ); let p1 = res.get_item(py, 0).extract(py).expect( "rust-copy-tracing: rev_info return is invalid, first item \ is a not a revision", ); let p2 = res.get_item(py, 1).extract(py).expect( "rust-copy-tracing: rev_info return is invalid, first item \ is a not a revision", ); let files = match res.get_item(py, 2).extract::(py) { Ok(raw) => { // Give responsability for the raw bytes lifetime to // hg-core d.data = Some(raw); let addrs = d.data.as_ref().expect( "rust-copy-tracing: failed to get a reference to the \ raw bytes for copy data").data(py); ChangedFiles::new(addrs) } // value was presumably None, meaning they was no copy data. Err(_) => ChangedFiles::new_empty(), }; (p1, p2, files) }); let children_count: PyResult<_> = children_count .items(py) .iter() .map(|(k, v)| Ok((k.extract(py)?, v.extract(py)?))) .collect(); let res = combine_changeset_copies( revs?, children_count?, target_rev, rev_info_maker, &is_ancestor_wrap, ); let out = PyDict::new(py); for (dest, source) in res.into_iter() { out.set_item( py, PyBytes::new(py, &dest.into_vec()), PyBytes::new(py, &source.into_vec()), )?; } Ok(out) } /// Create the module, with `__package__` given from parent pub fn init_module(py: Python, package: &str) -> PyResult { let dotted_name = &format!("{}.copy_tracing", package); let m = PyModule::new(py, dotted_name)?; m.add(py, "__package__", package)?; m.add(py, "__doc__", "Copy tracing - Rust implementation")?; m.add( py, "combine_changeset_copies", py_fn!( py, combine_changeset_copies_wrapper( revs: PyList, children: PyDict, target_rev: Revision, rev_info: PyObject, is_ancestor: PyObject ) ), )?; let sys = PyModule::import(py, "sys")?; let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; sys_modules.set_item(py, dotted_name, &m)?; Ok(m) }