##// END OF EJS Templates
rust-hg-cpython: update status bridge with the new `traversedir` support...
Raphaël Gomès -
r45354:01afda7e default
parent child Browse files
Show More
@@ -1,146 +1,147 b''
1 1 // dirstate.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::dirstate` module provided by the
9 9 //! `hg-core` package.
10 10 //!
11 11 //! From Python, this will be seen as `mercurial.rustext.dirstate`
12 12 mod copymap;
13 13 mod dirs_multiset;
14 14 mod dirstate_map;
15 15 mod non_normal_entries;
16 16 mod status;
17 17 use crate::{
18 18 dirstate::{
19 19 dirs_multiset::Dirs, dirstate_map::DirstateMap, status::status_wrapper,
20 20 },
21 21 exceptions,
22 22 };
23 23 use cpython::{
24 24 exc, PyBytes, PyDict, PyErr, PyList, PyModule, PyObject, PyResult,
25 25 PySequence, Python,
26 26 };
27 27 use hg::{
28 28 utils::hg_path::HgPathBuf, DirstateEntry, DirstateParseError, EntryState,
29 29 StateMap,
30 30 };
31 31 use libc::{c_char, c_int};
32 32 use std::convert::TryFrom;
33 33
34 34 // C code uses a custom `dirstate_tuple` type, checks in multiple instances
35 35 // for this type, and raises a Python `Exception` if the check does not pass.
36 36 // Because this type differs only in name from the regular Python tuple, it
37 37 // would be a good idea in the near future to remove it entirely to allow
38 38 // for a pure Python tuple of the same effective structure to be used,
39 39 // rendering this type and the capsule below useless.
40 40 py_capsule_fn!(
41 41 from mercurial.cext.parsers import make_dirstate_tuple_CAPI
42 42 as make_dirstate_tuple_capi
43 43 signature (
44 44 state: c_char,
45 45 mode: c_int,
46 46 size: c_int,
47 47 mtime: c_int,
48 48 ) -> *mut RawPyObject
49 49 );
50 50
51 51 pub fn make_dirstate_tuple(
52 52 py: Python,
53 53 entry: &DirstateEntry,
54 54 ) -> PyResult<PyObject> {
55 55 // might be silly to retrieve capsule function in hot loop
56 56 let make = make_dirstate_tuple_capi::retrieve(py)?;
57 57
58 58 let &DirstateEntry {
59 59 state,
60 60 mode,
61 61 size,
62 62 mtime,
63 63 } = entry;
64 64 // Explicitly go through u8 first, then cast to platform-specific `c_char`
65 65 // because Into<u8> has a specific implementation while `as c_char` would
66 66 // just do a naive enum cast.
67 67 let state_code: u8 = state.into();
68 68
69 69 let maybe_obj = unsafe {
70 70 let ptr = make(state_code as c_char, mode, size, mtime);
71 71 PyObject::from_owned_ptr_opt(py, ptr)
72 72 };
73 73 maybe_obj.ok_or_else(|| PyErr::fetch(py))
74 74 }
75 75
76 76 pub fn extract_dirstate(py: Python, dmap: &PyDict) -> Result<StateMap, PyErr> {
77 77 dmap.items(py)
78 78 .iter()
79 79 .map(|(filename, stats)| {
80 80 let stats = stats.extract::<PySequence>(py)?;
81 81 let state = stats.get_item(py, 0)?.extract::<PyBytes>(py)?;
82 82 let state = EntryState::try_from(state.data(py)[0]).map_err(
83 83 |e: DirstateParseError| {
84 84 PyErr::new::<exc::ValueError, _>(py, e.to_string())
85 85 },
86 86 )?;
87 87 let mode = stats.get_item(py, 1)?.extract(py)?;
88 88 let size = stats.get_item(py, 2)?.extract(py)?;
89 89 let mtime = stats.get_item(py, 3)?.extract(py)?;
90 90 let filename = filename.extract::<PyBytes>(py)?;
91 91 let filename = filename.data(py);
92 92 Ok((
93 93 HgPathBuf::from(filename.to_owned()),
94 94 DirstateEntry {
95 95 state,
96 96 mode,
97 97 size,
98 98 mtime,
99 99 },
100 100 ))
101 101 })
102 102 .collect()
103 103 }
104 104
105 105 /// Create the module, with `__package__` given from parent
106 106 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
107 107 let dotted_name = &format!("{}.dirstate", package);
108 108 let m = PyModule::new(py, dotted_name)?;
109 109
110 110 simple_logger::init_by_env();
111 111
112 112 m.add(py, "__package__", package)?;
113 113 m.add(py, "__doc__", "Dirstate - Rust implementation")?;
114 114
115 115 m.add(
116 116 py,
117 117 "FallbackError",
118 118 py.get_type::<exceptions::FallbackError>(),
119 119 )?;
120 120 m.add_class::<Dirs>(py)?;
121 121 m.add_class::<DirstateMap>(py)?;
122 122 m.add(
123 123 py,
124 124 "status",
125 125 py_fn!(
126 126 py,
127 127 status_wrapper(
128 128 dmap: DirstateMap,
129 129 root_dir: PyObject,
130 130 matcher: PyObject,
131 131 ignorefiles: PyList,
132 132 check_exec: bool,
133 133 last_normal_time: i64,
134 134 list_clean: bool,
135 135 list_ignored: bool,
136 list_unknown: bool
136 list_unknown: bool,
137 collect_traversed_dirs: bool
137 138 )
138 139 ),
139 140 )?;
140 141
141 142 let sys = PyModule::import(py, "sys")?;
142 143 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
143 144 sys_modules.set_item(py, dotted_name, &m)?;
144 145
145 146 Ok(m)
146 147 }
@@ -1,297 +1,303 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::{
18 18 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
19 19 parse_pattern_syntax, status,
20 20 utils::{
21 21 files::{get_bytes_from_path, get_path_from_bytes},
22 22 hg_path::{HgPath, HgPathBuf},
23 23 },
24 24 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
25 25 StatusOptions,
26 26 };
27 27 use std::borrow::{Borrow, Cow};
28 28
29 29 /// This will be useless once trait impls for collection are added to `PyBytes`
30 30 /// upstream.
31 31 fn collect_pybytes_list(
32 32 py: Python,
33 33 collection: &[impl AsRef<HgPath>],
34 34 ) -> PyList {
35 35 let list = PyList::new(py, &[]);
36 36
37 37 for path in collection.iter() {
38 38 list.append(
39 39 py,
40 40 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
41 41 )
42 42 }
43 43
44 44 list
45 45 }
46 46
47 47 fn collect_bad_matches(
48 48 py: Python,
49 49 collection: &[(impl AsRef<HgPath>, BadMatch)],
50 50 ) -> PyResult<PyList> {
51 51 let list = PyList::new(py, &[]);
52 52
53 53 let os = py.import("os")?;
54 54 let get_error_message = |code: i32| -> PyResult<_> {
55 55 os.call(
56 56 py,
57 57 "strerror",
58 58 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
59 59 None,
60 60 )
61 61 };
62 62
63 63 for (path, bad_match) in collection.iter() {
64 64 let message = match bad_match {
65 65 BadMatch::OsError(code) => get_error_message(*code)?,
66 66 BadMatch::BadType(bad_type) => format!(
67 67 "unsupported file type (type is {})",
68 68 bad_type.to_string()
69 69 )
70 70 .to_py_object(py)
71 71 .into_object(),
72 72 };
73 73 list.append(
74 74 py,
75 75 (PyBytes::new(py, path.as_ref().as_bytes()), message)
76 76 .to_py_object(py)
77 77 .into_object(),
78 78 )
79 79 }
80 80
81 81 Ok(list)
82 82 }
83 83
84 84 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
85 85 match err {
86 86 StatusError::Pattern(e) => {
87 87 let as_string = e.to_string();
88 88 log::trace!("Rust status fallback: `{}`", &as_string);
89 89
90 90 PyErr::new::<FallbackError, _>(py, &as_string)
91 91 }
92 92 e => PyErr::new::<ValueError, _>(py, e.to_string()),
93 93 }
94 94 }
95 95
96 96 pub fn status_wrapper(
97 97 py: Python,
98 98 dmap: DirstateMap,
99 99 matcher: PyObject,
100 100 root_dir: PyObject,
101 101 ignore_files: PyList,
102 102 check_exec: bool,
103 103 last_normal_time: i64,
104 104 list_clean: bool,
105 105 list_ignored: bool,
106 106 list_unknown: bool,
107 collect_traversed_dirs: bool,
107 108 ) -> PyResult<PyTuple> {
108 109 let bytes = root_dir.extract::<PyBytes>(py)?;
109 110 let root_dir = get_path_from_bytes(bytes.data(py));
110 111
111 112 let dmap: DirstateMap = dmap.to_py_object(py);
112 113 let dmap = dmap.get_inner(py);
113 114
114 115 let ignore_files: PyResult<Vec<_>> = ignore_files
115 116 .iter(py)
116 117 .map(|b| {
117 118 let file = b.extract::<PyBytes>(py)?;
118 119 Ok(get_path_from_bytes(file.data(py)).to_owned())
119 120 })
120 121 .collect();
121 122 let ignore_files = ignore_files?;
122 123
123 124 match matcher.get_type(py).name(py).borrow() {
124 125 "alwaysmatcher" => {
125 126 let matcher = AlwaysMatcher;
126 127 let ((lookup, status_res), warnings) = status(
127 128 &dmap,
128 129 &matcher,
129 130 &root_dir,
130 131 ignore_files,
131 132 StatusOptions {
132 133 check_exec,
133 134 last_normal_time,
134 135 list_clean,
135 136 list_ignored,
136 137 list_unknown,
138 collect_traversed_dirs,
137 139 },
138 140 )
139 141 .map_err(|e| handle_fallback(py, e))?;
140 142 build_response(py, lookup, status_res, warnings)
141 143 }
142 144 "exactmatcher" => {
143 145 let files = matcher.call_method(
144 146 py,
145 147 "files",
146 148 PyTuple::new(py, &[]),
147 149 None,
148 150 )?;
149 151 let files: PyList = files.cast_into(py)?;
150 152 let files: PyResult<Vec<HgPathBuf>> = files
151 153 .iter(py)
152 154 .map(|f| {
153 155 Ok(HgPathBuf::from_bytes(
154 156 f.extract::<PyBytes>(py)?.data(py),
155 157 ))
156 158 })
157 159 .collect();
158 160
159 161 let files = files?;
160 162 let matcher = FileMatcher::new(&files)
161 163 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
162 164 let ((lookup, status_res), warnings) = status(
163 165 &dmap,
164 166 &matcher,
165 167 &root_dir,
166 168 ignore_files,
167 169 StatusOptions {
168 170 check_exec,
169 171 last_normal_time,
170 172 list_clean,
171 173 list_ignored,
172 174 list_unknown,
175 collect_traversed_dirs,
173 176 },
174 177 )
175 178 .map_err(|e| handle_fallback(py, e))?;
176 179 build_response(py, lookup, status_res, warnings)
177 180 }
178 181 "includematcher" => {
179 182 // Get the patterns from Python even though most of them are
180 183 // redundant with those we will parse later on, as they include
181 184 // those passed from the command line.
182 185 let ignore_patterns: PyResult<Vec<_>> = matcher
183 186 .getattr(py, "_kindpats")?
184 187 .iter(py)?
185 188 .map(|k| {
186 189 let k = k?;
187 190 let syntax = parse_pattern_syntax(
188 191 &[
189 192 k.get_item(py, 0)?
190 193 .extract::<PyBytes>(py)?
191 194 .data(py),
192 195 &b":"[..],
193 196 ]
194 197 .concat(),
195 198 )
196 199 .map_err(|e| {
197 200 handle_fallback(py, StatusError::Pattern(e))
198 201 })?;
199 202 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
200 203 let pattern = pattern.data(py);
201 204 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
202 205 let source = get_path_from_bytes(source.data(py));
203 206 let new = IgnorePattern::new(syntax, pattern, source);
204 207 Ok(new)
205 208 })
206 209 .collect();
207 210
208 211 let ignore_patterns = ignore_patterns?;
209 212 let mut all_warnings = vec![];
210 213
211 214 let (matcher, warnings) =
212 215 IncludeMatcher::new(ignore_patterns, &root_dir)
213 216 .map_err(|e| handle_fallback(py, e.into()))?;
214 217 all_warnings.extend(warnings);
215 218
216 219 let ((lookup, status_res), warnings) = status(
217 220 &dmap,
218 221 &matcher,
219 222 &root_dir,
220 223 ignore_files,
221 224 StatusOptions {
222 225 check_exec,
223 226 last_normal_time,
224 227 list_clean,
225 228 list_ignored,
226 229 list_unknown,
230 collect_traversed_dirs,
227 231 },
228 232 )
229 233 .map_err(|e| handle_fallback(py, e))?;
230 234
231 235 all_warnings.extend(warnings);
232 236
233 237 build_response(py, lookup, status_res, all_warnings)
234 238 }
235 239 e => {
236 240 return Err(PyErr::new::<ValueError, _>(
237 241 py,
238 242 format!("Unsupported matcher {}", e),
239 243 ));
240 244 }
241 245 }
242 246 }
243 247
244 248 fn build_response(
245 249 py: Python,
246 250 lookup: Vec<Cow<HgPath>>,
247 251 status_res: DirstateStatus,
248 252 warnings: Vec<PatternFileWarning>,
249 253 ) -> PyResult<PyTuple> {
250 254 let modified = collect_pybytes_list(py, status_res.modified.as_ref());
251 255 let added = collect_pybytes_list(py, status_res.added.as_ref());
252 256 let removed = collect_pybytes_list(py, status_res.removed.as_ref());
253 257 let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
254 258 let clean = collect_pybytes_list(py, status_res.clean.as_ref());
255 259 let ignored = collect_pybytes_list(py, status_res.ignored.as_ref());
256 260 let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
257 261 let lookup = collect_pybytes_list(py, lookup.as_ref());
258 262 let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
263 let traversed = collect_pybytes_list(py, status_res.traversed.as_ref());
259 264 let py_warnings = PyList::new(py, &[]);
260 265 for warning in warnings.iter() {
261 266 // We use duck-typing on the Python side for dispatch, good enough for
262 267 // now.
263 268 match warning {
264 269 PatternFileWarning::InvalidSyntax(file, syn) => {
265 270 py_warnings.append(
266 271 py,
267 272 (
268 273 PyBytes::new(py, &get_bytes_from_path(&file)),
269 274 PyBytes::new(py, syn),
270 275 )
271 276 .to_py_object(py)
272 277 .into_object(),
273 278 );
274 279 }
275 280 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
276 281 py,
277 282 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
278 283 ),
279 284 }
280 285 }
281 286
282 287 Ok(PyTuple::new(
283 288 py,
284 289 &[
285 290 lookup.into_object(),
286 291 modified.into_object(),
287 292 added.into_object(),
288 293 removed.into_object(),
289 294 deleted.into_object(),
290 295 clean.into_object(),
291 296 ignored.into_object(),
292 297 unknown.into_object(),
293 298 py_warnings.into_object(),
294 299 bad.into_object(),
300 traversed.into_object(),
295 301 ][..],
296 302 ))
297 303 }
General Comments 0
You need to be logged in to leave comments. Login now