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