comparison rust/rhg/src/commands/status.rs @ 50252:a6b8b1ab9116

branching: merge stable into default The clippy god had to be appeased on some aspect.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 02 Mar 2023 19:02:52 +0100
parents 1cffc156f7cd 8fcd5302243a
children 668a871454e8 98fc949bec14
comparison
equal deleted inserted replaced
50251:2fbc109fd58a 50252:a6b8b1ab9116
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 }