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