##// END OF EJS Templates
rust-status: add function for sequential traversal of the working directory...
Raphaël Gomès -
r45014:1debb589 default
parent child Browse files
Show More
@@ -1,361 +1,536 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 matchers::{Matcher, VisitChildrenSet},
15 utils::{
15 utils::{
16 files::HgMetadata,
16 files::HgMetadata,
17 hg_path::{
17 hg_path::{
18 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
18 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
19 },
19 },
20 },
20 },
21 CopyMap, DirstateEntry, DirstateMap, EntryState,
21 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
22 };
22 };
23 use rayon::prelude::*;
23 use rayon::prelude::*;
24 use std::collections::HashSet;
24 use std::borrow::Cow;
25 use std::collections::{HashSet, VecDeque};
25 use std::fs::{read_dir, DirEntry};
26 use std::fs::{read_dir, DirEntry};
27 use std::io::ErrorKind;
28 use std::ops::Deref;
26 use std::path::Path;
29 use std::path::Path;
27
30
28 /// Wrong type of file from a `BadMatch`
31 /// Wrong type of file from a `BadMatch`
29 /// Note: a lot of those don't exist on all platforms.
32 /// Note: a lot of those don't exist on all platforms.
30 #[derive(Debug)]
33 #[derive(Debug)]
31 pub enum BadType {
34 pub enum BadType {
32 CharacterDevice,
35 CharacterDevice,
33 BlockDevice,
36 BlockDevice,
34 FIFO,
37 FIFO,
35 Socket,
38 Socket,
36 Directory,
39 Directory,
37 Unknown,
40 Unknown,
38 }
41 }
39
42
40 /// Was explicitly matched but cannot be found/accessed
43 /// Was explicitly matched but cannot be found/accessed
41 #[derive(Debug)]
44 #[derive(Debug)]
42 pub enum BadMatch {
45 pub enum BadMatch {
43 OsError(i32),
46 OsError(i32),
44 BadType(BadType),
47 BadType(BadType),
45 }
48 }
46
49
47 /// Marker enum used to dispatch new status entries into the right collections.
50 /// Marker enum used to dispatch new status entries into the right collections.
48 /// Is similar to `crate::EntryState`, but represents the transient state of
51 /// Is similar to `crate::EntryState`, but represents the transient state of
49 /// entries during the lifetime of a command.
52 /// entries during the lifetime of a command.
50 enum Dispatch {
53 enum Dispatch {
51 Unsure,
54 Unsure,
52 Modified,
55 Modified,
53 Added,
56 Added,
54 Removed,
57 Removed,
55 Deleted,
58 Deleted,
56 Clean,
59 Clean,
57 Unknown,
60 Unknown,
58 Ignored,
61 Ignored,
59 /// Empty dispatch, the file is not worth listing
62 /// Empty dispatch, the file is not worth listing
60 None,
63 None,
61 /// Was explicitly matched but cannot be found/accessed
64 /// Was explicitly matched but cannot be found/accessed
62 Bad(BadMatch),
65 Bad(BadMatch),
63 Directory {
66 Directory {
64 /// True if the directory used to be a file in the dmap so we can say
67 /// True if the directory used to be a file in the dmap so we can say
65 /// that it's been removed.
68 /// that it's been removed.
66 was_file: bool,
69 was_file: bool,
67 },
70 },
68 }
71 }
69
72
70 type IoResult<T> = std::io::Result<T>;
73 type IoResult<T> = std::io::Result<T>;
71
74
72 /// Dates and times that are outside the 31-bit signed range are compared
75 /// Dates and times that are outside the 31-bit signed range are compared
73 /// modulo 2^31. This should prevent hg from behaving badly with very large
76 /// modulo 2^31. This should prevent hg from behaving badly with very large
74 /// files or corrupt dates while still having a high probability of detecting
77 /// files or corrupt dates while still having a high probability of detecting
75 /// changes. (issue2608)
78 /// changes. (issue2608)
76 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
79 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
77 /// is not defined for `i32`, and there is no `As` trait. This forces the
80 /// is not defined for `i32`, and there is no `As` trait. This forces the
78 /// caller to cast `b` as `i32`.
81 /// caller to cast `b` as `i32`.
79 fn mod_compare(a: i32, b: i32) -> bool {
82 fn mod_compare(a: i32, b: i32) -> bool {
80 a & i32::max_value() != b & i32::max_value()
83 a & i32::max_value() != b & i32::max_value()
81 }
84 }
82
85
83 /// Return a sorted list containing information about the entries
86 /// Return a sorted list containing information about the entries
84 /// in the directory.
87 /// in the directory.
85 ///
88 ///
86 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
89 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
87 fn list_directory(
90 fn list_directory(
88 path: impl AsRef<Path>,
91 path: impl AsRef<Path>,
89 skip_dot_hg: bool,
92 skip_dot_hg: bool,
90 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
93 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
91 let mut results = vec![];
94 let mut results = vec![];
92 let entries = read_dir(path.as_ref())?;
95 let entries = read_dir(path.as_ref())?;
93
96
94 for entry in entries {
97 for entry in entries {
95 let entry = entry?;
98 let entry = entry?;
96 let filename = os_string_to_hg_path_buf(entry.file_name())?;
99 let filename = os_string_to_hg_path_buf(entry.file_name())?;
97 let file_type = entry.file_type()?;
100 let file_type = entry.file_type()?;
98 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
101 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
99 return Ok(vec![]);
102 return Ok(vec![]);
100 } else {
103 } else {
101 results.push((HgPathBuf::from(filename), entry))
104 results.push((HgPathBuf::from(filename), entry))
102 }
105 }
103 }
106 }
104
107
105 results.sort_unstable_by_key(|e| e.0.clone());
108 results.sort_unstable_by_key(|e| e.0.clone());
106 Ok(results)
109 Ok(results)
107 }
110 }
108
111
109 /// The file corresponding to the dirstate entry was found on the filesystem.
112 /// The file corresponding to the dirstate entry was found on the filesystem.
110 fn dispatch_found(
113 fn dispatch_found(
111 filename: impl AsRef<HgPath>,
114 filename: impl AsRef<HgPath>,
112 entry: DirstateEntry,
115 entry: DirstateEntry,
113 metadata: HgMetadata,
116 metadata: HgMetadata,
114 copy_map: &CopyMap,
117 copy_map: &CopyMap,
115 options: StatusOptions,
118 options: StatusOptions,
116 ) -> Dispatch {
119 ) -> Dispatch {
117 let DirstateEntry {
120 let DirstateEntry {
118 state,
121 state,
119 mode,
122 mode,
120 mtime,
123 mtime,
121 size,
124 size,
122 } = entry;
125 } = entry;
123
126
124 let HgMetadata {
127 let HgMetadata {
125 st_mode,
128 st_mode,
126 st_size,
129 st_size,
127 st_mtime,
130 st_mtime,
128 ..
131 ..
129 } = metadata;
132 } = metadata;
130
133
131 match state {
134 match state {
132 EntryState::Normal => {
135 EntryState::Normal => {
133 let size_changed = mod_compare(size, st_size as i32);
136 let size_changed = mod_compare(size, st_size as i32);
134 let mode_changed =
137 let mode_changed =
135 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
138 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
136 let metadata_changed = size >= 0 && (size_changed || mode_changed);
139 let metadata_changed = size >= 0 && (size_changed || mode_changed);
137 let other_parent = size == SIZE_FROM_OTHER_PARENT;
140 let other_parent = size == SIZE_FROM_OTHER_PARENT;
138 if metadata_changed
141 if metadata_changed
139 || other_parent
142 || other_parent
140 || copy_map.contains_key(filename.as_ref())
143 || copy_map.contains_key(filename.as_ref())
141 {
144 {
142 Dispatch::Modified
145 Dispatch::Modified
143 } else if mod_compare(mtime, st_mtime as i32) {
146 } else if mod_compare(mtime, st_mtime as i32) {
144 Dispatch::Unsure
147 Dispatch::Unsure
145 } else if st_mtime == options.last_normal_time {
148 } else if st_mtime == options.last_normal_time {
146 // the file may have just been marked as normal and
149 // the file may have just been marked as normal and
147 // it may have changed in the same second without
150 // it may have changed in the same second without
148 // changing its size. This can happen if we quickly
151 // changing its size. This can happen if we quickly
149 // do multiple commits. Force lookup, so we don't
152 // do multiple commits. Force lookup, so we don't
150 // miss such a racy file change.
153 // miss such a racy file change.
151 Dispatch::Unsure
154 Dispatch::Unsure
152 } else if options.list_clean {
155 } else if options.list_clean {
153 Dispatch::Clean
156 Dispatch::Clean
154 } else {
157 } else {
155 Dispatch::Unknown
158 Dispatch::Unknown
156 }
159 }
157 }
160 }
158 EntryState::Merged => Dispatch::Modified,
161 EntryState::Merged => Dispatch::Modified,
159 EntryState::Added => Dispatch::Added,
162 EntryState::Added => Dispatch::Added,
160 EntryState::Removed => Dispatch::Removed,
163 EntryState::Removed => Dispatch::Removed,
161 EntryState::Unknown => Dispatch::Unknown,
164 EntryState::Unknown => Dispatch::Unknown,
162 }
165 }
163 }
166 }
164
167
165 /// The file corresponding to this Dirstate entry is missing.
168 /// The file corresponding to this Dirstate entry is missing.
166 fn dispatch_missing(state: EntryState) -> Dispatch {
169 fn dispatch_missing(state: EntryState) -> Dispatch {
167 match state {
170 match state {
168 // File was removed from the filesystem during commands
171 // File was removed from the filesystem during commands
169 EntryState::Normal | EntryState::Merged | EntryState::Added => {
172 EntryState::Normal | EntryState::Merged | EntryState::Added => {
170 Dispatch::Deleted
173 Dispatch::Deleted
171 }
174 }
172 // File was removed, everything is normal
175 // File was removed, everything is normal
173 EntryState::Removed => Dispatch::Removed,
176 EntryState::Removed => Dispatch::Removed,
174 // File is unknown to Mercurial, everything is normal
177 // File is unknown to Mercurial, everything is normal
175 EntryState::Unknown => Dispatch::Unknown,
178 EntryState::Unknown => Dispatch::Unknown,
176 }
179 }
177 }
180 }
178
181
179 /// Get stat data about the files explicitly specified by match.
182 /// Get stat data about the files explicitly specified by match.
180 /// TODO subrepos
183 /// TODO subrepos
181 fn walk_explicit<'a>(
184 fn walk_explicit<'a>(
182 files: &'a HashSet<&HgPath>,
185 files: &'a HashSet<&HgPath>,
183 dmap: &'a DirstateMap,
186 dmap: &'a DirstateMap,
184 root_dir: impl AsRef<Path> + Sync + Send,
187 root_dir: impl AsRef<Path> + Sync + Send,
185 options: StatusOptions,
188 options: StatusOptions,
186 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
189 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
187 files.par_iter().filter_map(move |filename| {
190 files.par_iter().filter_map(move |filename| {
188 // TODO normalization
191 // TODO normalization
189 let normalized = filename.as_ref();
192 let normalized = filename.as_ref();
190
193
191 let buf = match hg_path_to_path_buf(normalized) {
194 let buf = match hg_path_to_path_buf(normalized) {
192 Ok(x) => x,
195 Ok(x) => x,
193 Err(e) => return Some(Err(e.into())),
196 Err(e) => return Some(Err(e.into())),
194 };
197 };
195 let target = root_dir.as_ref().join(buf);
198 let target = root_dir.as_ref().join(buf);
196 let st = target.symlink_metadata();
199 let st = target.symlink_metadata();
197 match st {
200 match st {
198 Ok(meta) => {
201 Ok(meta) => {
199 let file_type = meta.file_type();
202 let file_type = meta.file_type();
200 if file_type.is_file() || file_type.is_symlink() {
203 if file_type.is_file() || file_type.is_symlink() {
201 if let Some(entry) = dmap.get(normalized) {
204 if let Some(entry) = dmap.get(normalized) {
202 return Some(Ok((
205 return Some(Ok((
203 normalized,
206 normalized,
204 dispatch_found(
207 dispatch_found(
205 &normalized,
208 &normalized,
206 *entry,
209 *entry,
207 HgMetadata::from_metadata(meta),
210 HgMetadata::from_metadata(meta),
208 &dmap.copy_map,
211 &dmap.copy_map,
209 options,
212 options,
210 ),
213 ),
211 )));
214 )));
212 }
215 }
213 } else {
216 } else {
214 if dmap.contains_key(normalized) {
217 if dmap.contains_key(normalized) {
215 return Some(Ok((normalized, Dispatch::Removed)));
218 return Some(Ok((normalized, Dispatch::Removed)));
216 }
219 }
217 }
220 }
218 }
221 }
219 Err(_) => {
222 Err(_) => {
220 if let Some(entry) = dmap.get(normalized) {
223 if let Some(entry) = dmap.get(normalized) {
221 return Some(Ok((
224 return Some(Ok((
222 normalized,
225 normalized,
223 dispatch_missing(entry.state),
226 dispatch_missing(entry.state),
224 )));
227 )));
225 }
228 }
226 }
229 }
227 };
230 };
228 None
231 None
229 })
232 })
230 }
233 }
231
234
232 #[derive(Debug, Copy, Clone)]
235 #[derive(Debug, Copy, Clone)]
233 pub struct StatusOptions {
236 pub struct StatusOptions {
234 /// Remember the most recent modification timeslot for status, to make
237 /// Remember the most recent modification timeslot for status, to make
235 /// sure we won't miss future size-preserving file content modifications
238 /// sure we won't miss future size-preserving file content modifications
236 /// that happen within the same timeslot.
239 /// that happen within the same timeslot.
237 pub last_normal_time: i64,
240 pub last_normal_time: i64,
238 /// Whether we are on a filesystem with UNIX-like exec flags
241 /// Whether we are on a filesystem with UNIX-like exec flags
239 pub check_exec: bool,
242 pub check_exec: bool,
240 pub list_clean: bool,
243 pub list_clean: bool,
244 pub list_unknown: bool,
245 pub list_ignored: bool,
246 }
247
248 /// Dispatch a single file found during `traverse`.
249 /// If `file` is a folder that needs to be traversed, it will be pushed into
250 /// `work`.
251 fn traverse_worker<'a>(
252 work: &mut VecDeque<HgPathBuf>,
253 matcher: &impl Matcher,
254 dmap: &DirstateMap,
255 filename: impl AsRef<HgPath>,
256 dir_entry: &DirEntry,
257 ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
258 dir_ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool,
259 options: StatusOptions,
260 ) -> Option<IoResult<(Cow<'a, HgPath>, Dispatch)>> {
261 let file_type = match dir_entry.file_type() {
262 Ok(x) => x,
263 Err(e) => return Some(Err(e.into())),
264 };
265 let filename = filename.as_ref();
266 let entry_option = dmap.get(filename);
267
268 if file_type.is_dir() {
269 // Do we need to traverse it?
270 if !ignore_fn(&filename) || options.list_ignored {
271 work.push_front(filename.to_owned());
272 }
273 // Nested `if` until `rust-lang/rust#53668` is stable
274 if let Some(entry) = entry_option {
275 // Used to be a file, is now a folder
276 if matcher.matches_everything() || matcher.matches(&filename) {
277 return Some(Ok((
278 Cow::Owned(filename.to_owned()),
279 dispatch_missing(entry.state),
280 )));
281 }
282 }
283 } else if file_type.is_file() || file_type.is_symlink() {
284 if let Some(entry) = entry_option {
285 if matcher.matches_everything() || matcher.matches(&filename) {
286 let metadata = match dir_entry.metadata() {
287 Ok(x) => x,
288 Err(e) => return Some(Err(e.into())),
289 };
290 return Some(Ok((
291 Cow::Owned(filename.to_owned()),
292 dispatch_found(
293 &filename,
294 *entry,
295 HgMetadata::from_metadata(metadata),
296 &dmap.copy_map,
297 options,
298 ),
299 )));
300 }
301 } else if (matcher.matches_everything() || matcher.matches(&filename))
302 && !ignore_fn(&filename)
303 {
304 if (options.list_ignored || matcher.exact_match(&filename))
305 && dir_ignore_fn(&filename)
306 {
307 if options.list_ignored {
308 return Some(Ok((
309 Cow::Owned(filename.to_owned()),
310 Dispatch::Ignored,
311 )));
312 }
313 } else {
314 return Some(Ok((
315 Cow::Owned(filename.to_owned()),
316 Dispatch::Unknown,
317 )));
318 }
319 }
320 } else if let Some(entry) = entry_option {
321 // Used to be a file or a folder, now something else.
322 if matcher.matches_everything() || matcher.matches(&filename) {
323 return Some(Ok((
324 Cow::Owned(filename.to_owned()),
325 dispatch_missing(entry.state),
326 )));
327 }
328 }
329 None
330 }
331
332 /// Walk the working directory recursively to look for changes compared to the
333 /// current `DirstateMap`.
334 fn traverse<'a>(
335 matcher: &(impl Matcher + Sync),
336 root_dir: impl AsRef<Path>,
337 dmap: &DirstateMap,
338 path: impl AsRef<HgPath>,
339 old_results: FastHashMap<Cow<'a, HgPath>, Dispatch>,
340 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
341 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
342 options: StatusOptions,
343 ) -> IoResult<FastHashMap<Cow<'a, HgPath>, Dispatch>> {
344 let root_dir = root_dir.as_ref();
345 let mut new_results = FastHashMap::default();
346
347 let mut work = VecDeque::new();
348 work.push_front(path.as_ref().to_owned());
349
350 while let Some(ref directory) = work.pop_front() {
351 if directory.as_bytes() == b".hg" {
352 continue;
353 }
354 let visit_entries = match matcher.visit_children_set(directory) {
355 VisitChildrenSet::Empty => continue,
356 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
357 VisitChildrenSet::Set(set) => Some(set),
358 };
359 let buf = hg_path_to_path_buf(directory)?;
360 let dir_path = root_dir.join(buf);
361
362 let skip_dot_hg = !directory.as_bytes().is_empty();
363 let entries = match list_directory(dir_path, skip_dot_hg) {
364 Err(e) => match e.kind() {
365 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
366 new_results.insert(
367 Cow::Owned(directory.to_owned()),
368 Dispatch::Bad(BadMatch::OsError(
369 // Unwrapping here is OK because the error always
370 // is a real os error
371 e.raw_os_error().unwrap(),
372 )),
373 );
374 continue;
375 }
376 _ => return Err(e),
377 },
378 Ok(entries) => entries,
379 };
380
381 for (filename, dir_entry) in entries {
382 if let Some(ref set) = visit_entries {
383 if !set.contains(filename.deref()) {
384 continue;
385 }
386 }
387 // TODO normalize
388 let filename = if directory.is_empty() {
389 filename.to_owned()
390 } else {
391 directory.join(&filename)
392 };
393
394 if !old_results.contains_key(filename.deref()) {
395 if let Some((res, dispatch)) = traverse_worker(
396 &mut work,
397 matcher,
398 &dmap,
399 &filename,
400 &dir_entry,
401 &ignore_fn,
402 &dir_ignore_fn,
403 options,
404 )
405 .transpose()?
406 {
407 new_results.insert(res, dispatch);
408 }
409 }
410 }
411 }
412
413 new_results.extend(old_results.into_iter());
414
415 Ok(new_results)
241 }
416 }
242
417
243 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
418 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
244 /// the relevant collections.
419 /// the relevant collections.
245 fn stat_dmap_entries(
420 fn stat_dmap_entries(
246 dmap: &DirstateMap,
421 dmap: &DirstateMap,
247 root_dir: impl AsRef<Path> + Sync + Send,
422 root_dir: impl AsRef<Path> + Sync + Send,
248 options: StatusOptions,
423 options: StatusOptions,
249 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
424 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
250 dmap.par_iter().map(move |(filename, entry)| {
425 dmap.par_iter().map(move |(filename, entry)| {
251 let filename: &HgPath = filename;
426 let filename: &HgPath = filename;
252 let filename_as_path = hg_path_to_path_buf(filename)?;
427 let filename_as_path = hg_path_to_path_buf(filename)?;
253 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
428 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
254
429
255 match meta {
430 match meta {
256 Ok(ref m)
431 Ok(ref m)
257 if !(m.file_type().is_file()
432 if !(m.file_type().is_file()
258 || m.file_type().is_symlink()) =>
433 || m.file_type().is_symlink()) =>
259 {
434 {
260 Ok((filename, dispatch_missing(entry.state)))
435 Ok((filename, dispatch_missing(entry.state)))
261 }
436 }
262 Ok(m) => Ok((
437 Ok(m) => Ok((
263 filename,
438 filename,
264 dispatch_found(
439 dispatch_found(
265 filename,
440 filename,
266 *entry,
441 *entry,
267 HgMetadata::from_metadata(m),
442 HgMetadata::from_metadata(m),
268 &dmap.copy_map,
443 &dmap.copy_map,
269 options,
444 options,
270 ),
445 ),
271 )),
446 )),
272 Err(ref e)
447 Err(ref e)
273 if e.kind() == std::io::ErrorKind::NotFound
448 if e.kind() == std::io::ErrorKind::NotFound
274 || e.raw_os_error() == Some(20) =>
449 || e.raw_os_error() == Some(20) =>
275 {
450 {
276 // Rust does not yet have an `ErrorKind` for
451 // Rust does not yet have an `ErrorKind` for
277 // `NotADirectory` (errno 20)
452 // `NotADirectory` (errno 20)
278 // It happens if the dirstate contains `foo/bar` and
453 // It happens if the dirstate contains `foo/bar` and
279 // foo is not a directory
454 // foo is not a directory
280 Ok((filename, dispatch_missing(entry.state)))
455 Ok((filename, dispatch_missing(entry.state)))
281 }
456 }
282 Err(e) => Err(e),
457 Err(e) => Err(e),
283 }
458 }
284 })
459 })
285 }
460 }
286
461
287 pub struct DirstateStatus<'a> {
462 pub struct DirstateStatus<'a> {
288 pub modified: Vec<&'a HgPath>,
463 pub modified: Vec<&'a HgPath>,
289 pub added: Vec<&'a HgPath>,
464 pub added: Vec<&'a HgPath>,
290 pub removed: Vec<&'a HgPath>,
465 pub removed: Vec<&'a HgPath>,
291 pub deleted: Vec<&'a HgPath>,
466 pub deleted: Vec<&'a HgPath>,
292 pub clean: Vec<&'a HgPath>,
467 pub clean: Vec<&'a HgPath>,
293 pub ignored: Vec<&'a HgPath>,
468 pub ignored: Vec<&'a HgPath>,
294 pub unknown: Vec<&'a HgPath>,
469 pub unknown: Vec<&'a HgPath>,
295 pub bad: Vec<(&'a HgPath, BadMatch)>,
470 pub bad: Vec<(&'a HgPath, BadMatch)>,
296 }
471 }
297
472
298 fn build_response<'a>(
473 fn build_response<'a>(
299 results: impl IntoIterator<Item = IoResult<(&'a HgPath, Dispatch)>>,
474 results: impl IntoIterator<Item = IoResult<(&'a HgPath, Dispatch)>>,
300 ) -> IoResult<(Vec<&'a HgPath>, DirstateStatus<'a>)> {
475 ) -> IoResult<(Vec<&'a HgPath>, DirstateStatus<'a>)> {
301 let mut lookup = vec![];
476 let mut lookup = vec![];
302 let mut modified = vec![];
477 let mut modified = vec![];
303 let mut added = vec![];
478 let mut added = vec![];
304 let mut removed = vec![];
479 let mut removed = vec![];
305 let mut deleted = vec![];
480 let mut deleted = vec![];
306 let mut clean = vec![];
481 let mut clean = vec![];
307 let mut ignored = vec![];
482 let mut ignored = vec![];
308 let mut unknown = vec![];
483 let mut unknown = vec![];
309 let mut bad = vec![];
484 let mut bad = vec![];
310
485
311 for res in results.into_iter() {
486 for res in results.into_iter() {
312 let (filename, dispatch) = res?;
487 let (filename, dispatch) = res?;
313 match dispatch {
488 match dispatch {
314 Dispatch::Unknown => unknown.push(filename),
489 Dispatch::Unknown => unknown.push(filename),
315 Dispatch::Unsure => lookup.push(filename),
490 Dispatch::Unsure => lookup.push(filename),
316 Dispatch::Modified => modified.push(filename),
491 Dispatch::Modified => modified.push(filename),
317 Dispatch::Added => added.push(filename),
492 Dispatch::Added => added.push(filename),
318 Dispatch::Removed => removed.push(filename),
493 Dispatch::Removed => removed.push(filename),
319 Dispatch::Deleted => deleted.push(filename),
494 Dispatch::Deleted => deleted.push(filename),
320 Dispatch::Clean => clean.push(filename),
495 Dispatch::Clean => clean.push(filename),
321 Dispatch::Ignored => ignored.push(filename),
496 Dispatch::Ignored => ignored.push(filename),
322 Dispatch::None => {}
497 Dispatch::None => {}
323 Dispatch::Bad(reason) => bad.push((filename, reason)),
498 Dispatch::Bad(reason) => bad.push((filename, reason)),
324 Dispatch::Directory { .. } => {}
499 Dispatch::Directory { .. } => {}
325 }
500 }
326 }
501 }
327
502
328 Ok((
503 Ok((
329 lookup,
504 lookup,
330 DirstateStatus {
505 DirstateStatus {
331 modified,
506 modified,
332 added,
507 added,
333 removed,
508 removed,
334 deleted,
509 deleted,
335 clean,
510 clean,
336 ignored,
511 ignored,
337 unknown,
512 unknown,
338 bad,
513 bad,
339 },
514 },
340 ))
515 ))
341 }
516 }
342
517
343 pub fn status<'a: 'c, 'b: 'c, 'c>(
518 pub fn status<'a: 'c, 'b: 'c, 'c>(
344 dmap: &'a DirstateMap,
519 dmap: &'a DirstateMap,
345 matcher: &'b impl Matcher,
520 matcher: &'b impl Matcher,
346 root_dir: impl AsRef<Path> + Sync + Send + Copy,
521 root_dir: impl AsRef<Path> + Sync + Send + Copy,
347 options: StatusOptions,
522 options: StatusOptions,
348 ) -> IoResult<(Vec<&'c HgPath>, DirstateStatus<'c>)> {
523 ) -> IoResult<(Vec<&'c HgPath>, DirstateStatus<'c>)> {
349 let files = matcher.file_set();
524 let files = matcher.file_set();
350 let mut results = vec![];
525 let mut results = vec![];
351 if let Some(files) = files {
526 if let Some(files) = files {
352 results.par_extend(walk_explicit(&files, &dmap, root_dir, options));
527 results.par_extend(walk_explicit(&files, &dmap, root_dir, options));
353 }
528 }
354
529
355 if !matcher.is_exact() {
530 if !matcher.is_exact() {
356 let stat_results = stat_dmap_entries(&dmap, root_dir, options);
531 let stat_results = stat_dmap_entries(&dmap, root_dir, options);
357 results.par_extend(stat_results);
532 results.par_extend(stat_results);
358 }
533 }
359
534
360 build_response(results)
535 build_response(results)
361 }
536 }
General Comments 0
You need to be logged in to leave comments. Login now