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