##// END OF EJS Templates
match: avoid rust fast path if the matcher was tampered with...
Arseniy Alekseyev -
r52495:68929cf3 default
parent child Browse files
Show More
@@ -1,307 +1,319 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::status` module provided by the
8 //! Bindings for the `hg::status` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as
9 //! `hg-core` crate. From Python, this will be seen as
10 //! `rustext.dirstate.status`.
10 //! `rustext.dirstate.status`.
11
11
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
13 use cpython::{
13 use cpython::{
14 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
14 exc::ValueError, ObjectProtocol, PyBool, PyBytes, PyErr, PyList, PyObject,
15 PyResult, PyTuple, Python, PythonObject, ToPyObject,
15 PyResult, PyTuple, Python, PythonObject, ToPyObject,
16 };
16 };
17 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::status::StatusPath;
18 use hg::matchers::{
18 use hg::matchers::{
19 DifferenceMatcher, IntersectionMatcher, Matcher, NeverMatcher,
19 DifferenceMatcher, IntersectionMatcher, Matcher, NeverMatcher,
20 UnionMatcher,
20 UnionMatcher,
21 };
21 };
22 use hg::{
22 use hg::{
23 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
23 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
24 parse_pattern_syntax,
24 parse_pattern_syntax,
25 utils::{
25 utils::{
26 files::{get_bytes_from_path, get_path_from_bytes},
26 files::{get_bytes_from_path, get_path_from_bytes},
27 hg_path::{HgPath, HgPathBuf},
27 hg_path::{HgPath, HgPathBuf},
28 },
28 },
29 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
29 BadMatch, DirstateStatus, IgnorePattern, PatternError, PatternFileWarning,
30 StatusOptions,
30 StatusError, StatusOptions,
31 };
31 };
32 use std::borrow::Borrow;
32 use std::borrow::Borrow;
33
33
34 fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList {
34 fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList {
35 collect_pybytes_list(py, paths.iter().map(|item| &*item.path))
35 collect_pybytes_list(py, paths.iter().map(|item| &*item.path))
36 }
36 }
37
37
38 /// This will be useless once trait impls for collection are added to `PyBytes`
38 /// This will be useless once trait impls for collection are added to `PyBytes`
39 /// upstream.
39 /// upstream.
40 fn collect_pybytes_list(
40 fn collect_pybytes_list(
41 py: Python,
41 py: Python,
42 iter: impl Iterator<Item = impl AsRef<HgPath>>,
42 iter: impl Iterator<Item = impl AsRef<HgPath>>,
43 ) -> PyList {
43 ) -> PyList {
44 let list = PyList::new(py, &[]);
44 let list = PyList::new(py, &[]);
45
45
46 for path in iter {
46 for path in iter {
47 list.append(
47 list.append(
48 py,
48 py,
49 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
49 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
50 )
50 )
51 }
51 }
52
52
53 list
53 list
54 }
54 }
55
55
56 fn collect_bad_matches(
56 fn collect_bad_matches(
57 py: Python,
57 py: Python,
58 collection: &[(impl AsRef<HgPath>, BadMatch)],
58 collection: &[(impl AsRef<HgPath>, BadMatch)],
59 ) -> PyResult<PyList> {
59 ) -> PyResult<PyList> {
60 let list = PyList::new(py, &[]);
60 let list = PyList::new(py, &[]);
61
61
62 let os = py.import("os")?;
62 let os = py.import("os")?;
63 let get_error_message = |code: i32| -> PyResult<_> {
63 let get_error_message = |code: i32| -> PyResult<_> {
64 os.call(
64 os.call(
65 py,
65 py,
66 "strerror",
66 "strerror",
67 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
67 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
68 None,
68 None,
69 )
69 )
70 };
70 };
71
71
72 for (path, bad_match) in collection.iter() {
72 for (path, bad_match) in collection.iter() {
73 let message = match bad_match {
73 let message = match bad_match {
74 BadMatch::OsError(code) => get_error_message(*code)?,
74 BadMatch::OsError(code) => get_error_message(*code)?,
75 BadMatch::BadType(bad_type) => {
75 BadMatch::BadType(bad_type) => {
76 format!("unsupported file type (type is {})", bad_type)
76 format!("unsupported file type (type is {})", bad_type)
77 .to_py_object(py)
77 .to_py_object(py)
78 .into_object()
78 .into_object()
79 }
79 }
80 };
80 };
81 list.append(
81 list.append(
82 py,
82 py,
83 (PyBytes::new(py, path.as_ref().as_bytes()), message)
83 (PyBytes::new(py, path.as_ref().as_bytes()), message)
84 .to_py_object(py)
84 .to_py_object(py)
85 .into_object(),
85 .into_object(),
86 )
86 )
87 }
87 }
88
88
89 Ok(list)
89 Ok(list)
90 }
90 }
91
91
92 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
92 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
93 match err {
93 match err {
94 StatusError::Pattern(e) => {
94 StatusError::Pattern(e) => {
95 let as_string = e.to_string();
95 let as_string = e.to_string();
96 log::trace!("Rust status fallback: `{}`", &as_string);
96 log::trace!("Rust status fallback: `{}`", &as_string);
97
97
98 PyErr::new::<FallbackError, _>(py, &as_string)
98 PyErr::new::<FallbackError, _>(py, &as_string)
99 }
99 }
100 e => PyErr::new::<ValueError, _>(py, e.to_string()),
100 e => PyErr::new::<ValueError, _>(py, e.to_string()),
101 }
101 }
102 }
102 }
103
103
104 pub fn status_wrapper(
104 pub fn status_wrapper(
105 py: Python,
105 py: Python,
106 dmap: DirstateMap,
106 dmap: DirstateMap,
107 matcher: PyObject,
107 matcher: PyObject,
108 root_dir: PyObject,
108 root_dir: PyObject,
109 ignore_files: PyList,
109 ignore_files: PyList,
110 check_exec: bool,
110 check_exec: bool,
111 list_clean: bool,
111 list_clean: bool,
112 list_ignored: bool,
112 list_ignored: bool,
113 list_unknown: bool,
113 list_unknown: bool,
114 collect_traversed_dirs: bool,
114 collect_traversed_dirs: bool,
115 ) -> PyResult<PyTuple> {
115 ) -> PyResult<PyTuple> {
116 let bytes = root_dir.extract::<PyBytes>(py)?;
116 let bytes = root_dir.extract::<PyBytes>(py)?;
117 let root_dir = get_path_from_bytes(bytes.data(py));
117 let root_dir = get_path_from_bytes(bytes.data(py));
118
118
119 let dmap: DirstateMap = dmap.to_py_object(py);
119 let dmap: DirstateMap = dmap.to_py_object(py);
120 let mut dmap = dmap.get_inner_mut(py);
120 let mut dmap = dmap.get_inner_mut(py);
121
121
122 let ignore_files: PyResult<Vec<_>> = ignore_files
122 let ignore_files: PyResult<Vec<_>> = ignore_files
123 .iter(py)
123 .iter(py)
124 .map(|b| {
124 .map(|b| {
125 let file = b.extract::<PyBytes>(py)?;
125 let file = b.extract::<PyBytes>(py)?;
126 Ok(get_path_from_bytes(file.data(py)).to_owned())
126 Ok(get_path_from_bytes(file.data(py)).to_owned())
127 })
127 })
128 .collect();
128 .collect();
129 let ignore_files = ignore_files?;
129 let ignore_files = ignore_files?;
130 // The caller may call `copymap.items()` separately
130 // The caller may call `copymap.items()` separately
131 let list_copies = false;
131 let list_copies = false;
132
132
133 let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| {
133 let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| {
134 let (status_res, warnings) =
134 let (status_res, warnings) =
135 res.map_err(|e| handle_fallback(py, e))?;
135 res.map_err(|e| handle_fallback(py, e))?;
136 build_response(py, status_res, warnings)
136 build_response(py, status_res, warnings)
137 };
137 };
138
138
139 let matcher = extract_matcher(py, matcher)?;
139 let matcher = extract_matcher(py, matcher)?;
140 dmap.with_status(
140 dmap.with_status(
141 &*matcher,
141 &*matcher,
142 root_dir.to_path_buf(),
142 root_dir.to_path_buf(),
143 ignore_files,
143 ignore_files,
144 StatusOptions {
144 StatusOptions {
145 check_exec,
145 check_exec,
146 list_clean,
146 list_clean,
147 list_ignored,
147 list_ignored,
148 list_unknown,
148 list_unknown,
149 list_copies,
149 list_copies,
150 collect_traversed_dirs,
150 collect_traversed_dirs,
151 },
151 },
152 after_status,
152 after_status,
153 )
153 )
154 }
154 }
155
155
156 /// Transform a Python matcher into a Rust matcher.
156 /// Transform a Python matcher into a Rust matcher.
157 fn extract_matcher(
157 fn extract_matcher(
158 py: Python,
158 py: Python,
159 matcher: PyObject,
159 matcher: PyObject,
160 ) -> PyResult<Box<dyn Matcher + Sync>> {
160 ) -> PyResult<Box<dyn Matcher + Sync>> {
161 let tampered = matcher
162 .call_method(py, "was_tampered_with", PyTuple::empty(py), None)?
163 .extract::<PyBool>(py)?
164 .is_true();
165 if tampered {
166 return Err(handle_fallback(
167 py,
168 StatusError::Pattern(PatternError::UnsupportedSyntax(
169 "Pattern matcher was tampered with!".to_string(),
170 )),
171 ));
172 };
161 match matcher.get_type(py).name(py).borrow() {
173 match matcher.get_type(py).name(py).borrow() {
162 "alwaysmatcher" => Ok(Box::new(AlwaysMatcher)),
174 "alwaysmatcher" => Ok(Box::new(AlwaysMatcher)),
163 "nevermatcher" => Ok(Box::new(NeverMatcher)),
175 "nevermatcher" => Ok(Box::new(NeverMatcher)),
164 "exactmatcher" => {
176 "exactmatcher" => {
165 let files = matcher.call_method(
177 let files = matcher.call_method(
166 py,
178 py,
167 "files",
179 "files",
168 PyTuple::new(py, &[]),
180 PyTuple::new(py, &[]),
169 None,
181 None,
170 )?;
182 )?;
171 let files: PyList = files.cast_into(py)?;
183 let files: PyList = files.cast_into(py)?;
172 let files: PyResult<Vec<HgPathBuf>> = files
184 let files: PyResult<Vec<HgPathBuf>> = files
173 .iter(py)
185 .iter(py)
174 .map(|f| {
186 .map(|f| {
175 Ok(HgPathBuf::from_bytes(
187 Ok(HgPathBuf::from_bytes(
176 f.extract::<PyBytes>(py)?.data(py),
188 f.extract::<PyBytes>(py)?.data(py),
177 ))
189 ))
178 })
190 })
179 .collect();
191 .collect();
180
192
181 let files = files?;
193 let files = files?;
182 let file_matcher = FileMatcher::new(files)
194 let file_matcher = FileMatcher::new(files)
183 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
195 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
184 Ok(Box::new(file_matcher))
196 Ok(Box::new(file_matcher))
185 }
197 }
186 "includematcher" => {
198 "includematcher" => {
187 // Get the patterns from Python even though most of them are
199 // Get the patterns from Python even though most of them are
188 // redundant with those we will parse later on, as they include
200 // redundant with those we will parse later on, as they include
189 // those passed from the command line.
201 // those passed from the command line.
190 let ignore_patterns: PyResult<Vec<_>> = matcher
202 let ignore_patterns: PyResult<Vec<_>> = matcher
191 .getattr(py, "_kindpats")?
203 .getattr(py, "_kindpats")?
192 .iter(py)?
204 .iter(py)?
193 .map(|k| {
205 .map(|k| {
194 let k = k?;
206 let k = k?;
195 let syntax = parse_pattern_syntax(
207 let syntax = parse_pattern_syntax(
196 &[
208 &[
197 k.get_item(py, 0)?
209 k.get_item(py, 0)?
198 .extract::<PyBytes>(py)?
210 .extract::<PyBytes>(py)?
199 .data(py),
211 .data(py),
200 &b":"[..],
212 &b":"[..],
201 ]
213 ]
202 .concat(),
214 .concat(),
203 )
215 )
204 .map_err(|e| {
216 .map_err(|e| {
205 handle_fallback(py, StatusError::Pattern(e))
217 handle_fallback(py, StatusError::Pattern(e))
206 })?;
218 })?;
207 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
219 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
208 let pattern = pattern.data(py);
220 let pattern = pattern.data(py);
209 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
221 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
210 let source = get_path_from_bytes(source.data(py));
222 let source = get_path_from_bytes(source.data(py));
211 let new = IgnorePattern::new(syntax, pattern, source);
223 let new = IgnorePattern::new(syntax, pattern, source);
212 Ok(new)
224 Ok(new)
213 })
225 })
214 .collect();
226 .collect();
215
227
216 let ignore_patterns = ignore_patterns?;
228 let ignore_patterns = ignore_patterns?;
217
229
218 let matcher = IncludeMatcher::new(ignore_patterns)
230 let matcher = IncludeMatcher::new(ignore_patterns)
219 .map_err(|e| handle_fallback(py, e.into()))?;
231 .map_err(|e| handle_fallback(py, e.into()))?;
220
232
221 Ok(Box::new(matcher))
233 Ok(Box::new(matcher))
222 }
234 }
223 "unionmatcher" => {
235 "unionmatcher" => {
224 let matchers: PyResult<Vec<_>> = matcher
236 let matchers: PyResult<Vec<_>> = matcher
225 .getattr(py, "_matchers")?
237 .getattr(py, "_matchers")?
226 .iter(py)?
238 .iter(py)?
227 .map(|py_matcher| extract_matcher(py, py_matcher?))
239 .map(|py_matcher| extract_matcher(py, py_matcher?))
228 .collect();
240 .collect();
229
241
230 Ok(Box::new(UnionMatcher::new(matchers?)))
242 Ok(Box::new(UnionMatcher::new(matchers?)))
231 }
243 }
232 "intersectionmatcher" => {
244 "intersectionmatcher" => {
233 let m1 = extract_matcher(py, matcher.getattr(py, "_m1")?)?;
245 let m1 = extract_matcher(py, matcher.getattr(py, "_m1")?)?;
234 let m2 = extract_matcher(py, matcher.getattr(py, "_m2")?)?;
246 let m2 = extract_matcher(py, matcher.getattr(py, "_m2")?)?;
235
247
236 Ok(Box::new(IntersectionMatcher::new(m1, m2)))
248 Ok(Box::new(IntersectionMatcher::new(m1, m2)))
237 }
249 }
238 "differencematcher" => {
250 "differencematcher" => {
239 let m1 = extract_matcher(py, matcher.getattr(py, "_m1")?)?;
251 let m1 = extract_matcher(py, matcher.getattr(py, "_m1")?)?;
240 let m2 = extract_matcher(py, matcher.getattr(py, "_m2")?)?;
252 let m2 = extract_matcher(py, matcher.getattr(py, "_m2")?)?;
241
253
242 Ok(Box::new(DifferenceMatcher::new(m1, m2)))
254 Ok(Box::new(DifferenceMatcher::new(m1, m2)))
243 }
255 }
244 e => Err(PyErr::new::<FallbackError, _>(
256 e => Err(PyErr::new::<FallbackError, _>(
245 py,
257 py,
246 format!("Unsupported matcher {}", e),
258 format!("Unsupported matcher {}", e),
247 )),
259 )),
248 }
260 }
249 }
261 }
250
262
251 fn build_response(
263 fn build_response(
252 py: Python,
264 py: Python,
253 status_res: DirstateStatus,
265 status_res: DirstateStatus,
254 warnings: Vec<PatternFileWarning>,
266 warnings: Vec<PatternFileWarning>,
255 ) -> PyResult<PyTuple> {
267 ) -> PyResult<PyTuple> {
256 let modified = collect_status_path_list(py, &status_res.modified);
268 let modified = collect_status_path_list(py, &status_res.modified);
257 let added = collect_status_path_list(py, &status_res.added);
269 let added = collect_status_path_list(py, &status_res.added);
258 let removed = collect_status_path_list(py, &status_res.removed);
270 let removed = collect_status_path_list(py, &status_res.removed);
259 let deleted = collect_status_path_list(py, &status_res.deleted);
271 let deleted = collect_status_path_list(py, &status_res.deleted);
260 let clean = collect_status_path_list(py, &status_res.clean);
272 let clean = collect_status_path_list(py, &status_res.clean);
261 let ignored = collect_status_path_list(py, &status_res.ignored);
273 let ignored = collect_status_path_list(py, &status_res.ignored);
262 let unknown = collect_status_path_list(py, &status_res.unknown);
274 let unknown = collect_status_path_list(py, &status_res.unknown);
263 let unsure = collect_status_path_list(py, &status_res.unsure);
275 let unsure = collect_status_path_list(py, &status_res.unsure);
264 let bad = collect_bad_matches(py, &status_res.bad)?;
276 let bad = collect_bad_matches(py, &status_res.bad)?;
265 let traversed = collect_pybytes_list(py, status_res.traversed.iter());
277 let traversed = collect_pybytes_list(py, status_res.traversed.iter());
266 let dirty = status_res.dirty.to_py_object(py);
278 let dirty = status_res.dirty.to_py_object(py);
267 let py_warnings = PyList::new(py, &[]);
279 let py_warnings = PyList::new(py, &[]);
268 for warning in warnings.iter() {
280 for warning in warnings.iter() {
269 // We use duck-typing on the Python side for dispatch, good enough for
281 // We use duck-typing on the Python side for dispatch, good enough for
270 // now.
282 // now.
271 match warning {
283 match warning {
272 PatternFileWarning::InvalidSyntax(file, syn) => {
284 PatternFileWarning::InvalidSyntax(file, syn) => {
273 py_warnings.append(
285 py_warnings.append(
274 py,
286 py,
275 (
287 (
276 PyBytes::new(py, &get_bytes_from_path(file)),
288 PyBytes::new(py, &get_bytes_from_path(file)),
277 PyBytes::new(py, syn),
289 PyBytes::new(py, syn),
278 )
290 )
279 .to_py_object(py)
291 .to_py_object(py)
280 .into_object(),
292 .into_object(),
281 );
293 );
282 }
294 }
283 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
295 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
284 py,
296 py,
285 PyBytes::new(py, &get_bytes_from_path(file)).into_object(),
297 PyBytes::new(py, &get_bytes_from_path(file)).into_object(),
286 ),
298 ),
287 }
299 }
288 }
300 }
289
301
290 Ok(PyTuple::new(
302 Ok(PyTuple::new(
291 py,
303 py,
292 &[
304 &[
293 unsure.into_object(),
305 unsure.into_object(),
294 modified.into_object(),
306 modified.into_object(),
295 added.into_object(),
307 added.into_object(),
296 removed.into_object(),
308 removed.into_object(),
297 deleted.into_object(),
309 deleted.into_object(),
298 clean.into_object(),
310 clean.into_object(),
299 ignored.into_object(),
311 ignored.into_object(),
300 unknown.into_object(),
312 unknown.into_object(),
301 py_warnings.into_object(),
313 py_warnings.into_object(),
302 bad.into_object(),
314 bad.into_object(),
303 traversed.into_object(),
315 traversed.into_object(),
304 dirty.into_object(),
316 dirty.into_object(),
305 ][..],
317 ][..],
306 ))
318 ))
307 }
319 }
General Comments 0
You need to be logged in to leave comments. Login now