##// END OF EJS Templates
dirstate-tree: Add tree traversal/iteration...
dirstate-tree: Add tree traversal/iteration Like Python’s, Rust’s iterators are "external" in that they are driven by a caller who calls a `next` method. This is as opposed to "internal" iterators who drive themselves and call a callback for each item. Writing an internal iterator traversing a tree is easy with recursion, but internal iterators cannot rely on the call stack in that way, they must save in an explicit object all state that they need to be preserved across two `next` calls. This algorithm uses a `Vec` as a stack that contains what would be local variables on the call stack if we could use recursion. Differential Revision: https://phab.mercurial-scm.org/D10370

File last commit:

r47863:787ff5d2 default
r47870:caa3031c default
Show More
status.rs
303 lines | 10.0 KiB | application/rls-services+xml | RustLexer
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 // status.rs
//
// Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
//! Bindings for the `hg::status` module provided by the
Yuya Nishihara
rust-cpython: run cargo fmt
r43612 //! `hg-core` crate. From Python, this will be seen as
//! `rustext.dirstate.status`.
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
Raphaël Gomès
rust-status: properly translate OSError to Python...
r46465 use cpython::exc::OSError;
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 use cpython::{
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
PyResult, PyTuple, Python, PythonObject, ToPyObject,
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 };
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 use hg::{
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
Simon Sapin
dirstate-tree: Make Rust DirstateMap bindings go through a trait object...
r47863 parse_pattern_syntax,
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 utils::{
files::{get_bytes_from_path, get_path_from_bytes},
hg_path::{HgPath, HgPathBuf},
},
BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
StatusOptions,
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 };
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 use std::borrow::{Borrow, Cow};
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567
/// This will be useless once trait impls for collection are added to `PyBytes`
/// upstream.
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 fn collect_pybytes_list(
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 py: Python,
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 collection: &[impl AsRef<HgPath>],
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 ) -> PyList {
let list = PyList::new(py, &[]);
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 for path in collection.iter() {
list.append(
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 py,
PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
)
}
list
}
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 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) => {
Raphaël Gomès
rust-status: add trace-level logging for Rust status fallback for debugging...
r45062 let as_string = e.to_string();
log::trace!("Rust status fallback: `{}`", &as_string);
PyErr::new::<FallbackError, _>(py, &as_string)
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 }
Raphaël Gomès
rust-status: properly translate OSError to Python...
r46465 StatusError::IO(e) => PyErr::new::<OSError, _>(py, e.to_string()),
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 e => PyErr::new::<ValueError, _>(py, e.to_string()),
}
}
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 pub fn status_wrapper(
py: Python,
dmap: DirstateMap,
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 matcher: PyObject,
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 root_dir: PyObject,
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 ignore_files: PyList,
check_exec: bool,
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 last_normal_time: i64,
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 list_clean: bool,
list_ignored: bool,
list_unknown: bool,
Raphaël Gomès
rust-hg-cpython: update status bridge with the new `traversedir` support...
r45354 collect_traversed_dirs: bool,
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 ) -> PyResult<PyTuple> {
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 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);
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 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?;
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 match matcher.get_type(py).name(py).borrow() {
"alwaysmatcher" => {
let matcher = AlwaysMatcher;
Simon Sapin
dirstate-tree: Make Rust DirstateMap bindings go through a trait object...
r47863 let ((lookup, status_res), warnings) = dmap
.status(
&matcher,
root_dir.to_path_buf(),
ignore_files,
StatusOptions {
check_exec,
last_normal_time,
list_clean,
list_ignored,
list_unknown,
collect_traversed_dirs,
},
)
.map_err(|e| handle_fallback(py, e))?;
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 build_response(py, lookup, status_res, warnings)
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 }
"exactmatcher" => {
let files = matcher.call_method(
py,
"files",
PyTuple::new(py, &[]),
None,
)?;
let files: PyList = files.cast_into(py)?;
let files: PyResult<Vec<HgPathBuf>> = files
.iter(py)
.map(|f| {
Ok(HgPathBuf::from_bytes(
f.extract::<PyBytes>(py)?.data(py),
))
})
.collect();
Raphaël Gomès
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
r44367
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 let files = files?;
Raphaël Gomès
rust: start plugging the dirstate tree behind a feature gate...
r46185 let matcher = FileMatcher::new(files.as_ref())
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
Simon Sapin
dirstate-tree: Make Rust DirstateMap bindings go through a trait object...
r47863 let ((lookup, status_res), warnings) = dmap
.status(
&matcher,
root_dir.to_path_buf(),
ignore_files,
StatusOptions {
check_exec,
last_normal_time,
list_clean,
list_ignored,
list_unknown,
collect_traversed_dirs,
},
)
.map_err(|e| handle_fallback(py, e))?;
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 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);
Simon Sapin
dirstate-tree: Make Rust DirstateMap bindings go through a trait object...
r47863 let ((lookup, status_res), warnings) = dmap
.status(
&matcher,
root_dir.to_path_buf(),
ignore_files,
StatusOptions {
check_exec,
last_normal_time,
list_clean,
list_ignored,
list_unknown,
collect_traversed_dirs,
},
)
.map_err(|e| handle_fallback(py, e))?;
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016
all_warnings.extend(warnings);
build_response(py, lookup, status_res, all_warnings)
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 }
Raphaël Gomès
rust: do a clippy pass...
r45500 e => Err(PyErr::new::<ValueError, _>(
py,
format!("Unsupported matcher {}", e),
)),
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 }
}
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567
Raphaël Gomès
rust-dirstate-status: update bridge for new rust version of `dirstate.status`...
r44368 fn build_response(
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 py: Python,
lookup: Vec<Cow<HgPath>>,
Raphaël Gomès
rust-status: rename `StatusResult` to `DirstateStatus`...
r45012 status_res: DirstateStatus,
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 warnings: Vec<PatternFileWarning>,
) -> PyResult<PyTuple> {
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 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());
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 let ignored = collect_pybytes_list(py, status_res.ignored.as_ref());
let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 let lookup = collect_pybytes_list(py, lookup.as_ref());
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
Raphaël Gomès
rust-hg-cpython: update status bridge with the new `traversedir` support...
r45354 let traversed = collect_pybytes_list(py, status_res.traversed.as_ref());
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 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(),
),
}
}
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 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(),
Raphaël Gomès
rust-hg-cpython: update status bridge with the new `traversedir` support...
r45354 traversed.into_object(),
Raphaël Gomès
rust-status: update rust-cpython bridge to account for the changes in core...
r45016 ][..],
))
Raphaël Gomès
rust-dirstate-status: rust-cpython bindings for `dirstate.status`...
r43567 }