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