##// END OF EJS Templates
rust-dirstate-status: add `walk_explicit` implementation, use `Matcher` trait...
Raphaël Gomès -
r44367:8c778261 default
parent child Browse files
Show More
@@ -1,228 +1,306 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 //! Rust implementation of dirstate.status (dirstate.py).
8 //! Rust implementation of dirstate.status (dirstate.py).
9 //! It is currently missing a lot of functionality compared to the Python one
9 //! It is currently missing a lot of functionality compared to the Python one
10 //! and will only be triggered in narrow cases.
10 //! and will only be triggered in narrow cases.
11
11
12 use crate::{
12 use crate::{
13 dirstate::SIZE_FROM_OTHER_PARENT,
13 dirstate::SIZE_FROM_OTHER_PARENT,
14 matchers::Matcher,
14 utils::{
15 utils::{
15 files::HgMetadata,
16 files::HgMetadata,
16 hg_path::{hg_path_to_path_buf, HgPath},
17 hg_path::{hg_path_to_path_buf, HgPath},
17 },
18 },
18 CopyMap, DirstateEntry, DirstateMap, EntryState,
19 CopyMap, DirstateEntry, DirstateMap, EntryState,
19 };
20 };
20 use rayon::prelude::*;
21 use rayon::prelude::*;
22 use std::collections::HashSet;
21 use std::path::Path;
23 use std::path::Path;
22
24
23 /// Marker enum used to dispatch new status entries into the right collections.
25 /// Marker enum used to dispatch new status entries into the right collections.
24 /// Is similar to `crate::EntryState`, but represents the transient state of
26 /// Is similar to `crate::EntryState`, but represents the transient state of
25 /// entries during the lifetime of a command.
27 /// entries during the lifetime of a command.
26 enum Dispatch {
28 enum Dispatch {
27 Unsure,
29 Unsure,
28 Modified,
30 Modified,
29 Added,
31 Added,
30 Removed,
32 Removed,
31 Deleted,
33 Deleted,
32 Clean,
34 Clean,
33 Unknown,
35 Unknown,
34 }
36 }
35
37
38 type IoResult<T> = std::io::Result<T>;
39
36 /// Dates and times that are outside the 31-bit signed range are compared
40 /// Dates and times that are outside the 31-bit signed range are compared
37 /// modulo 2^31. This should prevent hg from behaving badly with very large
41 /// modulo 2^31. This should prevent hg from behaving badly with very large
38 /// files or corrupt dates while still having a high probability of detecting
42 /// files or corrupt dates while still having a high probability of detecting
39 /// changes. (issue2608)
43 /// changes. (issue2608)
40 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
44 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
41 /// is not defined for `i32`, and there is no `As` trait. This forces the
45 /// is not defined for `i32`, and there is no `As` trait. This forces the
42 /// caller to cast `b` as `i32`.
46 /// caller to cast `b` as `i32`.
43 fn mod_compare(a: i32, b: i32) -> bool {
47 fn mod_compare(a: i32, b: i32) -> bool {
44 a & i32::max_value() != b & i32::max_value()
48 a & i32::max_value() != b & i32::max_value()
45 }
49 }
46
50
47 /// The file corresponding to the dirstate entry was found on the filesystem.
51 /// The file corresponding to the dirstate entry was found on the filesystem.
48 fn dispatch_found(
52 fn dispatch_found(
49 filename: impl AsRef<HgPath>,
53 filename: impl AsRef<HgPath>,
50 entry: DirstateEntry,
54 entry: DirstateEntry,
51 metadata: HgMetadata,
55 metadata: HgMetadata,
52 copy_map: &CopyMap,
56 copy_map: &CopyMap,
53 check_exec: bool,
57 check_exec: bool,
54 list_clean: bool,
58 list_clean: bool,
55 last_normal_time: i64,
59 last_normal_time: i64,
56 ) -> Dispatch {
60 ) -> Dispatch {
57 let DirstateEntry {
61 let DirstateEntry {
58 state,
62 state,
59 mode,
63 mode,
60 mtime,
64 mtime,
61 size,
65 size,
62 } = entry;
66 } = entry;
63
67
64 let HgMetadata {
68 let HgMetadata {
65 st_mode,
69 st_mode,
66 st_size,
70 st_size,
67 st_mtime,
71 st_mtime,
68 ..
72 ..
69 } = metadata;
73 } = metadata;
70
74
71 match state {
75 match state {
72 EntryState::Normal => {
76 EntryState::Normal => {
73 let size_changed = mod_compare(size, st_size as i32);
77 let size_changed = mod_compare(size, st_size as i32);
74 let mode_changed =
78 let mode_changed =
75 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
79 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
76 let metadata_changed = size >= 0 && (size_changed || mode_changed);
80 let metadata_changed = size >= 0 && (size_changed || mode_changed);
77 let other_parent = size == SIZE_FROM_OTHER_PARENT;
81 let other_parent = size == SIZE_FROM_OTHER_PARENT;
78 if metadata_changed
82 if metadata_changed
79 || other_parent
83 || other_parent
80 || copy_map.contains_key(filename.as_ref())
84 || copy_map.contains_key(filename.as_ref())
81 {
85 {
82 Dispatch::Modified
86 Dispatch::Modified
83 } else if mod_compare(mtime, st_mtime as i32) {
87 } else if mod_compare(mtime, st_mtime as i32) {
84 Dispatch::Unsure
88 Dispatch::Unsure
85 } else if st_mtime == last_normal_time {
89 } else if st_mtime == last_normal_time {
86 // the file may have just been marked as normal and
90 // the file may have just been marked as normal and
87 // it may have changed in the same second without
91 // it may have changed in the same second without
88 // changing its size. This can happen if we quickly
92 // changing its size. This can happen if we quickly
89 // do multiple commits. Force lookup, so we don't
93 // do multiple commits. Force lookup, so we don't
90 // miss such a racy file change.
94 // miss such a racy file change.
91 Dispatch::Unsure
95 Dispatch::Unsure
92 } else if list_clean {
96 } else if list_clean {
93 Dispatch::Clean
97 Dispatch::Clean
94 } else {
98 } else {
95 Dispatch::Unknown
99 Dispatch::Unknown
96 }
100 }
97 }
101 }
98 EntryState::Merged => Dispatch::Modified,
102 EntryState::Merged => Dispatch::Modified,
99 EntryState::Added => Dispatch::Added,
103 EntryState::Added => Dispatch::Added,
100 EntryState::Removed => Dispatch::Removed,
104 EntryState::Removed => Dispatch::Removed,
101 EntryState::Unknown => Dispatch::Unknown,
105 EntryState::Unknown => Dispatch::Unknown,
102 }
106 }
103 }
107 }
104
108
105 /// The file corresponding to this Dirstate entry is missing.
109 /// The file corresponding to this Dirstate entry is missing.
106 fn dispatch_missing(state: EntryState) -> Dispatch {
110 fn dispatch_missing(state: EntryState) -> Dispatch {
107 match state {
111 match state {
108 // File was removed from the filesystem during commands
112 // File was removed from the filesystem during commands
109 EntryState::Normal | EntryState::Merged | EntryState::Added => {
113 EntryState::Normal | EntryState::Merged | EntryState::Added => {
110 Dispatch::Deleted
114 Dispatch::Deleted
111 }
115 }
112 // File was removed, everything is normal
116 // File was removed, everything is normal
113 EntryState::Removed => Dispatch::Removed,
117 EntryState::Removed => Dispatch::Removed,
114 // File is unknown to Mercurial, everything is normal
118 // File is unknown to Mercurial, everything is normal
115 EntryState::Unknown => Dispatch::Unknown,
119 EntryState::Unknown => Dispatch::Unknown,
116 }
120 }
117 }
121 }
118
122
123 /// Get stat data about the files explicitly specified by match.
124 /// TODO subrepos
125 fn walk_explicit<'a>(
126 files: &'a HashSet<&HgPath>,
127 dmap: &'a DirstateMap,
128 root_dir: impl AsRef<Path> + Sync + Send,
129 check_exec: bool,
130 list_clean: bool,
131 last_normal_time: i64,
132 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
133 files.par_iter().filter_map(move |filename| {
134 // TODO normalization
135 let normalized = filename.as_ref();
136
137 let buf = match hg_path_to_path_buf(normalized) {
138 Ok(x) => x,
139 Err(e) => return Some(Err(e.into())),
140 };
141 let target = root_dir.as_ref().join(buf);
142 let st = target.symlink_metadata();
143 match st {
144 Ok(meta) => {
145 let file_type = meta.file_type();
146 if file_type.is_file() || file_type.is_symlink() {
147 if let Some(entry) = dmap.get(normalized) {
148 return Some(Ok((
149 normalized,
150 dispatch_found(
151 &normalized,
152 *entry,
153 HgMetadata::from_metadata(meta),
154 &dmap.copy_map,
155 check_exec,
156 list_clean,
157 last_normal_time,
158 ),
159 )));
160 }
161 } else {
162 if dmap.contains_key(normalized) {
163 return Some(Ok((normalized, Dispatch::Removed)));
164 }
165 }
166 }
167 Err(_) => {
168 if let Some(entry) = dmap.get(normalized) {
169 return Some(Ok((
170 normalized,
171 dispatch_missing(entry.state),
172 )));
173 }
174 }
175 };
176 None
177 })
178 }
179
119 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
180 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
120 /// the relevant collections.
181 /// the relevant collections.
121 fn stat_dmap_entries(
182 fn stat_dmap_entries(
122 dmap: &DirstateMap,
183 dmap: &DirstateMap,
123 root_dir: impl AsRef<Path> + Sync + Send,
184 root_dir: impl AsRef<Path> + Sync + Send,
124 check_exec: bool,
185 check_exec: bool,
125 list_clean: bool,
186 list_clean: bool,
126 last_normal_time: i64,
187 last_normal_time: i64,
127 ) -> impl ParallelIterator<Item = std::io::Result<(&HgPath, Dispatch)>> {
188 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
128 dmap.par_iter().map(move |(filename, entry)| {
189 dmap.par_iter().map(move |(filename, entry)| {
129 let filename: &HgPath = filename;
190 let filename: &HgPath = filename;
130 let filename_as_path = hg_path_to_path_buf(filename)?;
191 let filename_as_path = hg_path_to_path_buf(filename)?;
131 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
192 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
132
193
133 match meta {
194 match meta {
134 Ok(ref m)
195 Ok(ref m)
135 if !(m.file_type().is_file()
196 if !(m.file_type().is_file()
136 || m.file_type().is_symlink()) =>
197 || m.file_type().is_symlink()) =>
137 {
198 {
138 Ok((filename, dispatch_missing(entry.state)))
199 Ok((filename, dispatch_missing(entry.state)))
139 }
200 }
140 Ok(m) => Ok((
201 Ok(m) => Ok((
141 filename,
202 filename,
142 dispatch_found(
203 dispatch_found(
143 filename,
204 filename,
144 *entry,
205 *entry,
145 HgMetadata::from_metadata(m),
206 HgMetadata::from_metadata(m),
146 &dmap.copy_map,
207 &dmap.copy_map,
147 check_exec,
208 check_exec,
148 list_clean,
209 list_clean,
149 last_normal_time,
210 last_normal_time,
150 ),
211 ),
151 )),
212 )),
152 Err(ref e)
213 Err(ref e)
153 if e.kind() == std::io::ErrorKind::NotFound
214 if e.kind() == std::io::ErrorKind::NotFound
154 || e.raw_os_error() == Some(20) =>
215 || e.raw_os_error() == Some(20) =>
155 {
216 {
156 // Rust does not yet have an `ErrorKind` for
217 // Rust does not yet have an `ErrorKind` for
157 // `NotADirectory` (errno 20)
218 // `NotADirectory` (errno 20)
158 // It happens if the dirstate contains `foo/bar` and
219 // It happens if the dirstate contains `foo/bar` and
159 // foo is not a directory
220 // foo is not a directory
160 Ok((filename, dispatch_missing(entry.state)))
221 Ok((filename, dispatch_missing(entry.state)))
161 }
222 }
162 Err(e) => Err(e),
223 Err(e) => Err(e),
163 }
224 }
164 })
225 })
165 }
226 }
166
227
167 pub struct StatusResult<'a> {
228 pub struct StatusResult<'a> {
168 pub modified: Vec<&'a HgPath>,
229 pub modified: Vec<&'a HgPath>,
169 pub added: Vec<&'a HgPath>,
230 pub added: Vec<&'a HgPath>,
170 pub removed: Vec<&'a HgPath>,
231 pub removed: Vec<&'a HgPath>,
171 pub deleted: Vec<&'a HgPath>,
232 pub deleted: Vec<&'a HgPath>,
172 pub clean: Vec<&'a HgPath>,
233 pub clean: Vec<&'a HgPath>,
173 /* TODO ignored
234 /* TODO ignored
174 * TODO unknown */
235 * TODO unknown */
175 }
236 }
176
237
177 fn build_response(
238 fn build_response<'a>(
178 results: Vec<(&HgPath, Dispatch)>,
239 results: impl IntoIterator<Item = IoResult<(&'a HgPath, Dispatch)>>,
179 ) -> (Vec<&HgPath>, StatusResult) {
240 ) -> IoResult<(Vec<&'a HgPath>, StatusResult<'a>)> {
180 let mut lookup = vec![];
241 let mut lookup = vec![];
181 let mut modified = vec![];
242 let mut modified = vec![];
182 let mut added = vec![];
243 let mut added = vec![];
183 let mut removed = vec![];
244 let mut removed = vec![];
184 let mut deleted = vec![];
245 let mut deleted = vec![];
185 let mut clean = vec![];
246 let mut clean = vec![];
186
247
187 for (filename, dispatch) in results.into_iter() {
248 for res in results.into_iter() {
249 let (filename, dispatch) = res?;
188 match dispatch {
250 match dispatch {
189 Dispatch::Unknown => {}
251 Dispatch::Unknown => {}
190 Dispatch::Unsure => lookup.push(filename),
252 Dispatch::Unsure => lookup.push(filename),
191 Dispatch::Modified => modified.push(filename),
253 Dispatch::Modified => modified.push(filename),
192 Dispatch::Added => added.push(filename),
254 Dispatch::Added => added.push(filename),
193 Dispatch::Removed => removed.push(filename),
255 Dispatch::Removed => removed.push(filename),
194 Dispatch::Deleted => deleted.push(filename),
256 Dispatch::Deleted => deleted.push(filename),
195 Dispatch::Clean => clean.push(filename),
257 Dispatch::Clean => clean.push(filename),
196 }
258 }
197 }
259 }
198
260
199 (
261 Ok((
200 lookup,
262 lookup,
201 StatusResult {
263 StatusResult {
202 modified,
264 modified,
203 added,
265 added,
204 removed,
266 removed,
205 deleted,
267 deleted,
206 clean,
268 clean,
207 },
269 },
208 )
270 ))
209 }
271 }
210
272
211 pub fn status(
273 pub fn status<'a: 'c, 'b: 'c, 'c>(
212 dmap: &DirstateMap,
274 dmap: &'a DirstateMap,
275 matcher: &'b (impl Matcher),
213 root_dir: impl AsRef<Path> + Sync + Send + Copy,
276 root_dir: impl AsRef<Path> + Sync + Send + Copy,
214 list_clean: bool,
277 list_clean: bool,
215 last_normal_time: i64,
278 last_normal_time: i64,
216 check_exec: bool,
279 check_exec: bool,
217 ) -> std::io::Result<(Vec<&HgPath>, StatusResult)> {
280 ) -> IoResult<(Vec<&'c HgPath>, StatusResult<'c>)> {
218 let results: std::io::Result<_> = stat_dmap_entries(
281 let files = matcher.file_set();
219 &dmap,
282 let mut results = vec![];
220 root_dir,
283 if let Some(files) = files {
221 check_exec,
284 results.par_extend(walk_explicit(
222 list_clean,
285 &files,
223 last_normal_time,
286 &dmap,
224 )
287 root_dir,
225 .collect();
288 check_exec,
289 list_clean,
290 last_normal_time,
291 ));
292 }
226
293
227 Ok(build_response(results?))
294 if !matcher.is_exact() {
295 let stat_results = stat_dmap_entries(
296 &dmap,
297 root_dir,
298 check_exec,
299 list_clean,
300 last_normal_time,
301 );
302 results.par_extend(stat_results);
303 }
304
305 build_response(results)
228 }
306 }
@@ -1,69 +1,80 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;
12 use crate::dirstate::DirstateMap;
13 use cpython::exc::ValueError;
13 use cpython::exc::ValueError;
14 use cpython::{
14 use cpython::{
15 PyBytes, PyErr, PyList, PyObject, PyResult, Python, PythonObject,
15 PyBytes, PyErr, PyList, PyObject, PyResult, Python, PythonObject,
16 ToPyObject,
16 ToPyObject,
17 };
17 };
18 use hg::utils::files::get_path_from_bytes;
18 use hg::utils::files::get_path_from_bytes;
19
19
20 use hg::matchers::AlwaysMatcher;
20 use hg::status;
21 use hg::status;
21 use hg::utils::hg_path::HgPath;
22 use hg::utils::hg_path::HgPath;
22
23
23 /// This will be useless once trait impls for collection are added to `PyBytes`
24 /// This will be useless once trait impls for collection are added to `PyBytes`
24 /// upstream.
25 /// upstream.
25 fn collect_pybytes_list<P: AsRef<HgPath>>(
26 fn collect_pybytes_list<P: AsRef<HgPath>>(
26 py: Python,
27 py: Python,
27 collection: &[P],
28 collection: &[P],
28 ) -> PyList {
29 ) -> PyList {
29 let list = PyList::new(py, &[]);
30 let list = PyList::new(py, &[]);
30
31
31 for (i, path) in collection.iter().enumerate() {
32 for (i, path) in collection.iter().enumerate() {
32 list.insert_item(
33 list.insert_item(
33 py,
34 py,
34 i,
35 i,
35 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
36 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
36 )
37 )
37 }
38 }
38
39
39 list
40 list
40 }
41 }
41
42
42 pub fn status_wrapper(
43 pub fn status_wrapper(
43 py: Python,
44 py: Python,
44 dmap: DirstateMap,
45 dmap: DirstateMap,
45 root_dir: PyObject,
46 root_dir: PyObject,
46 list_clean: bool,
47 list_clean: bool,
47 last_normal_time: i64,
48 last_normal_time: i64,
48 check_exec: bool,
49 check_exec: bool,
49 ) -> PyResult<(PyList, PyList, PyList, PyList, PyList, PyList, PyList)> {
50 ) -> PyResult<(PyList, PyList, PyList, PyList, PyList, PyList, PyList)> {
50 let bytes = root_dir.extract::<PyBytes>(py)?;
51 let bytes = root_dir.extract::<PyBytes>(py)?;
51 let root_dir = get_path_from_bytes(bytes.data(py));
52 let root_dir = get_path_from_bytes(bytes.data(py));
52
53
53 let dmap: DirstateMap = dmap.to_py_object(py);
54 let dmap: DirstateMap = dmap.to_py_object(py);
54 let dmap = dmap.get_inner(py);
55 let dmap = dmap.get_inner(py);
55
56
56 let (lookup, status_res) =
57 // TODO removed in the next patch to get the code to compile. This patch
57 status(&dmap, &root_dir, list_clean, last_normal_time, check_exec)
58 // is part of a series and does not make real sense on its own.
58 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
59 let matcher = AlwaysMatcher;
60
61 let (lookup, status_res) = status(
62 &dmap,
63 &matcher,
64 &root_dir,
65 list_clean,
66 last_normal_time,
67 check_exec,
68 )
69 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
59
70
60 let modified = collect_pybytes_list(py, status_res.modified.as_ref());
71 let modified = collect_pybytes_list(py, status_res.modified.as_ref());
61 let added = collect_pybytes_list(py, status_res.added.as_ref());
72 let added = collect_pybytes_list(py, status_res.added.as_ref());
62 let removed = collect_pybytes_list(py, status_res.removed.as_ref());
73 let removed = collect_pybytes_list(py, status_res.removed.as_ref());
63 let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
74 let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
64 let clean = collect_pybytes_list(py, status_res.clean.as_ref());
75 let clean = collect_pybytes_list(py, status_res.clean.as_ref());
65 let lookup = collect_pybytes_list(py, lookup.as_ref());
76 let lookup = collect_pybytes_list(py, lookup.as_ref());
66 let unknown = PyList::new(py, &[]);
77 let unknown = PyList::new(py, &[]);
67
78
68 Ok((lookup, modified, added, removed, deleted, unknown, clean))
79 Ok((lookup, modified, added, removed, deleted, unknown, clean))
69 }
80 }
General Comments 0
You need to be logged in to leave comments. Login now