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