19 use hg::errors::{HgError, IoResultExt}; |
19 use hg::errors::{HgError, IoResultExt}; |
20 use hg::lock::LockError; |
20 use hg::lock::LockError; |
21 use hg::manifest::Manifest; |
21 use hg::manifest::Manifest; |
22 use hg::matchers::{AlwaysMatcher, IntersectionMatcher}; |
22 use hg::matchers::{AlwaysMatcher, IntersectionMatcher}; |
23 use hg::repo::Repo; |
23 use hg::repo::Repo; |
|
24 use hg::utils::debug::debug_wait_for_file; |
24 use hg::utils::files::get_bytes_from_os_string; |
25 use hg::utils::files::get_bytes_from_os_string; |
25 use hg::utils::files::get_path_from_bytes; |
26 use hg::utils::files::get_path_from_bytes; |
26 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; |
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; |
27 use hg::DirstateStatus; |
28 use hg::DirstateStatus; |
28 use hg::PatternFileWarning; |
29 use hg::PatternFileWarning; |
306 let store_vfs = repo.store_vfs(); |
307 let store_vfs = repo.store_vfs(); |
307 let res: Vec<_> = ds_status |
308 let res: Vec<_> = ds_status |
308 .unsure |
309 .unsure |
309 .into_par_iter() |
310 .into_par_iter() |
310 .map(|to_check| { |
311 .map(|to_check| { |
311 unsure_is_modified( |
312 // The compiler seems to get a bit confused with complex |
|
313 // inference when using a parallel iterator + map |
|
314 // + map_err + collect, so let's just inline some of the |
|
315 // logic. |
|
316 match unsure_is_modified( |
312 working_directory_vfs, |
317 working_directory_vfs, |
313 store_vfs, |
318 store_vfs, |
314 check_exec, |
319 check_exec, |
315 &manifest, |
320 &manifest, |
316 &to_check.path, |
321 &to_check.path, |
317 ) |
322 ) { |
318 .map(|modified| (to_check, modified)) |
323 Err(HgError::IoError { .. }) => { |
|
324 // IO errors most likely stem from the file being |
|
325 // deleted even though we know it's in the |
|
326 // dirstate. |
|
327 Ok((to_check, UnsureOutcome::Deleted)) |
|
328 } |
|
329 Ok(outcome) => Ok((to_check, outcome)), |
|
330 Err(e) => Err(e), |
|
331 } |
319 }) |
332 }) |
320 .collect::<Result<_, _>>()?; |
333 .collect::<Result<_, _>>()?; |
321 for (status_path, is_modified) in res.into_iter() { |
334 for (status_path, outcome) in res.into_iter() { |
322 if is_modified { |
335 match outcome { |
323 if display_states.modified { |
336 UnsureOutcome::Clean => { |
324 ds_status.modified.push(status_path); |
337 if display_states.clean { |
|
338 ds_status.clean.push(status_path.clone()); |
|
339 } |
|
340 fixup.push(status_path.path.into_owned()) |
325 } |
341 } |
326 } else { |
342 UnsureOutcome::Modified => { |
327 if display_states.clean { |
343 if display_states.modified { |
328 ds_status.clean.push(status_path.clone()); |
344 ds_status.modified.push(status_path); |
|
345 } |
329 } |
346 } |
330 fixup.push(status_path.path.into_owned()) |
347 UnsureOutcome::Deleted => { |
|
348 if display_states.deleted { |
|
349 ds_status.deleted.push(status_path); |
|
350 } |
|
351 } |
331 } |
352 } |
332 } |
353 } |
333 } |
354 } |
334 let relative_paths = config |
355 let relative_paths = config |
335 .get_option(b"commands", b"status.relative")? |
356 .get_option(b"commands", b"status.relative")? |
399 ignore_files(repo, config), |
420 ignore_files(repo, config), |
400 options, |
421 options, |
401 after_status, |
422 after_status, |
402 )?; |
423 )?; |
403 |
424 |
|
425 // Development config option to test write races |
|
426 if let Err(e) = |
|
427 debug_wait_for_file(config, "status.pre-dirstate-write-file") |
|
428 { |
|
429 ui.write_stderr(e.as_bytes()).ok(); |
|
430 } |
|
431 |
404 if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) |
432 if (fixup.is_empty() || filesystem_time_at_status_start.is_none()) |
405 && !dirstate_write_needed |
433 && !dirstate_write_needed |
406 { |
434 { |
407 // Nothing to update |
435 // Nothing to update |
408 return Ok(()); |
436 return Ok(()); |
418 .expect("HgPath conversion"); |
446 .expect("HgPath conversion"); |
419 // Specifically do not reuse `fs_metadata` from |
447 // Specifically do not reuse `fs_metadata` from |
420 // `unsure_is_clean` which was needed before reading |
448 // `unsure_is_clean` which was needed before reading |
421 // contents. Here we access metadata again after reading |
449 // contents. Here we access metadata again after reading |
422 // content, in case it changed in the meantime. |
450 // content, in case it changed in the meantime. |
423 let fs_metadata = repo |
451 let metadata_res = repo |
424 .working_directory_vfs() |
452 .working_directory_vfs() |
425 .symlink_metadata(&fs_path)?; |
453 .symlink_metadata(&fs_path); |
|
454 let fs_metadata = match metadata_res { |
|
455 Ok(meta) => meta, |
|
456 Err(err) => match err { |
|
457 HgError::IoError { .. } => { |
|
458 // The file has probably been deleted. In any |
|
459 // case, it was in the dirstate before, so |
|
460 // let's ignore the error. |
|
461 continue; |
|
462 } |
|
463 _ => return Err(err.into()), |
|
464 }, |
|
465 }; |
426 if let Some(mtime) = |
466 if let Some(mtime) = |
427 TruncatedTimestamp::for_reliable_mtime_of( |
467 TruncatedTimestamp::for_reliable_mtime_of( |
428 &fs_metadata, |
468 &fs_metadata, |
429 &mtime_boundary, |
469 &mtime_boundary, |
430 ) |
470 ) |
447 Ok(closure_result) => closure_result?, |
487 Ok(closure_result) => closure_result?, |
448 Err(LockError::AlreadyHeld) => { |
488 Err(LockError::AlreadyHeld) => { |
449 // Not updating the dirstate is not ideal but not critical: |
489 // Not updating the dirstate is not ideal but not critical: |
450 // don’t keep our caller waiting until some other Mercurial |
490 // don’t keep our caller waiting until some other Mercurial |
451 // process releases the lock. |
491 // process releases the lock. |
|
492 log::info!("not writing dirstate from `status`: lock is held") |
452 } |
493 } |
453 Err(LockError::Other(HgError::IoError { error, .. })) |
494 Err(LockError::Other(HgError::IoError { error, .. })) |
454 if error.kind() == io::ErrorKind::PermissionDenied => |
495 if error.kind() == io::ErrorKind::PermissionDenied => |
455 { |
496 { |
456 // `hg status` on a read-only repository is fine |
497 // `hg status` on a read-only repository is fine |
526 } |
567 } |
527 Ok(()) |
568 Ok(()) |
528 } |
569 } |
529 } |
570 } |
530 |
571 |
|
572 /// Outcome of the additional check for an ambiguous tracked file |
|
573 enum UnsureOutcome { |
|
574 /// The file is actually clean |
|
575 Clean, |
|
576 /// The file has been modified |
|
577 Modified, |
|
578 /// The file was deleted on disk (or became another type of fs entry) |
|
579 Deleted, |
|
580 } |
|
581 |
531 /// Check if a file is modified by comparing actual repo store and file system. |
582 /// Check if a file is modified by comparing actual repo store and file system. |
532 /// |
583 /// |
533 /// This meant to be used for those that the dirstate cannot resolve, due |
584 /// This meant to be used for those that the dirstate cannot resolve, due |
534 /// to time resolution limits. |
585 /// to time resolution limits. |
535 fn unsure_is_modified( |
586 fn unsure_is_modified( |
536 working_directory_vfs: hg::vfs::Vfs, |
587 working_directory_vfs: hg::vfs::Vfs, |
537 store_vfs: hg::vfs::Vfs, |
588 store_vfs: hg::vfs::Vfs, |
538 check_exec: bool, |
589 check_exec: bool, |
539 manifest: &Manifest, |
590 manifest: &Manifest, |
540 hg_path: &HgPath, |
591 hg_path: &HgPath, |
541 ) -> Result<bool, HgError> { |
592 ) -> Result<UnsureOutcome, HgError> { |
542 let vfs = working_directory_vfs; |
593 let vfs = working_directory_vfs; |
543 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion"); |
594 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion"); |
544 let fs_metadata = vfs.symlink_metadata(&fs_path)?; |
595 let fs_metadata = vfs.symlink_metadata(&fs_path)?; |
545 let is_symlink = fs_metadata.file_type().is_symlink(); |
596 let is_symlink = fs_metadata.file_type().is_symlink(); |
546 |
597 |
565 } else { |
616 } else { |
566 entry.flags |
617 entry.flags |
567 }; |
618 }; |
568 |
619 |
569 if entry_flags != fs_flags { |
620 if entry_flags != fs_flags { |
570 return Ok(true); |
621 return Ok(UnsureOutcome::Modified); |
571 } |
622 } |
572 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?; |
623 let filelog = hg::filelog::Filelog::open_vfs(&store_vfs, hg_path)?; |
573 let fs_len = fs_metadata.len(); |
624 let fs_len = fs_metadata.len(); |
574 let file_node = entry.node_id()?; |
625 let file_node = entry.node_id()?; |
575 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| { |
626 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| { |
579 )) |
630 )) |
580 })?; |
631 })?; |
581 if filelog_entry.file_data_len_not_equal_to(fs_len) { |
632 if filelog_entry.file_data_len_not_equal_to(fs_len) { |
582 // No need to read file contents: |
633 // No need to read file contents: |
583 // it cannot be equal if it has a different length. |
634 // it cannot be equal if it has a different length. |
584 return Ok(true); |
635 return Ok(UnsureOutcome::Modified); |
585 } |
636 } |
586 |
637 |
587 let p1_filelog_data = filelog_entry.data()?; |
638 let p1_filelog_data = filelog_entry.data()?; |
588 let p1_contents = p1_filelog_data.file_data()?; |
639 let p1_contents = p1_filelog_data.file_data()?; |
589 if p1_contents.len() as u64 != fs_len { |
640 if p1_contents.len() as u64 != fs_len { |
590 // No need to read file contents: |
641 // No need to read file contents: |
591 // it cannot be equal if it has a different length. |
642 // it cannot be equal if it has a different length. |
592 return Ok(true); |
643 return Ok(UnsureOutcome::Modified); |
593 } |
644 } |
594 |
645 |
595 let fs_contents = if is_symlink { |
646 let fs_contents = if is_symlink { |
596 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string()) |
647 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string()) |
597 } else { |
648 } else { |
598 vfs.read(fs_path)? |
649 vfs.read(fs_path)? |
599 }; |
650 }; |
600 Ok(p1_contents != &*fs_contents) |
651 |
601 } |
652 Ok(if p1_contents != &*fs_contents { |
|
653 UnsureOutcome::Modified |
|
654 } else { |
|
655 UnsureOutcome::Clean |
|
656 }) |
|
657 } |