##// END OF EJS Templates
rust: Move "lookup" a.k.a. "unsure" paths into `DirstateStatus` struct...
Simon Sapin -
r47880:9c6b458a default
parent child Browse files
Show More
@@ -1,913 +1,912 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::utils::path_auditor::PathAuditor;
12 use crate::utils::path_auditor::PathAuditor;
13 use crate::{
13 use crate::{
14 dirstate::SIZE_FROM_OTHER_PARENT,
14 dirstate::SIZE_FROM_OTHER_PARENT,
15 filepatterns::PatternFileWarning,
15 filepatterns::PatternFileWarning,
16 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
16 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
17 utils::{
17 utils::{
18 files::{find_dirs, HgMetadata},
18 files::{find_dirs, HgMetadata},
19 hg_path::{
19 hg_path::{
20 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
20 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
21 HgPathError,
21 HgPathError,
22 },
22 },
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 fmt,
33 fmt,
34 fs::{read_dir, DirEntry},
34 fs::{read_dir, DirEntry},
35 io::ErrorKind,
35 io::ErrorKind,
36 ops::Deref,
36 ops::Deref,
37 path::{Path, PathBuf},
37 path::{Path, PathBuf},
38 };
38 };
39
39
40 /// Wrong type of file from a `BadMatch`
40 /// Wrong type of file from a `BadMatch`
41 /// Note: a lot of those don't exist on all platforms.
41 /// Note: a lot of those don't exist on all platforms.
42 #[derive(Debug, Copy, Clone)]
42 #[derive(Debug, Copy, Clone)]
43 pub enum BadType {
43 pub enum BadType {
44 CharacterDevice,
44 CharacterDevice,
45 BlockDevice,
45 BlockDevice,
46 FIFO,
46 FIFO,
47 Socket,
47 Socket,
48 Directory,
48 Directory,
49 Unknown,
49 Unknown,
50 }
50 }
51
51
52 impl fmt::Display for BadType {
52 impl fmt::Display for BadType {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 f.write_str(match self {
54 f.write_str(match self {
55 BadType::CharacterDevice => "character device",
55 BadType::CharacterDevice => "character device",
56 BadType::BlockDevice => "block device",
56 BadType::BlockDevice => "block device",
57 BadType::FIFO => "fifo",
57 BadType::FIFO => "fifo",
58 BadType::Socket => "socket",
58 BadType::Socket => "socket",
59 BadType::Directory => "directory",
59 BadType::Directory => "directory",
60 BadType::Unknown => "unknown",
60 BadType::Unknown => "unknown",
61 })
61 })
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 /// 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
98 /// `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
99 /// 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".
100 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>;
101
101
102 /// 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
103 /// the dirstate/explicit) paths, this comes up a lot.
103 /// the dirstate/explicit) paths, this comes up a lot.
104 pub type HgPathCow<'a> = Cow<'a, HgPath>;
104 pub type HgPathCow<'a> = Cow<'a, HgPath>;
105
105
106 /// A path with its computed ``Dispatch`` information
106 /// A path with its computed ``Dispatch`` information
107 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
107 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
108
108
109 /// The conversion from `HgPath` to a real fs path failed.
109 /// The conversion from `HgPath` to a real fs path failed.
110 /// `22` is the error code for "Invalid argument"
110 /// `22` is the error code for "Invalid argument"
111 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
111 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
112
112
113 /// Dates and times that are outside the 31-bit signed range are compared
113 /// Dates and times that are outside the 31-bit signed range are compared
114 /// modulo 2^31. This should prevent hg from behaving badly with very large
114 /// modulo 2^31. This should prevent hg from behaving badly with very large
115 /// files or corrupt dates while still having a high probability of detecting
115 /// files or corrupt dates while still having a high probability of detecting
116 /// changes. (issue2608)
116 /// changes. (issue2608)
117 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
117 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
118 /// is not defined for `i32`, and there is no `As` trait. This forces the
118 /// is not defined for `i32`, and there is no `As` trait. This forces the
119 /// caller to cast `b` as `i32`.
119 /// caller to cast `b` as `i32`.
120 fn mod_compare(a: i32, b: i32) -> bool {
120 fn mod_compare(a: i32, b: i32) -> bool {
121 a & i32::max_value() != b & i32::max_value()
121 a & i32::max_value() != b & i32::max_value()
122 }
122 }
123
123
124 /// Return a sorted list containing information about the entries
124 /// Return a sorted list containing information about the entries
125 /// in the directory.
125 /// in the directory.
126 ///
126 ///
127 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
127 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
128 fn list_directory(
128 fn list_directory(
129 path: impl AsRef<Path>,
129 path: impl AsRef<Path>,
130 skip_dot_hg: bool,
130 skip_dot_hg: bool,
131 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
131 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
132 let mut results = vec![];
132 let mut results = vec![];
133 let entries = read_dir(path.as_ref())?;
133 let entries = read_dir(path.as_ref())?;
134
134
135 for entry in entries {
135 for entry in entries {
136 let entry = entry?;
136 let entry = entry?;
137 let filename = os_string_to_hg_path_buf(entry.file_name())?;
137 let filename = os_string_to_hg_path_buf(entry.file_name())?;
138 let file_type = entry.file_type()?;
138 let file_type = entry.file_type()?;
139 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
139 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
140 return Ok(vec![]);
140 return Ok(vec![]);
141 } else {
141 } else {
142 results.push((filename, entry))
142 results.push((filename, entry))
143 }
143 }
144 }
144 }
145
145
146 results.sort_unstable_by_key(|e| e.0.clone());
146 results.sort_unstable_by_key(|e| e.0.clone());
147 Ok(results)
147 Ok(results)
148 }
148 }
149
149
150 /// The file corresponding to the dirstate entry was found on the filesystem.
150 /// The file corresponding to the dirstate entry was found on the filesystem.
151 fn dispatch_found(
151 fn dispatch_found(
152 filename: impl AsRef<HgPath>,
152 filename: impl AsRef<HgPath>,
153 entry: DirstateEntry,
153 entry: DirstateEntry,
154 metadata: HgMetadata,
154 metadata: HgMetadata,
155 copy_map: &CopyMap,
155 copy_map: &CopyMap,
156 options: StatusOptions,
156 options: StatusOptions,
157 ) -> Dispatch {
157 ) -> Dispatch {
158 let DirstateEntry {
158 let DirstateEntry {
159 state,
159 state,
160 mode,
160 mode,
161 mtime,
161 mtime,
162 size,
162 size,
163 } = entry;
163 } = entry;
164
164
165 let HgMetadata {
165 let HgMetadata {
166 st_mode,
166 st_mode,
167 st_size,
167 st_size,
168 st_mtime,
168 st_mtime,
169 ..
169 ..
170 } = metadata;
170 } = metadata;
171
171
172 match state {
172 match state {
173 EntryState::Normal => {
173 EntryState::Normal => {
174 let size_changed = mod_compare(size, st_size as i32);
174 let size_changed = mod_compare(size, st_size as i32);
175 let mode_changed =
175 let mode_changed =
176 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
176 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
177 let metadata_changed = size >= 0 && (size_changed || mode_changed);
177 let metadata_changed = size >= 0 && (size_changed || mode_changed);
178 let other_parent = size == SIZE_FROM_OTHER_PARENT;
178 let other_parent = size == SIZE_FROM_OTHER_PARENT;
179
179
180 if metadata_changed
180 if metadata_changed
181 || other_parent
181 || other_parent
182 || copy_map.contains_key(filename.as_ref())
182 || copy_map.contains_key(filename.as_ref())
183 {
183 {
184 if metadata.is_symlink() && size_changed {
184 if metadata.is_symlink() && size_changed {
185 // issue6456: Size returned may be longer due to encryption
185 // issue6456: Size returned may be longer due to encryption
186 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
186 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
187 Dispatch::Unsure
187 Dispatch::Unsure
188 } else {
188 } else {
189 Dispatch::Modified
189 Dispatch::Modified
190 }
190 }
191 } else if mod_compare(mtime, st_mtime as i32)
191 } else if mod_compare(mtime, st_mtime as i32)
192 || st_mtime == options.last_normal_time
192 || st_mtime == options.last_normal_time
193 {
193 {
194 // the file may have just been marked as normal and
194 // the file may have just been marked as normal and
195 // it may have changed in the same second without
195 // it may have changed in the same second without
196 // changing its size. This can happen if we quickly
196 // changing its size. This can happen if we quickly
197 // do multiple commits. Force lookup, so we don't
197 // do multiple commits. Force lookup, so we don't
198 // miss such a racy file change.
198 // miss such a racy file change.
199 Dispatch::Unsure
199 Dispatch::Unsure
200 } else if options.list_clean {
200 } else if options.list_clean {
201 Dispatch::Clean
201 Dispatch::Clean
202 } else {
202 } else {
203 Dispatch::None
203 Dispatch::None
204 }
204 }
205 }
205 }
206 EntryState::Merged => Dispatch::Modified,
206 EntryState::Merged => Dispatch::Modified,
207 EntryState::Added => Dispatch::Added,
207 EntryState::Added => Dispatch::Added,
208 EntryState::Removed => Dispatch::Removed,
208 EntryState::Removed => Dispatch::Removed,
209 EntryState::Unknown => Dispatch::Unknown,
209 EntryState::Unknown => Dispatch::Unknown,
210 }
210 }
211 }
211 }
212
212
213 /// The file corresponding to this Dirstate entry is missing.
213 /// The file corresponding to this Dirstate entry is missing.
214 fn dispatch_missing(state: EntryState) -> Dispatch {
214 fn dispatch_missing(state: EntryState) -> Dispatch {
215 match state {
215 match state {
216 // File was removed from the filesystem during commands
216 // File was removed from the filesystem during commands
217 EntryState::Normal | EntryState::Merged | EntryState::Added => {
217 EntryState::Normal | EntryState::Merged | EntryState::Added => {
218 Dispatch::Deleted
218 Dispatch::Deleted
219 }
219 }
220 // File was removed, everything is normal
220 // File was removed, everything is normal
221 EntryState::Removed => Dispatch::Removed,
221 EntryState::Removed => Dispatch::Removed,
222 // File is unknown to Mercurial, everything is normal
222 // File is unknown to Mercurial, everything is normal
223 EntryState::Unknown => Dispatch::Unknown,
223 EntryState::Unknown => Dispatch::Unknown,
224 }
224 }
225 }
225 }
226
226
227 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
227 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
228 Dispatch::Bad(BadMatch::OsError(
228 Dispatch::Bad(BadMatch::OsError(
229 e.raw_os_error().expect("expected real OS error"),
229 e.raw_os_error().expect("expected real OS error"),
230 ))
230 ))
231 }
231 }
232
232
233 lazy_static! {
233 lazy_static! {
234 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
234 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
235 let mut h = HashSet::new();
235 let mut h = HashSet::new();
236 h.insert(HgPath::new(b""));
236 h.insert(HgPath::new(b""));
237 h
237 h
238 };
238 };
239 }
239 }
240
240
241 #[derive(Debug, Copy, Clone)]
241 #[derive(Debug, Copy, Clone)]
242 pub struct StatusOptions {
242 pub struct StatusOptions {
243 /// Remember the most recent modification timeslot for status, to make
243 /// Remember the most recent modification timeslot for status, to make
244 /// sure we won't miss future size-preserving file content modifications
244 /// sure we won't miss future size-preserving file content modifications
245 /// that happen within the same timeslot.
245 /// that happen within the same timeslot.
246 pub last_normal_time: i64,
246 pub last_normal_time: i64,
247 /// Whether we are on a filesystem with UNIX-like exec flags
247 /// Whether we are on a filesystem with UNIX-like exec flags
248 pub check_exec: bool,
248 pub check_exec: bool,
249 pub list_clean: bool,
249 pub list_clean: bool,
250 pub list_unknown: bool,
250 pub list_unknown: bool,
251 pub list_ignored: bool,
251 pub list_ignored: bool,
252 /// Whether to collect traversed dirs for applying a callback later.
252 /// Whether to collect traversed dirs for applying a callback later.
253 /// Used by `hg purge` for example.
253 /// Used by `hg purge` for example.
254 pub collect_traversed_dirs: bool,
254 pub collect_traversed_dirs: bool,
255 }
255 }
256
256
257 #[derive(Debug)]
257 #[derive(Debug)]
258 pub struct DirstateStatus<'a> {
258 pub struct DirstateStatus<'a> {
259 pub modified: Vec<HgPathCow<'a>>,
259 pub modified: Vec<HgPathCow<'a>>,
260 pub added: Vec<HgPathCow<'a>>,
260 pub added: Vec<HgPathCow<'a>>,
261 pub removed: Vec<HgPathCow<'a>>,
261 pub removed: Vec<HgPathCow<'a>>,
262 pub deleted: Vec<HgPathCow<'a>>,
262 pub deleted: Vec<HgPathCow<'a>>,
263 pub clean: Vec<HgPathCow<'a>>,
263 pub clean: Vec<HgPathCow<'a>>,
264 pub ignored: Vec<HgPathCow<'a>>,
264 pub ignored: Vec<HgPathCow<'a>>,
265 pub unknown: Vec<HgPathCow<'a>>,
265 pub unknown: Vec<HgPathCow<'a>>,
266 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
266 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
267 /// Either clean or modified, but we can’t tell from filesystem metadata
268 /// alone. The file contents need to be read and compared with that in
269 /// the parent.
270 pub unsure: Vec<HgPathCow<'a>>,
267 /// Only filled if `collect_traversed_dirs` is `true`
271 /// Only filled if `collect_traversed_dirs` is `true`
268 pub traversed: Vec<HgPathBuf>,
272 pub traversed: Vec<HgPathBuf>,
269 }
273 }
270
274
271 #[derive(Debug, derive_more::From)]
275 #[derive(Debug, derive_more::From)]
272 pub enum StatusError {
276 pub enum StatusError {
273 /// Generic IO error
277 /// Generic IO error
274 IO(std::io::Error),
278 IO(std::io::Error),
275 /// An invalid path that cannot be represented in Mercurial was found
279 /// An invalid path that cannot be represented in Mercurial was found
276 Path(HgPathError),
280 Path(HgPathError),
277 /// An invalid "ignore" pattern was found
281 /// An invalid "ignore" pattern was found
278 Pattern(PatternError),
282 Pattern(PatternError),
279 }
283 }
280
284
281 pub type StatusResult<T> = Result<T, StatusError>;
285 pub type StatusResult<T> = Result<T, StatusError>;
282
286
283 impl fmt::Display for StatusError {
287 impl fmt::Display for StatusError {
284 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285 match self {
289 match self {
286 StatusError::IO(error) => error.fmt(f),
290 StatusError::IO(error) => error.fmt(f),
287 StatusError::Path(error) => error.fmt(f),
291 StatusError::Path(error) => error.fmt(f),
288 StatusError::Pattern(error) => error.fmt(f),
292 StatusError::Pattern(error) => error.fmt(f),
289 }
293 }
290 }
294 }
291 }
295 }
292
296
293 /// Gives information about which files are changed in the working directory
297 /// Gives information about which files are changed in the working directory
294 /// and how, compared to the revision we're based on
298 /// and how, compared to the revision we're based on
295 pub struct Status<'a, M: ?Sized + Matcher + Sync> {
299 pub struct Status<'a, M: ?Sized + Matcher + Sync> {
296 dmap: &'a DirstateMap,
300 dmap: &'a DirstateMap,
297 pub(crate) matcher: &'a M,
301 pub(crate) matcher: &'a M,
298 root_dir: PathBuf,
302 root_dir: PathBuf,
299 pub(crate) options: StatusOptions,
303 pub(crate) options: StatusOptions,
300 ignore_fn: IgnoreFnType<'a>,
304 ignore_fn: IgnoreFnType<'a>,
301 }
305 }
302
306
303 impl<'a, M> Status<'a, M>
307 impl<'a, M> Status<'a, M>
304 where
308 where
305 M: ?Sized + Matcher + Sync,
309 M: ?Sized + Matcher + Sync,
306 {
310 {
307 pub fn new(
311 pub fn new(
308 dmap: &'a DirstateMap,
312 dmap: &'a DirstateMap,
309 matcher: &'a M,
313 matcher: &'a M,
310 root_dir: PathBuf,
314 root_dir: PathBuf,
311 ignore_files: Vec<PathBuf>,
315 ignore_files: Vec<PathBuf>,
312 options: StatusOptions,
316 options: StatusOptions,
313 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
317 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
314 // Needs to outlive `dir_ignore_fn` since it's captured.
318 // Needs to outlive `dir_ignore_fn` since it's captured.
315
319
316 let (ignore_fn, warnings): (IgnoreFnType, _) =
320 let (ignore_fn, warnings): (IgnoreFnType, _) =
317 if options.list_ignored || options.list_unknown {
321 if options.list_ignored || options.list_unknown {
318 get_ignore_function(ignore_files, &root_dir)?
322 get_ignore_function(ignore_files, &root_dir)?
319 } else {
323 } else {
320 (Box::new(|&_| true), vec![])
324 (Box::new(|&_| true), vec![])
321 };
325 };
322
326
323 Ok((
327 Ok((
324 Self {
328 Self {
325 dmap,
329 dmap,
326 matcher,
330 matcher,
327 root_dir,
331 root_dir,
328 options,
332 options,
329 ignore_fn,
333 ignore_fn,
330 },
334 },
331 warnings,
335 warnings,
332 ))
336 ))
333 }
337 }
334
338
335 /// Is the path ignored?
339 /// Is the path ignored?
336 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
340 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
337 (self.ignore_fn)(path.as_ref())
341 (self.ignore_fn)(path.as_ref())
338 }
342 }
339
343
340 /// Is the path or one of its ancestors ignored?
344 /// Is the path or one of its ancestors ignored?
341 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
345 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
342 // Only involve ignore mechanism if we're listing unknowns or ignored.
346 // Only involve ignore mechanism if we're listing unknowns or ignored.
343 if self.options.list_ignored || self.options.list_unknown {
347 if self.options.list_ignored || self.options.list_unknown {
344 if self.is_ignored(&dir) {
348 if self.is_ignored(&dir) {
345 true
349 true
346 } else {
350 } else {
347 for p in find_dirs(dir.as_ref()) {
351 for p in find_dirs(dir.as_ref()) {
348 if self.is_ignored(p) {
352 if self.is_ignored(p) {
349 return true;
353 return true;
350 }
354 }
351 }
355 }
352 false
356 false
353 }
357 }
354 } else {
358 } else {
355 true
359 true
356 }
360 }
357 }
361 }
358
362
359 /// Get stat data about the files explicitly specified by the matcher.
363 /// 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
364 /// Returns a tuple of the directories that need to be traversed and the
361 /// files with their corresponding `Dispatch`.
365 /// files with their corresponding `Dispatch`.
362 /// TODO subrepos
366 /// TODO subrepos
363 #[timed]
367 #[timed]
364 pub fn walk_explicit(
368 pub fn walk_explicit(
365 &self,
369 &self,
366 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
370 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
367 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
371 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
368 self.matcher
372 self.matcher
369 .file_set()
373 .file_set()
370 .unwrap_or(&DEFAULT_WORK)
374 .unwrap_or(&DEFAULT_WORK)
371 .par_iter()
375 .par_iter()
372 .flat_map(|&filename| -> Option<_> {
376 .flat_map(|&filename| -> Option<_> {
373 // TODO normalization
377 // TODO normalization
374 let normalized = filename;
378 let normalized = filename;
375
379
376 let buf = match hg_path_to_path_buf(normalized) {
380 let buf = match hg_path_to_path_buf(normalized) {
377 Ok(x) => x,
381 Ok(x) => x,
378 Err(_) => {
382 Err(_) => {
379 return Some((
383 return Some((
380 Cow::Borrowed(normalized),
384 Cow::Borrowed(normalized),
381 INVALID_PATH_DISPATCH,
385 INVALID_PATH_DISPATCH,
382 ))
386 ))
383 }
387 }
384 };
388 };
385 let target = self.root_dir.join(buf);
389 let target = self.root_dir.join(buf);
386 let st = target.symlink_metadata();
390 let st = target.symlink_metadata();
387 let in_dmap = self.dmap.get(normalized);
391 let in_dmap = self.dmap.get(normalized);
388 match st {
392 match st {
389 Ok(meta) => {
393 Ok(meta) => {
390 let file_type = meta.file_type();
394 let file_type = meta.file_type();
391 return if file_type.is_file() || file_type.is_symlink()
395 return if file_type.is_file() || file_type.is_symlink()
392 {
396 {
393 if let Some(entry) = in_dmap {
397 if let Some(entry) = in_dmap {
394 return Some((
398 return Some((
395 Cow::Borrowed(normalized),
399 Cow::Borrowed(normalized),
396 dispatch_found(
400 dispatch_found(
397 &normalized,
401 &normalized,
398 *entry,
402 *entry,
399 HgMetadata::from_metadata(meta),
403 HgMetadata::from_metadata(meta),
400 &self.dmap.copy_map,
404 &self.dmap.copy_map,
401 self.options,
405 self.options,
402 ),
406 ),
403 ));
407 ));
404 }
408 }
405 Some((
409 Some((
406 Cow::Borrowed(normalized),
410 Cow::Borrowed(normalized),
407 Dispatch::Unknown,
411 Dispatch::Unknown,
408 ))
412 ))
409 } else if file_type.is_dir() {
413 } else if file_type.is_dir() {
410 if self.options.collect_traversed_dirs {
414 if self.options.collect_traversed_dirs {
411 traversed_sender
415 traversed_sender
412 .send(normalized.to_owned())
416 .send(normalized.to_owned())
413 .expect("receiver should outlive sender");
417 .expect("receiver should outlive sender");
414 }
418 }
415 Some((
419 Some((
416 Cow::Borrowed(normalized),
420 Cow::Borrowed(normalized),
417 Dispatch::Directory {
421 Dispatch::Directory {
418 was_file: in_dmap.is_some(),
422 was_file: in_dmap.is_some(),
419 },
423 },
420 ))
424 ))
421 } else {
425 } else {
422 Some((
426 Some((
423 Cow::Borrowed(normalized),
427 Cow::Borrowed(normalized),
424 Dispatch::Bad(BadMatch::BadType(
428 Dispatch::Bad(BadMatch::BadType(
425 // TODO do more than unknown
429 // TODO do more than unknown
426 // Support for all `BadType` variant
430 // Support for all `BadType` variant
427 // varies greatly between platforms.
431 // varies greatly between platforms.
428 // So far, no tests check the type and
432 // So far, no tests check the type and
429 // this should be good enough for most
433 // this should be good enough for most
430 // users.
434 // users.
431 BadType::Unknown,
435 BadType::Unknown,
432 )),
436 )),
433 ))
437 ))
434 };
438 };
435 }
439 }
436 Err(_) => {
440 Err(_) => {
437 if let Some(entry) = in_dmap {
441 if let Some(entry) = in_dmap {
438 return Some((
442 return Some((
439 Cow::Borrowed(normalized),
443 Cow::Borrowed(normalized),
440 dispatch_missing(entry.state),
444 dispatch_missing(entry.state),
441 ));
445 ));
442 }
446 }
443 }
447 }
444 };
448 };
445 None
449 None
446 })
450 })
447 .partition(|(_, dispatch)| match dispatch {
451 .partition(|(_, dispatch)| match dispatch {
448 Dispatch::Directory { .. } => true,
452 Dispatch::Directory { .. } => true,
449 _ => false,
453 _ => false,
450 })
454 })
451 }
455 }
452
456
453 /// Walk the working directory recursively to look for changes compared to
457 /// Walk the working directory recursively to look for changes compared to
454 /// the current `DirstateMap`.
458 /// the current `DirstateMap`.
455 ///
459 ///
456 /// This takes a mutable reference to the results to account for the
460 /// This takes a mutable reference to the results to account for the
457 /// `extend` in timings
461 /// `extend` in timings
458 #[timed]
462 #[timed]
459 pub fn traverse(
463 pub fn traverse(
460 &self,
464 &self,
461 path: impl AsRef<HgPath>,
465 path: impl AsRef<HgPath>,
462 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
466 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
463 results: &mut Vec<DispatchedPath<'a>>,
467 results: &mut Vec<DispatchedPath<'a>>,
464 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
468 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
465 ) {
469 ) {
466 // The traversal is done in parallel, so use a channel to gather
470 // The traversal is done in parallel, so use a channel to gather
467 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
471 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
468 // is not.
472 // is not.
469 let (files_transmitter, files_receiver) =
473 let (files_transmitter, files_receiver) =
470 crossbeam_channel::unbounded();
474 crossbeam_channel::unbounded();
471
475
472 self.traverse_dir(
476 self.traverse_dir(
473 &files_transmitter,
477 &files_transmitter,
474 path,
478 path,
475 &old_results,
479 &old_results,
476 traversed_sender,
480 traversed_sender,
477 );
481 );
478
482
479 // Disconnect the channel so the receiver stops waiting
483 // Disconnect the channel so the receiver stops waiting
480 drop(files_transmitter);
484 drop(files_transmitter);
481
485
482 let new_results = files_receiver
486 let new_results = files_receiver
483 .into_iter()
487 .into_iter()
484 .par_bridge()
488 .par_bridge()
485 .map(|(f, d)| (Cow::Owned(f), d));
489 .map(|(f, d)| (Cow::Owned(f), d));
486
490
487 results.par_extend(new_results);
491 results.par_extend(new_results);
488 }
492 }
489
493
490 /// Dispatch a single entry (file, folder, symlink...) found during
494 /// Dispatch a single entry (file, folder, symlink...) found during
491 /// `traverse`. If the entry is a folder that needs to be traversed, it
495 /// `traverse`. If the entry is a folder that needs to be traversed, it
492 /// will be handled in a separate thread.
496 /// will be handled in a separate thread.
493 fn handle_traversed_entry<'b>(
497 fn handle_traversed_entry<'b>(
494 &'a self,
498 &'a self,
495 scope: &rayon::Scope<'b>,
499 scope: &rayon::Scope<'b>,
496 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
500 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
497 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
501 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
498 filename: HgPathBuf,
502 filename: HgPathBuf,
499 dir_entry: DirEntry,
503 dir_entry: DirEntry,
500 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
504 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
501 ) -> IoResult<()>
505 ) -> IoResult<()>
502 where
506 where
503 'a: 'b,
507 'a: 'b,
504 {
508 {
505 let file_type = dir_entry.file_type()?;
509 let file_type = dir_entry.file_type()?;
506 let entry_option = self.dmap.get(&filename);
510 let entry_option = self.dmap.get(&filename);
507
511
508 if filename.as_bytes() == b".hg" {
512 if filename.as_bytes() == b".hg" {
509 // Could be a directory or a symlink
513 // Could be a directory or a symlink
510 return Ok(());
514 return Ok(());
511 }
515 }
512
516
513 if file_type.is_dir() {
517 if file_type.is_dir() {
514 self.handle_traversed_dir(
518 self.handle_traversed_dir(
515 scope,
519 scope,
516 files_sender,
520 files_sender,
517 old_results,
521 old_results,
518 entry_option,
522 entry_option,
519 filename,
523 filename,
520 traversed_sender,
524 traversed_sender,
521 );
525 );
522 } else if file_type.is_file() || file_type.is_symlink() {
526 } else if file_type.is_file() || file_type.is_symlink() {
523 if let Some(entry) = entry_option {
527 if let Some(entry) = entry_option {
524 if self.matcher.matches_everything()
528 if self.matcher.matches_everything()
525 || self.matcher.matches(&filename)
529 || self.matcher.matches(&filename)
526 {
530 {
527 let metadata = dir_entry.metadata()?;
531 let metadata = dir_entry.metadata()?;
528 files_sender
532 files_sender
529 .send((
533 .send((
530 filename.to_owned(),
534 filename.to_owned(),
531 dispatch_found(
535 dispatch_found(
532 &filename,
536 &filename,
533 *entry,
537 *entry,
534 HgMetadata::from_metadata(metadata),
538 HgMetadata::from_metadata(metadata),
535 &self.dmap.copy_map,
539 &self.dmap.copy_map,
536 self.options,
540 self.options,
537 ),
541 ),
538 ))
542 ))
539 .unwrap();
543 .unwrap();
540 }
544 }
541 } else if (self.matcher.matches_everything()
545 } else if (self.matcher.matches_everything()
542 || self.matcher.matches(&filename))
546 || self.matcher.matches(&filename))
543 && !self.is_ignored(&filename)
547 && !self.is_ignored(&filename)
544 {
548 {
545 if (self.options.list_ignored
549 if (self.options.list_ignored
546 || self.matcher.exact_match(&filename))
550 || self.matcher.exact_match(&filename))
547 && self.dir_ignore(&filename)
551 && self.dir_ignore(&filename)
548 {
552 {
549 if self.options.list_ignored {
553 if self.options.list_ignored {
550 files_sender
554 files_sender
551 .send((filename.to_owned(), Dispatch::Ignored))
555 .send((filename.to_owned(), Dispatch::Ignored))
552 .unwrap();
556 .unwrap();
553 }
557 }
554 } else if self.options.list_unknown {
558 } else if self.options.list_unknown {
555 files_sender
559 files_sender
556 .send((filename.to_owned(), Dispatch::Unknown))
560 .send((filename.to_owned(), Dispatch::Unknown))
557 .unwrap();
561 .unwrap();
558 }
562 }
559 } else if self.is_ignored(&filename) && self.options.list_ignored {
563 } else if self.is_ignored(&filename) && self.options.list_ignored {
560 files_sender
564 files_sender
561 .send((filename.to_owned(), Dispatch::Ignored))
565 .send((filename.to_owned(), Dispatch::Ignored))
562 .unwrap();
566 .unwrap();
563 }
567 }
564 } else if let Some(entry) = entry_option {
568 } else if let Some(entry) = entry_option {
565 // Used to be a file or a folder, now something else.
569 // Used to be a file or a folder, now something else.
566 if self.matcher.matches_everything()
570 if self.matcher.matches_everything()
567 || self.matcher.matches(&filename)
571 || self.matcher.matches(&filename)
568 {
572 {
569 files_sender
573 files_sender
570 .send((filename.to_owned(), dispatch_missing(entry.state)))
574 .send((filename.to_owned(), dispatch_missing(entry.state)))
571 .unwrap();
575 .unwrap();
572 }
576 }
573 }
577 }
574
578
575 Ok(())
579 Ok(())
576 }
580 }
577
581
578 /// A directory was found in the filesystem and needs to be traversed
582 /// A directory was found in the filesystem and needs to be traversed
579 fn handle_traversed_dir<'b>(
583 fn handle_traversed_dir<'b>(
580 &'a self,
584 &'a self,
581 scope: &rayon::Scope<'b>,
585 scope: &rayon::Scope<'b>,
582 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
586 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
583 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
587 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
584 entry_option: Option<&'a DirstateEntry>,
588 entry_option: Option<&'a DirstateEntry>,
585 directory: HgPathBuf,
589 directory: HgPathBuf,
586 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
590 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
587 ) where
591 ) where
588 'a: 'b,
592 'a: 'b,
589 {
593 {
590 scope.spawn(move |_| {
594 scope.spawn(move |_| {
591 // Nested `if` until `rust-lang/rust#53668` is stable
595 // Nested `if` until `rust-lang/rust#53668` is stable
592 if let Some(entry) = entry_option {
596 if let Some(entry) = entry_option {
593 // Used to be a file, is now a folder
597 // Used to be a file, is now a folder
594 if self.matcher.matches_everything()
598 if self.matcher.matches_everything()
595 || self.matcher.matches(&directory)
599 || self.matcher.matches(&directory)
596 {
600 {
597 files_sender
601 files_sender
598 .send((
602 .send((
599 directory.to_owned(),
603 directory.to_owned(),
600 dispatch_missing(entry.state),
604 dispatch_missing(entry.state),
601 ))
605 ))
602 .unwrap();
606 .unwrap();
603 }
607 }
604 }
608 }
605 // Do we need to traverse it?
609 // Do we need to traverse it?
606 if !self.is_ignored(&directory) || self.options.list_ignored {
610 if !self.is_ignored(&directory) || self.options.list_ignored {
607 self.traverse_dir(
611 self.traverse_dir(
608 files_sender,
612 files_sender,
609 directory,
613 directory,
610 &old_results,
614 &old_results,
611 traversed_sender,
615 traversed_sender,
612 )
616 )
613 }
617 }
614 });
618 });
615 }
619 }
616
620
617 /// Decides whether the directory needs to be listed, and if so handles the
621 /// Decides whether the directory needs to be listed, and if so handles the
618 /// entries in a separate thread.
622 /// entries in a separate thread.
619 fn traverse_dir(
623 fn traverse_dir(
620 &self,
624 &self,
621 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
625 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
622 directory: impl AsRef<HgPath>,
626 directory: impl AsRef<HgPath>,
623 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
627 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
624 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
628 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
625 ) {
629 ) {
626 let directory = directory.as_ref();
630 let directory = directory.as_ref();
627
631
628 if self.options.collect_traversed_dirs {
632 if self.options.collect_traversed_dirs {
629 traversed_sender
633 traversed_sender
630 .send(directory.to_owned())
634 .send(directory.to_owned())
631 .expect("receiver should outlive sender");
635 .expect("receiver should outlive sender");
632 }
636 }
633
637
634 let visit_entries = match self.matcher.visit_children_set(directory) {
638 let visit_entries = match self.matcher.visit_children_set(directory) {
635 VisitChildrenSet::Empty => return,
639 VisitChildrenSet::Empty => return,
636 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
640 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
637 VisitChildrenSet::Set(set) => Some(set),
641 VisitChildrenSet::Set(set) => Some(set),
638 };
642 };
639 let buf = match hg_path_to_path_buf(directory) {
643 let buf = match hg_path_to_path_buf(directory) {
640 Ok(b) => b,
644 Ok(b) => b,
641 Err(_) => {
645 Err(_) => {
642 files_sender
646 files_sender
643 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
647 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
644 .expect("receiver should outlive sender");
648 .expect("receiver should outlive sender");
645 return;
649 return;
646 }
650 }
647 };
651 };
648 let dir_path = self.root_dir.join(buf);
652 let dir_path = self.root_dir.join(buf);
649
653
650 let skip_dot_hg = !directory.as_bytes().is_empty();
654 let skip_dot_hg = !directory.as_bytes().is_empty();
651 let entries = match list_directory(dir_path, skip_dot_hg) {
655 let entries = match list_directory(dir_path, skip_dot_hg) {
652 Err(e) => {
656 Err(e) => {
653 files_sender
657 files_sender
654 .send((directory.to_owned(), dispatch_os_error(&e)))
658 .send((directory.to_owned(), dispatch_os_error(&e)))
655 .expect("receiver should outlive sender");
659 .expect("receiver should outlive sender");
656 return;
660 return;
657 }
661 }
658 Ok(entries) => entries,
662 Ok(entries) => entries,
659 };
663 };
660
664
661 rayon::scope(|scope| {
665 rayon::scope(|scope| {
662 for (filename, dir_entry) in entries {
666 for (filename, dir_entry) in entries {
663 if let Some(ref set) = visit_entries {
667 if let Some(ref set) = visit_entries {
664 if !set.contains(filename.deref()) {
668 if !set.contains(filename.deref()) {
665 continue;
669 continue;
666 }
670 }
667 }
671 }
668 // TODO normalize
672 // TODO normalize
669 let filename = if directory.is_empty() {
673 let filename = if directory.is_empty() {
670 filename.to_owned()
674 filename.to_owned()
671 } else {
675 } else {
672 directory.join(&filename)
676 directory.join(&filename)
673 };
677 };
674
678
675 if !old_results.contains_key(filename.deref()) {
679 if !old_results.contains_key(filename.deref()) {
676 match self.handle_traversed_entry(
680 match self.handle_traversed_entry(
677 scope,
681 scope,
678 files_sender,
682 files_sender,
679 old_results,
683 old_results,
680 filename,
684 filename,
681 dir_entry,
685 dir_entry,
682 traversed_sender.clone(),
686 traversed_sender.clone(),
683 ) {
687 ) {
684 Err(e) => {
688 Err(e) => {
685 files_sender
689 files_sender
686 .send((
690 .send((
687 directory.to_owned(),
691 directory.to_owned(),
688 dispatch_os_error(&e),
692 dispatch_os_error(&e),
689 ))
693 ))
690 .expect("receiver should outlive sender");
694 .expect("receiver should outlive sender");
691 }
695 }
692 Ok(_) => {}
696 Ok(_) => {}
693 }
697 }
694 }
698 }
695 }
699 }
696 })
700 })
697 }
701 }
698
702
699 /// Add the files in the dirstate to the results.
703 /// Add the files in the dirstate to the results.
700 ///
704 ///
701 /// This takes a mutable reference to the results to account for the
705 /// This takes a mutable reference to the results to account for the
702 /// `extend` in timings
706 /// `extend` in timings
703 #[timed]
707 #[timed]
704 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
708 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
705 results.par_extend(
709 results.par_extend(
706 self.dmap
710 self.dmap
707 .par_iter()
711 .par_iter()
708 .filter(|(path, _)| self.matcher.matches(path))
712 .filter(|(path, _)| self.matcher.matches(path))
709 .map(move |(filename, entry)| {
713 .map(move |(filename, entry)| {
710 let filename: &HgPath = filename;
714 let filename: &HgPath = filename;
711 let filename_as_path = match hg_path_to_path_buf(filename)
715 let filename_as_path = match hg_path_to_path_buf(filename)
712 {
716 {
713 Ok(f) => f,
717 Ok(f) => f,
714 Err(_) => {
718 Err(_) => {
715 return (
719 return (
716 Cow::Borrowed(filename),
720 Cow::Borrowed(filename),
717 INVALID_PATH_DISPATCH,
721 INVALID_PATH_DISPATCH,
718 )
722 )
719 }
723 }
720 };
724 };
721 let meta = self
725 let meta = self
722 .root_dir
726 .root_dir
723 .join(filename_as_path)
727 .join(filename_as_path)
724 .symlink_metadata();
728 .symlink_metadata();
725 match meta {
729 match meta {
726 Ok(m)
730 Ok(m)
727 if !(m.file_type().is_file()
731 if !(m.file_type().is_file()
728 || m.file_type().is_symlink()) =>
732 || m.file_type().is_symlink()) =>
729 {
733 {
730 (
734 (
731 Cow::Borrowed(filename),
735 Cow::Borrowed(filename),
732 dispatch_missing(entry.state),
736 dispatch_missing(entry.state),
733 )
737 )
734 }
738 }
735 Ok(m) => (
739 Ok(m) => (
736 Cow::Borrowed(filename),
740 Cow::Borrowed(filename),
737 dispatch_found(
741 dispatch_found(
738 filename,
742 filename,
739 *entry,
743 *entry,
740 HgMetadata::from_metadata(m),
744 HgMetadata::from_metadata(m),
741 &self.dmap.copy_map,
745 &self.dmap.copy_map,
742 self.options,
746 self.options,
743 ),
747 ),
744 ),
748 ),
745 Err(e)
749 Err(e)
746 if e.kind() == ErrorKind::NotFound
750 if e.kind() == ErrorKind::NotFound
747 || e.raw_os_error() == Some(20) =>
751 || e.raw_os_error() == Some(20) =>
748 {
752 {
749 // Rust does not yet have an `ErrorKind` for
753 // Rust does not yet have an `ErrorKind` for
750 // `NotADirectory` (errno 20)
754 // `NotADirectory` (errno 20)
751 // It happens if the dirstate contains `foo/bar`
755 // It happens if the dirstate contains `foo/bar`
752 // and foo is not a
756 // and foo is not a
753 // directory
757 // directory
754 (
758 (
755 Cow::Borrowed(filename),
759 Cow::Borrowed(filename),
756 dispatch_missing(entry.state),
760 dispatch_missing(entry.state),
757 )
761 )
758 }
762 }
759 Err(e) => {
763 Err(e) => {
760 (Cow::Borrowed(filename), dispatch_os_error(&e))
764 (Cow::Borrowed(filename), dispatch_os_error(&e))
761 }
765 }
762 }
766 }
763 }),
767 }),
764 );
768 );
765 }
769 }
766
770
767 /// Checks all files that are in the dirstate but were not found during the
771 /// Checks all files that are in the dirstate but were not found during the
768 /// working directory traversal. This means that the rest must
772 /// working directory traversal. This means that the rest must
769 /// be either ignored, under a symlink or under a new nested repo.
773 /// be either ignored, under a symlink or under a new nested repo.
770 ///
774 ///
771 /// This takes a mutable reference to the results to account for the
775 /// This takes a mutable reference to the results to account for the
772 /// `extend` in timings
776 /// `extend` in timings
773 #[timed]
777 #[timed]
774 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
778 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
775 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
779 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
776 if results.is_empty() && self.matcher.matches_everything() {
780 if results.is_empty() && self.matcher.matches_everything() {
777 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
781 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
778 } else {
782 } else {
779 // Only convert to a hashmap if needed.
783 // Only convert to a hashmap if needed.
780 let old_results: FastHashMap<_, _> =
784 let old_results: FastHashMap<_, _> =
781 results.iter().cloned().collect();
785 results.iter().cloned().collect();
782 self.dmap
786 self.dmap
783 .iter()
787 .iter()
784 .filter_map(move |(f, e)| {
788 .filter_map(move |(f, e)| {
785 if !old_results.contains_key(f.deref())
789 if !old_results.contains_key(f.deref())
786 && self.matcher.matches(f)
790 && self.matcher.matches(f)
787 {
791 {
788 Some((f.deref(), e))
792 Some((f.deref(), e))
789 } else {
793 } else {
790 None
794 None
791 }
795 }
792 })
796 })
793 .collect()
797 .collect()
794 };
798 };
795
799
796 let path_auditor = PathAuditor::new(&self.root_dir);
800 let path_auditor = PathAuditor::new(&self.root_dir);
797
801
798 let new_results = to_visit.into_par_iter().filter_map(
802 let new_results = to_visit.into_par_iter().filter_map(
799 |(filename, entry)| -> Option<_> {
803 |(filename, entry)| -> Option<_> {
800 // Report ignored items in the dmap as long as they are not
804 // Report ignored items in the dmap as long as they are not
801 // under a symlink directory.
805 // under a symlink directory.
802 if path_auditor.check(filename) {
806 if path_auditor.check(filename) {
803 // TODO normalize for case-insensitive filesystems
807 // TODO normalize for case-insensitive filesystems
804 let buf = match hg_path_to_path_buf(filename) {
808 let buf = match hg_path_to_path_buf(filename) {
805 Ok(x) => x,
809 Ok(x) => x,
806 Err(_) => {
810 Err(_) => {
807 return Some((
811 return Some((
808 Cow::Owned(filename.to_owned()),
812 Cow::Owned(filename.to_owned()),
809 INVALID_PATH_DISPATCH,
813 INVALID_PATH_DISPATCH,
810 ));
814 ));
811 }
815 }
812 };
816 };
813 Some((
817 Some((
814 Cow::Owned(filename.to_owned()),
818 Cow::Owned(filename.to_owned()),
815 match self.root_dir.join(&buf).symlink_metadata() {
819 match self.root_dir.join(&buf).symlink_metadata() {
816 // File was just ignored, no links, and exists
820 // File was just ignored, no links, and exists
817 Ok(meta) => {
821 Ok(meta) => {
818 let metadata = HgMetadata::from_metadata(meta);
822 let metadata = HgMetadata::from_metadata(meta);
819 dispatch_found(
823 dispatch_found(
820 filename,
824 filename,
821 *entry,
825 *entry,
822 metadata,
826 metadata,
823 &self.dmap.copy_map,
827 &self.dmap.copy_map,
824 self.options,
828 self.options,
825 )
829 )
826 }
830 }
827 // File doesn't exist
831 // File doesn't exist
828 Err(_) => dispatch_missing(entry.state),
832 Err(_) => dispatch_missing(entry.state),
829 },
833 },
830 ))
834 ))
831 } else {
835 } else {
832 // It's either missing or under a symlink directory which
836 // It's either missing or under a symlink directory which
833 // we, in this case, report as missing.
837 // we, in this case, report as missing.
834 Some((
838 Some((
835 Cow::Owned(filename.to_owned()),
839 Cow::Owned(filename.to_owned()),
836 dispatch_missing(entry.state),
840 dispatch_missing(entry.state),
837 ))
841 ))
838 }
842 }
839 },
843 },
840 );
844 );
841
845
842 results.par_extend(new_results);
846 results.par_extend(new_results);
843 }
847 }
844 }
848 }
845
849
846 #[timed]
850 #[timed]
847 pub fn build_response<'a>(
851 pub fn build_response<'a>(
848 results: impl IntoIterator<Item = DispatchedPath<'a>>,
852 results: impl IntoIterator<Item = DispatchedPath<'a>>,
849 traversed: Vec<HgPathBuf>,
853 traversed: Vec<HgPathBuf>,
850 ) -> (Vec<HgPathCow<'a>>, DirstateStatus<'a>) {
854 ) -> DirstateStatus<'a> {
851 let mut lookup = vec![];
855 let mut unsure = vec![];
852 let mut modified = vec![];
856 let mut modified = vec![];
853 let mut added = vec![];
857 let mut added = vec![];
854 let mut removed = vec![];
858 let mut removed = vec![];
855 let mut deleted = vec![];
859 let mut deleted = vec![];
856 let mut clean = vec![];
860 let mut clean = vec![];
857 let mut ignored = vec![];
861 let mut ignored = vec![];
858 let mut unknown = vec![];
862 let mut unknown = vec![];
859 let mut bad = vec![];
863 let mut bad = vec![];
860
864
861 for (filename, dispatch) in results.into_iter() {
865 for (filename, dispatch) in results.into_iter() {
862 match dispatch {
866 match dispatch {
863 Dispatch::Unknown => unknown.push(filename),
867 Dispatch::Unknown => unknown.push(filename),
864 Dispatch::Unsure => lookup.push(filename),
868 Dispatch::Unsure => unsure.push(filename),
865 Dispatch::Modified => modified.push(filename),
869 Dispatch::Modified => modified.push(filename),
866 Dispatch::Added => added.push(filename),
870 Dispatch::Added => added.push(filename),
867 Dispatch::Removed => removed.push(filename),
871 Dispatch::Removed => removed.push(filename),
868 Dispatch::Deleted => deleted.push(filename),
872 Dispatch::Deleted => deleted.push(filename),
869 Dispatch::Clean => clean.push(filename),
873 Dispatch::Clean => clean.push(filename),
870 Dispatch::Ignored => ignored.push(filename),
874 Dispatch::Ignored => ignored.push(filename),
871 Dispatch::None => {}
875 Dispatch::None => {}
872 Dispatch::Bad(reason) => bad.push((filename, reason)),
876 Dispatch::Bad(reason) => bad.push((filename, reason)),
873 Dispatch::Directory { .. } => {}
877 Dispatch::Directory { .. } => {}
874 }
878 }
875 }
879 }
876
880
877 (
881 DirstateStatus {
878 lookup,
882 modified,
879 DirstateStatus {
883 added,
880 modified,
884 removed,
881 added,
885 deleted,
882 removed,
886 clean,
883 deleted,
887 ignored,
884 clean,
888 unknown,
885 ignored,
889 bad,
886 unknown,
890 unsure,
887 bad,
891 traversed,
888 traversed,
892 }
889 },
890 )
891 }
893 }
892
894
893 /// Get the status of files in the working directory.
895 /// Get the status of files in the working directory.
894 ///
896 ///
895 /// This is the current entry-point for `hg-core` and is realistically unusable
897 /// This is the current entry-point for `hg-core` and is realistically unusable
896 /// outside of a Python context because its arguments need to provide a lot of
898 /// outside of a Python context because its arguments need to provide a lot of
897 /// information that will not be necessary in the future.
899 /// information that will not be necessary in the future.
898 #[timed]
900 #[timed]
899 pub fn status<'a>(
901 pub fn status<'a>(
900 dmap: &'a DirstateMap,
902 dmap: &'a DirstateMap,
901 matcher: &'a (dyn Matcher + Sync),
903 matcher: &'a (dyn Matcher + Sync),
902 root_dir: PathBuf,
904 root_dir: PathBuf,
903 ignore_files: Vec<PathBuf>,
905 ignore_files: Vec<PathBuf>,
904 options: StatusOptions,
906 options: StatusOptions,
905 ) -> StatusResult<(
907 ) -> StatusResult<(DirstateStatus<'a>, Vec<PatternFileWarning>)> {
906 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
907 Vec<PatternFileWarning>,
908 )> {
909 let (status, warnings) =
908 let (status, warnings) =
910 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
909 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
911
910
912 Ok((status.run()?, warnings))
911 Ok((status.run()?, warnings))
913 }
912 }
@@ -1,658 +1,652 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use std::path::PathBuf;
2 use std::path::PathBuf;
3 use std::{collections::BTreeMap, convert::TryInto};
3 use std::{collections::BTreeMap, convert::TryInto};
4
4
5 use super::path_with_basename::WithBasename;
5 use super::path_with_basename::WithBasename;
6 use crate::dirstate::parsers::clear_ambiguous_mtime;
6 use crate::dirstate::parsers::clear_ambiguous_mtime;
7 use crate::dirstate::parsers::pack_entry;
7 use crate::dirstate::parsers::pack_entry;
8 use crate::dirstate::parsers::packed_entry_size;
8 use crate::dirstate::parsers::packed_entry_size;
9 use crate::dirstate::parsers::parse_dirstate_entries;
9 use crate::dirstate::parsers::parse_dirstate_entries;
10 use crate::dirstate::parsers::parse_dirstate_parents;
10 use crate::dirstate::parsers::parse_dirstate_parents;
11 use crate::dirstate::parsers::Timestamp;
11 use crate::dirstate::parsers::Timestamp;
12 use crate::matchers::Matcher;
12 use crate::matchers::Matcher;
13 use crate::revlog::node::NULL_NODE;
13 use crate::revlog::node::NULL_NODE;
14 use crate::utils::hg_path::{HgPath, HgPathBuf};
14 use crate::utils::hg_path::{HgPath, HgPathBuf};
15 use crate::CopyMapIter;
15 use crate::CopyMapIter;
16 use crate::DirstateEntry;
16 use crate::DirstateEntry;
17 use crate::DirstateError;
17 use crate::DirstateError;
18 use crate::DirstateMapError;
18 use crate::DirstateMapError;
19 use crate::DirstateParents;
19 use crate::DirstateParents;
20 use crate::DirstateStatus;
20 use crate::DirstateStatus;
21 use crate::EntryState;
21 use crate::EntryState;
22 use crate::HgPathCow;
23 use crate::PatternFileWarning;
22 use crate::PatternFileWarning;
24 use crate::StateMapIter;
23 use crate::StateMapIter;
25 use crate::StatusError;
24 use crate::StatusError;
26 use crate::StatusOptions;
25 use crate::StatusOptions;
27
26
28 pub struct DirstateMap {
27 pub struct DirstateMap {
29 parents: Option<DirstateParents>,
28 parents: Option<DirstateParents>,
30 dirty_parents: bool,
29 dirty_parents: bool,
31 root: ChildNodes,
30 root: ChildNodes,
32
31
33 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
32 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
34 nodes_with_entry_count: usize,
33 nodes_with_entry_count: usize,
35
34
36 /// Number of nodes anywhere in the tree that have
35 /// Number of nodes anywhere in the tree that have
37 /// `.copy_source.is_some()`.
36 /// `.copy_source.is_some()`.
38 nodes_with_copy_source_count: usize,
37 nodes_with_copy_source_count: usize,
39 }
38 }
40
39
41 /// Using a plain `HgPathBuf` of the full path from the repository root as a
40 /// Using a plain `HgPathBuf` of the full path from the repository root as a
42 /// map key would also work: all paths in a given map have the same parent
41 /// map key would also work: all paths in a given map have the same parent
43 /// path, so comparing full paths gives the same result as comparing base
42 /// path, so comparing full paths gives the same result as comparing base
44 /// names. However `BTreeMap` would waste time always re-comparing the same
43 /// names. However `BTreeMap` would waste time always re-comparing the same
45 /// string prefix.
44 /// string prefix.
46 type ChildNodes = BTreeMap<WithBasename<HgPathBuf>, Node>;
45 type ChildNodes = BTreeMap<WithBasename<HgPathBuf>, Node>;
47
46
48 /// Represents a file or a directory
47 /// Represents a file or a directory
49 #[derive(Default)]
48 #[derive(Default)]
50 struct Node {
49 struct Node {
51 /// `None` for directories
50 /// `None` for directories
52 entry: Option<DirstateEntry>,
51 entry: Option<DirstateEntry>,
53
52
54 copy_source: Option<HgPathBuf>,
53 copy_source: Option<HgPathBuf>,
55
54
56 children: ChildNodes,
55 children: ChildNodes,
57
56
58 /// How many (non-inclusive) descendants of this node are tracked files
57 /// How many (non-inclusive) descendants of this node are tracked files
59 tracked_descendants_count: usize,
58 tracked_descendants_count: usize,
60 }
59 }
61
60
62 impl Node {
61 impl Node {
63 /// Whether this node has a `DirstateEntry` with `.state.is_tracked()`
62 /// Whether this node has a `DirstateEntry` with `.state.is_tracked()`
64 fn is_tracked_file(&self) -> bool {
63 fn is_tracked_file(&self) -> bool {
65 if let Some(entry) = &self.entry {
64 if let Some(entry) = &self.entry {
66 entry.state.is_tracked()
65 entry.state.is_tracked()
67 } else {
66 } else {
68 false
67 false
69 }
68 }
70 }
69 }
71 }
70 }
72
71
73 /// `(full_path, entry, copy_source)`
72 /// `(full_path, entry, copy_source)`
74 type NodeDataMut<'a> = (
73 type NodeDataMut<'a> = (
75 &'a WithBasename<HgPathBuf>,
74 &'a WithBasename<HgPathBuf>,
76 &'a mut Option<DirstateEntry>,
75 &'a mut Option<DirstateEntry>,
77 &'a mut Option<HgPathBuf>,
76 &'a mut Option<HgPathBuf>,
78 );
77 );
79
78
80 impl DirstateMap {
79 impl DirstateMap {
81 pub fn new() -> Self {
80 pub fn new() -> Self {
82 Self {
81 Self {
83 parents: None,
82 parents: None,
84 dirty_parents: false,
83 dirty_parents: false,
85 root: ChildNodes::new(),
84 root: ChildNodes::new(),
86 nodes_with_entry_count: 0,
85 nodes_with_entry_count: 0,
87 nodes_with_copy_source_count: 0,
86 nodes_with_copy_source_count: 0,
88 }
87 }
89 }
88 }
90
89
91 fn get_node(&self, path: &HgPath) -> Option<&Node> {
90 fn get_node(&self, path: &HgPath) -> Option<&Node> {
92 let mut children = &self.root;
91 let mut children = &self.root;
93 let mut components = path.components();
92 let mut components = path.components();
94 let mut component =
93 let mut component =
95 components.next().expect("expected at least one components");
94 components.next().expect("expected at least one components");
96 loop {
95 loop {
97 let child = children.get(component)?;
96 let child = children.get(component)?;
98 if let Some(next_component) = components.next() {
97 if let Some(next_component) = components.next() {
99 component = next_component;
98 component = next_component;
100 children = &child.children;
99 children = &child.children;
101 } else {
100 } else {
102 return Some(child);
101 return Some(child);
103 }
102 }
104 }
103 }
105 }
104 }
106
105
107 /// Returns a mutable reference to the node at `path` if it exists
106 /// Returns a mutable reference to the node at `path` if it exists
108 ///
107 ///
109 /// This takes `root` instead of `&mut self` so that callers can mutate
108 /// This takes `root` instead of `&mut self` so that callers can mutate
110 /// other fields while the returned borrow is still valid
109 /// other fields while the returned borrow is still valid
111 fn get_node_mut<'tree>(
110 fn get_node_mut<'tree>(
112 root: &'tree mut ChildNodes,
111 root: &'tree mut ChildNodes,
113 path: &HgPath,
112 path: &HgPath,
114 ) -> Option<&'tree mut Node> {
113 ) -> Option<&'tree mut Node> {
115 Self::each_and_get(root, path, |_| {})
114 Self::each_and_get(root, path, |_| {})
116 }
115 }
117
116
118 /// Call `each` for each ancestor node of the one at `path` (not including
117 /// Call `each` for each ancestor node of the one at `path` (not including
119 /// that node itself), starting from nearest the root.
118 /// that node itself), starting from nearest the root.
120 ///
119 ///
121 /// Panics (possibly after some calls to `each`) if there is no node at
120 /// Panics (possibly after some calls to `each`) if there is no node at
122 /// `path`.
121 /// `path`.
123 fn for_each_ancestor_node<'tree>(
122 fn for_each_ancestor_node<'tree>(
124 &mut self,
123 &mut self,
125 path: &HgPath,
124 path: &HgPath,
126 each: impl FnMut(&mut Node),
125 each: impl FnMut(&mut Node),
127 ) {
126 ) {
128 let parent = path.parent();
127 let parent = path.parent();
129 if !parent.is_empty() {
128 if !parent.is_empty() {
130 Self::each_and_get(&mut self.root, parent, each)
129 Self::each_and_get(&mut self.root, parent, each)
131 .expect("missing dirstate node");
130 .expect("missing dirstate node");
132 }
131 }
133 }
132 }
134
133
135 /// Common implementation detail of `get_node_mut` and
134 /// Common implementation detail of `get_node_mut` and
136 /// `for_each_ancestor_node`
135 /// `for_each_ancestor_node`
137 fn each_and_get<'tree>(
136 fn each_and_get<'tree>(
138 root: &'tree mut ChildNodes,
137 root: &'tree mut ChildNodes,
139 path: &HgPath,
138 path: &HgPath,
140 mut each: impl FnMut(&mut Node),
139 mut each: impl FnMut(&mut Node),
141 ) -> Option<&'tree mut Node> {
140 ) -> Option<&'tree mut Node> {
142 let mut children = root;
141 let mut children = root;
143 let mut components = path.components();
142 let mut components = path.components();
144 let mut component =
143 let mut component =
145 components.next().expect("expected at least one components");
144 components.next().expect("expected at least one components");
146 loop {
145 loop {
147 let child = children.get_mut(component)?;
146 let child = children.get_mut(component)?;
148 each(child);
147 each(child);
149 if let Some(next_component) = components.next() {
148 if let Some(next_component) = components.next() {
150 component = next_component;
149 component = next_component;
151 children = &mut child.children;
150 children = &mut child.children;
152 } else {
151 } else {
153 return Some(child);
152 return Some(child);
154 }
153 }
155 }
154 }
156 }
155 }
157
156
158 fn get_or_insert_node<'tree>(
157 fn get_or_insert_node<'tree>(
159 root: &'tree mut ChildNodes,
158 root: &'tree mut ChildNodes,
160 path: &HgPath,
159 path: &HgPath,
161 ) -> &'tree mut Node {
160 ) -> &'tree mut Node {
162 let mut child_nodes = root;
161 let mut child_nodes = root;
163 let mut inclusive_ancestor_paths =
162 let mut inclusive_ancestor_paths =
164 WithBasename::inclusive_ancestors_of(path);
163 WithBasename::inclusive_ancestors_of(path);
165 let mut ancestor_path = inclusive_ancestor_paths
164 let mut ancestor_path = inclusive_ancestor_paths
166 .next()
165 .next()
167 .expect("expected at least one inclusive ancestor");
166 .expect("expected at least one inclusive ancestor");
168 loop {
167 loop {
169 // TODO: can we avoid double lookup in all cases without allocating
168 // TODO: can we avoid double lookup in all cases without allocating
170 // an owned key in cases where the map already contains that key?
169 // an owned key in cases where the map already contains that key?
171 let child_node =
170 let child_node =
172 if child_nodes.contains_key(ancestor_path.base_name()) {
171 if child_nodes.contains_key(ancestor_path.base_name()) {
173 child_nodes.get_mut(ancestor_path.base_name()).unwrap()
172 child_nodes.get_mut(ancestor_path.base_name()).unwrap()
174 } else {
173 } else {
175 // This is always a vacant entry, using `.entry()` lets us
174 // This is always a vacant entry, using `.entry()` lets us
176 // return a `&mut Node` of the newly-inserted node without
175 // return a `&mut Node` of the newly-inserted node without
177 // yet another lookup. `BTreeMap::insert` doesn’t do this.
176 // yet another lookup. `BTreeMap::insert` doesn’t do this.
178 child_nodes.entry(ancestor_path.to_owned()).or_default()
177 child_nodes.entry(ancestor_path.to_owned()).or_default()
179 };
178 };
180 if let Some(next) = inclusive_ancestor_paths.next() {
179 if let Some(next) = inclusive_ancestor_paths.next() {
181 ancestor_path = next;
180 ancestor_path = next;
182 child_nodes = &mut child_node.children;
181 child_nodes = &mut child_node.children;
183 } else {
182 } else {
184 return child_node;
183 return child_node;
185 }
184 }
186 }
185 }
187 }
186 }
188
187
189 /// The meaning of `new_copy_source` is:
188 /// The meaning of `new_copy_source` is:
190 ///
189 ///
191 /// * `Some(Some(x))`: set `Node::copy_source` to `Some(x)`
190 /// * `Some(Some(x))`: set `Node::copy_source` to `Some(x)`
192 /// * `Some(None)`: set `Node::copy_source` to `None`
191 /// * `Some(None)`: set `Node::copy_source` to `None`
193 /// * `None`: leave `Node::copy_source` unchanged
192 /// * `None`: leave `Node::copy_source` unchanged
194 fn add_file_node(
193 fn add_file_node(
195 &mut self,
194 &mut self,
196 path: &HgPath,
195 path: &HgPath,
197 new_entry: DirstateEntry,
196 new_entry: DirstateEntry,
198 new_copy_source: Option<Option<HgPathBuf>>,
197 new_copy_source: Option<Option<HgPathBuf>>,
199 ) {
198 ) {
200 let node = Self::get_or_insert_node(&mut self.root, path);
199 let node = Self::get_or_insert_node(&mut self.root, path);
201 if node.entry.is_none() {
200 if node.entry.is_none() {
202 self.nodes_with_entry_count += 1
201 self.nodes_with_entry_count += 1
203 }
202 }
204 if let Some(source) = &new_copy_source {
203 if let Some(source) = &new_copy_source {
205 if node.copy_source.is_none() && source.is_some() {
204 if node.copy_source.is_none() && source.is_some() {
206 self.nodes_with_copy_source_count += 1
205 self.nodes_with_copy_source_count += 1
207 }
206 }
208 if node.copy_source.is_some() && source.is_none() {
207 if node.copy_source.is_some() && source.is_none() {
209 self.nodes_with_copy_source_count -= 1
208 self.nodes_with_copy_source_count -= 1
210 }
209 }
211 }
210 }
212 let tracked_count_increment =
211 let tracked_count_increment =
213 match (node.is_tracked_file(), new_entry.state.is_tracked()) {
212 match (node.is_tracked_file(), new_entry.state.is_tracked()) {
214 (false, true) => 1,
213 (false, true) => 1,
215 (true, false) => -1,
214 (true, false) => -1,
216 _ => 0,
215 _ => 0,
217 };
216 };
218
217
219 node.entry = Some(new_entry);
218 node.entry = Some(new_entry);
220 if let Some(source) = new_copy_source {
219 if let Some(source) = new_copy_source {
221 node.copy_source = source
220 node.copy_source = source
222 }
221 }
223 // Borrow of `self.root` through `node` ends here
222 // Borrow of `self.root` through `node` ends here
224
223
225 match tracked_count_increment {
224 match tracked_count_increment {
226 1 => self.for_each_ancestor_node(path, |node| {
225 1 => self.for_each_ancestor_node(path, |node| {
227 node.tracked_descendants_count += 1
226 node.tracked_descendants_count += 1
228 }),
227 }),
229 // We can’t use `+= -1` because the counter is unsigned
228 // We can’t use `+= -1` because the counter is unsigned
230 -1 => self.for_each_ancestor_node(path, |node| {
229 -1 => self.for_each_ancestor_node(path, |node| {
231 node.tracked_descendants_count -= 1
230 node.tracked_descendants_count -= 1
232 }),
231 }),
233 _ => {}
232 _ => {}
234 }
233 }
235 }
234 }
236
235
237 fn iter_nodes<'a>(
236 fn iter_nodes<'a>(
238 &'a self,
237 &'a self,
239 ) -> impl Iterator<Item = (&'a WithBasename<HgPathBuf>, &'a Node)> + 'a
238 ) -> impl Iterator<Item = (&'a WithBasename<HgPathBuf>, &'a Node)> + 'a
240 {
239 {
241 // Depth first tree traversal.
240 // Depth first tree traversal.
242 //
241 //
243 // If we could afford internal iteration and recursion,
242 // If we could afford internal iteration and recursion,
244 // this would look like:
243 // this would look like:
245 //
244 //
246 // ```
245 // ```
247 // fn traverse_children(
246 // fn traverse_children(
248 // children: &ChildNodes,
247 // children: &ChildNodes,
249 // each: &mut impl FnMut(&Node),
248 // each: &mut impl FnMut(&Node),
250 // ) {
249 // ) {
251 // for child in children.values() {
250 // for child in children.values() {
252 // traverse_children(&child.children, each);
251 // traverse_children(&child.children, each);
253 // each(child);
252 // each(child);
254 // }
253 // }
255 // }
254 // }
256 // ```
255 // ```
257 //
256 //
258 // However we want an external iterator and therefore can’t use the
257 // However we want an external iterator and therefore can’t use the
259 // call stack. Use an explicit stack instead:
258 // call stack. Use an explicit stack instead:
260 let mut stack = Vec::new();
259 let mut stack = Vec::new();
261 let mut iter = self.root.iter();
260 let mut iter = self.root.iter();
262 std::iter::from_fn(move || {
261 std::iter::from_fn(move || {
263 while let Some((key, child_node)) = iter.next() {
262 while let Some((key, child_node)) = iter.next() {
264 // Pseudo-recursion
263 // Pseudo-recursion
265 let new_iter = child_node.children.iter();
264 let new_iter = child_node.children.iter();
266 let old_iter = std::mem::replace(&mut iter, new_iter);
265 let old_iter = std::mem::replace(&mut iter, new_iter);
267 stack.push((key, child_node, old_iter));
266 stack.push((key, child_node, old_iter));
268 }
267 }
269 // Found the end of a `children.iter()` iterator.
268 // Found the end of a `children.iter()` iterator.
270 if let Some((key, child_node, next_iter)) = stack.pop() {
269 if let Some((key, child_node, next_iter)) = stack.pop() {
271 // "Return" from pseudo-recursion by restoring state from the
270 // "Return" from pseudo-recursion by restoring state from the
272 // explicit stack
271 // explicit stack
273 iter = next_iter;
272 iter = next_iter;
274
273
275 Some((key, child_node))
274 Some((key, child_node))
276 } else {
275 } else {
277 // Reached the bottom of the stack, we’re done
276 // Reached the bottom of the stack, we’re done
278 None
277 None
279 }
278 }
280 })
279 })
281 }
280 }
282
281
283 /// Mutable iterator for the `(entry, copy source)` of each node.
282 /// Mutable iterator for the `(entry, copy source)` of each node.
284 ///
283 ///
285 /// It would not be safe to yield mutable references to nodes themeselves
284 /// It would not be safe to yield mutable references to nodes themeselves
286 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
285 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
287 /// reachable from their ancestor nodes, potentially creating multiple
286 /// reachable from their ancestor nodes, potentially creating multiple
288 /// `&mut` references to a given node.
287 /// `&mut` references to a given node.
289 fn iter_node_data_mut<'a>(
288 fn iter_node_data_mut<'a>(
290 &'a mut self,
289 &'a mut self,
291 ) -> impl Iterator<Item = NodeDataMut<'a>> + 'a {
290 ) -> impl Iterator<Item = NodeDataMut<'a>> + 'a {
292 // Explict stack for pseudo-recursion, see `iter_nodes` above.
291 // Explict stack for pseudo-recursion, see `iter_nodes` above.
293 let mut stack = Vec::new();
292 let mut stack = Vec::new();
294 let mut iter = self.root.iter_mut();
293 let mut iter = self.root.iter_mut();
295 std::iter::from_fn(move || {
294 std::iter::from_fn(move || {
296 while let Some((key, child_node)) = iter.next() {
295 while let Some((key, child_node)) = iter.next() {
297 // Pseudo-recursion
296 // Pseudo-recursion
298 let data =
297 let data =
299 (key, &mut child_node.entry, &mut child_node.copy_source);
298 (key, &mut child_node.entry, &mut child_node.copy_source);
300 let new_iter = child_node.children.iter_mut();
299 let new_iter = child_node.children.iter_mut();
301 let old_iter = std::mem::replace(&mut iter, new_iter);
300 let old_iter = std::mem::replace(&mut iter, new_iter);
302 stack.push((data, old_iter));
301 stack.push((data, old_iter));
303 }
302 }
304 // Found the end of a `children.values_mut()` iterator.
303 // Found the end of a `children.values_mut()` iterator.
305 if let Some((data, next_iter)) = stack.pop() {
304 if let Some((data, next_iter)) = stack.pop() {
306 // "Return" from pseudo-recursion by restoring state from the
305 // "Return" from pseudo-recursion by restoring state from the
307 // explicit stack
306 // explicit stack
308 iter = next_iter;
307 iter = next_iter;
309
308
310 Some(data)
309 Some(data)
311 } else {
310 } else {
312 // Reached the bottom of the stack, we’re done
311 // Reached the bottom of the stack, we’re done
313 None
312 None
314 }
313 }
315 })
314 })
316 }
315 }
317 }
316 }
318
317
319 impl super::dispatch::DirstateMapMethods for DirstateMap {
318 impl super::dispatch::DirstateMapMethods for DirstateMap {
320 fn clear(&mut self) {
319 fn clear(&mut self) {
321 self.set_parents(&DirstateParents {
320 self.set_parents(&DirstateParents {
322 p1: NULL_NODE,
321 p1: NULL_NODE,
323 p2: NULL_NODE,
322 p2: NULL_NODE,
324 });
323 });
325 self.root.clear();
324 self.root.clear();
326 self.nodes_with_entry_count = 0;
325 self.nodes_with_entry_count = 0;
327 self.nodes_with_copy_source_count = 0;
326 self.nodes_with_copy_source_count = 0;
328 }
327 }
329
328
330 fn add_file(
329 fn add_file(
331 &mut self,
330 &mut self,
332 filename: &HgPath,
331 filename: &HgPath,
333 _old_state: EntryState,
332 _old_state: EntryState,
334 entry: DirstateEntry,
333 entry: DirstateEntry,
335 ) -> Result<(), DirstateMapError> {
334 ) -> Result<(), DirstateMapError> {
336 self.add_file_node(filename, entry, None);
335 self.add_file_node(filename, entry, None);
337 Ok(())
336 Ok(())
338 }
337 }
339
338
340 fn remove_file(
339 fn remove_file(
341 &mut self,
340 &mut self,
342 filename: &HgPath,
341 filename: &HgPath,
343 _old_state: EntryState,
342 _old_state: EntryState,
344 size: i32,
343 size: i32,
345 ) -> Result<(), DirstateMapError> {
344 ) -> Result<(), DirstateMapError> {
346 let entry = DirstateEntry {
345 let entry = DirstateEntry {
347 state: EntryState::Removed,
346 state: EntryState::Removed,
348 mode: 0,
347 mode: 0,
349 size,
348 size,
350 mtime: 0,
349 mtime: 0,
351 };
350 };
352 self.add_file_node(filename, entry, None);
351 self.add_file_node(filename, entry, None);
353 Ok(())
352 Ok(())
354 }
353 }
355
354
356 fn drop_file(
355 fn drop_file(
357 &mut self,
356 &mut self,
358 filename: &HgPath,
357 filename: &HgPath,
359 _old_state: EntryState,
358 _old_state: EntryState,
360 ) -> Result<bool, DirstateMapError> {
359 ) -> Result<bool, DirstateMapError> {
361 if let Some(node) = Self::get_node_mut(&mut self.root, filename) {
360 if let Some(node) = Self::get_node_mut(&mut self.root, filename) {
362 let was_tracked = node.is_tracked_file();
361 let was_tracked = node.is_tracked_file();
363 let had_entry = node.entry.is_some();
362 let had_entry = node.entry.is_some();
364 let had_copy_source = node.copy_source.is_some();
363 let had_copy_source = node.copy_source.is_some();
365
364
366 // TODO: this leaves in the tree a "non-file" node. Should we
365 // TODO: this leaves in the tree a "non-file" node. Should we
367 // remove the node instead, together with ancestor nodes for
366 // remove the node instead, together with ancestor nodes for
368 // directories that become empty?
367 // directories that become empty?
369 node.entry = None;
368 node.entry = None;
370 node.copy_source = None;
369 node.copy_source = None;
371
370
372 if had_entry {
371 if had_entry {
373 self.nodes_with_entry_count -= 1
372 self.nodes_with_entry_count -= 1
374 }
373 }
375 if had_copy_source {
374 if had_copy_source {
376 self.nodes_with_copy_source_count -= 1
375 self.nodes_with_copy_source_count -= 1
377 }
376 }
378 if was_tracked {
377 if was_tracked {
379 self.for_each_ancestor_node(filename, |node| {
378 self.for_each_ancestor_node(filename, |node| {
380 node.tracked_descendants_count -= 1
379 node.tracked_descendants_count -= 1
381 })
380 })
382 }
381 }
383 Ok(had_entry)
382 Ok(had_entry)
384 } else {
383 } else {
385 Ok(false)
384 Ok(false)
386 }
385 }
387 }
386 }
388
387
389 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
388 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
390 for filename in filenames {
389 for filename in filenames {
391 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
390 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
392 if let Some(entry) = node.entry.as_mut() {
391 if let Some(entry) = node.entry.as_mut() {
393 clear_ambiguous_mtime(entry, now);
392 clear_ambiguous_mtime(entry, now);
394 }
393 }
395 }
394 }
396 }
395 }
397 }
396 }
398
397
399 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
398 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
400 self.get_node(key)
399 self.get_node(key)
401 .and_then(|node| node.entry.as_ref())
400 .and_then(|node| node.entry.as_ref())
402 .map_or(false, DirstateEntry::is_non_normal)
401 .map_or(false, DirstateEntry::is_non_normal)
403 }
402 }
404
403
405 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
404 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
406 // Do nothing, this `DirstateMap` does not have a separate "non normal
405 // Do nothing, this `DirstateMap` does not have a separate "non normal
407 // entries" set that need to be kept up to date
406 // entries" set that need to be kept up to date
408 }
407 }
409
408
410 fn non_normal_or_other_parent_paths(
409 fn non_normal_or_other_parent_paths(
411 &mut self,
410 &mut self,
412 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
411 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
413 Box::new(self.iter_nodes().filter_map(|(path, node)| {
412 Box::new(self.iter_nodes().filter_map(|(path, node)| {
414 node.entry
413 node.entry
415 .as_ref()
414 .as_ref()
416 .filter(|entry| {
415 .filter(|entry| {
417 entry.is_non_normal() || entry.is_from_other_parent()
416 entry.is_non_normal() || entry.is_from_other_parent()
418 })
417 })
419 .map(|_| path.full_path())
418 .map(|_| path.full_path())
420 }))
419 }))
421 }
420 }
422
421
423 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
422 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
424 // Do nothing, this `DirstateMap` does not have a separate "non normal
423 // Do nothing, this `DirstateMap` does not have a separate "non normal
425 // entries" and "from other parent" sets that need to be recomputed
424 // entries" and "from other parent" sets that need to be recomputed
426 }
425 }
427
426
428 fn iter_non_normal_paths(
427 fn iter_non_normal_paths(
429 &mut self,
428 &mut self,
430 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
429 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
431 self.iter_non_normal_paths_panic()
430 self.iter_non_normal_paths_panic()
432 }
431 }
433
432
434 fn iter_non_normal_paths_panic(
433 fn iter_non_normal_paths_panic(
435 &self,
434 &self,
436 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
435 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
437 Box::new(self.iter_nodes().filter_map(|(path, node)| {
436 Box::new(self.iter_nodes().filter_map(|(path, node)| {
438 node.entry
437 node.entry
439 .as_ref()
438 .as_ref()
440 .filter(|entry| entry.is_non_normal())
439 .filter(|entry| entry.is_non_normal())
441 .map(|_| path.full_path())
440 .map(|_| path.full_path())
442 }))
441 }))
443 }
442 }
444
443
445 fn iter_other_parent_paths(
444 fn iter_other_parent_paths(
446 &mut self,
445 &mut self,
447 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
446 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
448 Box::new(self.iter_nodes().filter_map(|(path, node)| {
447 Box::new(self.iter_nodes().filter_map(|(path, node)| {
449 node.entry
448 node.entry
450 .as_ref()
449 .as_ref()
451 .filter(|entry| entry.is_from_other_parent())
450 .filter(|entry| entry.is_from_other_parent())
452 .map(|_| path.full_path())
451 .map(|_| path.full_path())
453 }))
452 }))
454 }
453 }
455
454
456 fn has_tracked_dir(
455 fn has_tracked_dir(
457 &mut self,
456 &mut self,
458 directory: &HgPath,
457 directory: &HgPath,
459 ) -> Result<bool, DirstateMapError> {
458 ) -> Result<bool, DirstateMapError> {
460 if let Some(node) = self.get_node(directory) {
459 if let Some(node) = self.get_node(directory) {
461 // A node without a `DirstateEntry` was created to hold child
460 // A node without a `DirstateEntry` was created to hold child
462 // nodes, and is therefore a directory.
461 // nodes, and is therefore a directory.
463 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
462 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
464 } else {
463 } else {
465 Ok(false)
464 Ok(false)
466 }
465 }
467 }
466 }
468
467
469 fn has_dir(
468 fn has_dir(
470 &mut self,
469 &mut self,
471 directory: &HgPath,
470 directory: &HgPath,
472 ) -> Result<bool, DirstateMapError> {
471 ) -> Result<bool, DirstateMapError> {
473 if let Some(node) = self.get_node(directory) {
472 if let Some(node) = self.get_node(directory) {
474 // A node without a `DirstateEntry` was created to hold child
473 // A node without a `DirstateEntry` was created to hold child
475 // nodes, and is therefore a directory.
474 // nodes, and is therefore a directory.
476 Ok(node.entry.is_none())
475 Ok(node.entry.is_none())
477 } else {
476 } else {
478 Ok(false)
477 Ok(false)
479 }
478 }
480 }
479 }
481
480
482 fn parents(
481 fn parents(
483 &mut self,
482 &mut self,
484 file_contents: &[u8],
483 file_contents: &[u8],
485 ) -> Result<&DirstateParents, DirstateError> {
484 ) -> Result<&DirstateParents, DirstateError> {
486 if self.parents.is_none() {
485 if self.parents.is_none() {
487 let parents = if !file_contents.is_empty() {
486 let parents = if !file_contents.is_empty() {
488 parse_dirstate_parents(file_contents)?.clone()
487 parse_dirstate_parents(file_contents)?.clone()
489 } else {
488 } else {
490 DirstateParents {
489 DirstateParents {
491 p1: NULL_NODE,
490 p1: NULL_NODE,
492 p2: NULL_NODE,
491 p2: NULL_NODE,
493 }
492 }
494 };
493 };
495 self.parents = Some(parents);
494 self.parents = Some(parents);
496 }
495 }
497 Ok(self.parents.as_ref().unwrap())
496 Ok(self.parents.as_ref().unwrap())
498 }
497 }
499
498
500 fn set_parents(&mut self, parents: &DirstateParents) {
499 fn set_parents(&mut self, parents: &DirstateParents) {
501 self.parents = Some(parents.clone());
500 self.parents = Some(parents.clone());
502 self.dirty_parents = true;
501 self.dirty_parents = true;
503 }
502 }
504
503
505 fn read<'a>(
504 fn read<'a>(
506 &mut self,
505 &mut self,
507 file_contents: &'a [u8],
506 file_contents: &'a [u8],
508 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
507 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
509 if file_contents.is_empty() {
508 if file_contents.is_empty() {
510 return Ok(None);
509 return Ok(None);
511 }
510 }
512
511
513 let parents = parse_dirstate_entries(
512 let parents = parse_dirstate_entries(
514 file_contents,
513 file_contents,
515 |path, entry, copy_source| {
514 |path, entry, copy_source| {
516 self.add_file_node(
515 self.add_file_node(
517 path,
516 path,
518 *entry,
517 *entry,
519 Some(copy_source.map(HgPath::to_owned)),
518 Some(copy_source.map(HgPath::to_owned)),
520 )
519 )
521 },
520 },
522 )?;
521 )?;
523
522
524 if !self.dirty_parents {
523 if !self.dirty_parents {
525 self.set_parents(parents);
524 self.set_parents(parents);
526 }
525 }
527
526
528 Ok(Some(parents))
527 Ok(Some(parents))
529 }
528 }
530
529
531 fn pack(
530 fn pack(
532 &mut self,
531 &mut self,
533 parents: DirstateParents,
532 parents: DirstateParents,
534 now: Timestamp,
533 now: Timestamp,
535 ) -> Result<Vec<u8>, DirstateError> {
534 ) -> Result<Vec<u8>, DirstateError> {
536 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
535 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
537 // reallocations
536 // reallocations
538 let mut size = parents.as_bytes().len();
537 let mut size = parents.as_bytes().len();
539 for (path, node) in self.iter_nodes() {
538 for (path, node) in self.iter_nodes() {
540 if node.entry.is_some() {
539 if node.entry.is_some() {
541 size += packed_entry_size(
540 size += packed_entry_size(
542 path.full_path(),
541 path.full_path(),
543 node.copy_source.as_ref(),
542 node.copy_source.as_ref(),
544 )
543 )
545 }
544 }
546 }
545 }
547
546
548 let mut packed = Vec::with_capacity(size);
547 let mut packed = Vec::with_capacity(size);
549 packed.extend(parents.as_bytes());
548 packed.extend(parents.as_bytes());
550
549
551 let now: i32 = now.0.try_into().expect("time overflow");
550 let now: i32 = now.0.try_into().expect("time overflow");
552 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
551 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
553 if let Some(entry) = opt_entry {
552 if let Some(entry) = opt_entry {
554 clear_ambiguous_mtime(entry, now);
553 clear_ambiguous_mtime(entry, now);
555 pack_entry(
554 pack_entry(
556 path.full_path(),
555 path.full_path(),
557 entry,
556 entry,
558 copy_source.as_ref(),
557 copy_source.as_ref(),
559 &mut packed,
558 &mut packed,
560 );
559 );
561 }
560 }
562 }
561 }
563 self.dirty_parents = false;
562 self.dirty_parents = false;
564 Ok(packed)
563 Ok(packed)
565 }
564 }
566
565
567 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
566 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
568 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
567 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
569 // needs to be recomputed
568 // needs to be recomputed
570 Ok(())
569 Ok(())
571 }
570 }
572
571
573 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
572 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
574 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
573 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
575 // to be recomputed
574 // to be recomputed
576 Ok(())
575 Ok(())
577 }
576 }
578
577
579 fn status<'a>(
578 fn status<'a>(
580 &'a self,
579 &'a self,
581 _matcher: &'a (dyn Matcher + Sync),
580 _matcher: &'a (dyn Matcher + Sync),
582 _root_dir: PathBuf,
581 _root_dir: PathBuf,
583 _ignore_files: Vec<PathBuf>,
582 _ignore_files: Vec<PathBuf>,
584 _options: StatusOptions,
583 _options: StatusOptions,
585 ) -> Result<
584 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
586 (
585 {
587 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
588 Vec<PatternFileWarning>,
589 ),
590 StatusError,
591 > {
592 todo!()
586 todo!()
593 }
587 }
594
588
595 fn copy_map_len(&self) -> usize {
589 fn copy_map_len(&self) -> usize {
596 self.nodes_with_copy_source_count
590 self.nodes_with_copy_source_count
597 }
591 }
598
592
599 fn copy_map_iter(&self) -> CopyMapIter<'_> {
593 fn copy_map_iter(&self) -> CopyMapIter<'_> {
600 Box::new(self.iter_nodes().filter_map(|(path, node)| {
594 Box::new(self.iter_nodes().filter_map(|(path, node)| {
601 node.copy_source
595 node.copy_source
602 .as_ref()
596 .as_ref()
603 .map(|copy_source| (path.full_path(), copy_source))
597 .map(|copy_source| (path.full_path(), copy_source))
604 }))
598 }))
605 }
599 }
606
600
607 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
601 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
608 if let Some(node) = self.get_node(key) {
602 if let Some(node) = self.get_node(key) {
609 node.copy_source.is_some()
603 node.copy_source.is_some()
610 } else {
604 } else {
611 false
605 false
612 }
606 }
613 }
607 }
614
608
615 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
609 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
616 self.get_node(key)?.copy_source.as_ref()
610 self.get_node(key)?.copy_source.as_ref()
617 }
611 }
618
612
619 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
613 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
620 let count = &mut self.nodes_with_copy_source_count;
614 let count = &mut self.nodes_with_copy_source_count;
621 Self::get_node_mut(&mut self.root, key).and_then(|node| {
615 Self::get_node_mut(&mut self.root, key).and_then(|node| {
622 if node.copy_source.is_some() {
616 if node.copy_source.is_some() {
623 *count -= 1
617 *count -= 1
624 }
618 }
625 node.copy_source.take()
619 node.copy_source.take()
626 })
620 })
627 }
621 }
628
622
629 fn copy_map_insert(
623 fn copy_map_insert(
630 &mut self,
624 &mut self,
631 key: HgPathBuf,
625 key: HgPathBuf,
632 value: HgPathBuf,
626 value: HgPathBuf,
633 ) -> Option<HgPathBuf> {
627 ) -> Option<HgPathBuf> {
634 let node = Self::get_or_insert_node(&mut self.root, &key);
628 let node = Self::get_or_insert_node(&mut self.root, &key);
635 if node.copy_source.is_none() {
629 if node.copy_source.is_none() {
636 self.nodes_with_copy_source_count += 1
630 self.nodes_with_copy_source_count += 1
637 }
631 }
638 node.copy_source.replace(value)
632 node.copy_source.replace(value)
639 }
633 }
640
634
641 fn len(&self) -> usize {
635 fn len(&self) -> usize {
642 self.nodes_with_entry_count
636 self.nodes_with_entry_count
643 }
637 }
644
638
645 fn contains_key(&self, key: &HgPath) -> bool {
639 fn contains_key(&self, key: &HgPath) -> bool {
646 self.get(key).is_some()
640 self.get(key).is_some()
647 }
641 }
648
642
649 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
643 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
650 self.get_node(key)?.entry.as_ref()
644 self.get_node(key)?.entry.as_ref()
651 }
645 }
652
646
653 fn iter(&self) -> StateMapIter<'_> {
647 fn iter(&self) -> StateMapIter<'_> {
654 Box::new(self.iter_nodes().filter_map(|(path, node)| {
648 Box::new(self.iter_nodes().filter_map(|(path, node)| {
655 node.entry.as_ref().map(|entry| (path.full_path(), entry))
649 node.entry.as_ref().map(|entry| (path.full_path(), entry))
656 }))
650 }))
657 }
651 }
658 }
652 }
@@ -1,326 +1,314 b''
1 use std::path::PathBuf;
1 use std::path::PathBuf;
2
2
3 use crate::dirstate::parsers::Timestamp;
3 use crate::dirstate::parsers::Timestamp;
4 use crate::matchers::Matcher;
4 use crate::matchers::Matcher;
5 use crate::utils::hg_path::{HgPath, HgPathBuf};
5 use crate::utils::hg_path::{HgPath, HgPathBuf};
6 use crate::CopyMapIter;
6 use crate::CopyMapIter;
7 use crate::DirstateEntry;
7 use crate::DirstateEntry;
8 use crate::DirstateError;
8 use crate::DirstateError;
9 use crate::DirstateMap;
9 use crate::DirstateMap;
10 use crate::DirstateMapError;
10 use crate::DirstateMapError;
11 use crate::DirstateParents;
11 use crate::DirstateParents;
12 use crate::DirstateStatus;
12 use crate::DirstateStatus;
13 use crate::EntryState;
13 use crate::EntryState;
14 use crate::HgPathCow;
15 use crate::PatternFileWarning;
14 use crate::PatternFileWarning;
16 use crate::StateMapIter;
15 use crate::StateMapIter;
17 use crate::StatusError;
16 use crate::StatusError;
18 use crate::StatusOptions;
17 use crate::StatusOptions;
19
18
20 pub trait DirstateMapMethods {
19 pub trait DirstateMapMethods {
21 fn clear(&mut self);
20 fn clear(&mut self);
22
21
23 fn add_file(
22 fn add_file(
24 &mut self,
23 &mut self,
25 filename: &HgPath,
24 filename: &HgPath,
26 old_state: EntryState,
25 old_state: EntryState,
27 entry: DirstateEntry,
26 entry: DirstateEntry,
28 ) -> Result<(), DirstateMapError>;
27 ) -> Result<(), DirstateMapError>;
29
28
30 fn remove_file(
29 fn remove_file(
31 &mut self,
30 &mut self,
32 filename: &HgPath,
31 filename: &HgPath,
33 old_state: EntryState,
32 old_state: EntryState,
34 size: i32,
33 size: i32,
35 ) -> Result<(), DirstateMapError>;
34 ) -> Result<(), DirstateMapError>;
36
35
37 fn drop_file(
36 fn drop_file(
38 &mut self,
37 &mut self,
39 filename: &HgPath,
38 filename: &HgPath,
40 old_state: EntryState,
39 old_state: EntryState,
41 ) -> Result<bool, DirstateMapError>;
40 ) -> Result<bool, DirstateMapError>;
42
41
43 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32);
42 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32);
44
43
45 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool;
44 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool;
46
45
47 fn non_normal_entries_remove(&mut self, key: &HgPath);
46 fn non_normal_entries_remove(&mut self, key: &HgPath);
48
47
49 fn non_normal_or_other_parent_paths(
48 fn non_normal_or_other_parent_paths(
50 &mut self,
49 &mut self,
51 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_>;
50 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_>;
52
51
53 fn set_non_normal_other_parent_entries(&mut self, force: bool);
52 fn set_non_normal_other_parent_entries(&mut self, force: bool);
54
53
55 fn iter_non_normal_paths(
54 fn iter_non_normal_paths(
56 &mut self,
55 &mut self,
57 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_>;
56 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_>;
58
57
59 fn iter_non_normal_paths_panic(
58 fn iter_non_normal_paths_panic(
60 &self,
59 &self,
61 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_>;
60 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_>;
62
61
63 fn iter_other_parent_paths(
62 fn iter_other_parent_paths(
64 &mut self,
63 &mut self,
65 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_>;
64 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_>;
66
65
67 fn has_tracked_dir(
66 fn has_tracked_dir(
68 &mut self,
67 &mut self,
69 directory: &HgPath,
68 directory: &HgPath,
70 ) -> Result<bool, DirstateMapError>;
69 ) -> Result<bool, DirstateMapError>;
71
70
72 fn has_dir(
71 fn has_dir(
73 &mut self,
72 &mut self,
74 directory: &HgPath,
73 directory: &HgPath,
75 ) -> Result<bool, DirstateMapError>;
74 ) -> Result<bool, DirstateMapError>;
76
75
77 fn parents(
76 fn parents(
78 &mut self,
77 &mut self,
79 file_contents: &[u8],
78 file_contents: &[u8],
80 ) -> Result<&DirstateParents, DirstateError>;
79 ) -> Result<&DirstateParents, DirstateError>;
81
80
82 fn set_parents(&mut self, parents: &DirstateParents);
81 fn set_parents(&mut self, parents: &DirstateParents);
83
82
84 fn read<'a>(
83 fn read<'a>(
85 &mut self,
84 &mut self,
86 file_contents: &'a [u8],
85 file_contents: &'a [u8],
87 ) -> Result<Option<&'a DirstateParents>, DirstateError>;
86 ) -> Result<Option<&'a DirstateParents>, DirstateError>;
88
87
89 fn pack(
88 fn pack(
90 &mut self,
89 &mut self,
91 parents: DirstateParents,
90 parents: DirstateParents,
92 now: Timestamp,
91 now: Timestamp,
93 ) -> Result<Vec<u8>, DirstateError>;
92 ) -> Result<Vec<u8>, DirstateError>;
94
93
95 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError>;
94 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError>;
96
95
97 fn set_dirs(&mut self) -> Result<(), DirstateMapError>;
96 fn set_dirs(&mut self) -> Result<(), DirstateMapError>;
98
97
99 fn status<'a>(
98 fn status<'a>(
100 &'a self,
99 &'a self,
101 matcher: &'a (dyn Matcher + Sync),
100 matcher: &'a (dyn Matcher + Sync),
102 root_dir: PathBuf,
101 root_dir: PathBuf,
103 ignore_files: Vec<PathBuf>,
102 ignore_files: Vec<PathBuf>,
104 options: StatusOptions,
103 options: StatusOptions,
105 ) -> Result<
104 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
106 (
107 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
108 Vec<PatternFileWarning>,
109 ),
110 StatusError,
111 >;
112
105
113 fn copy_map_len(&self) -> usize;
106 fn copy_map_len(&self) -> usize;
114
107
115 fn copy_map_iter(&self) -> CopyMapIter<'_>;
108 fn copy_map_iter(&self) -> CopyMapIter<'_>;
116
109
117 fn copy_map_contains_key(&self, key: &HgPath) -> bool;
110 fn copy_map_contains_key(&self, key: &HgPath) -> bool;
118
111
119 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf>;
112 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf>;
120
113
121 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf>;
114 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf>;
122
115
123 fn copy_map_insert(
116 fn copy_map_insert(
124 &mut self,
117 &mut self,
125 key: HgPathBuf,
118 key: HgPathBuf,
126 value: HgPathBuf,
119 value: HgPathBuf,
127 ) -> Option<HgPathBuf>;
120 ) -> Option<HgPathBuf>;
128
121
129 fn len(&self) -> usize;
122 fn len(&self) -> usize;
130
123
131 fn contains_key(&self, key: &HgPath) -> bool;
124 fn contains_key(&self, key: &HgPath) -> bool;
132
125
133 fn get(&self, key: &HgPath) -> Option<&DirstateEntry>;
126 fn get(&self, key: &HgPath) -> Option<&DirstateEntry>;
134
127
135 fn iter(&self) -> StateMapIter<'_>;
128 fn iter(&self) -> StateMapIter<'_>;
136 }
129 }
137
130
138 impl DirstateMapMethods for DirstateMap {
131 impl DirstateMapMethods for DirstateMap {
139 fn clear(&mut self) {
132 fn clear(&mut self) {
140 self.clear()
133 self.clear()
141 }
134 }
142
135
143 fn add_file(
136 fn add_file(
144 &mut self,
137 &mut self,
145 filename: &HgPath,
138 filename: &HgPath,
146 old_state: EntryState,
139 old_state: EntryState,
147 entry: DirstateEntry,
140 entry: DirstateEntry,
148 ) -> Result<(), DirstateMapError> {
141 ) -> Result<(), DirstateMapError> {
149 self.add_file(filename, old_state, entry)
142 self.add_file(filename, old_state, entry)
150 }
143 }
151
144
152 fn remove_file(
145 fn remove_file(
153 &mut self,
146 &mut self,
154 filename: &HgPath,
147 filename: &HgPath,
155 old_state: EntryState,
148 old_state: EntryState,
156 size: i32,
149 size: i32,
157 ) -> Result<(), DirstateMapError> {
150 ) -> Result<(), DirstateMapError> {
158 self.remove_file(filename, old_state, size)
151 self.remove_file(filename, old_state, size)
159 }
152 }
160
153
161 fn drop_file(
154 fn drop_file(
162 &mut self,
155 &mut self,
163 filename: &HgPath,
156 filename: &HgPath,
164 old_state: EntryState,
157 old_state: EntryState,
165 ) -> Result<bool, DirstateMapError> {
158 ) -> Result<bool, DirstateMapError> {
166 self.drop_file(filename, old_state)
159 self.drop_file(filename, old_state)
167 }
160 }
168
161
169 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
162 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
170 self.clear_ambiguous_times(filenames, now)
163 self.clear_ambiguous_times(filenames, now)
171 }
164 }
172
165
173 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
166 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
174 let (non_normal, _other_parent) =
167 let (non_normal, _other_parent) =
175 self.get_non_normal_other_parent_entries();
168 self.get_non_normal_other_parent_entries();
176 non_normal.contains(key)
169 non_normal.contains(key)
177 }
170 }
178
171
179 fn non_normal_entries_remove(&mut self, key: &HgPath) {
172 fn non_normal_entries_remove(&mut self, key: &HgPath) {
180 self.non_normal_entries_remove(key)
173 self.non_normal_entries_remove(key)
181 }
174 }
182
175
183 fn non_normal_or_other_parent_paths(
176 fn non_normal_or_other_parent_paths(
184 &mut self,
177 &mut self,
185 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
178 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
186 let (non_normal, other_parent) =
179 let (non_normal, other_parent) =
187 self.get_non_normal_other_parent_entries();
180 self.get_non_normal_other_parent_entries();
188 Box::new(non_normal.union(other_parent))
181 Box::new(non_normal.union(other_parent))
189 }
182 }
190
183
191 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
184 fn set_non_normal_other_parent_entries(&mut self, force: bool) {
192 self.set_non_normal_other_parent_entries(force)
185 self.set_non_normal_other_parent_entries(force)
193 }
186 }
194
187
195 fn iter_non_normal_paths(
188 fn iter_non_normal_paths(
196 &mut self,
189 &mut self,
197 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
190 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
198 let (non_normal, _other_parent) =
191 let (non_normal, _other_parent) =
199 self.get_non_normal_other_parent_entries();
192 self.get_non_normal_other_parent_entries();
200 Box::new(non_normal.iter())
193 Box::new(non_normal.iter())
201 }
194 }
202
195
203 fn iter_non_normal_paths_panic(
196 fn iter_non_normal_paths_panic(
204 &self,
197 &self,
205 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
198 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
206 let (non_normal, _other_parent) =
199 let (non_normal, _other_parent) =
207 self.get_non_normal_other_parent_entries_panic();
200 self.get_non_normal_other_parent_entries_panic();
208 Box::new(non_normal.iter())
201 Box::new(non_normal.iter())
209 }
202 }
210
203
211 fn iter_other_parent_paths(
204 fn iter_other_parent_paths(
212 &mut self,
205 &mut self,
213 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
206 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
214 let (_non_normal, other_parent) =
207 let (_non_normal, other_parent) =
215 self.get_non_normal_other_parent_entries();
208 self.get_non_normal_other_parent_entries();
216 Box::new(other_parent.iter())
209 Box::new(other_parent.iter())
217 }
210 }
218
211
219 fn has_tracked_dir(
212 fn has_tracked_dir(
220 &mut self,
213 &mut self,
221 directory: &HgPath,
214 directory: &HgPath,
222 ) -> Result<bool, DirstateMapError> {
215 ) -> Result<bool, DirstateMapError> {
223 self.has_tracked_dir(directory)
216 self.has_tracked_dir(directory)
224 }
217 }
225
218
226 fn has_dir(
219 fn has_dir(
227 &mut self,
220 &mut self,
228 directory: &HgPath,
221 directory: &HgPath,
229 ) -> Result<bool, DirstateMapError> {
222 ) -> Result<bool, DirstateMapError> {
230 self.has_dir(directory)
223 self.has_dir(directory)
231 }
224 }
232
225
233 fn parents(
226 fn parents(
234 &mut self,
227 &mut self,
235 file_contents: &[u8],
228 file_contents: &[u8],
236 ) -> Result<&DirstateParents, DirstateError> {
229 ) -> Result<&DirstateParents, DirstateError> {
237 self.parents(file_contents)
230 self.parents(file_contents)
238 }
231 }
239
232
240 fn set_parents(&mut self, parents: &DirstateParents) {
233 fn set_parents(&mut self, parents: &DirstateParents) {
241 self.set_parents(parents)
234 self.set_parents(parents)
242 }
235 }
243
236
244 fn read<'a>(
237 fn read<'a>(
245 &mut self,
238 &mut self,
246 file_contents: &'a [u8],
239 file_contents: &'a [u8],
247 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
240 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
248 self.read(file_contents)
241 self.read(file_contents)
249 }
242 }
250
243
251 fn pack(
244 fn pack(
252 &mut self,
245 &mut self,
253 parents: DirstateParents,
246 parents: DirstateParents,
254 now: Timestamp,
247 now: Timestamp,
255 ) -> Result<Vec<u8>, DirstateError> {
248 ) -> Result<Vec<u8>, DirstateError> {
256 self.pack(parents, now)
249 self.pack(parents, now)
257 }
250 }
258
251
259 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
252 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
260 self.set_all_dirs()
253 self.set_all_dirs()
261 }
254 }
262
255
263 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
256 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
264 self.set_dirs()
257 self.set_dirs()
265 }
258 }
266
259
267 fn status<'a>(
260 fn status<'a>(
268 &'a self,
261 &'a self,
269 matcher: &'a (dyn Matcher + Sync),
262 matcher: &'a (dyn Matcher + Sync),
270 root_dir: PathBuf,
263 root_dir: PathBuf,
271 ignore_files: Vec<PathBuf>,
264 ignore_files: Vec<PathBuf>,
272 options: StatusOptions,
265 options: StatusOptions,
273 ) -> Result<
266 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
274 (
267 {
275 (Vec<HgPathCow<'a>>, DirstateStatus<'a>),
276 Vec<PatternFileWarning>,
277 ),
278 StatusError,
279 > {
280 crate::status(self, matcher, root_dir, ignore_files, options)
268 crate::status(self, matcher, root_dir, ignore_files, options)
281 }
269 }
282
270
283 fn copy_map_len(&self) -> usize {
271 fn copy_map_len(&self) -> usize {
284 self.copy_map.len()
272 self.copy_map.len()
285 }
273 }
286
274
287 fn copy_map_iter(&self) -> CopyMapIter<'_> {
275 fn copy_map_iter(&self) -> CopyMapIter<'_> {
288 Box::new(self.copy_map.iter())
276 Box::new(self.copy_map.iter())
289 }
277 }
290
278
291 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
279 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
292 self.copy_map.contains_key(key)
280 self.copy_map.contains_key(key)
293 }
281 }
294
282
295 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
283 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
296 self.copy_map.get(key)
284 self.copy_map.get(key)
297 }
285 }
298
286
299 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
287 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
300 self.copy_map.remove(key)
288 self.copy_map.remove(key)
301 }
289 }
302
290
303 fn copy_map_insert(
291 fn copy_map_insert(
304 &mut self,
292 &mut self,
305 key: HgPathBuf,
293 key: HgPathBuf,
306 value: HgPathBuf,
294 value: HgPathBuf,
307 ) -> Option<HgPathBuf> {
295 ) -> Option<HgPathBuf> {
308 self.copy_map.insert(key, value)
296 self.copy_map.insert(key, value)
309 }
297 }
310
298
311 fn len(&self) -> usize {
299 fn len(&self) -> usize {
312 (&**self).len()
300 (&**self).len()
313 }
301 }
314
302
315 fn contains_key(&self, key: &HgPath) -> bool {
303 fn contains_key(&self, key: &HgPath) -> bool {
316 (&**self).contains_key(key)
304 (&**self).contains_key(key)
317 }
305 }
318
306
319 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
307 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
320 (&**self).get(key)
308 (&**self).get(key)
321 }
309 }
322
310
323 fn iter(&self) -> StateMapIter<'_> {
311 fn iter(&self) -> StateMapIter<'_> {
324 Box::new((&**self).iter())
312 Box::new((&**self).iter())
325 }
313 }
326 }
314 }
@@ -1,73 +1,68 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, Status};
9 use crate::matchers::Matcher;
9 use crate::matchers::Matcher;
10 use crate::{DirstateStatus, StatusError};
10 use crate::{DirstateStatus, StatusError};
11
11
12 /// A tuple of the paths that need to be checked in the filelog because it's
13 /// ambiguous whether they've changed, and the rest of the already dispatched
14 /// files.
15 pub type LookupAndStatus<'a> = (Vec<HgPathCow<'a>>, DirstateStatus<'a>);
16
17 impl<'a, M: ?Sized + Matcher + Sync> Status<'a, M> {
12 impl<'a, M: ?Sized + Matcher + Sync> Status<'a, M> {
18 pub(crate) fn run(&self) -> Result<LookupAndStatus<'a>, StatusError> {
13 pub(crate) fn run(&self) -> Result<DirstateStatus<'a>, StatusError> {
19 let (traversed_sender, traversed_receiver) =
14 let (traversed_sender, traversed_receiver) =
20 crossbeam_channel::unbounded();
15 crossbeam_channel::unbounded();
21
16
22 // Step 1: check the files explicitly mentioned by the user
17 // Step 1: check the files explicitly mentioned by the user
23 let (work, mut results) = self.walk_explicit(traversed_sender.clone());
18 let (work, mut results) = self.walk_explicit(traversed_sender.clone());
24
19
25 if !work.is_empty() {
20 if !work.is_empty() {
26 // Hashmaps are quite a bit slower to build than vecs, so only
21 // Hashmaps are quite a bit slower to build than vecs, so only
27 // build it if needed.
22 // build it if needed.
28 let old_results = results.iter().cloned().collect();
23 let old_results = results.iter().cloned().collect();
29
24
30 // Step 2: recursively check the working directory for changes if
25 // Step 2: recursively check the working directory for changes if
31 // needed
26 // needed
32 for (dir, dispatch) in work {
27 for (dir, dispatch) in work {
33 match dispatch {
28 match dispatch {
34 Dispatch::Directory { was_file } => {
29 Dispatch::Directory { was_file } => {
35 if was_file {
30 if was_file {
36 results.push((dir.to_owned(), Dispatch::Removed));
31 results.push((dir.to_owned(), Dispatch::Removed));
37 }
32 }
38 if self.options.list_ignored
33 if self.options.list_ignored
39 || self.options.list_unknown
34 || self.options.list_unknown
40 && !self.dir_ignore(&dir)
35 && !self.dir_ignore(&dir)
41 {
36 {
42 self.traverse(
37 self.traverse(
43 &dir,
38 &dir,
44 &old_results,
39 &old_results,
45 &mut results,
40 &mut results,
46 traversed_sender.clone(),
41 traversed_sender.clone(),
47 );
42 );
48 }
43 }
49 }
44 }
50 _ => {
45 _ => {
51 unreachable!("There can only be directories in `work`")
46 unreachable!("There can only be directories in `work`")
52 }
47 }
53 }
48 }
54 }
49 }
55 }
50 }
56
51
57 if !self.matcher.is_exact() {
52 if !self.matcher.is_exact() {
58 if self.options.list_unknown {
53 if self.options.list_unknown {
59 self.handle_unknowns(&mut results);
54 self.handle_unknowns(&mut results);
60 } else {
55 } else {
61 // TODO this is incorrect, see issue6335
56 // TODO this is incorrect, see issue6335
62 // This requires a fix in both Python and Rust that can happen
57 // This requires a fix in both Python and Rust that can happen
63 // with other pending changes to `status`.
58 // with other pending changes to `status`.
64 self.extend_from_dmap(&mut results);
59 self.extend_from_dmap(&mut results);
65 }
60 }
66 }
61 }
67
62
68 drop(traversed_sender);
63 drop(traversed_sender);
69 let traversed = traversed_receiver.into_iter().collect();
64 let traversed = traversed_receiver.into_iter().collect();
70
65
71 Ok(build_response(results, traversed))
66 Ok(build_response(results, traversed))
72 }
67 }
73 }
68 }
@@ -1,303 +1,302 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 //! Bindings for the `hg::status` module provided by the
8 //! Bindings for the `hg::status` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as
9 //! `hg-core` crate. From Python, this will be seen as
10 //! `rustext.dirstate.status`.
10 //! `rustext.dirstate.status`.
11
11
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
13 use cpython::exc::OSError;
13 use cpython::exc::OSError;
14 use cpython::{
14 use cpython::{
15 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
15 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
16 PyResult, PyTuple, Python, PythonObject, ToPyObject,
16 PyResult, PyTuple, Python, PythonObject, ToPyObject,
17 };
17 };
18 use hg::{
18 use hg::{
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
20 parse_pattern_syntax,
20 parse_pattern_syntax,
21 utils::{
21 utils::{
22 files::{get_bytes_from_path, get_path_from_bytes},
22 files::{get_bytes_from_path, get_path_from_bytes},
23 hg_path::{HgPath, HgPathBuf},
23 hg_path::{HgPath, HgPathBuf},
24 },
24 },
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
26 StatusOptions,
26 StatusOptions,
27 };
27 };
28 use std::borrow::{Borrow, Cow};
28 use std::borrow::Borrow;
29
29
30 /// This will be useless once trait impls for collection are added to `PyBytes`
30 /// This will be useless once trait impls for collection are added to `PyBytes`
31 /// upstream.
31 /// upstream.
32 fn collect_pybytes_list(
32 fn collect_pybytes_list(
33 py: Python,
33 py: Python,
34 collection: &[impl AsRef<HgPath>],
34 collection: &[impl AsRef<HgPath>],
35 ) -> PyList {
35 ) -> PyList {
36 let list = PyList::new(py, &[]);
36 let list = PyList::new(py, &[]);
37
37
38 for path in collection.iter() {
38 for path in collection.iter() {
39 list.append(
39 list.append(
40 py,
40 py,
41 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
41 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
42 )
42 )
43 }
43 }
44
44
45 list
45 list
46 }
46 }
47
47
48 fn collect_bad_matches(
48 fn collect_bad_matches(
49 py: Python,
49 py: Python,
50 collection: &[(impl AsRef<HgPath>, BadMatch)],
50 collection: &[(impl AsRef<HgPath>, BadMatch)],
51 ) -> PyResult<PyList> {
51 ) -> PyResult<PyList> {
52 let list = PyList::new(py, &[]);
52 let list = PyList::new(py, &[]);
53
53
54 let os = py.import("os")?;
54 let os = py.import("os")?;
55 let get_error_message = |code: i32| -> PyResult<_> {
55 let get_error_message = |code: i32| -> PyResult<_> {
56 os.call(
56 os.call(
57 py,
57 py,
58 "strerror",
58 "strerror",
59 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
59 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
60 None,
60 None,
61 )
61 )
62 };
62 };
63
63
64 for (path, bad_match) in collection.iter() {
64 for (path, bad_match) in collection.iter() {
65 let message = match bad_match {
65 let message = match bad_match {
66 BadMatch::OsError(code) => get_error_message(*code)?,
66 BadMatch::OsError(code) => get_error_message(*code)?,
67 BadMatch::BadType(bad_type) => format!(
67 BadMatch::BadType(bad_type) => format!(
68 "unsupported file type (type is {})",
68 "unsupported file type (type is {})",
69 bad_type.to_string()
69 bad_type.to_string()
70 )
70 )
71 .to_py_object(py)
71 .to_py_object(py)
72 .into_object(),
72 .into_object(),
73 };
73 };
74 list.append(
74 list.append(
75 py,
75 py,
76 (PyBytes::new(py, path.as_ref().as_bytes()), message)
76 (PyBytes::new(py, path.as_ref().as_bytes()), message)
77 .to_py_object(py)
77 .to_py_object(py)
78 .into_object(),
78 .into_object(),
79 )
79 )
80 }
80 }
81
81
82 Ok(list)
82 Ok(list)
83 }
83 }
84
84
85 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
85 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
86 match err {
86 match err {
87 StatusError::Pattern(e) => {
87 StatusError::Pattern(e) => {
88 let as_string = e.to_string();
88 let as_string = e.to_string();
89 log::trace!("Rust status fallback: `{}`", &as_string);
89 log::trace!("Rust status fallback: `{}`", &as_string);
90
90
91 PyErr::new::<FallbackError, _>(py, &as_string)
91 PyErr::new::<FallbackError, _>(py, &as_string)
92 }
92 }
93 StatusError::IO(e) => PyErr::new::<OSError, _>(py, e.to_string()),
93 StatusError::IO(e) => PyErr::new::<OSError, _>(py, e.to_string()),
94 e => PyErr::new::<ValueError, _>(py, e.to_string()),
94 e => PyErr::new::<ValueError, _>(py, e.to_string()),
95 }
95 }
96 }
96 }
97
97
98 pub fn status_wrapper(
98 pub fn status_wrapper(
99 py: Python,
99 py: Python,
100 dmap: DirstateMap,
100 dmap: DirstateMap,
101 matcher: PyObject,
101 matcher: PyObject,
102 root_dir: PyObject,
102 root_dir: PyObject,
103 ignore_files: PyList,
103 ignore_files: PyList,
104 check_exec: bool,
104 check_exec: bool,
105 last_normal_time: i64,
105 last_normal_time: i64,
106 list_clean: bool,
106 list_clean: bool,
107 list_ignored: bool,
107 list_ignored: bool,
108 list_unknown: bool,
108 list_unknown: bool,
109 collect_traversed_dirs: bool,
109 collect_traversed_dirs: bool,
110 ) -> PyResult<PyTuple> {
110 ) -> PyResult<PyTuple> {
111 let bytes = root_dir.extract::<PyBytes>(py)?;
111 let bytes = root_dir.extract::<PyBytes>(py)?;
112 let root_dir = get_path_from_bytes(bytes.data(py));
112 let root_dir = get_path_from_bytes(bytes.data(py));
113
113
114 let dmap: DirstateMap = dmap.to_py_object(py);
114 let dmap: DirstateMap = dmap.to_py_object(py);
115 let dmap = dmap.get_inner(py);
115 let dmap = dmap.get_inner(py);
116
116
117 let ignore_files: PyResult<Vec<_>> = ignore_files
117 let ignore_files: PyResult<Vec<_>> = ignore_files
118 .iter(py)
118 .iter(py)
119 .map(|b| {
119 .map(|b| {
120 let file = b.extract::<PyBytes>(py)?;
120 let file = b.extract::<PyBytes>(py)?;
121 Ok(get_path_from_bytes(file.data(py)).to_owned())
121 Ok(get_path_from_bytes(file.data(py)).to_owned())
122 })
122 })
123 .collect();
123 .collect();
124 let ignore_files = ignore_files?;
124 let ignore_files = ignore_files?;
125
125
126 match matcher.get_type(py).name(py).borrow() {
126 match matcher.get_type(py).name(py).borrow() {
127 "alwaysmatcher" => {
127 "alwaysmatcher" => {
128 let matcher = AlwaysMatcher;
128 let matcher = AlwaysMatcher;
129 let ((lookup, status_res), warnings) = dmap
129 let (status_res, warnings) = dmap
130 .status(
130 .status(
131 &matcher,
131 &matcher,
132 root_dir.to_path_buf(),
132 root_dir.to_path_buf(),
133 ignore_files,
133 ignore_files,
134 StatusOptions {
134 StatusOptions {
135 check_exec,
135 check_exec,
136 last_normal_time,
136 last_normal_time,
137 list_clean,
137 list_clean,
138 list_ignored,
138 list_ignored,
139 list_unknown,
139 list_unknown,
140 collect_traversed_dirs,
140 collect_traversed_dirs,
141 },
141 },
142 )
142 )
143 .map_err(|e| handle_fallback(py, e))?;
143 .map_err(|e| handle_fallback(py, e))?;
144 build_response(py, lookup, status_res, warnings)
144 build_response(py, status_res, warnings)
145 }
145 }
146 "exactmatcher" => {
146 "exactmatcher" => {
147 let files = matcher.call_method(
147 let files = matcher.call_method(
148 py,
148 py,
149 "files",
149 "files",
150 PyTuple::new(py, &[]),
150 PyTuple::new(py, &[]),
151 None,
151 None,
152 )?;
152 )?;
153 let files: PyList = files.cast_into(py)?;
153 let files: PyList = files.cast_into(py)?;
154 let files: PyResult<Vec<HgPathBuf>> = files
154 let files: PyResult<Vec<HgPathBuf>> = files
155 .iter(py)
155 .iter(py)
156 .map(|f| {
156 .map(|f| {
157 Ok(HgPathBuf::from_bytes(
157 Ok(HgPathBuf::from_bytes(
158 f.extract::<PyBytes>(py)?.data(py),
158 f.extract::<PyBytes>(py)?.data(py),
159 ))
159 ))
160 })
160 })
161 .collect();
161 .collect();
162
162
163 let files = files?;
163 let files = files?;
164 let matcher = FileMatcher::new(files.as_ref())
164 let matcher = FileMatcher::new(files.as_ref())
165 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
165 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
166 let ((lookup, status_res), warnings) = dmap
166 let (status_res, warnings) = dmap
167 .status(
167 .status(
168 &matcher,
168 &matcher,
169 root_dir.to_path_buf(),
169 root_dir.to_path_buf(),
170 ignore_files,
170 ignore_files,
171 StatusOptions {
171 StatusOptions {
172 check_exec,
172 check_exec,
173 last_normal_time,
173 last_normal_time,
174 list_clean,
174 list_clean,
175 list_ignored,
175 list_ignored,
176 list_unknown,
176 list_unknown,
177 collect_traversed_dirs,
177 collect_traversed_dirs,
178 },
178 },
179 )
179 )
180 .map_err(|e| handle_fallback(py, e))?;
180 .map_err(|e| handle_fallback(py, e))?;
181 build_response(py, lookup, status_res, warnings)
181 build_response(py, status_res, warnings)
182 }
182 }
183 "includematcher" => {
183 "includematcher" => {
184 // Get the patterns from Python even though most of them are
184 // Get the patterns from Python even though most of them are
185 // redundant with those we will parse later on, as they include
185 // redundant with those we will parse later on, as they include
186 // those passed from the command line.
186 // those passed from the command line.
187 let ignore_patterns: PyResult<Vec<_>> = matcher
187 let ignore_patterns: PyResult<Vec<_>> = matcher
188 .getattr(py, "_kindpats")?
188 .getattr(py, "_kindpats")?
189 .iter(py)?
189 .iter(py)?
190 .map(|k| {
190 .map(|k| {
191 let k = k?;
191 let k = k?;
192 let syntax = parse_pattern_syntax(
192 let syntax = parse_pattern_syntax(
193 &[
193 &[
194 k.get_item(py, 0)?
194 k.get_item(py, 0)?
195 .extract::<PyBytes>(py)?
195 .extract::<PyBytes>(py)?
196 .data(py),
196 .data(py),
197 &b":"[..],
197 &b":"[..],
198 ]
198 ]
199 .concat(),
199 .concat(),
200 )
200 )
201 .map_err(|e| {
201 .map_err(|e| {
202 handle_fallback(py, StatusError::Pattern(e))
202 handle_fallback(py, StatusError::Pattern(e))
203 })?;
203 })?;
204 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
204 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
205 let pattern = pattern.data(py);
205 let pattern = pattern.data(py);
206 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
206 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
207 let source = get_path_from_bytes(source.data(py));
207 let source = get_path_from_bytes(source.data(py));
208 let new = IgnorePattern::new(syntax, pattern, source);
208 let new = IgnorePattern::new(syntax, pattern, source);
209 Ok(new)
209 Ok(new)
210 })
210 })
211 .collect();
211 .collect();
212
212
213 let ignore_patterns = ignore_patterns?;
213 let ignore_patterns = ignore_patterns?;
214 let mut all_warnings = vec![];
214 let mut all_warnings = vec![];
215
215
216 let (matcher, warnings) =
216 let (matcher, warnings) =
217 IncludeMatcher::new(ignore_patterns, &root_dir)
217 IncludeMatcher::new(ignore_patterns, &root_dir)
218 .map_err(|e| handle_fallback(py, e.into()))?;
218 .map_err(|e| handle_fallback(py, e.into()))?;
219 all_warnings.extend(warnings);
219 all_warnings.extend(warnings);
220
220
221 let ((lookup, status_res), warnings) = dmap
221 let (status_res, warnings) = dmap
222 .status(
222 .status(
223 &matcher,
223 &matcher,
224 root_dir.to_path_buf(),
224 root_dir.to_path_buf(),
225 ignore_files,
225 ignore_files,
226 StatusOptions {
226 StatusOptions {
227 check_exec,
227 check_exec,
228 last_normal_time,
228 last_normal_time,
229 list_clean,
229 list_clean,
230 list_ignored,
230 list_ignored,
231 list_unknown,
231 list_unknown,
232 collect_traversed_dirs,
232 collect_traversed_dirs,
233 },
233 },
234 )
234 )
235 .map_err(|e| handle_fallback(py, e))?;
235 .map_err(|e| handle_fallback(py, e))?;
236
236
237 all_warnings.extend(warnings);
237 all_warnings.extend(warnings);
238
238
239 build_response(py, lookup, status_res, all_warnings)
239 build_response(py, status_res, all_warnings)
240 }
240 }
241 e => Err(PyErr::new::<ValueError, _>(
241 e => Err(PyErr::new::<ValueError, _>(
242 py,
242 py,
243 format!("Unsupported matcher {}", e),
243 format!("Unsupported matcher {}", e),
244 )),
244 )),
245 }
245 }
246 }
246 }
247
247
248 fn build_response(
248 fn build_response(
249 py: Python,
249 py: Python,
250 lookup: Vec<Cow<HgPath>>,
251 status_res: DirstateStatus,
250 status_res: DirstateStatus,
252 warnings: Vec<PatternFileWarning>,
251 warnings: Vec<PatternFileWarning>,
253 ) -> PyResult<PyTuple> {
252 ) -> PyResult<PyTuple> {
254 let modified = collect_pybytes_list(py, status_res.modified.as_ref());
253 let modified = collect_pybytes_list(py, status_res.modified.as_ref());
255 let added = collect_pybytes_list(py, status_res.added.as_ref());
254 let added = collect_pybytes_list(py, status_res.added.as_ref());
256 let removed = collect_pybytes_list(py, status_res.removed.as_ref());
255 let removed = collect_pybytes_list(py, status_res.removed.as_ref());
257 let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
256 let deleted = collect_pybytes_list(py, status_res.deleted.as_ref());
258 let clean = collect_pybytes_list(py, status_res.clean.as_ref());
257 let clean = collect_pybytes_list(py, status_res.clean.as_ref());
259 let ignored = collect_pybytes_list(py, status_res.ignored.as_ref());
258 let ignored = collect_pybytes_list(py, status_res.ignored.as_ref());
260 let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
259 let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
261 let lookup = collect_pybytes_list(py, lookup.as_ref());
260 let unsure = collect_pybytes_list(py, status_res.unsure.as_ref());
262 let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
261 let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
263 let traversed = collect_pybytes_list(py, status_res.traversed.as_ref());
262 let traversed = collect_pybytes_list(py, status_res.traversed.as_ref());
264 let py_warnings = PyList::new(py, &[]);
263 let py_warnings = PyList::new(py, &[]);
265 for warning in warnings.iter() {
264 for warning in warnings.iter() {
266 // We use duck-typing on the Python side for dispatch, good enough for
265 // We use duck-typing on the Python side for dispatch, good enough for
267 // now.
266 // now.
268 match warning {
267 match warning {
269 PatternFileWarning::InvalidSyntax(file, syn) => {
268 PatternFileWarning::InvalidSyntax(file, syn) => {
270 py_warnings.append(
269 py_warnings.append(
271 py,
270 py,
272 (
271 (
273 PyBytes::new(py, &get_bytes_from_path(&file)),
272 PyBytes::new(py, &get_bytes_from_path(&file)),
274 PyBytes::new(py, syn),
273 PyBytes::new(py, syn),
275 )
274 )
276 .to_py_object(py)
275 .to_py_object(py)
277 .into_object(),
276 .into_object(),
278 );
277 );
279 }
278 }
280 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
279 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
281 py,
280 py,
282 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
281 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
283 ),
282 ),
284 }
283 }
285 }
284 }
286
285
287 Ok(PyTuple::new(
286 Ok(PyTuple::new(
288 py,
287 py,
289 &[
288 &[
290 lookup.into_object(),
289 unsure.into_object(),
291 modified.into_object(),
290 modified.into_object(),
292 added.into_object(),
291 added.into_object(),
293 removed.into_object(),
292 removed.into_object(),
294 deleted.into_object(),
293 deleted.into_object(),
295 clean.into_object(),
294 clean.into_object(),
296 ignored.into_object(),
295 ignored.into_object(),
297 unknown.into_object(),
296 unknown.into_object(),
298 py_warnings.into_object(),
297 py_warnings.into_object(),
299 bad.into_object(),
298 bad.into_object(),
300 traversed.into_object(),
299 traversed.into_object(),
301 ][..],
300 ][..],
302 ))
301 ))
303 }
302 }
@@ -1,315 +1,315 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@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::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use clap::{Arg, SubCommand};
10 use clap::{Arg, SubCommand};
11 use hg;
11 use hg;
12 use hg::errors::IoResultExt;
12 use hg::errors::IoResultExt;
13 use hg::matchers::AlwaysMatcher;
13 use hg::matchers::AlwaysMatcher;
14 use hg::operations::cat;
14 use hg::operations::cat;
15 use hg::repo::Repo;
15 use hg::repo::Repo;
16 use hg::revlog::node::Node;
16 use hg::revlog::node::Node;
17 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
17 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
18 use hg::{DirstateMap, StatusError};
18 use hg::{DirstateMap, StatusError};
19 use hg::{HgPathCow, StatusOptions};
19 use hg::{HgPathCow, StatusOptions};
20 use log::{info, warn};
20 use log::{info, warn};
21 use std::convert::TryInto;
21 use std::convert::TryInto;
22 use std::fs;
22 use std::fs;
23 use std::io::BufReader;
23 use std::io::BufReader;
24 use std::io::Read;
24 use std::io::Read;
25
25
26 pub const HELP_TEXT: &str = "
26 pub const HELP_TEXT: &str = "
27 Show changed files in the working directory
27 Show changed files in the working directory
28
28
29 This is a pure Rust version of `hg status`.
29 This is a pure Rust version of `hg status`.
30
30
31 Some options might be missing, check the list below.
31 Some options might be missing, check the list below.
32 ";
32 ";
33
33
34 pub fn args() -> clap::App<'static, 'static> {
34 pub fn args() -> clap::App<'static, 'static> {
35 SubCommand::with_name("status")
35 SubCommand::with_name("status")
36 .alias("st")
36 .alias("st")
37 .about(HELP_TEXT)
37 .about(HELP_TEXT)
38 .arg(
38 .arg(
39 Arg::with_name("all")
39 Arg::with_name("all")
40 .help("show status of all files")
40 .help("show status of all files")
41 .short("-A")
41 .short("-A")
42 .long("--all"),
42 .long("--all"),
43 )
43 )
44 .arg(
44 .arg(
45 Arg::with_name("modified")
45 Arg::with_name("modified")
46 .help("show only modified files")
46 .help("show only modified files")
47 .short("-m")
47 .short("-m")
48 .long("--modified"),
48 .long("--modified"),
49 )
49 )
50 .arg(
50 .arg(
51 Arg::with_name("added")
51 Arg::with_name("added")
52 .help("show only added files")
52 .help("show only added files")
53 .short("-a")
53 .short("-a")
54 .long("--added"),
54 .long("--added"),
55 )
55 )
56 .arg(
56 .arg(
57 Arg::with_name("removed")
57 Arg::with_name("removed")
58 .help("show only removed files")
58 .help("show only removed files")
59 .short("-r")
59 .short("-r")
60 .long("--removed"),
60 .long("--removed"),
61 )
61 )
62 .arg(
62 .arg(
63 Arg::with_name("clean")
63 Arg::with_name("clean")
64 .help("show only clean files")
64 .help("show only clean files")
65 .short("-c")
65 .short("-c")
66 .long("--clean"),
66 .long("--clean"),
67 )
67 )
68 .arg(
68 .arg(
69 Arg::with_name("deleted")
69 Arg::with_name("deleted")
70 .help("show only deleted files")
70 .help("show only deleted files")
71 .short("-d")
71 .short("-d")
72 .long("--deleted"),
72 .long("--deleted"),
73 )
73 )
74 .arg(
74 .arg(
75 Arg::with_name("unknown")
75 Arg::with_name("unknown")
76 .help("show only unknown (not tracked) files")
76 .help("show only unknown (not tracked) files")
77 .short("-u")
77 .short("-u")
78 .long("--unknown"),
78 .long("--unknown"),
79 )
79 )
80 .arg(
80 .arg(
81 Arg::with_name("ignored")
81 Arg::with_name("ignored")
82 .help("show only ignored files")
82 .help("show only ignored files")
83 .short("-i")
83 .short("-i")
84 .long("--ignored"),
84 .long("--ignored"),
85 )
85 )
86 }
86 }
87
87
88 /// Pure data type allowing the caller to specify file states to display
88 /// Pure data type allowing the caller to specify file states to display
89 #[derive(Copy, Clone, Debug)]
89 #[derive(Copy, Clone, Debug)]
90 pub struct DisplayStates {
90 pub struct DisplayStates {
91 pub modified: bool,
91 pub modified: bool,
92 pub added: bool,
92 pub added: bool,
93 pub removed: bool,
93 pub removed: bool,
94 pub clean: bool,
94 pub clean: bool,
95 pub deleted: bool,
95 pub deleted: bool,
96 pub unknown: bool,
96 pub unknown: bool,
97 pub ignored: bool,
97 pub ignored: bool,
98 }
98 }
99
99
100 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
100 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
101 modified: true,
101 modified: true,
102 added: true,
102 added: true,
103 removed: true,
103 removed: true,
104 clean: false,
104 clean: false,
105 deleted: true,
105 deleted: true,
106 unknown: true,
106 unknown: true,
107 ignored: false,
107 ignored: false,
108 };
108 };
109
109
110 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
110 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
111 modified: true,
111 modified: true,
112 added: true,
112 added: true,
113 removed: true,
113 removed: true,
114 clean: true,
114 clean: true,
115 deleted: true,
115 deleted: true,
116 unknown: true,
116 unknown: true,
117 ignored: true,
117 ignored: true,
118 };
118 };
119
119
120 impl DisplayStates {
120 impl DisplayStates {
121 pub fn is_empty(&self) -> bool {
121 pub fn is_empty(&self) -> bool {
122 !(self.modified
122 !(self.modified
123 || self.added
123 || self.added
124 || self.removed
124 || self.removed
125 || self.clean
125 || self.clean
126 || self.deleted
126 || self.deleted
127 || self.unknown
127 || self.unknown
128 || self.ignored)
128 || self.ignored)
129 }
129 }
130 }
130 }
131
131
132 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
132 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
133 let status_enabled_default = false;
133 let status_enabled_default = false;
134 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
134 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
135 if !status_enabled.unwrap_or(status_enabled_default) {
135 if !status_enabled.unwrap_or(status_enabled_default) {
136 return Err(CommandError::unsupported(
136 return Err(CommandError::unsupported(
137 "status is experimental in rhg (enable it with 'rhg.status = true' \
137 "status is experimental in rhg (enable it with 'rhg.status = true' \
138 or enable fallback with 'rhg.on-unsupported = fallback')"
138 or enable fallback with 'rhg.on-unsupported = fallback')"
139 ));
139 ));
140 }
140 }
141
141
142 let ui = invocation.ui;
142 let ui = invocation.ui;
143 let args = invocation.subcommand_args;
143 let args = invocation.subcommand_args;
144 let display_states = if args.is_present("all") {
144 let display_states = if args.is_present("all") {
145 // TODO when implementing `--quiet`: it excludes clean files
145 // TODO when implementing `--quiet`: it excludes clean files
146 // from `--all`
146 // from `--all`
147 ALL_DISPLAY_STATES
147 ALL_DISPLAY_STATES
148 } else {
148 } else {
149 let requested = DisplayStates {
149 let requested = DisplayStates {
150 modified: args.is_present("modified"),
150 modified: args.is_present("modified"),
151 added: args.is_present("added"),
151 added: args.is_present("added"),
152 removed: args.is_present("removed"),
152 removed: args.is_present("removed"),
153 clean: args.is_present("clean"),
153 clean: args.is_present("clean"),
154 deleted: args.is_present("deleted"),
154 deleted: args.is_present("deleted"),
155 unknown: args.is_present("unknown"),
155 unknown: args.is_present("unknown"),
156 ignored: args.is_present("ignored"),
156 ignored: args.is_present("ignored"),
157 };
157 };
158 if requested.is_empty() {
158 if requested.is_empty() {
159 DEFAULT_DISPLAY_STATES
159 DEFAULT_DISPLAY_STATES
160 } else {
160 } else {
161 requested
161 requested
162 }
162 }
163 };
163 };
164
164
165 let repo = invocation.repo?;
165 let repo = invocation.repo?;
166 let mut dmap = DirstateMap::new();
166 let mut dmap = DirstateMap::new();
167 let dirstate_data = repo.hg_vfs().mmap_open("dirstate")?;
167 let dirstate_data = repo.hg_vfs().mmap_open("dirstate")?;
168 let parents = dmap.read(&dirstate_data)?;
168 let parents = dmap.read(&dirstate_data)?;
169 let options = StatusOptions {
169 let options = StatusOptions {
170 // TODO should be provided by the dirstate parsing and
170 // TODO should be provided by the dirstate parsing and
171 // hence be stored on dmap. Using a value that assumes we aren't
171 // hence be stored on dmap. Using a value that assumes we aren't
172 // below the time resolution granularity of the FS and the
172 // below the time resolution granularity of the FS and the
173 // dirstate.
173 // dirstate.
174 last_normal_time: 0,
174 last_normal_time: 0,
175 // we're currently supporting file systems with exec flags only
175 // we're currently supporting file systems with exec flags only
176 // anyway
176 // anyway
177 check_exec: true,
177 check_exec: true,
178 list_clean: display_states.clean,
178 list_clean: display_states.clean,
179 list_unknown: display_states.unknown,
179 list_unknown: display_states.unknown,
180 list_ignored: display_states.ignored,
180 list_ignored: display_states.ignored,
181 collect_traversed_dirs: false,
181 collect_traversed_dirs: false,
182 };
182 };
183 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
183 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
184 let ((lookup, ds_status), pattern_warnings) = hg::status(
184 let (ds_status, pattern_warnings) = hg::status(
185 &dmap,
185 &dmap,
186 &AlwaysMatcher,
186 &AlwaysMatcher,
187 repo.working_directory_path().to_owned(),
187 repo.working_directory_path().to_owned(),
188 vec![ignore_file],
188 vec![ignore_file],
189 options,
189 options,
190 )?;
190 )?;
191 if !pattern_warnings.is_empty() {
191 if !pattern_warnings.is_empty() {
192 warn!("Pattern warnings: {:?}", &pattern_warnings);
192 warn!("Pattern warnings: {:?}", &pattern_warnings);
193 }
193 }
194
194
195 if !ds_status.bad.is_empty() {
195 if !ds_status.bad.is_empty() {
196 warn!("Bad matches {:?}", &(ds_status.bad))
196 warn!("Bad matches {:?}", &(ds_status.bad))
197 }
197 }
198 if !lookup.is_empty() {
198 if !ds_status.unsure.is_empty() {
199 info!(
199 info!(
200 "Files to be rechecked by retrieval from filelog: {:?}",
200 "Files to be rechecked by retrieval from filelog: {:?}",
201 &lookup
201 &ds_status.unsure
202 );
202 );
203 }
203 }
204 // TODO check ordering to match `hg status` output.
204 // TODO check ordering to match `hg status` output.
205 // (this is as in `hg help status`)
205 // (this is as in `hg help status`)
206 if display_states.modified {
206 if display_states.modified {
207 display_status_paths(ui, &(ds_status.modified), b"M")?;
207 display_status_paths(ui, &(ds_status.modified), b"M")?;
208 }
208 }
209 if !lookup.is_empty() {
209 if !ds_status.unsure.is_empty() {
210 let p1: Node = parents
210 let p1: Node = parents
211 .expect(
211 .expect(
212 "Dirstate with no parents should not list any file to
212 "Dirstate with no parents should not list any file to
213 be rechecked for modifications",
213 be rechecked for modifications",
214 )
214 )
215 .p1
215 .p1
216 .into();
216 .into();
217 let p1_hex = format!("{:x}", p1);
217 let p1_hex = format!("{:x}", p1);
218 let mut rechecked_modified: Vec<HgPathCow> = Vec::new();
218 let mut rechecked_modified: Vec<HgPathCow> = Vec::new();
219 let mut rechecked_clean: Vec<HgPathCow> = Vec::new();
219 let mut rechecked_clean: Vec<HgPathCow> = Vec::new();
220 for to_check in lookup {
220 for to_check in ds_status.unsure {
221 if cat_file_is_modified(repo, &to_check, &p1_hex)? {
221 if cat_file_is_modified(repo, &to_check, &p1_hex)? {
222 rechecked_modified.push(to_check);
222 rechecked_modified.push(to_check);
223 } else {
223 } else {
224 rechecked_clean.push(to_check);
224 rechecked_clean.push(to_check);
225 }
225 }
226 }
226 }
227 if display_states.modified {
227 if display_states.modified {
228 display_status_paths(ui, &rechecked_modified, b"M")?;
228 display_status_paths(ui, &rechecked_modified, b"M")?;
229 }
229 }
230 if display_states.clean {
230 if display_states.clean {
231 display_status_paths(ui, &rechecked_clean, b"C")?;
231 display_status_paths(ui, &rechecked_clean, b"C")?;
232 }
232 }
233 }
233 }
234 if display_states.added {
234 if display_states.added {
235 display_status_paths(ui, &(ds_status.added), b"A")?;
235 display_status_paths(ui, &(ds_status.added), b"A")?;
236 }
236 }
237 if display_states.clean {
237 if display_states.clean {
238 display_status_paths(ui, &(ds_status.clean), b"C")?;
238 display_status_paths(ui, &(ds_status.clean), b"C")?;
239 }
239 }
240 if display_states.removed {
240 if display_states.removed {
241 display_status_paths(ui, &(ds_status.removed), b"R")?;
241 display_status_paths(ui, &(ds_status.removed), b"R")?;
242 }
242 }
243 if display_states.deleted {
243 if display_states.deleted {
244 display_status_paths(ui, &(ds_status.deleted), b"!")?;
244 display_status_paths(ui, &(ds_status.deleted), b"!")?;
245 }
245 }
246 if display_states.unknown {
246 if display_states.unknown {
247 display_status_paths(ui, &(ds_status.unknown), b"?")?;
247 display_status_paths(ui, &(ds_status.unknown), b"?")?;
248 }
248 }
249 if display_states.ignored {
249 if display_states.ignored {
250 display_status_paths(ui, &(ds_status.ignored), b"I")?;
250 display_status_paths(ui, &(ds_status.ignored), b"I")?;
251 }
251 }
252 Ok(())
252 Ok(())
253 }
253 }
254
254
255 // Probably more elegant to use a Deref or Borrow trait rather than
255 // Probably more elegant to use a Deref or Borrow trait rather than
256 // harcode HgPathBuf, but probably not really useful at this point
256 // harcode HgPathBuf, but probably not really useful at this point
257 fn display_status_paths(
257 fn display_status_paths(
258 ui: &Ui,
258 ui: &Ui,
259 paths: &[HgPathCow],
259 paths: &[HgPathCow],
260 status_prefix: &[u8],
260 status_prefix: &[u8],
261 ) -> Result<(), CommandError> {
261 ) -> Result<(), CommandError> {
262 for path in paths {
262 for path in paths {
263 // Same TODO as in commands::root
263 // Same TODO as in commands::root
264 let bytes: &[u8] = path.as_bytes();
264 let bytes: &[u8] = path.as_bytes();
265 // TODO optim, probably lots of unneeded copies here, especially
265 // TODO optim, probably lots of unneeded copies here, especially
266 // if out stream is buffered
266 // if out stream is buffered
267 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
267 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
268 }
268 }
269 Ok(())
269 Ok(())
270 }
270 }
271
271
272 /// Check if a file is modified by comparing actual repo store and file system.
272 /// Check if a file is modified by comparing actual repo store and file system.
273 ///
273 ///
274 /// This meant to be used for those that the dirstate cannot resolve, due
274 /// This meant to be used for those that the dirstate cannot resolve, due
275 /// to time resolution limits.
275 /// to time resolution limits.
276 ///
276 ///
277 /// TODO: detect permission bits and similar metadata modifications
277 /// TODO: detect permission bits and similar metadata modifications
278 fn cat_file_is_modified(
278 fn cat_file_is_modified(
279 repo: &Repo,
279 repo: &Repo,
280 hg_path: &HgPath,
280 hg_path: &HgPath,
281 rev: &str,
281 rev: &str,
282 ) -> Result<bool, CommandError> {
282 ) -> Result<bool, CommandError> {
283 // TODO CatRev expects &[HgPathBuf], something like
283 // TODO CatRev expects &[HgPathBuf], something like
284 // &[impl Deref<HgPath>] would be nicer and should avoid the copy
284 // &[impl Deref<HgPath>] would be nicer and should avoid the copy
285 let path_bufs = [hg_path.into()];
285 let path_bufs = [hg_path.into()];
286 // TODO IIUC CatRev returns a simple Vec<u8> for all files
286 // TODO IIUC CatRev returns a simple Vec<u8> for all files
287 // being able to tell them apart as (path, bytes) would be nicer
287 // being able to tell them apart as (path, bytes) would be nicer
288 // and OPTIM would allow manifest resolution just once.
288 // and OPTIM would allow manifest resolution just once.
289 let output = cat(repo, rev, &path_bufs).map_err(|e| (e, rev))?;
289 let output = cat(repo, rev, &path_bufs).map_err(|e| (e, rev))?;
290
290
291 let fs_path = repo
291 let fs_path = repo
292 .working_directory_vfs()
292 .working_directory_vfs()
293 .join(hg_path_to_os_string(hg_path).expect("HgPath conversion"));
293 .join(hg_path_to_os_string(hg_path).expect("HgPath conversion"));
294 let hg_data_len: u64 = match output.concatenated.len().try_into() {
294 let hg_data_len: u64 = match output.concatenated.len().try_into() {
295 Ok(v) => v,
295 Ok(v) => v,
296 Err(_) => {
296 Err(_) => {
297 // conversion of data length to u64 failed,
297 // conversion of data length to u64 failed,
298 // good luck for any file to have this content
298 // good luck for any file to have this content
299 return Ok(true);
299 return Ok(true);
300 }
300 }
301 };
301 };
302 let fobj = fs::File::open(&fs_path).when_reading_file(&fs_path)?;
302 let fobj = fs::File::open(&fs_path).when_reading_file(&fs_path)?;
303 if fobj.metadata().map_err(|e| StatusError::from(e))?.len() != hg_data_len
303 if fobj.metadata().map_err(|e| StatusError::from(e))?.len() != hg_data_len
304 {
304 {
305 return Ok(true);
305 return Ok(true);
306 }
306 }
307 for (fs_byte, hg_byte) in
307 for (fs_byte, hg_byte) in
308 BufReader::new(fobj).bytes().zip(output.concatenated)
308 BufReader::new(fobj).bytes().zip(output.concatenated)
309 {
309 {
310 if fs_byte.map_err(|e| StatusError::from(e))? != hg_byte {
310 if fs_byte.map_err(|e| StatusError::from(e))? != hg_byte {
311 return Ok(true);
311 return Ok(true);
312 }
312 }
313 }
313 }
314 Ok(false)
314 Ok(false)
315 }
315 }
General Comments 0
You need to be logged in to leave comments. Login now