changeset 44529:f96b28aa4b79

rust-status: update rust-cpython bridge to account for the changes in core Differential Revision: https://phab.mercurial-scm.org/D7930
author Raphaël Gomès <rgomes@octobus.net>
date Fri, 17 Jan 2020 15:09:02 +0100
parents c8891bca40fb
children 4d1634e59f13
files rust/hg-core/src/dirstate/status.rs rust/hg-cpython/src/dirstate.rs rust/hg-cpython/src/dirstate/status.rs rust/hg-cpython/src/exceptions.rs
diffstat 4 files changed, 233 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-core/src/dirstate/status.rs	Fri Jan 17 15:43:46 2020 +0100
+++ b/rust/hg-core/src/dirstate/status.rs	Fri Jan 17 15:09:02 2020 +0100
@@ -48,6 +48,20 @@
     Unknown,
 }
 
+impl ToString for BadType {
+    fn to_string(&self) -> String {
+        match self {
+            BadType::CharacterDevice => "character device",
+            BadType::BlockDevice => "block device",
+            BadType::FIFO => "fifo",
+            BadType::Socket => "socket",
+            BadType::Directory => "directory",
+            BadType::Unknown => "unknown",
+        }
+        .to_string()
+    }
+}
+
 /// Was explicitly matched but cannot be found/accessed
 #[derive(Debug)]
 pub enum BadMatch {
--- a/rust/hg-cpython/src/dirstate.rs	Fri Jan 17 15:43:46 2020 +0100
+++ b/rust/hg-cpython/src/dirstate.rs	Fri Jan 17 15:09:02 2020 +0100
@@ -14,12 +14,15 @@
 mod dirstate_map;
 mod non_normal_entries;
 mod status;
-use crate::dirstate::{
-    dirs_multiset::Dirs, dirstate_map::DirstateMap, status::status_wrapper,
+use crate::{
+    dirstate::{
+        dirs_multiset::Dirs, dirstate_map::DirstateMap, status::status_wrapper,
+    },
+    exceptions,
 };
 use cpython::{
-    exc, PyBytes, PyDict, PyErr, PyModule, PyObject, PyResult, PySequence,
-    Python,
+    exc, PyBytes, PyDict, PyErr, PyList, PyModule, PyObject, PyResult,
+    PySequence, Python,
 };
 use hg::{
     utils::hg_path::HgPathBuf, DirstateEntry, DirstateParseError, EntryState,
@@ -107,6 +110,11 @@
     m.add(py, "__package__", package)?;
     m.add(py, "__doc__", "Dirstate - Rust implementation")?;
 
+    m.add(
+        py,
+        "FallbackError",
+        py.get_type::<exceptions::FallbackError>(),
+    )?;
     m.add_class::<Dirs>(py)?;
     m.add_class::<DirstateMap>(py)?;
     m.add(
@@ -118,9 +126,12 @@
                 dmap: DirstateMap,
                 root_dir: PyObject,
                 matcher: PyObject,
-                list_clean: bool,
+                ignorefiles: PyList,
+                check_exec: bool,
                 last_normal_time: i64,
-                check_exec: bool
+                list_clean: bool,
+                list_ignored: bool,
+                list_unknown: bool
             )
         ),
     )?;
--- a/rust/hg-cpython/src/dirstate/status.rs	Fri Jan 17 15:43:46 2020 +0100
+++ b/rust/hg-cpython/src/dirstate/status.rs	Fri Jan 17 15:09:02 2020 +0100
@@ -9,33 +9,34 @@
 //! `hg-core` crate. From Python, this will be seen as
 //! `rustext.dirstate.status`.
 
-use crate::dirstate::DirstateMap;
-use cpython::exc::ValueError;
+use crate::{dirstate::DirstateMap, exceptions::FallbackError};
 use cpython::{
-    ObjectProtocol, PyBytes, PyErr, PyList, PyObject, PyResult, PyTuple,
-    Python, PythonObject, ToPyObject,
+    exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
+    PyResult, PyTuple, Python, PythonObject, ToPyObject,
 };
-use hg::utils::hg_path::HgPathBuf;
 use hg::{
-    matchers::{AlwaysMatcher, FileMatcher},
-    status,
-    utils::{files::get_path_from_bytes, hg_path::HgPath},
-    DirstateStatus,
+    matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
+    parse_pattern_syntax, status,
+    utils::{
+        files::{get_bytes_from_path, get_path_from_bytes},
+        hg_path::{HgPath, HgPathBuf},
+    },
+    BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
+    StatusOptions,
 };
-use std::borrow::Borrow;
+use std::borrow::{Borrow, Cow};
 
 /// This will be useless once trait impls for collection are added to `PyBytes`
 /// upstream.
-fn collect_pybytes_list<P: AsRef<HgPath>>(
+fn collect_pybytes_list(
     py: Python,
-    collection: &[P],
+    collection: &[impl AsRef<HgPath>],
 ) -> PyList {
     let list = PyList::new(py, &[]);
 
-    for (i, path) in collection.iter().enumerate() {
-        list.insert(
+    for path in collection.iter() {
+        list.append(
             py,
-            i,
             PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
         )
     }
@@ -43,34 +44,97 @@
     list
 }
 
+fn collect_bad_matches(
+    py: Python,
+    collection: &[(impl AsRef<HgPath>, BadMatch)],
+) -> PyResult<PyList> {
+    let list = PyList::new(py, &[]);
+
+    let os = py.import("os")?;
+    let get_error_message = |code: i32| -> PyResult<_> {
+        os.call(
+            py,
+            "strerror",
+            PyTuple::new(py, &[code.to_py_object(py).into_object()]),
+            None,
+        )
+    };
+
+    for (path, bad_match) in collection.iter() {
+        let message = match bad_match {
+            BadMatch::OsError(code) => get_error_message(*code)?,
+            BadMatch::BadType(bad_type) => format!(
+                "unsupported file type (type is {})",
+                bad_type.to_string()
+            )
+            .to_py_object(py)
+            .into_object(),
+        };
+        list.append(
+            py,
+            (PyBytes::new(py, path.as_ref().as_bytes()), message)
+                .to_py_object(py)
+                .into_object(),
+        )
+    }
+
+    Ok(list)
+}
+
+fn handle_fallback(py: Python, err: StatusError) -> PyErr {
+    match err {
+        StatusError::Pattern(e) => {
+            PyErr::new::<FallbackError, _>(py, e.to_string())
+        }
+        e => PyErr::new::<ValueError, _>(py, e.to_string()),
+    }
+}
+
 pub fn status_wrapper(
     py: Python,
     dmap: DirstateMap,
     matcher: PyObject,
     root_dir: PyObject,
-    list_clean: bool,
+    ignore_files: PyList,
+    check_exec: bool,
     last_normal_time: i64,
-    check_exec: bool,
-) -> PyResult<(PyList, PyList, PyList, PyList, PyList, PyList, PyList)> {
+    list_clean: bool,
+    list_ignored: bool,
+    list_unknown: bool,
+) -> PyResult<PyTuple> {
     let bytes = root_dir.extract::<PyBytes>(py)?;
     let root_dir = get_path_from_bytes(bytes.data(py));
 
     let dmap: DirstateMap = dmap.to_py_object(py);
     let dmap = dmap.get_inner(py);
 
+    let ignore_files: PyResult<Vec<_>> = ignore_files
+        .iter(py)
+        .map(|b| {
+            let file = b.extract::<PyBytes>(py)?;
+            Ok(get_path_from_bytes(file.data(py)).to_owned())
+        })
+        .collect();
+    let ignore_files = ignore_files?;
+
     match matcher.get_type(py).name(py).borrow() {
         "alwaysmatcher" => {
             let matcher = AlwaysMatcher;
-            let (lookup, status_res) = status(
+            let ((lookup, status_res), warnings) = status(
                 &dmap,
                 &matcher,
                 &root_dir,
-                list_clean,
-                last_normal_time,
-                check_exec,
+                &ignore_files,
+                StatusOptions {
+                    check_exec,
+                    last_normal_time,
+                    list_clean,
+                    list_ignored,
+                    list_unknown,
+                },
             )
-            .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
-            build_response(lookup, status_res, py)
+            .map_err(|e| handle_fallback(py, e))?;
+            build_response(py, lookup, status_res, warnings)
         }
         "exactmatcher" => {
             let files = matcher.call_method(
@@ -92,16 +156,78 @@
             let files = files?;
             let matcher = FileMatcher::new(&files)
                 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
-            let (lookup, status_res) = status(
+            let ((lookup, status_res), warnings) = status(
                 &dmap,
                 &matcher,
                 &root_dir,
-                list_clean,
-                last_normal_time,
-                check_exec,
+                &ignore_files,
+                StatusOptions {
+                    check_exec,
+                    last_normal_time,
+                    list_clean,
+                    list_ignored,
+                    list_unknown,
+                },
             )
-            .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
-            build_response(lookup, status_res, py)
+            .map_err(|e| handle_fallback(py, e))?;
+            build_response(py, lookup, status_res, warnings)
+        }
+        "includematcher" => {
+            // Get the patterns from Python even though most of them are
+            // redundant with those we will parse later on, as they include
+            // those passed from the command line.
+            let ignore_patterns: PyResult<Vec<_>> = matcher
+                .getattr(py, "_kindpats")?
+                .iter(py)?
+                .map(|k| {
+                    let k = k?;
+                    let syntax = parse_pattern_syntax(
+                        &[
+                            k.get_item(py, 0)?
+                                .extract::<PyBytes>(py)?
+                                .data(py),
+                            &b":"[..],
+                        ]
+                        .concat(),
+                    )
+                    .map_err(|e| {
+                        handle_fallback(py, StatusError::Pattern(e))
+                    })?;
+                    let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
+                    let pattern = pattern.data(py);
+                    let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
+                    let source = get_path_from_bytes(source.data(py));
+                    let new = IgnorePattern::new(syntax, pattern, source);
+                    Ok(new)
+                })
+                .collect();
+
+            let ignore_patterns = ignore_patterns?;
+            let mut all_warnings = vec![];
+
+            let (matcher, warnings) =
+                IncludeMatcher::new(ignore_patterns, &root_dir)
+                    .map_err(|e| handle_fallback(py, e.into()))?;
+            all_warnings.extend(warnings);
+
+            let ((lookup, status_res), warnings) = status(
+                &dmap,
+                &matcher,
+                &root_dir,
+                &ignore_files,
+                StatusOptions {
+                    check_exec,
+                    last_normal_time,
+                    list_clean,
+                    list_ignored,
+                    list_unknown,
+                },
+            )
+            .map_err(|e| handle_fallback(py, e))?;
+
+            all_warnings.extend(warnings);
+
+            build_response(py, lookup, status_res, all_warnings)
         }
         e => {
             return Err(PyErr::new::<ValueError, _>(
@@ -113,17 +239,56 @@
 }
 
 fn build_response(
-    lookup: Vec<&HgPath>,
+    py: Python,
+    lookup: Vec<Cow<HgPath>>,
     status_res: DirstateStatus,
-    py: Python,
-) -> PyResult<(PyList, PyList, PyList, PyList, PyList, PyList, PyList)> {
+    warnings: Vec<PatternFileWarning>,
+) -> PyResult<PyTuple> {
     let modified = collect_pybytes_list(py, status_res.modified.as_ref());
     let added = collect_pybytes_list(py, status_res.added.as_ref());
     let removed = collect_pybytes_list(py, status_res.removed.as_ref());
     let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
     let clean = collect_pybytes_list(py, status_res.clean.as_ref());
+    let ignored = collect_pybytes_list(py, status_res.ignored.as_ref());
+    let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
     let lookup = collect_pybytes_list(py, lookup.as_ref());
-    let unknown = PyList::new(py, &[]);
+    let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
+    let py_warnings = PyList::new(py, &[]);
+    for warning in warnings.iter() {
+        // We use duck-typing on the Python side for dispatch, good enough for
+        // now.
+        match warning {
+            PatternFileWarning::InvalidSyntax(file, syn) => {
+                py_warnings.append(
+                    py,
+                    (
+                        PyBytes::new(py, &get_bytes_from_path(&file)),
+                        PyBytes::new(py, syn),
+                    )
+                        .to_py_object(py)
+                        .into_object(),
+                );
+            }
+            PatternFileWarning::NoSuchFile(file) => py_warnings.append(
+                py,
+                PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
+            ),
+        }
+    }
 
-    Ok((lookup, modified, added, removed, deleted, unknown, clean))
+    Ok(PyTuple::new(
+        py,
+        &[
+            lookup.into_object(),
+            modified.into_object(),
+            added.into_object(),
+            removed.into_object(),
+            deleted.into_object(),
+            clean.into_object(),
+            ignored.into_object(),
+            unknown.into_object(),
+            py_warnings.into_object(),
+            bad.into_object(),
+        ][..],
+    ))
 }
--- a/rust/hg-cpython/src/exceptions.rs	Fri Jan 17 15:43:46 2020 +0100
+++ b/rust/hg-cpython/src/exceptions.rs	Fri Jan 17 15:09:02 2020 +0100
@@ -40,3 +40,5 @@
 }
 
 py_exception!(rustext, HgPathPyError, RuntimeError);
+py_exception!(rustext, FallbackError, RuntimeError);
+py_exception!(shared_ref, AlreadyBorrowed, RuntimeError);