##// END OF EJS Templates
dirstate-tree: Skip readdir() in `hg status -mard`...
Simon Sapin -
r48129:f27f2afb default
parent child Browse files
Show More
@@ -1,433 +1,466 b''
1 use crate::dirstate::status::IgnoreFnType;
1 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
2 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
3 use crate::dirstate_tree::dirstate_map::DirstateMap;
3 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::NodeRef;
4 use crate::dirstate_tree::dirstate_map::NodeRef;
5 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
5 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
6 use crate::matchers::get_ignore_function;
6 use crate::matchers::get_ignore_function;
7 use crate::matchers::Matcher;
7 use crate::matchers::Matcher;
8 use crate::utils::files::get_bytes_from_os_string;
8 use crate::utils::files::get_bytes_from_os_string;
9 use crate::utils::files::get_path_from_bytes;
9 use crate::utils::hg_path::HgPath;
10 use crate::utils::hg_path::HgPath;
10 use crate::BadMatch;
11 use crate::BadMatch;
11 use crate::DirstateStatus;
12 use crate::DirstateStatus;
12 use crate::EntryState;
13 use crate::EntryState;
13 use crate::HgPathBuf;
14 use crate::HgPathBuf;
14 use crate::PatternFileWarning;
15 use crate::PatternFileWarning;
15 use crate::StatusError;
16 use crate::StatusError;
16 use crate::StatusOptions;
17 use crate::StatusOptions;
17 use micro_timer::timed;
18 use micro_timer::timed;
18 use rayon::prelude::*;
19 use rayon::prelude::*;
19 use std::borrow::Cow;
20 use std::borrow::Cow;
20 use std::io;
21 use std::io;
21 use std::path::Path;
22 use std::path::Path;
22 use std::path::PathBuf;
23 use std::path::PathBuf;
23 use std::sync::Mutex;
24 use std::sync::Mutex;
24
25
25 /// Returns the status of the working directory compared to its parent
26 /// Returns the status of the working directory compared to its parent
26 /// changeset.
27 /// changeset.
27 ///
28 ///
28 /// This algorithm is based on traversing the filesystem tree (`fs` in function
29 /// This algorithm is based on traversing the filesystem tree (`fs` in function
29 /// and variable names) and dirstate tree at the same time. The core of this
30 /// and variable names) and dirstate tree at the same time. The core of this
30 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
31 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
31 /// and its use of `itertools::merge_join_by`. When reaching a path that only
32 /// and its use of `itertools::merge_join_by`. When reaching a path that only
32 /// exists in one of the two trees, depending on information requested by
33 /// exists in one of the two trees, depending on information requested by
33 /// `options` we may need to traverse the remaining subtree.
34 /// `options` we may need to traverse the remaining subtree.
34 #[timed]
35 #[timed]
35 pub fn status<'tree, 'on_disk: 'tree>(
36 pub fn status<'tree, 'on_disk: 'tree>(
36 dmap: &'tree mut DirstateMap<'on_disk>,
37 dmap: &'tree mut DirstateMap<'on_disk>,
37 matcher: &(dyn Matcher + Sync),
38 matcher: &(dyn Matcher + Sync),
38 root_dir: PathBuf,
39 root_dir: PathBuf,
39 ignore_files: Vec<PathBuf>,
40 ignore_files: Vec<PathBuf>,
40 options: StatusOptions,
41 options: StatusOptions,
41 ) -> Result<(DirstateStatus<'tree>, Vec<PatternFileWarning>), StatusError> {
42 ) -> Result<(DirstateStatus<'tree>, Vec<PatternFileWarning>), StatusError> {
42 let (ignore_fn, warnings): (IgnoreFnType, _) =
43 let (ignore_fn, warnings): (IgnoreFnType, _) =
43 if options.list_ignored || options.list_unknown {
44 if options.list_ignored || options.list_unknown {
44 get_ignore_function(ignore_files, &root_dir)?
45 get_ignore_function(ignore_files, &root_dir)?
45 } else {
46 } else {
46 (Box::new(|&_| true), vec![])
47 (Box::new(|&_| true), vec![])
47 };
48 };
48
49
49 let common = StatusCommon {
50 let common = StatusCommon {
50 dmap,
51 dmap,
51 options,
52 options,
52 matcher,
53 matcher,
53 ignore_fn,
54 ignore_fn,
54 outcome: Mutex::new(DirstateStatus::default()),
55 outcome: Mutex::new(DirstateStatus::default()),
55 };
56 };
56 let is_at_repo_root = true;
57 let is_at_repo_root = true;
57 let hg_path = HgPath::new("");
58 let hg_path = HgPath::new("");
58 let has_ignored_ancestor = false;
59 let has_ignored_ancestor = false;
59 common.traverse_fs_directory_and_dirstate(
60 common.traverse_fs_directory_and_dirstate(
60 has_ignored_ancestor,
61 has_ignored_ancestor,
61 dmap.root.as_ref(),
62 dmap.root.as_ref(),
62 hg_path,
63 hg_path,
63 &root_dir,
64 &root_dir,
64 is_at_repo_root,
65 is_at_repo_root,
65 )?;
66 )?;
66 Ok((common.outcome.into_inner().unwrap(), warnings))
67 Ok((common.outcome.into_inner().unwrap(), warnings))
67 }
68 }
68
69
69 /// Bag of random things needed by various parts of the algorithm. Reduces the
70 /// Bag of random things needed by various parts of the algorithm. Reduces the
70 /// number of parameters passed to functions.
71 /// number of parameters passed to functions.
71 struct StatusCommon<'tree, 'a, 'on_disk: 'tree> {
72 struct StatusCommon<'tree, 'a, 'on_disk: 'tree> {
72 dmap: &'tree DirstateMap<'on_disk>,
73 dmap: &'tree DirstateMap<'on_disk>,
73 options: StatusOptions,
74 options: StatusOptions,
74 matcher: &'a (dyn Matcher + Sync),
75 matcher: &'a (dyn Matcher + Sync),
75 ignore_fn: IgnoreFnType<'a>,
76 ignore_fn: IgnoreFnType<'a>,
76 outcome: Mutex<DirstateStatus<'tree>>,
77 outcome: Mutex<DirstateStatus<'tree>>,
77 }
78 }
78
79
79 impl<'tree, 'a> StatusCommon<'tree, 'a, '_> {
80 impl<'tree, 'a> StatusCommon<'tree, 'a, '_> {
80 fn read_dir(
81 fn read_dir(
81 &self,
82 &self,
82 hg_path: &HgPath,
83 hg_path: &HgPath,
83 fs_path: &Path,
84 fs_path: &Path,
84 is_at_repo_root: bool,
85 is_at_repo_root: bool,
85 ) -> Result<Vec<DirEntry>, ()> {
86 ) -> Result<Vec<DirEntry>, ()> {
86 DirEntry::read_dir(fs_path, is_at_repo_root).map_err(|error| {
87 DirEntry::read_dir(fs_path, is_at_repo_root)
88 .map_err(|error| self.io_error(error, hg_path))
89 }
90
91 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
87 let errno = error.raw_os_error().expect("expected real OS error");
92 let errno = error.raw_os_error().expect("expected real OS error");
88 self.outcome
93 self.outcome
89 .lock()
94 .lock()
90 .unwrap()
95 .unwrap()
91 .bad
96 .bad
92 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
97 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
93 })
94 }
98 }
95
99
96 fn traverse_fs_directory_and_dirstate(
100 fn traverse_fs_directory_and_dirstate(
97 &self,
101 &self,
98 has_ignored_ancestor: bool,
102 has_ignored_ancestor: bool,
99 dirstate_nodes: ChildNodesRef<'tree, '_>,
103 dirstate_nodes: ChildNodesRef<'tree, '_>,
100 directory_hg_path: &'tree HgPath,
104 directory_hg_path: &'tree HgPath,
101 directory_fs_path: &Path,
105 directory_fs_path: &Path,
102 is_at_repo_root: bool,
106 is_at_repo_root: bool,
103 ) -> Result<(), DirstateV2ParseError> {
107 ) -> Result<(), DirstateV2ParseError> {
108 if !self.options.list_unknown && !self.options.list_ignored {
109 // We only care about files in the dirstate, so we can skip listing
110 // filesystem directories entirely.
111 return dirstate_nodes
112 .par_iter()
113 .map(|dirstate_node| {
114 let fs_path = directory_fs_path.join(get_path_from_bytes(
115 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
116 ));
117 match std::fs::symlink_metadata(&fs_path) {
118 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
119 &fs_path,
120 &fs_metadata,
121 dirstate_node,
122 has_ignored_ancestor,
123 ),
124 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
125 self.traverse_dirstate_only(dirstate_node)
126 }
127 Err(error) => {
128 let hg_path =
129 dirstate_node.full_path(self.dmap.on_disk)?;
130 Ok(self.io_error(error, hg_path))
131 }
132 }
133 })
134 .collect();
135 }
136
104 let mut fs_entries = if let Ok(entries) = self.read_dir(
137 let mut fs_entries = if let Ok(entries) = self.read_dir(
105 directory_hg_path,
138 directory_hg_path,
106 directory_fs_path,
139 directory_fs_path,
107 is_at_repo_root,
140 is_at_repo_root,
108 ) {
141 ) {
109 entries
142 entries
110 } else {
143 } else {
111 return Ok(());
144 return Ok(());
112 };
145 };
113
146
114 // `merge_join_by` requires both its input iterators to be sorted:
147 // `merge_join_by` requires both its input iterators to be sorted:
115
148
116 let dirstate_nodes = dirstate_nodes.sorted();
149 let dirstate_nodes = dirstate_nodes.sorted();
117 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
150 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
118 // https://github.com/rust-lang/rust/issues/34162
151 // https://github.com/rust-lang/rust/issues/34162
119 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
152 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
120
153
121 // Propagate here any error that would happen inside the comparison
154 // Propagate here any error that would happen inside the comparison
122 // callback below
155 // callback below
123 for dirstate_node in &dirstate_nodes {
156 for dirstate_node in &dirstate_nodes {
124 dirstate_node.base_name(self.dmap.on_disk)?;
157 dirstate_node.base_name(self.dmap.on_disk)?;
125 }
158 }
126 itertools::merge_join_by(
159 itertools::merge_join_by(
127 dirstate_nodes,
160 dirstate_nodes,
128 &fs_entries,
161 &fs_entries,
129 |dirstate_node, fs_entry| {
162 |dirstate_node, fs_entry| {
130 // This `unwrap` never panics because we already propagated
163 // This `unwrap` never panics because we already propagated
131 // those errors above
164 // those errors above
132 dirstate_node
165 dirstate_node
133 .base_name(self.dmap.on_disk)
166 .base_name(self.dmap.on_disk)
134 .unwrap()
167 .unwrap()
135 .cmp(&fs_entry.base_name)
168 .cmp(&fs_entry.base_name)
136 },
169 },
137 )
170 )
138 .par_bridge()
171 .par_bridge()
139 .map(|pair| {
172 .map(|pair| {
140 use itertools::EitherOrBoth::*;
173 use itertools::EitherOrBoth::*;
141 match pair {
174 match pair {
142 Both(dirstate_node, fs_entry) => self
175 Both(dirstate_node, fs_entry) => self
143 .traverse_fs_and_dirstate(
176 .traverse_fs_and_dirstate(
144 fs_entry,
177 &fs_entry.full_path,
178 &fs_entry.metadata,
145 dirstate_node,
179 dirstate_node,
146 has_ignored_ancestor,
180 has_ignored_ancestor,
147 ),
181 ),
148 Left(dirstate_node) => {
182 Left(dirstate_node) => {
149 self.traverse_dirstate_only(dirstate_node)
183 self.traverse_dirstate_only(dirstate_node)
150 }
184 }
151 Right(fs_entry) => Ok(self.traverse_fs_only(
185 Right(fs_entry) => Ok(self.traverse_fs_only(
152 has_ignored_ancestor,
186 has_ignored_ancestor,
153 directory_hg_path,
187 directory_hg_path,
154 fs_entry,
188 fs_entry,
155 )),
189 )),
156 }
190 }
157 })
191 })
158 .collect()
192 .collect()
159 }
193 }
160
194
161 fn traverse_fs_and_dirstate(
195 fn traverse_fs_and_dirstate(
162 &self,
196 &self,
163 fs_entry: &DirEntry,
197 fs_path: &Path,
198 fs_metadata: &std::fs::Metadata,
164 dirstate_node: NodeRef<'tree, '_>,
199 dirstate_node: NodeRef<'tree, '_>,
165 has_ignored_ancestor: bool,
200 has_ignored_ancestor: bool,
166 ) -> Result<(), DirstateV2ParseError> {
201 ) -> Result<(), DirstateV2ParseError> {
167 let hg_path = dirstate_node.full_path(self.dmap.on_disk)?;
202 let hg_path = dirstate_node.full_path(self.dmap.on_disk)?;
168 let file_type = fs_entry.metadata.file_type();
203 let file_type = fs_metadata.file_type();
169 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
204 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
170 if !file_or_symlink {
205 if !file_or_symlink {
171 // If we previously had a file here, it was removed (with
206 // If we previously had a file here, it was removed (with
172 // `hg rm` or similar) or deleted before it could be
207 // `hg rm` or similar) or deleted before it could be
173 // replaced by a directory or something else.
208 // replaced by a directory or something else.
174 self.mark_removed_or_deleted_if_file(
209 self.mark_removed_or_deleted_if_file(
175 hg_path,
210 hg_path,
176 dirstate_node.state()?,
211 dirstate_node.state()?,
177 );
212 );
178 }
213 }
179 if file_type.is_dir() {
214 if file_type.is_dir() {
180 if self.options.collect_traversed_dirs {
215 if self.options.collect_traversed_dirs {
181 self.outcome.lock().unwrap().traversed.push(hg_path.into())
216 self.outcome.lock().unwrap().traversed.push(hg_path.into())
182 }
217 }
183 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
218 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
184 let is_at_repo_root = false;
219 let is_at_repo_root = false;
185 self.traverse_fs_directory_and_dirstate(
220 self.traverse_fs_directory_and_dirstate(
186 is_ignored,
221 is_ignored,
187 dirstate_node.children(self.dmap.on_disk)?,
222 dirstate_node.children(self.dmap.on_disk)?,
188 hg_path,
223 hg_path,
189 &fs_entry.full_path,
224 fs_path,
190 is_at_repo_root,
225 is_at_repo_root,
191 )?
226 )?
192 } else {
227 } else {
193 if file_or_symlink && self.matcher.matches(hg_path) {
228 if file_or_symlink && self.matcher.matches(hg_path) {
194 let full_path = Cow::from(hg_path);
229 let full_path = Cow::from(hg_path);
195 if let Some(state) = dirstate_node.state()? {
230 if let Some(state) = dirstate_node.state()? {
196 match state {
231 match state {
197 EntryState::Added => {
232 EntryState::Added => {
198 self.outcome.lock().unwrap().added.push(full_path)
233 self.outcome.lock().unwrap().added.push(full_path)
199 }
234 }
200 EntryState::Removed => self
235 EntryState::Removed => self
201 .outcome
236 .outcome
202 .lock()
237 .lock()
203 .unwrap()
238 .unwrap()
204 .removed
239 .removed
205 .push(full_path),
240 .push(full_path),
206 EntryState::Merged => self
241 EntryState::Merged => self
207 .outcome
242 .outcome
208 .lock()
243 .lock()
209 .unwrap()
244 .unwrap()
210 .modified
245 .modified
211 .push(full_path),
246 .push(full_path),
212 EntryState::Normal => {
247 EntryState::Normal => self
213 self.handle_normal_file(&dirstate_node, fs_entry)?
248 .handle_normal_file(&dirstate_node, fs_metadata)?,
214 }
215 // This variant is not used in DirstateMap
249 // This variant is not used in DirstateMap
216 // nodes
250 // nodes
217 EntryState::Unknown => unreachable!(),
251 EntryState::Unknown => unreachable!(),
218 }
252 }
219 } else {
253 } else {
220 // `node.entry.is_none()` indicates a "directory"
254 // `node.entry.is_none()` indicates a "directory"
221 // node, but the filesystem has a file
255 // node, but the filesystem has a file
222 self.mark_unknown_or_ignored(
256 self.mark_unknown_or_ignored(
223 has_ignored_ancestor,
257 has_ignored_ancestor,
224 full_path,
258 full_path,
225 )
259 )
226 }
260 }
227 }
261 }
228
262
229 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
263 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
230 {
264 {
231 self.traverse_dirstate_only(child_node)?
265 self.traverse_dirstate_only(child_node)?
232 }
266 }
233 }
267 }
234 Ok(())
268 Ok(())
235 }
269 }
236
270
237 /// A file with `EntryState::Normal` in the dirstate was found in the
271 /// A file with `EntryState::Normal` in the dirstate was found in the
238 /// filesystem
272 /// filesystem
239 fn handle_normal_file(
273 fn handle_normal_file(
240 &self,
274 &self,
241 dirstate_node: &NodeRef<'tree, '_>,
275 dirstate_node: &NodeRef<'tree, '_>,
242 fs_entry: &DirEntry,
276 fs_metadata: &std::fs::Metadata,
243 ) -> Result<(), DirstateV2ParseError> {
277 ) -> Result<(), DirstateV2ParseError> {
244 // Keep the low 31 bits
278 // Keep the low 31 bits
245 fn truncate_u64(value: u64) -> i32 {
279 fn truncate_u64(value: u64) -> i32 {
246 (value & 0x7FFF_FFFF) as i32
280 (value & 0x7FFF_FFFF) as i32
247 }
281 }
248 fn truncate_i64(value: i64) -> i32 {
282 fn truncate_i64(value: i64) -> i32 {
249 (value & 0x7FFF_FFFF) as i32
283 (value & 0x7FFF_FFFF) as i32
250 }
284 }
251
285
252 let entry = dirstate_node
286 let entry = dirstate_node
253 .entry()?
287 .entry()?
254 .expect("handle_normal_file called with entry-less node");
288 .expect("handle_normal_file called with entry-less node");
255 let full_path = Cow::from(dirstate_node.full_path(self.dmap.on_disk)?);
289 let full_path = Cow::from(dirstate_node.full_path(self.dmap.on_disk)?);
256 let mode_changed = || {
290 let mode_changed =
257 self.options.check_exec && entry.mode_changed(&fs_entry.metadata)
291 || self.options.check_exec && entry.mode_changed(fs_metadata);
258 };
292 let size_changed = entry.size != truncate_u64(fs_metadata.len());
259 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len());
260 if entry.size >= 0
293 if entry.size >= 0
261 && size_changed
294 && size_changed
262 && fs_entry.metadata.file_type().is_symlink()
295 && fs_metadata.file_type().is_symlink()
263 {
296 {
264 // issue6456: Size returned may be longer due to encryption
297 // issue6456: Size returned may be longer due to encryption
265 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
298 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
266 self.outcome.lock().unwrap().unsure.push(full_path)
299 self.outcome.lock().unwrap().unsure.push(full_path)
267 } else if dirstate_node.has_copy_source()
300 } else if dirstate_node.has_copy_source()
268 || entry.is_from_other_parent()
301 || entry.is_from_other_parent()
269 || (entry.size >= 0 && (size_changed || mode_changed()))
302 || (entry.size >= 0 && (size_changed || mode_changed()))
270 {
303 {
271 self.outcome.lock().unwrap().modified.push(full_path)
304 self.outcome.lock().unwrap().modified.push(full_path)
272 } else {
305 } else {
273 let mtime = mtime_seconds(&fs_entry.metadata);
306 let mtime = mtime_seconds(fs_metadata);
274 if truncate_i64(mtime) != entry.mtime
307 if truncate_i64(mtime) != entry.mtime
275 || mtime == self.options.last_normal_time
308 || mtime == self.options.last_normal_time
276 {
309 {
277 self.outcome.lock().unwrap().unsure.push(full_path)
310 self.outcome.lock().unwrap().unsure.push(full_path)
278 } else if self.options.list_clean {
311 } else if self.options.list_clean {
279 self.outcome.lock().unwrap().clean.push(full_path)
312 self.outcome.lock().unwrap().clean.push(full_path)
280 }
313 }
281 }
314 }
282 Ok(())
315 Ok(())
283 }
316 }
284
317
285 /// A node in the dirstate tree has no corresponding filesystem entry
318 /// A node in the dirstate tree has no corresponding filesystem entry
286 fn traverse_dirstate_only(
319 fn traverse_dirstate_only(
287 &self,
320 &self,
288 dirstate_node: NodeRef<'tree, '_>,
321 dirstate_node: NodeRef<'tree, '_>,
289 ) -> Result<(), DirstateV2ParseError> {
322 ) -> Result<(), DirstateV2ParseError> {
290 self.mark_removed_or_deleted_if_file(
323 self.mark_removed_or_deleted_if_file(
291 dirstate_node.full_path(self.dmap.on_disk)?,
324 dirstate_node.full_path(self.dmap.on_disk)?,
292 dirstate_node.state()?,
325 dirstate_node.state()?,
293 );
326 );
294 dirstate_node
327 dirstate_node
295 .children(self.dmap.on_disk)?
328 .children(self.dmap.on_disk)?
296 .par_iter()
329 .par_iter()
297 .map(|child_node| self.traverse_dirstate_only(child_node))
330 .map(|child_node| self.traverse_dirstate_only(child_node))
298 .collect()
331 .collect()
299 }
332 }
300
333
301 /// A node in the dirstate tree has no corresponding *file* on the
334 /// A node in the dirstate tree has no corresponding *file* on the
302 /// filesystem
335 /// filesystem
303 ///
336 ///
304 /// Does nothing on a "directory" node
337 /// Does nothing on a "directory" node
305 fn mark_removed_or_deleted_if_file(
338 fn mark_removed_or_deleted_if_file(
306 &self,
339 &self,
307 hg_path: &'tree HgPath,
340 hg_path: &'tree HgPath,
308 dirstate_node_state: Option<EntryState>,
341 dirstate_node_state: Option<EntryState>,
309 ) {
342 ) {
310 if let Some(state) = dirstate_node_state {
343 if let Some(state) = dirstate_node_state {
311 if self.matcher.matches(hg_path) {
344 if self.matcher.matches(hg_path) {
312 if let EntryState::Removed = state {
345 if let EntryState::Removed = state {
313 self.outcome.lock().unwrap().removed.push(hg_path.into())
346 self.outcome.lock().unwrap().removed.push(hg_path.into())
314 } else {
347 } else {
315 self.outcome.lock().unwrap().deleted.push(hg_path.into())
348 self.outcome.lock().unwrap().deleted.push(hg_path.into())
316 }
349 }
317 }
350 }
318 }
351 }
319 }
352 }
320
353
321 /// Something in the filesystem has no corresponding dirstate node
354 /// Something in the filesystem has no corresponding dirstate node
322 fn traverse_fs_only(
355 fn traverse_fs_only(
323 &self,
356 &self,
324 has_ignored_ancestor: bool,
357 has_ignored_ancestor: bool,
325 directory_hg_path: &HgPath,
358 directory_hg_path: &HgPath,
326 fs_entry: &DirEntry,
359 fs_entry: &DirEntry,
327 ) {
360 ) {
328 let hg_path = directory_hg_path.join(&fs_entry.base_name);
361 let hg_path = directory_hg_path.join(&fs_entry.base_name);
329 let file_type = fs_entry.metadata.file_type();
362 let file_type = fs_entry.metadata.file_type();
330 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
363 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
331 if file_type.is_dir() {
364 if file_type.is_dir() {
332 let is_ignored =
365 let is_ignored =
333 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
366 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
334 let traverse_children = if is_ignored {
367 let traverse_children = if is_ignored {
335 // Descendants of an ignored directory are all ignored
368 // Descendants of an ignored directory are all ignored
336 self.options.list_ignored
369 self.options.list_ignored
337 } else {
370 } else {
338 // Descendants of an unknown directory may be either unknown or
371 // Descendants of an unknown directory may be either unknown or
339 // ignored
372 // ignored
340 self.options.list_unknown || self.options.list_ignored
373 self.options.list_unknown || self.options.list_ignored
341 };
374 };
342 if traverse_children {
375 if traverse_children {
343 let is_at_repo_root = false;
376 let is_at_repo_root = false;
344 if let Ok(children_fs_entries) = self.read_dir(
377 if let Ok(children_fs_entries) = self.read_dir(
345 &hg_path,
378 &hg_path,
346 &fs_entry.full_path,
379 &fs_entry.full_path,
347 is_at_repo_root,
380 is_at_repo_root,
348 ) {
381 ) {
349 children_fs_entries.par_iter().for_each(|child_fs_entry| {
382 children_fs_entries.par_iter().for_each(|child_fs_entry| {
350 self.traverse_fs_only(
383 self.traverse_fs_only(
351 is_ignored,
384 is_ignored,
352 &hg_path,
385 &hg_path,
353 child_fs_entry,
386 child_fs_entry,
354 )
387 )
355 })
388 })
356 }
389 }
357 }
390 }
358 if self.options.collect_traversed_dirs {
391 if self.options.collect_traversed_dirs {
359 self.outcome.lock().unwrap().traversed.push(hg_path.into())
392 self.outcome.lock().unwrap().traversed.push(hg_path.into())
360 }
393 }
361 } else if file_or_symlink && self.matcher.matches(&hg_path) {
394 } else if file_or_symlink && self.matcher.matches(&hg_path) {
362 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path.into())
395 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path.into())
363 }
396 }
364 }
397 }
365
398
366 fn mark_unknown_or_ignored(
399 fn mark_unknown_or_ignored(
367 &self,
400 &self,
368 has_ignored_ancestor: bool,
401 has_ignored_ancestor: bool,
369 hg_path: Cow<'tree, HgPath>,
402 hg_path: Cow<'tree, HgPath>,
370 ) {
403 ) {
371 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
404 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
372 if is_ignored {
405 if is_ignored {
373 if self.options.list_ignored {
406 if self.options.list_ignored {
374 self.outcome.lock().unwrap().ignored.push(hg_path)
407 self.outcome.lock().unwrap().ignored.push(hg_path)
375 }
408 }
376 } else {
409 } else {
377 if self.options.list_unknown {
410 if self.options.list_unknown {
378 self.outcome.lock().unwrap().unknown.push(hg_path)
411 self.outcome.lock().unwrap().unknown.push(hg_path)
379 }
412 }
380 }
413 }
381 }
414 }
382 }
415 }
383
416
384 #[cfg(unix)] // TODO
417 #[cfg(unix)] // TODO
385 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
418 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
386 // Going through `Metadata::modified()` would be portable, but would take
419 // Going through `Metadata::modified()` would be portable, but would take
387 // care to construct a `SystemTime` value with sub-second precision just
420 // care to construct a `SystemTime` value with sub-second precision just
388 // for us to throw that away here.
421 // for us to throw that away here.
389 use std::os::unix::fs::MetadataExt;
422 use std::os::unix::fs::MetadataExt;
390 metadata.mtime()
423 metadata.mtime()
391 }
424 }
392
425
393 struct DirEntry {
426 struct DirEntry {
394 base_name: HgPathBuf,
427 base_name: HgPathBuf,
395 full_path: PathBuf,
428 full_path: PathBuf,
396 metadata: std::fs::Metadata,
429 metadata: std::fs::Metadata,
397 }
430 }
398
431
399 impl DirEntry {
432 impl DirEntry {
400 /// Returns **unsorted** entries in the given directory, with name and
433 /// Returns **unsorted** entries in the given directory, with name and
401 /// metadata.
434 /// metadata.
402 ///
435 ///
403 /// If a `.hg` sub-directory is encountered:
436 /// If a `.hg` sub-directory is encountered:
404 ///
437 ///
405 /// * At the repository root, ignore that sub-directory
438 /// * At the repository root, ignore that sub-directory
406 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
439 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
407 /// list instead.
440 /// list instead.
408 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
441 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
409 let mut results = Vec::new();
442 let mut results = Vec::new();
410 for entry in path.read_dir()? {
443 for entry in path.read_dir()? {
411 let entry = entry?;
444 let entry = entry?;
412 let metadata = entry.metadata()?;
445 let metadata = entry.metadata()?;
413 let name = get_bytes_from_os_string(entry.file_name());
446 let name = get_bytes_from_os_string(entry.file_name());
414 // FIXME don't do this when cached
447 // FIXME don't do this when cached
415 if name == b".hg" {
448 if name == b".hg" {
416 if is_at_repo_root {
449 if is_at_repo_root {
417 // Skip the repo’s own .hg (might be a symlink)
450 // Skip the repo’s own .hg (might be a symlink)
418 continue;
451 continue;
419 } else if metadata.is_dir() {
452 } else if metadata.is_dir() {
420 // A .hg sub-directory at another location means a subrepo,
453 // A .hg sub-directory at another location means a subrepo,
421 // skip it entirely.
454 // skip it entirely.
422 return Ok(Vec::new());
455 return Ok(Vec::new());
423 }
456 }
424 }
457 }
425 results.push(DirEntry {
458 results.push(DirEntry {
426 base_name: name.into(),
459 base_name: name.into(),
427 full_path: entry.path(),
460 full_path: entry.path(),
428 metadata,
461 metadata,
429 })
462 })
430 }
463 }
431 Ok(results)
464 Ok(results)
432 }
465 }
433 }
466 }
General Comments 0
You need to be logged in to leave comments. Login now