status.rs
307 lines
| 9.8 KiB
| application/rls-services+xml
|
RustLexer
Raphaël Gomès
|
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
|
r43612 | //! `hg-core` crate. From Python, this will be seen as | ||
//! `rustext.dirstate.status`. | ||||
Raphaël Gomès
|
r43567 | |||
Raphaël Gomès
|
r45016 | use crate::{dirstate::DirstateMap, exceptions::FallbackError}; | ||
Raphaël Gomès
|
r43567 | use cpython::{ | ||
Raphaël Gomès
|
r45016 | exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject, | ||
PyResult, PyTuple, Python, PythonObject, ToPyObject, | ||||
Raphaël Gomès
|
r43567 | }; | ||
Simon Sapin
|
r49285 | use hg::dirstate::status::StatusPath; | ||
Raphaël Gomès
|
r50374 | use hg::matchers::{ | ||
DifferenceMatcher, IntersectionMatcher, Matcher, NeverMatcher, | ||||
UnionMatcher, | ||||
}; | ||||
Raphaël Gomès
|
r44368 | use hg::{ | ||
Raphaël Gomès
|
r45016 | matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher}, | ||
Simon Sapin
|
r47863 | parse_pattern_syntax, | ||
Raphaël Gomès
|
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
|
r44368 | }; | ||
Simon Sapin
|
r47880 | use std::borrow::Borrow; | ||
Raphaël Gomès
|
r43567 | |||
Simon Sapin
|
r49285 | fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList { | ||
collect_pybytes_list(py, paths.iter().map(|item| &*item.path)) | ||||
} | ||||
Raphaël Gomès
|
r43567 | /// This will be useless once trait impls for collection are added to `PyBytes` | ||
/// upstream. | ||||
Raphaël Gomès
|
r45016 | fn collect_pybytes_list( | ||
Raphaël Gomès
|
r43567 | py: Python, | ||
Simon Sapin
|
r49285 | iter: impl Iterator<Item = impl AsRef<HgPath>>, | ||
Raphaël Gomès
|
r43567 | ) -> PyList { | ||
let list = PyList::new(py, &[]); | ||||
Simon Sapin
|
r49285 | for path in iter { | ||
Raphaël Gomès
|
r45016 | list.append( | ||
Raphaël Gomès
|
r43567 | py, | ||
PyBytes::new(py, path.as_ref().as_bytes()).into_object(), | ||||
) | ||||
} | ||||
list | ||||
} | ||||
Raphaël Gomès
|
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)?, | ||||
Raphaël Gomès
|
r50809 | BadMatch::BadType(bad_type) => { | ||
format!("unsupported file type (type is {})", bad_type) | ||||
.to_py_object(py) | ||||
.into_object() | ||||
} | ||||
Raphaël Gomès
|
r45016 | }; | ||
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
|
r45062 | let as_string = e.to_string(); | ||
log::trace!("Rust status fallback: `{}`", &as_string); | ||||
PyErr::new::<FallbackError, _>(py, &as_string) | ||||
Raphaël Gomès
|
r45016 | } | ||
e => PyErr::new::<ValueError, _>(py, e.to_string()), | ||||
} | ||||
} | ||||
Raphaël Gomès
|
r43567 | pub fn status_wrapper( | ||
py: Python, | ||||
dmap: DirstateMap, | ||||
Raphaël Gomès
|
r44368 | matcher: PyObject, | ||
Raphaël Gomès
|
r43567 | root_dir: PyObject, | ||
Raphaël Gomès
|
r45016 | ignore_files: PyList, | ||
check_exec: bool, | ||||
list_clean: bool, | ||||
list_ignored: bool, | ||||
list_unknown: bool, | ||||
Raphaël Gomès
|
r45354 | collect_traversed_dirs: bool, | ||
Raphaël Gomès
|
r45016 | ) -> PyResult<PyTuple> { | ||
Raphaël Gomès
|
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); | ||||
Simon Sapin
|
r47882 | let mut dmap = dmap.get_inner_mut(py); | ||
Raphaël Gomès
|
r43567 | |||
Raphaël Gomès
|
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?; | ||||
Simon Sapin
|
r49285 | // The caller may call `copymap.items()` separately | ||
let list_copies = false; | ||||
Raphaël Gomès
|
r45016 | |||
Raphaël Gomès
|
r49864 | let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| { | ||
let (status_res, warnings) = | ||||
res.map_err(|e| handle_fallback(py, e))?; | ||||
build_response(py, status_res, warnings) | ||||
}; | ||||
Raphaël Gomès
|
r50242 | let matcher = extract_matcher(py, matcher)?; | ||
dmap.with_status( | ||||
&*matcher, | ||||
root_dir.to_path_buf(), | ||||
ignore_files, | ||||
StatusOptions { | ||||
check_exec, | ||||
list_clean, | ||||
list_ignored, | ||||
list_unknown, | ||||
list_copies, | ||||
collect_traversed_dirs, | ||||
}, | ||||
after_status, | ||||
) | ||||
} | ||||
/// Transform a Python matcher into a Rust matcher. | ||||
fn extract_matcher( | ||||
py: Python, | ||||
matcher: PyObject, | ||||
) -> PyResult<Box<dyn Matcher + Sync>> { | ||||
Raphaël Gomès
|
r44368 | match matcher.get_type(py).name(py).borrow() { | ||
Raphaël Gomès
|
r50242 | "alwaysmatcher" => Ok(Box::new(AlwaysMatcher)), | ||
Raphaël Gomès
|
r50247 | "nevermatcher" => Ok(Box::new(NeverMatcher)), | ||
Raphaël Gomès
|
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
|
r44367 | |||
Raphaël Gomès
|
r44368 | let files = files?; | ||
Raphaël Gomès
|
r50242 | let file_matcher = FileMatcher::new(files) | ||
Raphaël Gomès
|
r44368 | .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?; | ||
Raphaël Gomès
|
r50242 | Ok(Box::new(file_matcher)) | ||
Raphaël Gomès
|
r45016 | } | ||
"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?; | ||||
Simon Sapin
|
r48170 | let matcher = IncludeMatcher::new(ignore_patterns) | ||
.map_err(|e| handle_fallback(py, e.into()))?; | ||||
Raphaël Gomès
|
r45016 | |||
Raphaël Gomès
|
r50242 | Ok(Box::new(matcher)) | ||
Raphaël Gomès
|
r44368 | } | ||
Raphaël Gomès
|
r50244 | "unionmatcher" => { | ||
let matchers: PyResult<Vec<_>> = matcher | ||||
.getattr(py, "_matchers")? | ||||
.iter(py)? | ||||
.map(|py_matcher| extract_matcher(py, py_matcher?)) | ||||
.collect(); | ||||
Ok(Box::new(UnionMatcher::new(matchers?))) | ||||
} | ||||
Raphaël Gomès
|
r50246 | "intersectionmatcher" => { | ||
let m1 = extract_matcher(py, matcher.getattr(py, "_m1")?)?; | ||||
let m2 = extract_matcher(py, matcher.getattr(py, "_m2")?)?; | ||||
Ok(Box::new(IntersectionMatcher::new(m1, m2))) | ||||
} | ||||
Raphaël Gomès
|
r50374 | "differencematcher" => { | ||
let m1 = extract_matcher(py, matcher.getattr(py, "_m1")?)?; | ||||
let m2 = extract_matcher(py, matcher.getattr(py, "_m2")?)?; | ||||
Ok(Box::new(DifferenceMatcher::new(m1, m2))) | ||||
} | ||||
Raphaël Gomès
|
r50240 | e => Err(PyErr::new::<FallbackError, _>( | ||
Raphaël Gomès
|
r45500 | py, | ||
format!("Unsupported matcher {}", e), | ||||
)), | ||||
Raphaël Gomès
|
r44368 | } | ||
} | ||||
Raphaël Gomès
|
r43567 | |||
Raphaël Gomès
|
r44368 | fn build_response( | ||
Raphaël Gomès
|
r45016 | py: Python, | ||
Raphaël Gomès
|
r45012 | status_res: DirstateStatus, | ||
Raphaël Gomès
|
r45016 | warnings: Vec<PatternFileWarning>, | ||
) -> PyResult<PyTuple> { | ||||
Simon Sapin
|
r49285 | let modified = collect_status_path_list(py, &status_res.modified); | ||
let added = collect_status_path_list(py, &status_res.added); | ||||
let removed = collect_status_path_list(py, &status_res.removed); | ||||
let deleted = collect_status_path_list(py, &status_res.deleted); | ||||
let clean = collect_status_path_list(py, &status_res.clean); | ||||
let ignored = collect_status_path_list(py, &status_res.ignored); | ||||
let unknown = collect_status_path_list(py, &status_res.unknown); | ||||
let unsure = collect_status_path_list(py, &status_res.unsure); | ||||
let bad = collect_bad_matches(py, &status_res.bad)?; | ||||
let traversed = collect_pybytes_list(py, status_res.traversed.iter()); | ||||
Simon Sapin
|
r48139 | let dirty = status_res.dirty.to_py_object(py); | ||
Raphaël Gomès
|
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
|
r43567 | |||
Raphaël Gomès
|
r45016 | Ok(PyTuple::new( | ||
py, | ||||
&[ | ||||
Simon Sapin
|
r47880 | unsure.into_object(), | ||
Raphaël Gomès
|
r45016 | 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
|
r45354 | traversed.into_object(), | ||
Simon Sapin
|
r48139 | dirty.into_object(), | ||
Raphaël Gomès
|
r45016 | ][..], | ||
)) | ||||
Raphaël Gomès
|
r43567 | } | ||