rust/hg-cpython/src/copy_tracing.rs
changeset 45945 50c5ee3bdf9a
child 46057 e0313b0a6f7e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hg-cpython/src/copy_tracing.rs	Thu Oct 01 18:51:40 2020 +0200
@@ -0,0 +1,295 @@
+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::RevInfo;
+use hg::utils::hg_path::HgPathBuf;
+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: PyDict,
+    target_rev: Revision,
+    rev_info: PyObject,
+    is_ancestor: PyObject,
+) -> PyResult<PyDict> {
+    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::<PyBool>(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 = |rev: Revision| -> 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, second item is a not a revision",
+        );
+
+        let changes = res.get_item(py, 2);
+
+        let files;
+        if !changes
+            .hasattr(py, "copied_from_p1")
+            .expect("rust-copy-tracing: python call to `hasattr` failed")
+        {
+            files = ChangedFiles::new_empty();
+        } else {
+            let p1_copies: PyDict = changes
+                .getattr(py, "copied_from_p1")
+                .expect(
+                    "rust-copy-tracing: retrieval of python attribute \
+                    `copied_from_p1` failed",
+                )
+                .cast_into(py)
+                .expect(
+                    "rust-copy-tracing: failed to convert `copied_from_p1` \
+                    to PyDict",
+                );
+            let p1_copies: PyResult<_> = p1_copies
+                .items(py)
+                .iter()
+                .map(|(key, value)| {
+                    let key = key.extract::<PyBytes>(py).expect(
+                        "rust-copy-tracing: conversion of copy destination to\
+                        PyBytes failed",
+                    );
+                    let key = key.data(py);
+                    let value = value.extract::<PyBytes>(py).expect(
+                        "rust-copy-tracing: conversion of copy source to \
+                        PyBytes failed",
+                    );
+                    let value = value.data(py);
+                    Ok((
+                        HgPathBuf::from_bytes(key),
+                        HgPathBuf::from_bytes(value),
+                    ))
+                })
+                .collect();
+
+            let p2_copies: PyDict = changes
+                .getattr(py, "copied_from_p2")
+                .expect(
+                    "rust-copy-tracing: retrieval of python attribute \
+                    `copied_from_p2` failed",
+                )
+                .cast_into(py)
+                .expect(
+                    "rust-copy-tracing: failed to convert `copied_from_p2` \
+                    to PyDict",
+                );
+            let p2_copies: PyResult<_> = p2_copies
+                .items(py)
+                .iter()
+                .map(|(key, value)| {
+                    let key = key.extract::<PyBytes>(py).expect(
+                        "rust-copy-tracing: conversion of copy destination to \
+                        PyBytes failed");
+                    let key = key.data(py);
+                    let value = value.extract::<PyBytes>(py).expect(
+                        "rust-copy-tracing: conversion of copy source to \
+                        PyBytes failed",
+                    );
+                    let value = value.data(py);
+                    Ok((
+                        HgPathBuf::from_bytes(key),
+                        HgPathBuf::from_bytes(value),
+                    ))
+                })
+                .collect();
+
+            let removed: PyObject = changes.getattr(py, "removed").expect(
+                "rust-copy-tracing: retrieval of python attribute \
+                    `removed` failed",
+            );
+            let removed: PyResult<_> = removed
+                .iter(py)
+                .expect(
+                    "rust-copy-tracing: getting a python iterator over the \
+                    `removed` set failed",
+                )
+                .map(|filename| {
+                    let filename = filename
+                        .expect(
+                            "rust-copy-tracing: python iteration over the \
+                            `removed` set failed",
+                        )
+                        .extract::<PyBytes>(py)
+                        .expect(
+                            "rust-copy-tracing: \
+                            conversion of `removed` item to PyBytes failed",
+                        );
+                    let filename = filename.data(py);
+                    Ok(HgPathBuf::from_bytes(filename))
+                })
+                .collect();
+
+            let merged: PyObject = changes.getattr(py, "merged").expect(
+                "rust-copy-tracing: retrieval of python attribute \
+                    `merged` failed",
+            );
+            let merged: PyResult<_> = merged
+                .iter(py)
+                .expect(
+                    "rust-copy-tracing: getting a python iterator over the \
+                    `merged` set failed",
+                )
+                .map(|filename| {
+                    let filename = filename
+                        .expect(
+                            "rust-copy-tracing: python iteration over the \
+                            `merged` set failed",
+                        )
+                        .extract::<PyBytes>(py)
+                        .expect(
+                            "rust-copy-tracing: \
+                            conversion of `merged` item to PyBytes failed",
+                        );
+                    let filename = filename.data(py);
+                    Ok(HgPathBuf::from_bytes(filename))
+                })
+                .collect();
+
+            let salvaged: PyObject = changes.getattr(py, "salvaged").expect(
+                "rust-copy-tracing: retrieval of python attribute \
+                    `salvaged` failed",
+            );
+            let salvaged: PyResult<_> = salvaged
+                .iter(py)
+                .expect(
+                    "rust-copy-tracing: getting a python iterator over the \
+                    `salvaged` set failed",
+                )
+                .map(|filename| {
+                    let filename = filename
+                        .expect(
+                            "rust-copy-tracing: python iteration over the \
+                            `salvaged` set failed",
+                        )
+                        .extract::<PyBytes>(py)
+                        .expect(
+                            "rust-copy-tracing: \
+                            conversion of `salvaged` item to PyBytes failed",
+                        );
+                    let filename = filename.data(py);
+                    Ok(HgPathBuf::from_bytes(filename))
+                })
+                .collect();
+            files = ChangedFiles::new(
+                removed.unwrap(),
+                merged.unwrap(),
+                salvaged.unwrap(),
+                p1_copies.unwrap(),
+                p2_copies.unwrap(),
+            );
+        }
+
+        (p1, p2, files)
+    };
+    let children: PyResult<_> = children
+        .items(py)
+        .iter()
+        .map(|(k, v)| {
+            let v: &PyList = v.cast_as(py)?;
+            let v: PyResult<_> =
+                v.iter(py).map(|child| Ok(child.extract(py)?)).collect();
+            Ok((k.extract(py)?, v?))
+        })
+        .collect();
+
+    let res = combine_changeset_copies(
+        revs?,
+        children?,
+        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<PyModule> {
+    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)
+}