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