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