##// END OF EJS Templates
rust: leverage improved match ergonomics...
Raphaël Gomès -
r46190:d5407b2e default
parent child Browse files
Show More
@@ -1,975 +1,975
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 #[cfg(feature = "dirstate-tree")]
12 #[cfg(feature = "dirstate-tree")]
13 use crate::dirstate::dirstate_tree::iter::StatusShortcut;
13 use crate::dirstate::dirstate_tree::iter::StatusShortcut;
14 #[cfg(not(feature = "dirstate-tree"))]
14 #[cfg(not(feature = "dirstate-tree"))]
15 use crate::utils::path_auditor::PathAuditor;
15 use crate::utils::path_auditor::PathAuditor;
16 use crate::{
16 use crate::{
17 dirstate::SIZE_FROM_OTHER_PARENT,
17 dirstate::SIZE_FROM_OTHER_PARENT,
18 filepatterns::PatternFileWarning,
18 filepatterns::PatternFileWarning,
19 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
19 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
20 utils::{
20 utils::{
21 files::{find_dirs, HgMetadata},
21 files::{find_dirs, HgMetadata},
22 hg_path::{
22 hg_path::{
23 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
23 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
24 HgPathError,
24 HgPathError,
25 },
25 },
26 },
26 },
27 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
27 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
28 PatternError,
28 PatternError,
29 };
29 };
30 use lazy_static::lazy_static;
30 use lazy_static::lazy_static;
31 use micro_timer::timed;
31 use micro_timer::timed;
32 use rayon::prelude::*;
32 use rayon::prelude::*;
33 use std::{
33 use std::{
34 borrow::Cow,
34 borrow::Cow,
35 collections::HashSet,
35 collections::HashSet,
36 fs::{read_dir, DirEntry},
36 fs::{read_dir, DirEntry},
37 io::ErrorKind,
37 io::ErrorKind,
38 ops::Deref,
38 ops::Deref,
39 path::{Path, PathBuf},
39 path::{Path, PathBuf},
40 };
40 };
41
41
42 /// Wrong type of file from a `BadMatch`
42 /// Wrong type of file from a `BadMatch`
43 /// Note: a lot of those don't exist on all platforms.
43 /// Note: a lot of those don't exist on all platforms.
44 #[derive(Debug, Copy, Clone)]
44 #[derive(Debug, Copy, Clone)]
45 pub enum BadType {
45 pub enum BadType {
46 CharacterDevice,
46 CharacterDevice,
47 BlockDevice,
47 BlockDevice,
48 FIFO,
48 FIFO,
49 Socket,
49 Socket,
50 Directory,
50 Directory,
51 Unknown,
51 Unknown,
52 }
52 }
53
53
54 impl ToString for BadType {
54 impl ToString for BadType {
55 fn to_string(&self) -> String {
55 fn to_string(&self) -> String {
56 match self {
56 match self {
57 BadType::CharacterDevice => "character device",
57 BadType::CharacterDevice => "character device",
58 BadType::BlockDevice => "block device",
58 BadType::BlockDevice => "block device",
59 BadType::FIFO => "fifo",
59 BadType::FIFO => "fifo",
60 BadType::Socket => "socket",
60 BadType::Socket => "socket",
61 BadType::Directory => "directory",
61 BadType::Directory => "directory",
62 BadType::Unknown => "unknown",
62 BadType::Unknown => "unknown",
63 }
63 }
64 .to_string()
64 .to_string()
65 }
65 }
66 }
66 }
67
67
68 /// Was explicitly matched but cannot be found/accessed
68 /// Was explicitly matched but cannot be found/accessed
69 #[derive(Debug, Copy, Clone)]
69 #[derive(Debug, Copy, Clone)]
70 pub enum BadMatch {
70 pub enum BadMatch {
71 OsError(i32),
71 OsError(i32),
72 BadType(BadType),
72 BadType(BadType),
73 }
73 }
74
74
75 /// Enum used to dispatch new status entries into the right collections.
75 /// Enum used to dispatch new status entries into the right collections.
76 /// Is similar to `crate::EntryState`, but represents the transient state of
76 /// Is similar to `crate::EntryState`, but represents the transient state of
77 /// entries during the lifetime of a command.
77 /// entries during the lifetime of a command.
78 #[derive(Debug, Copy, Clone)]
78 #[derive(Debug, Copy, Clone)]
79 pub enum Dispatch {
79 pub enum Dispatch {
80 Unsure,
80 Unsure,
81 Modified,
81 Modified,
82 Added,
82 Added,
83 Removed,
83 Removed,
84 Deleted,
84 Deleted,
85 Clean,
85 Clean,
86 Unknown,
86 Unknown,
87 Ignored,
87 Ignored,
88 /// Empty dispatch, the file is not worth listing
88 /// Empty dispatch, the file is not worth listing
89 None,
89 None,
90 /// Was explicitly matched but cannot be found/accessed
90 /// Was explicitly matched but cannot be found/accessed
91 Bad(BadMatch),
91 Bad(BadMatch),
92 Directory {
92 Directory {
93 /// True if the directory used to be a file in the dmap so we can say
93 /// True if the directory used to be a file in the dmap so we can say
94 /// that it's been removed.
94 /// that it's been removed.
95 was_file: bool,
95 was_file: bool,
96 },
96 },
97 }
97 }
98
98
99 type IoResult<T> = std::io::Result<T>;
99 type IoResult<T> = std::io::Result<T>;
100
100
101 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
101 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
102 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
102 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
103 type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
103 type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
104
104
105 /// We have a good mix of owned (from directory traversal) and borrowed (from
105 /// We have a good mix of owned (from directory traversal) and borrowed (from
106 /// the dirstate/explicit) paths, this comes up a lot.
106 /// the dirstate/explicit) paths, this comes up a lot.
107 pub type HgPathCow<'a> = Cow<'a, HgPath>;
107 pub type HgPathCow<'a> = Cow<'a, HgPath>;
108
108
109 /// A path with its computed ``Dispatch`` information
109 /// A path with its computed ``Dispatch`` information
110 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
110 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
111
111
112 /// Dates and times that are outside the 31-bit signed range are compared
112 /// Dates and times that are outside the 31-bit signed range are compared
113 /// modulo 2^31. This should prevent hg from behaving badly with very large
113 /// modulo 2^31. This should prevent hg from behaving badly with very large
114 /// files or corrupt dates while still having a high probability of detecting
114 /// files or corrupt dates while still having a high probability of detecting
115 /// changes. (issue2608)
115 /// changes. (issue2608)
116 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
116 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
117 /// is not defined for `i32`, and there is no `As` trait. This forces the
117 /// is not defined for `i32`, and there is no `As` trait. This forces the
118 /// caller to cast `b` as `i32`.
118 /// caller to cast `b` as `i32`.
119 fn mod_compare(a: i32, b: i32) -> bool {
119 fn mod_compare(a: i32, b: i32) -> bool {
120 a & i32::max_value() != b & i32::max_value()
120 a & i32::max_value() != b & i32::max_value()
121 }
121 }
122
122
123 /// Return a sorted list containing information about the entries
123 /// Return a sorted list containing information about the entries
124 /// in the directory.
124 /// in the directory.
125 ///
125 ///
126 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
126 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
127 fn list_directory(
127 fn list_directory(
128 path: impl AsRef<Path>,
128 path: impl AsRef<Path>,
129 skip_dot_hg: bool,
129 skip_dot_hg: bool,
130 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
130 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
131 let mut results = vec![];
131 let mut results = vec![];
132 let entries = read_dir(path.as_ref())?;
132 let entries = read_dir(path.as_ref())?;
133
133
134 for entry in entries {
134 for entry in entries {
135 let entry = entry?;
135 let entry = entry?;
136 let filename = os_string_to_hg_path_buf(entry.file_name())?;
136 let filename = os_string_to_hg_path_buf(entry.file_name())?;
137 let file_type = entry.file_type()?;
137 let file_type = entry.file_type()?;
138 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
138 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
139 return Ok(vec![]);
139 return Ok(vec![]);
140 } else {
140 } else {
141 results.push((filename, entry))
141 results.push((filename, entry))
142 }
142 }
143 }
143 }
144
144
145 results.sort_unstable_by_key(|e| e.0.clone());
145 results.sort_unstable_by_key(|e| e.0.clone());
146 Ok(results)
146 Ok(results)
147 }
147 }
148
148
149 /// The file corresponding to the dirstate entry was found on the filesystem.
149 /// The file corresponding to the dirstate entry was found on the filesystem.
150 fn dispatch_found(
150 fn dispatch_found(
151 filename: impl AsRef<HgPath>,
151 filename: impl AsRef<HgPath>,
152 entry: DirstateEntry,
152 entry: DirstateEntry,
153 metadata: HgMetadata,
153 metadata: HgMetadata,
154 copy_map: &CopyMap,
154 copy_map: &CopyMap,
155 options: StatusOptions,
155 options: StatusOptions,
156 ) -> Dispatch {
156 ) -> Dispatch {
157 let DirstateEntry {
157 let DirstateEntry {
158 state,
158 state,
159 mode,
159 mode,
160 mtime,
160 mtime,
161 size,
161 size,
162 } = entry;
162 } = entry;
163
163
164 let HgMetadata {
164 let HgMetadata {
165 st_mode,
165 st_mode,
166 st_size,
166 st_size,
167 st_mtime,
167 st_mtime,
168 ..
168 ..
169 } = metadata;
169 } = metadata;
170
170
171 match state {
171 match state {
172 EntryState::Normal => {
172 EntryState::Normal => {
173 let size_changed = mod_compare(size, st_size as i32);
173 let size_changed = mod_compare(size, st_size as i32);
174 let mode_changed =
174 let mode_changed =
175 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
175 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
176 let metadata_changed = size >= 0 && (size_changed || mode_changed);
176 let metadata_changed = size >= 0 && (size_changed || mode_changed);
177 let other_parent = size == SIZE_FROM_OTHER_PARENT;
177 let other_parent = size == SIZE_FROM_OTHER_PARENT;
178
178
179 if metadata_changed
179 if metadata_changed
180 || other_parent
180 || other_parent
181 || copy_map.contains_key(filename.as_ref())
181 || copy_map.contains_key(filename.as_ref())
182 {
182 {
183 Dispatch::Modified
183 Dispatch::Modified
184 } else if mod_compare(mtime, st_mtime as i32)
184 } else if mod_compare(mtime, st_mtime as i32)
185 || st_mtime == options.last_normal_time
185 || st_mtime == options.last_normal_time
186 {
186 {
187 // the file may have just been marked as normal and
187 // the file may have just been marked as normal and
188 // it may have changed in the same second without
188 // it may have changed in the same second without
189 // changing its size. This can happen if we quickly
189 // changing its size. This can happen if we quickly
190 // do multiple commits. Force lookup, so we don't
190 // do multiple commits. Force lookup, so we don't
191 // miss such a racy file change.
191 // miss such a racy file change.
192 Dispatch::Unsure
192 Dispatch::Unsure
193 } else if options.list_clean {
193 } else if options.list_clean {
194 Dispatch::Clean
194 Dispatch::Clean
195 } else {
195 } else {
196 Dispatch::None
196 Dispatch::None
197 }
197 }
198 }
198 }
199 EntryState::Merged => Dispatch::Modified,
199 EntryState::Merged => Dispatch::Modified,
200 EntryState::Added => Dispatch::Added,
200 EntryState::Added => Dispatch::Added,
201 EntryState::Removed => Dispatch::Removed,
201 EntryState::Removed => Dispatch::Removed,
202 EntryState::Unknown => Dispatch::Unknown,
202 EntryState::Unknown => Dispatch::Unknown,
203 }
203 }
204 }
204 }
205
205
206 /// The file corresponding to this Dirstate entry is missing.
206 /// The file corresponding to this Dirstate entry is missing.
207 fn dispatch_missing(state: EntryState) -> Dispatch {
207 fn dispatch_missing(state: EntryState) -> Dispatch {
208 match state {
208 match state {
209 // File was removed from the filesystem during commands
209 // File was removed from the filesystem during commands
210 EntryState::Normal | EntryState::Merged | EntryState::Added => {
210 EntryState::Normal | EntryState::Merged | EntryState::Added => {
211 Dispatch::Deleted
211 Dispatch::Deleted
212 }
212 }
213 // File was removed, everything is normal
213 // File was removed, everything is normal
214 EntryState::Removed => Dispatch::Removed,
214 EntryState::Removed => Dispatch::Removed,
215 // File is unknown to Mercurial, everything is normal
215 // File is unknown to Mercurial, everything is normal
216 EntryState::Unknown => Dispatch::Unknown,
216 EntryState::Unknown => Dispatch::Unknown,
217 }
217 }
218 }
218 }
219
219
220 lazy_static! {
220 lazy_static! {
221 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
221 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
222 let mut h = HashSet::new();
222 let mut h = HashSet::new();
223 h.insert(HgPath::new(b""));
223 h.insert(HgPath::new(b""));
224 h
224 h
225 };
225 };
226 }
226 }
227
227
228 #[derive(Debug, Copy, Clone)]
228 #[derive(Debug, Copy, Clone)]
229 pub struct StatusOptions {
229 pub struct StatusOptions {
230 /// Remember the most recent modification timeslot for status, to make
230 /// Remember the most recent modification timeslot for status, to make
231 /// sure we won't miss future size-preserving file content modifications
231 /// sure we won't miss future size-preserving file content modifications
232 /// that happen within the same timeslot.
232 /// that happen within the same timeslot.
233 pub last_normal_time: i64,
233 pub last_normal_time: i64,
234 /// Whether we are on a filesystem with UNIX-like exec flags
234 /// Whether we are on a filesystem with UNIX-like exec flags
235 pub check_exec: bool,
235 pub check_exec: bool,
236 pub list_clean: bool,
236 pub list_clean: bool,
237 pub list_unknown: bool,
237 pub list_unknown: bool,
238 pub list_ignored: bool,
238 pub list_ignored: bool,
239 /// Whether to collect traversed dirs for applying a callback later.
239 /// Whether to collect traversed dirs for applying a callback later.
240 /// Used by `hg purge` for example.
240 /// Used by `hg purge` for example.
241 pub collect_traversed_dirs: bool,
241 pub collect_traversed_dirs: bool,
242 }
242 }
243
243
244 #[derive(Debug)]
244 #[derive(Debug)]
245 pub struct DirstateStatus<'a> {
245 pub struct DirstateStatus<'a> {
246 pub modified: Vec<HgPathCow<'a>>,
246 pub modified: Vec<HgPathCow<'a>>,
247 pub added: Vec<HgPathCow<'a>>,
247 pub added: Vec<HgPathCow<'a>>,
248 pub removed: Vec<HgPathCow<'a>>,
248 pub removed: Vec<HgPathCow<'a>>,
249 pub deleted: Vec<HgPathCow<'a>>,
249 pub deleted: Vec<HgPathCow<'a>>,
250 pub clean: Vec<HgPathCow<'a>>,
250 pub clean: Vec<HgPathCow<'a>>,
251 pub ignored: Vec<HgPathCow<'a>>,
251 pub ignored: Vec<HgPathCow<'a>>,
252 pub unknown: Vec<HgPathCow<'a>>,
252 pub unknown: Vec<HgPathCow<'a>>,
253 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
253 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
254 /// Only filled if `collect_traversed_dirs` is `true`
254 /// Only filled if `collect_traversed_dirs` is `true`
255 pub traversed: Vec<HgPathBuf>,
255 pub traversed: Vec<HgPathBuf>,
256 }
256 }
257
257
258 #[derive(Debug)]
258 #[derive(Debug)]
259 pub enum StatusError {
259 pub enum StatusError {
260 /// Generic IO error
260 /// Generic IO error
261 IO(std::io::Error),
261 IO(std::io::Error),
262 /// An invalid path that cannot be represented in Mercurial was found
262 /// An invalid path that cannot be represented in Mercurial was found
263 Path(HgPathError),
263 Path(HgPathError),
264 /// An invalid "ignore" pattern was found
264 /// An invalid "ignore" pattern was found
265 Pattern(PatternError),
265 Pattern(PatternError),
266 }
266 }
267
267
268 pub type StatusResult<T> = Result<T, StatusError>;
268 pub type StatusResult<T> = Result<T, StatusError>;
269
269
270 impl From<PatternError> for StatusError {
270 impl From<PatternError> for StatusError {
271 fn from(e: PatternError) -> Self {
271 fn from(e: PatternError) -> Self {
272 StatusError::Pattern(e)
272 StatusError::Pattern(e)
273 }
273 }
274 }
274 }
275 impl From<HgPathError> for StatusError {
275 impl From<HgPathError> for StatusError {
276 fn from(e: HgPathError) -> Self {
276 fn from(e: HgPathError) -> Self {
277 StatusError::Path(e)
277 StatusError::Path(e)
278 }
278 }
279 }
279 }
280 impl From<std::io::Error> for StatusError {
280 impl From<std::io::Error> for StatusError {
281 fn from(e: std::io::Error) -> Self {
281 fn from(e: std::io::Error) -> Self {
282 StatusError::IO(e)
282 StatusError::IO(e)
283 }
283 }
284 }
284 }
285
285
286 impl ToString for StatusError {
286 impl ToString for StatusError {
287 fn to_string(&self) -> String {
287 fn to_string(&self) -> String {
288 match self {
288 match self {
289 StatusError::IO(e) => e.to_string(),
289 StatusError::IO(e) => e.to_string(),
290 StatusError::Path(e) => e.to_string(),
290 StatusError::Path(e) => e.to_string(),
291 StatusError::Pattern(e) => e.to_string(),
291 StatusError::Pattern(e) => e.to_string(),
292 }
292 }
293 }
293 }
294 }
294 }
295
295
296 /// Gives information about which files are changed in the working directory
296 /// Gives information about which files are changed in the working directory
297 /// and how, compared to the revision we're based on
297 /// and how, compared to the revision we're based on
298 pub struct Status<'a, M: Matcher + Sync> {
298 pub struct Status<'a, M: Matcher + Sync> {
299 dmap: &'a DirstateMap,
299 dmap: &'a DirstateMap,
300 pub(crate) matcher: &'a M,
300 pub(crate) matcher: &'a M,
301 root_dir: PathBuf,
301 root_dir: PathBuf,
302 pub(crate) options: StatusOptions,
302 pub(crate) options: StatusOptions,
303 ignore_fn: IgnoreFnType<'a>,
303 ignore_fn: IgnoreFnType<'a>,
304 }
304 }
305
305
306 impl<'a, M> Status<'a, M>
306 impl<'a, M> Status<'a, M>
307 where
307 where
308 M: Matcher + Sync,
308 M: Matcher + Sync,
309 {
309 {
310 pub fn new(
310 pub fn new(
311 dmap: &'a DirstateMap,
311 dmap: &'a DirstateMap,
312 matcher: &'a M,
312 matcher: &'a M,
313 root_dir: PathBuf,
313 root_dir: PathBuf,
314 ignore_files: Vec<PathBuf>,
314 ignore_files: Vec<PathBuf>,
315 options: StatusOptions,
315 options: StatusOptions,
316 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
316 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
317 // Needs to outlive `dir_ignore_fn` since it's captured.
317 // Needs to outlive `dir_ignore_fn` since it's captured.
318
318
319 let (ignore_fn, warnings): (IgnoreFnType, _) =
319 let (ignore_fn, warnings): (IgnoreFnType, _) =
320 if options.list_ignored || options.list_unknown {
320 if options.list_ignored || options.list_unknown {
321 get_ignore_function(ignore_files, &root_dir)?
321 get_ignore_function(ignore_files, &root_dir)?
322 } else {
322 } else {
323 (Box::new(|&_| true), vec![])
323 (Box::new(|&_| true), vec![])
324 };
324 };
325
325
326 Ok((
326 Ok((
327 Self {
327 Self {
328 dmap,
328 dmap,
329 matcher,
329 matcher,
330 root_dir,
330 root_dir,
331 options,
331 options,
332 ignore_fn,
332 ignore_fn,
333 },
333 },
334 warnings,
334 warnings,
335 ))
335 ))
336 }
336 }
337
337
338 /// Is the path ignored?
338 /// Is the path ignored?
339 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
339 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
340 (self.ignore_fn)(path.as_ref())
340 (self.ignore_fn)(path.as_ref())
341 }
341 }
342
342
343 /// Is the path or one of its ancestors ignored?
343 /// Is the path or one of its ancestors ignored?
344 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
344 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
345 // Only involve ignore mechanism if we're listing unknowns or ignored.
345 // Only involve ignore mechanism if we're listing unknowns or ignored.
346 if self.options.list_ignored || self.options.list_unknown {
346 if self.options.list_ignored || self.options.list_unknown {
347 if self.is_ignored(&dir) {
347 if self.is_ignored(&dir) {
348 true
348 true
349 } else {
349 } else {
350 for p in find_dirs(dir.as_ref()) {
350 for p in find_dirs(dir.as_ref()) {
351 if self.is_ignored(p) {
351 if self.is_ignored(p) {
352 return true;
352 return true;
353 }
353 }
354 }
354 }
355 false
355 false
356 }
356 }
357 } else {
357 } else {
358 true
358 true
359 }
359 }
360 }
360 }
361
361
362 /// Get stat data about the files explicitly specified by the matcher.
362 /// Get stat data about the files explicitly specified by the matcher.
363 /// Returns a tuple of the directories that need to be traversed and the
363 /// Returns a tuple of the directories that need to be traversed and the
364 /// files with their corresponding `Dispatch`.
364 /// files with their corresponding `Dispatch`.
365 /// TODO subrepos
365 /// TODO subrepos
366 #[timed]
366 #[timed]
367 pub fn walk_explicit(
367 pub fn walk_explicit(
368 &self,
368 &self,
369 traversed_sender: crossbeam::Sender<HgPathBuf>,
369 traversed_sender: crossbeam::Sender<HgPathBuf>,
370 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
370 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
371 self.matcher
371 self.matcher
372 .file_set()
372 .file_set()
373 .unwrap_or(&DEFAULT_WORK)
373 .unwrap_or(&DEFAULT_WORK)
374 .par_iter()
374 .par_iter()
375 .map(|&filename| -> Option<IoResult<_>> {
375 .map(|&filename| -> Option<IoResult<_>> {
376 // TODO normalization
376 // TODO normalization
377 let normalized = filename;
377 let normalized = filename;
378
378
379 let buf = match hg_path_to_path_buf(normalized) {
379 let buf = match hg_path_to_path_buf(normalized) {
380 Ok(x) => x,
380 Ok(x) => x,
381 Err(e) => return Some(Err(e.into())),
381 Err(e) => return Some(Err(e.into())),
382 };
382 };
383 let target = self.root_dir.join(buf);
383 let target = self.root_dir.join(buf);
384 let st = target.symlink_metadata();
384 let st = target.symlink_metadata();
385 let in_dmap = self.dmap.get(normalized);
385 let in_dmap = self.dmap.get(normalized);
386 match st {
386 match st {
387 Ok(meta) => {
387 Ok(meta) => {
388 let file_type = meta.file_type();
388 let file_type = meta.file_type();
389 return if file_type.is_file() || file_type.is_symlink()
389 return if file_type.is_file() || file_type.is_symlink()
390 {
390 {
391 if let Some(entry) = in_dmap {
391 if let Some(entry) = in_dmap {
392 return Some(Ok((
392 return Some(Ok((
393 Cow::Borrowed(normalized),
393 Cow::Borrowed(normalized),
394 dispatch_found(
394 dispatch_found(
395 &normalized,
395 &normalized,
396 *entry,
396 *entry,
397 HgMetadata::from_metadata(meta),
397 HgMetadata::from_metadata(meta),
398 &self.dmap.copy_map,
398 &self.dmap.copy_map,
399 self.options,
399 self.options,
400 ),
400 ),
401 )));
401 )));
402 }
402 }
403 Some(Ok((
403 Some(Ok((
404 Cow::Borrowed(normalized),
404 Cow::Borrowed(normalized),
405 Dispatch::Unknown,
405 Dispatch::Unknown,
406 )))
406 )))
407 } else if file_type.is_dir() {
407 } else if file_type.is_dir() {
408 if self.options.collect_traversed_dirs {
408 if self.options.collect_traversed_dirs {
409 traversed_sender
409 traversed_sender
410 .send(normalized.to_owned())
410 .send(normalized.to_owned())
411 .expect("receiver should outlive sender");
411 .expect("receiver should outlive sender");
412 }
412 }
413 Some(Ok((
413 Some(Ok((
414 Cow::Borrowed(normalized),
414 Cow::Borrowed(normalized),
415 Dispatch::Directory {
415 Dispatch::Directory {
416 was_file: in_dmap.is_some(),
416 was_file: in_dmap.is_some(),
417 },
417 },
418 )))
418 )))
419 } else {
419 } else {
420 Some(Ok((
420 Some(Ok((
421 Cow::Borrowed(normalized),
421 Cow::Borrowed(normalized),
422 Dispatch::Bad(BadMatch::BadType(
422 Dispatch::Bad(BadMatch::BadType(
423 // TODO do more than unknown
423 // TODO do more than unknown
424 // Support for all `BadType` variant
424 // Support for all `BadType` variant
425 // varies greatly between platforms.
425 // varies greatly between platforms.
426 // So far, no tests check the type and
426 // So far, no tests check the type and
427 // this should be good enough for most
427 // this should be good enough for most
428 // users.
428 // users.
429 BadType::Unknown,
429 BadType::Unknown,
430 )),
430 )),
431 )))
431 )))
432 };
432 };
433 }
433 }
434 Err(_) => {
434 Err(_) => {
435 if let Some(entry) = in_dmap {
435 if let Some(entry) = in_dmap {
436 return Some(Ok((
436 return Some(Ok((
437 Cow::Borrowed(normalized),
437 Cow::Borrowed(normalized),
438 dispatch_missing(entry.state),
438 dispatch_missing(entry.state),
439 )));
439 )));
440 }
440 }
441 }
441 }
442 };
442 };
443 None
443 None
444 })
444 })
445 .flatten()
445 .flatten()
446 .filter_map(Result::ok)
446 .filter_map(Result::ok)
447 .partition(|(_, dispatch)| match dispatch {
447 .partition(|(_, dispatch)| match dispatch {
448 Dispatch::Directory { .. } => true,
448 Dispatch::Directory { .. } => true,
449 _ => false,
449 _ => false,
450 })
450 })
451 }
451 }
452
452
453 /// Walk the working directory recursively to look for changes compared to
453 /// Walk the working directory recursively to look for changes compared to
454 /// the current `DirstateMap`.
454 /// the current `DirstateMap`.
455 ///
455 ///
456 /// This takes a mutable reference to the results to account for the
456 /// This takes a mutable reference to the results to account for the
457 /// `extend` in timings
457 /// `extend` in timings
458 #[timed]
458 #[timed]
459 pub fn traverse(
459 pub fn traverse(
460 &self,
460 &self,
461 path: impl AsRef<HgPath>,
461 path: impl AsRef<HgPath>,
462 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
462 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
463 results: &mut Vec<DispatchedPath<'a>>,
463 results: &mut Vec<DispatchedPath<'a>>,
464 traversed_sender: crossbeam::Sender<HgPathBuf>,
464 traversed_sender: crossbeam::Sender<HgPathBuf>,
465 ) -> IoResult<()> {
465 ) -> IoResult<()> {
466 // The traversal is done in parallel, so use a channel to gather
466 // The traversal is done in parallel, so use a channel to gather
467 // entries. `crossbeam::Sender` is `Sync`, while `mpsc::Sender`
467 // entries. `crossbeam::Sender` is `Sync`, while `mpsc::Sender`
468 // is not.
468 // is not.
469 let (files_transmitter, files_receiver) =
469 let (files_transmitter, files_receiver) =
470 crossbeam::channel::unbounded();
470 crossbeam::channel::unbounded();
471
471
472 self.traverse_dir(
472 self.traverse_dir(
473 &files_transmitter,
473 &files_transmitter,
474 path,
474 path,
475 &old_results,
475 &old_results,
476 traversed_sender,
476 traversed_sender,
477 )?;
477 )?;
478
478
479 // Disconnect the channel so the receiver stops waiting
479 // Disconnect the channel so the receiver stops waiting
480 drop(files_transmitter);
480 drop(files_transmitter);
481
481
482 // TODO don't collect. Find a way of replicating the behavior of
482 // TODO don't collect. Find a way of replicating the behavior of
483 // `itertools::process_results`, but for `rayon::ParallelIterator`
483 // `itertools::process_results`, but for `rayon::ParallelIterator`
484 let new_results: IoResult<Vec<(Cow<HgPath>, Dispatch)>> =
484 let new_results: IoResult<Vec<(Cow<HgPath>, Dispatch)>> =
485 files_receiver
485 files_receiver
486 .into_iter()
486 .into_iter()
487 .map(|item| {
487 .map(|item| {
488 let (f, d) = item?;
488 let (f, d) = item?;
489 Ok((Cow::Owned(f), d))
489 Ok((Cow::Owned(f), d))
490 })
490 })
491 .collect();
491 .collect();
492
492
493 results.par_extend(new_results?);
493 results.par_extend(new_results?);
494
494
495 Ok(())
495 Ok(())
496 }
496 }
497
497
498 /// Dispatch a single entry (file, folder, symlink...) found during
498 /// Dispatch a single entry (file, folder, symlink...) found during
499 /// `traverse`. If the entry is a folder that needs to be traversed, it
499 /// `traverse`. If the entry is a folder that needs to be traversed, it
500 /// will be handled in a separate thread.
500 /// will be handled in a separate thread.
501 fn handle_traversed_entry<'b>(
501 fn handle_traversed_entry<'b>(
502 &'a self,
502 &'a self,
503 scope: &rayon::Scope<'b>,
503 scope: &rayon::Scope<'b>,
504 files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
504 files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
505 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
505 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
506 filename: HgPathBuf,
506 filename: HgPathBuf,
507 dir_entry: DirEntry,
507 dir_entry: DirEntry,
508 traversed_sender: crossbeam::Sender<HgPathBuf>,
508 traversed_sender: crossbeam::Sender<HgPathBuf>,
509 ) -> IoResult<()>
509 ) -> IoResult<()>
510 where
510 where
511 'a: 'b,
511 'a: 'b,
512 {
512 {
513 let file_type = dir_entry.file_type()?;
513 let file_type = dir_entry.file_type()?;
514 let entry_option = self.dmap.get(&filename);
514 let entry_option = self.dmap.get(&filename);
515
515
516 if filename.as_bytes() == b".hg" {
516 if filename.as_bytes() == b".hg" {
517 // Could be a directory or a symlink
517 // Could be a directory or a symlink
518 return Ok(());
518 return Ok(());
519 }
519 }
520
520
521 if file_type.is_dir() {
521 if file_type.is_dir() {
522 self.handle_traversed_dir(
522 self.handle_traversed_dir(
523 scope,
523 scope,
524 files_sender,
524 files_sender,
525 old_results,
525 old_results,
526 entry_option,
526 entry_option,
527 filename,
527 filename,
528 traversed_sender,
528 traversed_sender,
529 );
529 );
530 } else if file_type.is_file() || file_type.is_symlink() {
530 } else if file_type.is_file() || file_type.is_symlink() {
531 if let Some(entry) = entry_option {
531 if let Some(entry) = entry_option {
532 if self.matcher.matches_everything()
532 if self.matcher.matches_everything()
533 || self.matcher.matches(&filename)
533 || self.matcher.matches(&filename)
534 {
534 {
535 let metadata = dir_entry.metadata()?;
535 let metadata = dir_entry.metadata()?;
536 files_sender
536 files_sender
537 .send(Ok((
537 .send(Ok((
538 filename.to_owned(),
538 filename.to_owned(),
539 dispatch_found(
539 dispatch_found(
540 &filename,
540 &filename,
541 *entry,
541 *entry,
542 HgMetadata::from_metadata(metadata),
542 HgMetadata::from_metadata(metadata),
543 &self.dmap.copy_map,
543 &self.dmap.copy_map,
544 self.options,
544 self.options,
545 ),
545 ),
546 )))
546 )))
547 .unwrap();
547 .unwrap();
548 }
548 }
549 } else if (self.matcher.matches_everything()
549 } else if (self.matcher.matches_everything()
550 || self.matcher.matches(&filename))
550 || self.matcher.matches(&filename))
551 && !self.is_ignored(&filename)
551 && !self.is_ignored(&filename)
552 {
552 {
553 if (self.options.list_ignored
553 if (self.options.list_ignored
554 || self.matcher.exact_match(&filename))
554 || self.matcher.exact_match(&filename))
555 && self.dir_ignore(&filename)
555 && self.dir_ignore(&filename)
556 {
556 {
557 if self.options.list_ignored {
557 if self.options.list_ignored {
558 files_sender
558 files_sender
559 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
559 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
560 .unwrap();
560 .unwrap();
561 }
561 }
562 } else if self.options.list_unknown {
562 } else if self.options.list_unknown {
563 files_sender
563 files_sender
564 .send(Ok((filename.to_owned(), Dispatch::Unknown)))
564 .send(Ok((filename.to_owned(), Dispatch::Unknown)))
565 .unwrap();
565 .unwrap();
566 }
566 }
567 } else if self.is_ignored(&filename) && self.options.list_ignored {
567 } else if self.is_ignored(&filename) && self.options.list_ignored {
568 files_sender
568 files_sender
569 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
569 .send(Ok((filename.to_owned(), Dispatch::Ignored)))
570 .unwrap();
570 .unwrap();
571 }
571 }
572 } else if let Some(entry) = entry_option {
572 } else if let Some(entry) = entry_option {
573 // Used to be a file or a folder, now something else.
573 // Used to be a file or a folder, now something else.
574 if self.matcher.matches_everything()
574 if self.matcher.matches_everything()
575 || self.matcher.matches(&filename)
575 || self.matcher.matches(&filename)
576 {
576 {
577 files_sender
577 files_sender
578 .send(Ok((
578 .send(Ok((
579 filename.to_owned(),
579 filename.to_owned(),
580 dispatch_missing(entry.state),
580 dispatch_missing(entry.state),
581 )))
581 )))
582 .unwrap();
582 .unwrap();
583 }
583 }
584 }
584 }
585
585
586 Ok(())
586 Ok(())
587 }
587 }
588
588
589 /// A directory was found in the filesystem and needs to be traversed
589 /// A directory was found in the filesystem and needs to be traversed
590 fn handle_traversed_dir<'b>(
590 fn handle_traversed_dir<'b>(
591 &'a self,
591 &'a self,
592 scope: &rayon::Scope<'b>,
592 scope: &rayon::Scope<'b>,
593 files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
593 files_sender: &'b crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
594 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
594 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
595 entry_option: Option<&'a DirstateEntry>,
595 entry_option: Option<&'a DirstateEntry>,
596 directory: HgPathBuf,
596 directory: HgPathBuf,
597 traversed_sender: crossbeam::Sender<HgPathBuf>,
597 traversed_sender: crossbeam::Sender<HgPathBuf>,
598 ) where
598 ) where
599 'a: 'b,
599 'a: 'b,
600 {
600 {
601 scope.spawn(move |_| {
601 scope.spawn(move |_| {
602 // Nested `if` until `rust-lang/rust#53668` is stable
602 // Nested `if` until `rust-lang/rust#53668` is stable
603 if let Some(entry) = entry_option {
603 if let Some(entry) = entry_option {
604 // Used to be a file, is now a folder
604 // Used to be a file, is now a folder
605 if self.matcher.matches_everything()
605 if self.matcher.matches_everything()
606 || self.matcher.matches(&directory)
606 || self.matcher.matches(&directory)
607 {
607 {
608 files_sender
608 files_sender
609 .send(Ok((
609 .send(Ok((
610 directory.to_owned(),
610 directory.to_owned(),
611 dispatch_missing(entry.state),
611 dispatch_missing(entry.state),
612 )))
612 )))
613 .unwrap();
613 .unwrap();
614 }
614 }
615 }
615 }
616 // Do we need to traverse it?
616 // Do we need to traverse it?
617 if !self.is_ignored(&directory) || self.options.list_ignored {
617 if !self.is_ignored(&directory) || self.options.list_ignored {
618 self.traverse_dir(
618 self.traverse_dir(
619 files_sender,
619 files_sender,
620 directory,
620 directory,
621 &old_results,
621 &old_results,
622 traversed_sender,
622 traversed_sender,
623 )
623 )
624 .unwrap_or_else(|e| files_sender.send(Err(e)).unwrap())
624 .unwrap_or_else(|e| files_sender.send(Err(e)).unwrap())
625 }
625 }
626 });
626 });
627 }
627 }
628
628
629 /// Decides whether the directory needs to be listed, and if so handles the
629 /// Decides whether the directory needs to be listed, and if so handles the
630 /// entries in a separate thread.
630 /// entries in a separate thread.
631 fn traverse_dir(
631 fn traverse_dir(
632 &self,
632 &self,
633 files_sender: &crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
633 files_sender: &crossbeam::Sender<IoResult<(HgPathBuf, Dispatch)>>,
634 directory: impl AsRef<HgPath>,
634 directory: impl AsRef<HgPath>,
635 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
635 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
636 traversed_sender: crossbeam::Sender<HgPathBuf>,
636 traversed_sender: crossbeam::Sender<HgPathBuf>,
637 ) -> IoResult<()> {
637 ) -> IoResult<()> {
638 let directory = directory.as_ref();
638 let directory = directory.as_ref();
639
639
640 if self.options.collect_traversed_dirs {
640 if self.options.collect_traversed_dirs {
641 traversed_sender
641 traversed_sender
642 .send(directory.to_owned())
642 .send(directory.to_owned())
643 .expect("receiver should outlive sender");
643 .expect("receiver should outlive sender");
644 }
644 }
645
645
646 let visit_entries = match self.matcher.visit_children_set(directory) {
646 let visit_entries = match self.matcher.visit_children_set(directory) {
647 VisitChildrenSet::Empty => return Ok(()),
647 VisitChildrenSet::Empty => return Ok(()),
648 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
648 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
649 VisitChildrenSet::Set(set) => Some(set),
649 VisitChildrenSet::Set(set) => Some(set),
650 };
650 };
651 let buf = hg_path_to_path_buf(directory)?;
651 let buf = hg_path_to_path_buf(directory)?;
652 let dir_path = self.root_dir.join(buf);
652 let dir_path = self.root_dir.join(buf);
653
653
654 let skip_dot_hg = !directory.as_bytes().is_empty();
654 let skip_dot_hg = !directory.as_bytes().is_empty();
655 let entries = match list_directory(dir_path, skip_dot_hg) {
655 let entries = match list_directory(dir_path, skip_dot_hg) {
656 Err(e) => {
656 Err(e) => {
657 return match e.kind() {
657 return match e.kind() {
658 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
658 ErrorKind::NotFound | ErrorKind::PermissionDenied => {
659 files_sender
659 files_sender
660 .send(Ok((
660 .send(Ok((
661 directory.to_owned(),
661 directory.to_owned(),
662 Dispatch::Bad(BadMatch::OsError(
662 Dispatch::Bad(BadMatch::OsError(
663 // Unwrapping here is OK because the error
663 // Unwrapping here is OK because the error
664 // always is a
664 // always is a
665 // real os error
665 // real os error
666 e.raw_os_error().unwrap(),
666 e.raw_os_error().unwrap(),
667 )),
667 )),
668 )))
668 )))
669 .expect("receiver should outlive sender");
669 .expect("receiver should outlive sender");
670 Ok(())
670 Ok(())
671 }
671 }
672 _ => Err(e),
672 _ => Err(e),
673 };
673 };
674 }
674 }
675 Ok(entries) => entries,
675 Ok(entries) => entries,
676 };
676 };
677
677
678 rayon::scope(|scope| -> IoResult<()> {
678 rayon::scope(|scope| -> IoResult<()> {
679 for (filename, dir_entry) in entries {
679 for (filename, dir_entry) in entries {
680 if let Some(ref set) = visit_entries {
680 if let Some(ref set) = visit_entries {
681 if !set.contains(filename.deref()) {
681 if !set.contains(filename.deref()) {
682 continue;
682 continue;
683 }
683 }
684 }
684 }
685 // TODO normalize
685 // TODO normalize
686 let filename = if directory.is_empty() {
686 let filename = if directory.is_empty() {
687 filename.to_owned()
687 filename.to_owned()
688 } else {
688 } else {
689 directory.join(&filename)
689 directory.join(&filename)
690 };
690 };
691
691
692 if !old_results.contains_key(filename.deref()) {
692 if !old_results.contains_key(filename.deref()) {
693 self.handle_traversed_entry(
693 self.handle_traversed_entry(
694 scope,
694 scope,
695 files_sender,
695 files_sender,
696 old_results,
696 old_results,
697 filename,
697 filename,
698 dir_entry,
698 dir_entry,
699 traversed_sender.clone(),
699 traversed_sender.clone(),
700 )?;
700 )?;
701 }
701 }
702 }
702 }
703 Ok(())
703 Ok(())
704 })
704 })
705 }
705 }
706
706
707 /// Add the files in the dirstate to the results.
707 /// Add the files in the dirstate to the results.
708 ///
708 ///
709 /// This takes a mutable reference to the results to account for the
709 /// This takes a mutable reference to the results to account for the
710 /// `extend` in timings
710 /// `extend` in timings
711 #[cfg(feature = "dirstate-tree")]
711 #[cfg(feature = "dirstate-tree")]
712 #[timed]
712 #[timed]
713 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
713 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
714 results.par_extend(
714 results.par_extend(
715 self.dmap
715 self.dmap
716 .fs_iter(self.root_dir.clone())
716 .fs_iter(self.root_dir.clone())
717 .par_bridge()
717 .par_bridge()
718 .filter(|(path, _)| self.matcher.matches(path))
718 .filter(|(path, _)| self.matcher.matches(path))
719 .flat_map(move |(filename, shortcut)| {
719 .flat_map(move |(filename, shortcut)| {
720 let entry = match shortcut {
720 let entry = match shortcut {
721 StatusShortcut::Entry(e) => e,
721 StatusShortcut::Entry(e) => e,
722 StatusShortcut::Dispatch(d) => {
722 StatusShortcut::Dispatch(d) => {
723 return Ok((Cow::Owned(filename), d))
723 return Ok((Cow::Owned(filename), d))
724 }
724 }
725 };
725 };
726 let filename_as_path = hg_path_to_path_buf(&filename)?;
726 let filename_as_path = hg_path_to_path_buf(&filename)?;
727 let meta = self
727 let meta = self
728 .root_dir
728 .root_dir
729 .join(filename_as_path)
729 .join(filename_as_path)
730 .symlink_metadata();
730 .symlink_metadata();
731
731
732 match meta {
732 match meta {
733 Ok(ref m)
733 Ok(m)
734 if !(m.file_type().is_file()
734 if !(m.file_type().is_file()
735 || m.file_type().is_symlink()) =>
735 || m.file_type().is_symlink()) =>
736 {
736 {
737 Ok((
737 Ok((
738 Cow::Owned(filename),
738 Cow::Owned(filename),
739 dispatch_missing(entry.state),
739 dispatch_missing(entry.state),
740 ))
740 ))
741 }
741 }
742 Ok(m) => {
742 Ok(m) => {
743 let dispatch = dispatch_found(
743 let dispatch = dispatch_found(
744 &filename,
744 &filename,
745 entry,
745 entry,
746 HgMetadata::from_metadata(m),
746 HgMetadata::from_metadata(m),
747 &self.dmap.copy_map,
747 &self.dmap.copy_map,
748 self.options,
748 self.options,
749 );
749 );
750 Ok((Cow::Owned(filename), dispatch))
750 Ok((Cow::Owned(filename), dispatch))
751 }
751 }
752 Err(ref e)
752 Err(e)
753 if e.kind() == ErrorKind::NotFound
753 if e.kind() == ErrorKind::NotFound
754 || e.raw_os_error() == Some(20) =>
754 || e.raw_os_error() == Some(20) =>
755 {
755 {
756 // Rust does not yet have an `ErrorKind` for
756 // Rust does not yet have an `ErrorKind` for
757 // `NotADirectory` (errno 20)
757 // `NotADirectory` (errno 20)
758 // It happens if the dirstate contains `foo/bar`
758 // It happens if the dirstate contains `foo/bar`
759 // and foo is not a
759 // and foo is not a
760 // directory
760 // directory
761 Ok((
761 Ok((
762 Cow::Owned(filename),
762 Cow::Owned(filename),
763 dispatch_missing(entry.state),
763 dispatch_missing(entry.state),
764 ))
764 ))
765 }
765 }
766 Err(e) => Err(e),
766 Err(e) => Err(e),
767 }
767 }
768 }),
768 }),
769 );
769 );
770 }
770 }
771
771
772 /// Add the files in the dirstate to the results.
772 /// Add the files in the dirstate to the results.
773 ///
773 ///
774 /// This takes a mutable reference to the results to account for the
774 /// This takes a mutable reference to the results to account for the
775 /// `extend` in timings
775 /// `extend` in timings
776 #[cfg(not(feature = "dirstate-tree"))]
776 #[cfg(not(feature = "dirstate-tree"))]
777 #[timed]
777 #[timed]
778 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
778 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
779 results.par_extend(self.dmap.par_iter().flat_map(
779 results.par_extend(self.dmap.par_iter().flat_map(
780 move |(filename, entry)| {
780 move |(filename, entry)| {
781 let filename: &HgPath = filename;
781 let filename: &HgPath = filename;
782 let filename_as_path = hg_path_to_path_buf(filename)?;
782 let filename_as_path = hg_path_to_path_buf(filename)?;
783 let meta =
783 let meta =
784 self.root_dir.join(filename_as_path).symlink_metadata();
784 self.root_dir.join(filename_as_path).symlink_metadata();
785 match meta {
785 match meta {
786 Ok(ref m)
786 Ok(m)
787 if !(m.file_type().is_file()
787 if !(m.file_type().is_file()
788 || m.file_type().is_symlink()) =>
788 || m.file_type().is_symlink()) =>
789 {
789 {
790 Ok((
790 Ok((
791 Cow::Borrowed(filename),
791 Cow::Borrowed(filename),
792 dispatch_missing(entry.state),
792 dispatch_missing(entry.state),
793 ))
793 ))
794 }
794 }
795 Ok(m) => Ok((
795 Ok(m) => Ok((
796 Cow::Borrowed(filename),
796 Cow::Borrowed(filename),
797 dispatch_found(
797 dispatch_found(
798 filename,
798 filename,
799 *entry,
799 *entry,
800 HgMetadata::from_metadata(m),
800 HgMetadata::from_metadata(m),
801 &self.dmap.copy_map,
801 &self.dmap.copy_map,
802 self.options,
802 self.options,
803 ),
803 ),
804 )),
804 )),
805 Err(ref e)
805 Err(e)
806 if e.kind() == ErrorKind::NotFound
806 if e.kind() == ErrorKind::NotFound
807 || e.raw_os_error() == Some(20) =>
807 || e.raw_os_error() == Some(20) =>
808 {
808 {
809 // Rust does not yet have an `ErrorKind` for
809 // Rust does not yet have an `ErrorKind` for
810 // `NotADirectory` (errno 20)
810 // `NotADirectory` (errno 20)
811 // It happens if the dirstate contains `foo/bar`
811 // It happens if the dirstate contains `foo/bar`
812 // and foo is not a
812 // and foo is not a
813 // directory
813 // directory
814 Ok((
814 Ok((
815 Cow::Borrowed(filename),
815 Cow::Borrowed(filename),
816 dispatch_missing(entry.state),
816 dispatch_missing(entry.state),
817 ))
817 ))
818 }
818 }
819 Err(e) => Err(e),
819 Err(e) => Err(e),
820 }
820 }
821 },
821 },
822 ));
822 ));
823 }
823 }
824
824
825 /// Checks all files that are in the dirstate but were not found during the
825 /// Checks all files that are in the dirstate but were not found during the
826 /// working directory traversal. This means that the rest must
826 /// working directory traversal. This means that the rest must
827 /// be either ignored, under a symlink or under a new nested repo.
827 /// be either ignored, under a symlink or under a new nested repo.
828 ///
828 ///
829 /// This takes a mutable reference to the results to account for the
829 /// This takes a mutable reference to the results to account for the
830 /// `extend` in timings
830 /// `extend` in timings
831 #[cfg(not(feature = "dirstate-tree"))]
831 #[cfg(not(feature = "dirstate-tree"))]
832 #[timed]
832 #[timed]
833 pub fn handle_unknowns(
833 pub fn handle_unknowns(
834 &self,
834 &self,
835 results: &mut Vec<DispatchedPath<'a>>,
835 results: &mut Vec<DispatchedPath<'a>>,
836 ) -> IoResult<()> {
836 ) -> IoResult<()> {
837 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
837 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
838 if results.is_empty() && self.matcher.matches_everything() {
838 if results.is_empty() && self.matcher.matches_everything() {
839 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
839 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
840 } else {
840 } else {
841 // Only convert to a hashmap if needed.
841 // Only convert to a hashmap if needed.
842 let old_results: FastHashMap<_, _> =
842 let old_results: FastHashMap<_, _> =
843 results.iter().cloned().collect();
843 results.iter().cloned().collect();
844 self.dmap
844 self.dmap
845 .iter()
845 .iter()
846 .filter_map(move |(f, e)| {
846 .filter_map(move |(f, e)| {
847 if !old_results.contains_key(f.deref())
847 if !old_results.contains_key(f.deref())
848 && self.matcher.matches(f)
848 && self.matcher.matches(f)
849 {
849 {
850 Some((f.deref(), e))
850 Some((f.deref(), e))
851 } else {
851 } else {
852 None
852 None
853 }
853 }
854 })
854 })
855 .collect()
855 .collect()
856 };
856 };
857
857
858 let path_auditor = PathAuditor::new(&self.root_dir);
858 let path_auditor = PathAuditor::new(&self.root_dir);
859
859
860 // TODO don't collect. Find a way of replicating the behavior of
860 // TODO don't collect. Find a way of replicating the behavior of
861 // `itertools::process_results`, but for `rayon::ParallelIterator`
861 // `itertools::process_results`, but for `rayon::ParallelIterator`
862 let new_results: IoResult<Vec<_>> = to_visit
862 let new_results: IoResult<Vec<_>> = to_visit
863 .into_par_iter()
863 .into_par_iter()
864 .filter_map(|(filename, entry)| -> Option<IoResult<_>> {
864 .filter_map(|(filename, entry)| -> Option<IoResult<_>> {
865 // Report ignored items in the dmap as long as they are not
865 // Report ignored items in the dmap as long as they are not
866 // under a symlink directory.
866 // under a symlink directory.
867 if path_auditor.check(filename) {
867 if path_auditor.check(filename) {
868 // TODO normalize for case-insensitive filesystems
868 // TODO normalize for case-insensitive filesystems
869 let buf = match hg_path_to_path_buf(filename) {
869 let buf = match hg_path_to_path_buf(filename) {
870 Ok(x) => x,
870 Ok(x) => x,
871 Err(e) => return Some(Err(e.into())),
871 Err(e) => return Some(Err(e.into())),
872 };
872 };
873 Some(Ok((
873 Some(Ok((
874 Cow::Borrowed(filename),
874 Cow::Borrowed(filename),
875 match self.root_dir.join(&buf).symlink_metadata() {
875 match self.root_dir.join(&buf).symlink_metadata() {
876 // File was just ignored, no links, and exists
876 // File was just ignored, no links, and exists
877 Ok(meta) => {
877 Ok(meta) => {
878 let metadata = HgMetadata::from_metadata(meta);
878 let metadata = HgMetadata::from_metadata(meta);
879 dispatch_found(
879 dispatch_found(
880 filename,
880 filename,
881 *entry,
881 *entry,
882 metadata,
882 metadata,
883 &self.dmap.copy_map,
883 &self.dmap.copy_map,
884 self.options,
884 self.options,
885 )
885 )
886 }
886 }
887 // File doesn't exist
887 // File doesn't exist
888 Err(_) => dispatch_missing(entry.state),
888 Err(_) => dispatch_missing(entry.state),
889 },
889 },
890 )))
890 )))
891 } else {
891 } else {
892 // It's either missing or under a symlink directory which
892 // It's either missing or under a symlink directory which
893 // we, in this case, report as missing.
893 // we, in this case, report as missing.
894 Some(Ok((
894 Some(Ok((
895 Cow::Borrowed(filename),
895 Cow::Borrowed(filename),
896 dispatch_missing(entry.state),
896 dispatch_missing(entry.state),
897 )))
897 )))
898 }
898 }
899 })
899 })
900 .collect();
900 .collect();
901
901
902 results.par_extend(new_results?);
902 results.par_extend(new_results?);
903
903
904 Ok(())
904 Ok(())
905 }
905 }
906 }
906 }
907
907
908 #[timed]
908 #[timed]
909 pub fn build_response<'a>(
909 pub fn build_response<'a>(
910 results: impl IntoIterator<Item = DispatchedPath<'a>>,
910 results: impl IntoIterator<Item = DispatchedPath<'a>>,
911 traversed: Vec<HgPathBuf>,
911 traversed: Vec<HgPathBuf>,
912 ) -> (Vec<HgPathCow<'a>>, DirstateStatus<'a>) {
912 ) -> (Vec<HgPathCow<'a>>, DirstateStatus<'a>) {
913 let mut lookup = vec![];
913 let mut lookup = vec![];
914 let mut modified = vec![];
914 let mut modified = vec![];
915 let mut added = vec![];
915 let mut added = vec![];
916 let mut removed = vec![];
916 let mut removed = vec![];
917 let mut deleted = vec![];
917 let mut deleted = vec![];
918 let mut clean = vec![];
918 let mut clean = vec![];
919 let mut ignored = vec![];
919 let mut ignored = vec![];
920 let mut unknown = vec![];
920 let mut unknown = vec![];
921 let mut bad = vec![];
921 let mut bad = vec![];
922
922
923 for (filename, dispatch) in results.into_iter() {
923 for (filename, dispatch) in results.into_iter() {
924 match dispatch {
924 match dispatch {
925 Dispatch::Unknown => unknown.push(filename),
925 Dispatch::Unknown => unknown.push(filename),
926 Dispatch::Unsure => lookup.push(filename),
926 Dispatch::Unsure => lookup.push(filename),
927 Dispatch::Modified => modified.push(filename),
927 Dispatch::Modified => modified.push(filename),
928 Dispatch::Added => added.push(filename),
928 Dispatch::Added => added.push(filename),
929 Dispatch::Removed => removed.push(filename),
929 Dispatch::Removed => removed.push(filename),
930 Dispatch::Deleted => deleted.push(filename),
930 Dispatch::Deleted => deleted.push(filename),
931 Dispatch::Clean => clean.push(filename),
931 Dispatch::Clean => clean.push(filename),
932 Dispatch::Ignored => ignored.push(filename),
932 Dispatch::Ignored => ignored.push(filename),
933 Dispatch::None => {}
933 Dispatch::None => {}
934 Dispatch::Bad(reason) => bad.push((filename, reason)),
934 Dispatch::Bad(reason) => bad.push((filename, reason)),
935 Dispatch::Directory { .. } => {}
935 Dispatch::Directory { .. } => {}
936 }
936 }
937 }
937 }
938
938
939 (
939 (
940 lookup,
940 lookup,
941 DirstateStatus {
941 DirstateStatus {
942 modified,
942 modified,
943 added,
943 added,
944 removed,
944 removed,
945 deleted,
945 deleted,
946 clean,
946 clean,
947 ignored,
947 ignored,
948 unknown,
948 unknown,
949 bad,
949 bad,
950 traversed,
950 traversed,
951 },
951 },
952 )
952 )
953 }
953 }
954
954
955 /// Get the status of files in the working directory.
955 /// Get the status of files in the working directory.
956 ///
956 ///
957 /// This is the current entry-point for `hg-core` and is realistically unusable
957 /// This is the current entry-point for `hg-core` and is realistically unusable
958 /// outside of a Python context because its arguments need to provide a lot of
958 /// outside of a Python context because its arguments need to provide a lot of
959 /// information that will not be necessary in the future.
959 /// information that will not be necessary in the future.
960 #[timed]
960 #[timed]
961 pub fn status<'a>(
961 pub fn status<'a>(
962 dmap: &'a DirstateMap,
962 dmap: &'a DirstateMap,
963 matcher: &'a (impl Matcher + Sync),
963 matcher: &'a (impl Matcher + Sync),
964 root_dir: PathBuf,
964 root_dir: PathBuf,
965 ignore_files: Vec<PathBuf>,
965 ignore_files: Vec<PathBuf>,
966 options: StatusOptions,
966 options: StatusOptions,
967 ) -> StatusResult<(
967 ) -> StatusResult<(
968 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
968 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
969 Vec<PatternFileWarning>,
969 Vec<PatternFileWarning>,
970 )> {
970 )> {
971 let (status, warnings) =
971 let (status, warnings) =
972 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
972 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
973
973
974 Ok((status.run()?, warnings))
974 Ok((status.run()?, warnings))
975 }
975 }
General Comments 0
You need to be logged in to leave comments. Login now