Show More
@@ -11,18 +11,21 b'' | |||
|
11 | 11 | |
|
12 | 12 | use crate::{ |
|
13 | 13 | dirstate::SIZE_FROM_OTHER_PARENT, |
|
14 | matchers::Matcher, | |
|
14 | matchers::{Matcher, VisitChildrenSet}, | |
|
15 | 15 | utils::{ |
|
16 | 16 | files::HgMetadata, |
|
17 | 17 | hg_path::{ |
|
18 | 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 | 23 | use rayon::prelude::*; |
|
24 | use std::collections::HashSet; | |
|
24 | use std::borrow::Cow; | |
|
25 | use std::collections::{HashSet, VecDeque}; | |
|
25 | 26 | use std::fs::{read_dir, DirEntry}; |
|
27 | use std::io::ErrorKind; | |
|
28 | use std::ops::Deref; | |
|
26 | 29 | use std::path::Path; |
|
27 | 30 | |
|
28 | 31 | /// Wrong type of file from a `BadMatch` |
@@ -238,6 +241,178 b' pub struct StatusOptions {' | |||
|
238 | 241 | /// Whether we are on a filesystem with UNIX-like exec flags |
|
239 | 242 | pub check_exec: bool, |
|
240 | 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 | 418 | /// Stat all entries in the `DirstateMap` and mark them for dispatch into |
General Comments 0
You need to be logged in to leave comments.
Login now