Mercurial > hg
comparison rust/hg-core/src/dirstate/status.rs @ 44527:1debb5894b39
rust-status: add function for sequential traversal of the working directory
This change also introduces helper structs to make things clearer.
Differential Revision: https://phab.mercurial-scm.org/D7928
author | Raphaël Gomès <rgomes@octobus.net> |
---|---|
date | Thu, 06 Feb 2020 14:32:16 +0100 |
parents | 61709b844420 |
children | c8891bca40fb |
comparison
equal
deleted
inserted
replaced
44526:61709b844420 | 44527:1debb5894b39 |
---|---|
9 //! It is currently missing a lot of functionality compared to the Python one | 9 //! It is currently missing a lot of functionality compared to the Python one |
10 //! and will only be triggered in narrow cases. | 10 //! and will only be triggered in narrow cases. |
11 | 11 |
12 use crate::{ | 12 use crate::{ |
13 dirstate::SIZE_FROM_OTHER_PARENT, | 13 dirstate::SIZE_FROM_OTHER_PARENT, |
14 matchers::Matcher, | 14 matchers::{Matcher, VisitChildrenSet}, |
15 utils::{ | 15 utils::{ |
16 files::HgMetadata, | 16 files::HgMetadata, |
17 hg_path::{ | 17 hg_path::{ |
18 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf, | 18 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf, |
19 }, | 19 }, |
20 }, | 20 }, |
21 CopyMap, DirstateEntry, DirstateMap, EntryState, | 21 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap, |
22 }; | 22 }; |
23 use rayon::prelude::*; | 23 use rayon::prelude::*; |
24 use std::collections::HashSet; | 24 use std::borrow::Cow; |
25 use std::collections::{HashSet, VecDeque}; | |
25 use std::fs::{read_dir, DirEntry}; | 26 use std::fs::{read_dir, DirEntry}; |
27 use std::io::ErrorKind; | |
28 use std::ops::Deref; | |
26 use std::path::Path; | 29 use std::path::Path; |
27 | 30 |
28 /// Wrong type of file from a `BadMatch` | 31 /// Wrong type of file from a `BadMatch` |
29 /// Note: a lot of those don't exist on all platforms. | 32 /// Note: a lot of those don't exist on all platforms. |
30 #[derive(Debug)] | 33 #[derive(Debug)] |
236 /// that happen within the same timeslot. | 239 /// that happen within the same timeslot. |
237 pub last_normal_time: i64, | 240 pub last_normal_time: i64, |
238 /// Whether we are on a filesystem with UNIX-like exec flags | 241 /// Whether we are on a filesystem with UNIX-like exec flags |
239 pub check_exec: bool, | 242 pub check_exec: bool, |
240 pub list_clean: bool, | 243 pub list_clean: bool, |
244 pub list_unknown: bool, | |
245 pub list_ignored: bool, | |
246 } | |
247 | |
248 /// Dispatch a single file found during `traverse`. | |
249 /// If `file` is a folder that needs to be traversed, it will be pushed into | |
250 /// `work`. | |
251 fn traverse_worker<'a>( | |
252 work: &mut VecDeque<HgPathBuf>, | |
253 matcher: &impl Matcher, | |
254 dmap: &DirstateMap, | |
255 filename: impl AsRef<HgPath>, | |
256 dir_entry: &DirEntry, | |
257 ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool, | |
258 dir_ignore_fn: &impl for<'r> Fn(&'r HgPath) -> bool, | |
259 options: StatusOptions, | |
260 ) -> Option<IoResult<(Cow<'a, HgPath>, Dispatch)>> { | |
261 let file_type = match dir_entry.file_type() { | |
262 Ok(x) => x, | |
263 Err(e) => return Some(Err(e.into())), | |
264 }; | |
265 let filename = filename.as_ref(); | |
266 let entry_option = dmap.get(filename); | |
267 | |
268 if file_type.is_dir() { | |
269 // Do we need to traverse it? | |
270 if !ignore_fn(&filename) || options.list_ignored { | |
271 work.push_front(filename.to_owned()); | |
272 } | |
273 // Nested `if` until `rust-lang/rust#53668` is stable | |
274 if let Some(entry) = entry_option { | |
275 // Used to be a file, is now a folder | |
276 if matcher.matches_everything() || matcher.matches(&filename) { | |
277 return Some(Ok(( | |
278 Cow::Owned(filename.to_owned()), | |
279 dispatch_missing(entry.state), | |
280 ))); | |
281 } | |
282 } | |
283 } else if file_type.is_file() || file_type.is_symlink() { | |
284 if let Some(entry) = entry_option { | |
285 if matcher.matches_everything() || matcher.matches(&filename) { | |
286 let metadata = match dir_entry.metadata() { | |
287 Ok(x) => x, | |
288 Err(e) => return Some(Err(e.into())), | |
289 }; | |
290 return Some(Ok(( | |
291 Cow::Owned(filename.to_owned()), | |
292 dispatch_found( | |
293 &filename, | |
294 *entry, | |
295 HgMetadata::from_metadata(metadata), | |
296 &dmap.copy_map, | |
297 options, | |
298 ), | |
299 ))); | |
300 } | |
301 } else if (matcher.matches_everything() || matcher.matches(&filename)) | |
302 && !ignore_fn(&filename) | |
303 { | |
304 if (options.list_ignored || matcher.exact_match(&filename)) | |
305 && dir_ignore_fn(&filename) | |
306 { | |
307 if options.list_ignored { | |
308 return Some(Ok(( | |
309 Cow::Owned(filename.to_owned()), | |
310 Dispatch::Ignored, | |
311 ))); | |
312 } | |
313 } else { | |
314 return Some(Ok(( | |
315 Cow::Owned(filename.to_owned()), | |
316 Dispatch::Unknown, | |
317 ))); | |
318 } | |
319 } | |
320 } else if let Some(entry) = entry_option { | |
321 // Used to be a file or a folder, now something else. | |
322 if matcher.matches_everything() || matcher.matches(&filename) { | |
323 return Some(Ok(( | |
324 Cow::Owned(filename.to_owned()), | |
325 dispatch_missing(entry.state), | |
326 ))); | |
327 } | |
328 } | |
329 None | |
330 } | |
331 | |
332 /// Walk the working directory recursively to look for changes compared to the | |
333 /// current `DirstateMap`. | |
334 fn traverse<'a>( | |
335 matcher: &(impl Matcher + Sync), | |
336 root_dir: impl AsRef<Path>, | |
337 dmap: &DirstateMap, | |
338 path: impl AsRef<HgPath>, | |
339 old_results: FastHashMap<Cow<'a, HgPath>, Dispatch>, | |
340 ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync), | |
341 dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync), | |
342 options: StatusOptions, | |
343 ) -> IoResult<FastHashMap<Cow<'a, HgPath>, Dispatch>> { | |
344 let root_dir = root_dir.as_ref(); | |
345 let mut new_results = FastHashMap::default(); | |
346 | |
347 let mut work = VecDeque::new(); | |
348 work.push_front(path.as_ref().to_owned()); | |
349 | |
350 while let Some(ref directory) = work.pop_front() { | |
351 if directory.as_bytes() == b".hg" { | |
352 continue; | |
353 } | |
354 let visit_entries = match matcher.visit_children_set(directory) { | |
355 VisitChildrenSet::Empty => continue, | |
356 VisitChildrenSet::This | VisitChildrenSet::Recursive => None, | |
357 VisitChildrenSet::Set(set) => Some(set), | |
358 }; | |
359 let buf = hg_path_to_path_buf(directory)?; | |
360 let dir_path = root_dir.join(buf); | |
361 | |
362 let skip_dot_hg = !directory.as_bytes().is_empty(); | |
363 let entries = match list_directory(dir_path, skip_dot_hg) { | |
364 Err(e) => match e.kind() { | |
365 ErrorKind::NotFound | ErrorKind::PermissionDenied => { | |
366 new_results.insert( | |
367 Cow::Owned(directory.to_owned()), | |
368 Dispatch::Bad(BadMatch::OsError( | |
369 // Unwrapping here is OK because the error always | |
370 // is a real os error | |
371 e.raw_os_error().unwrap(), | |
372 )), | |
373 ); | |
374 continue; | |
375 } | |
376 _ => return Err(e), | |
377 }, | |
378 Ok(entries) => entries, | |
379 }; | |
380 | |
381 for (filename, dir_entry) in entries { | |
382 if let Some(ref set) = visit_entries { | |
383 if !set.contains(filename.deref()) { | |
384 continue; | |
385 } | |
386 } | |
387 // TODO normalize | |
388 let filename = if directory.is_empty() { | |
389 filename.to_owned() | |
390 } else { | |
391 directory.join(&filename) | |
392 }; | |
393 | |
394 if !old_results.contains_key(filename.deref()) { | |
395 if let Some((res, dispatch)) = traverse_worker( | |
396 &mut work, | |
397 matcher, | |
398 &dmap, | |
399 &filename, | |
400 &dir_entry, | |
401 &ignore_fn, | |
402 &dir_ignore_fn, | |
403 options, | |
404 ) | |
405 .transpose()? | |
406 { | |
407 new_results.insert(res, dispatch); | |
408 } | |
409 } | |
410 } | |
411 } | |
412 | |
413 new_results.extend(old_results.into_iter()); | |
414 | |
415 Ok(new_results) | |
241 } | 416 } |
242 | 417 |
243 /// Stat all entries in the `DirstateMap` and mark them for dispatch into | 418 /// Stat all entries in the `DirstateMap` and mark them for dispatch into |
244 /// the relevant collections. | 419 /// the relevant collections. |
245 fn stat_dmap_entries( | 420 fn stat_dmap_entries( |