comparison rust/hg-core/src/dirstate_tree/status.rs @ 48454:473af5cbc209

rhg: Add support for `rhg status --copies` Copy sources are collected during `status()` rather than after the fact like in Python, because `status()` takes a `&mut` exclusive reference to the dirstate map (in order to potentially mutate it for directory mtimes) and returns `Cow<'_, HgPath>` that borrow the dirstate map. Even though with `Cow` only some shared borrows remain, the still extend the same lifetime of the initial `&mut` so the dirstate map cannot be borrowed again to access copy sources after the fact: https://doc.rust-lang.org/nomicon/lifetime-mismatch.html#limits-of-lifetimes Additionally, collecting copy sources during the dirstate tree traversal that `status()` already does avoids the cost of another traversal or other lookups (though I haven’t benchmarked that cost). Differential Revision: https://phab.mercurial-scm.org/D11899
author Simon Sapin <simon.sapin@octobus.net>
date Fri, 10 Dec 2021 16:18:58 +0100
parents 000130cfafb6
children 4afb9627dc77
comparison
equal deleted inserted replaced
48453:9b0e1f64656f 48454:473af5cbc209
1 use crate::dirstate::entry::TruncatedTimestamp; 1 use crate::dirstate::entry::TruncatedTimestamp;
2 use crate::dirstate::status::IgnoreFnType; 2 use crate::dirstate::status::IgnoreFnType;
3 use crate::dirstate::status::StatusPath;
3 use crate::dirstate_tree::dirstate_map::BorrowedPath; 4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
4 use crate::dirstate_tree::dirstate_map::ChildNodesRef; 5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
5 use crate::dirstate_tree::dirstate_map::DirstateMap; 6 use crate::dirstate_tree::dirstate_map::DirstateMap;
6 use crate::dirstate_tree::dirstate_map::NodeData; 7 use crate::dirstate_tree::dirstate_map::NodeData;
7 use crate::dirstate_tree::dirstate_map::NodeRef; 8 use crate::dirstate_tree::dirstate_map::NodeRef;
13 use crate::utils::hg_path::HgPath; 14 use crate::utils::hg_path::HgPath;
14 use crate::BadMatch; 15 use crate::BadMatch;
15 use crate::DirstateStatus; 16 use crate::DirstateStatus;
16 use crate::EntryState; 17 use crate::EntryState;
17 use crate::HgPathBuf; 18 use crate::HgPathBuf;
19 use crate::HgPathCow;
18 use crate::PatternFileWarning; 20 use crate::PatternFileWarning;
19 use crate::StatusError; 21 use crate::StatusError;
20 use crate::StatusOptions; 22 use crate::StatusOptions;
21 use micro_timer::timed; 23 use micro_timer::timed;
22 use rayon::prelude::*; 24 use rayon::prelude::*;
144 /// The current time at the start of the `status()` algorithm, as measured 146 /// The current time at the start of the `status()` algorithm, as measured
145 /// and possibly truncated by the filesystem. 147 /// and possibly truncated by the filesystem.
146 filesystem_time_at_status_start: Option<SystemTime>, 148 filesystem_time_at_status_start: Option<SystemTime>,
147 } 149 }
148 150
151 enum Outcome {
152 Modified,
153 Added,
154 Removed,
155 Deleted,
156 Clean,
157 Ignored,
158 Unknown,
159 Unsure,
160 }
161
149 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> { 162 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
163 fn push_outcome(
164 &self,
165 which: Outcome,
166 dirstate_node: &NodeRef<'tree, 'on_disk>,
167 ) -> Result<(), DirstateV2ParseError> {
168 let path = dirstate_node
169 .full_path_borrowed(self.dmap.on_disk)?
170 .detach_from_tree();
171 let copy_source = if self.options.list_copies {
172 dirstate_node
173 .copy_source_borrowed(self.dmap.on_disk)?
174 .map(|source| source.detach_from_tree())
175 } else {
176 None
177 };
178 self.push_outcome_common(which, path, copy_source);
179 Ok(())
180 }
181
182 fn push_outcome_without_copy_source(
183 &self,
184 which: Outcome,
185 path: &BorrowedPath<'_, 'on_disk>,
186 ) {
187 self.push_outcome_common(which, path.detach_from_tree(), None)
188 }
189
190 fn push_outcome_common(
191 &self,
192 which: Outcome,
193 path: HgPathCow<'on_disk>,
194 copy_source: Option<HgPathCow<'on_disk>>,
195 ) {
196 let mut outcome = self.outcome.lock().unwrap();
197 let vec = match which {
198 Outcome::Modified => &mut outcome.modified,
199 Outcome::Added => &mut outcome.added,
200 Outcome::Removed => &mut outcome.removed,
201 Outcome::Deleted => &mut outcome.deleted,
202 Outcome::Clean => &mut outcome.clean,
203 Outcome::Ignored => &mut outcome.ignored,
204 Outcome::Unknown => &mut outcome.unknown,
205 Outcome::Unsure => &mut outcome.unsure,
206 };
207 vec.push(StatusPath { path, copy_source });
208 }
209
150 fn read_dir( 210 fn read_dir(
151 &self, 211 &self,
152 hg_path: &HgPath, 212 hg_path: &HgPath,
153 fs_path: &Path, 213 fs_path: &Path,
154 is_at_repo_root: bool, 214 is_at_repo_root: bool,
345 let file_or_symlink = file_type.is_file() || file_type.is_symlink(); 405 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
346 if !file_or_symlink { 406 if !file_or_symlink {
347 // If we previously had a file here, it was removed (with 407 // If we previously had a file here, it was removed (with
348 // `hg rm` or similar) or deleted before it could be 408 // `hg rm` or similar) or deleted before it could be
349 // replaced by a directory or something else. 409 // replaced by a directory or something else.
350 self.mark_removed_or_deleted_if_file( 410 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
351 &hg_path,
352 dirstate_node.state()?,
353 );
354 } 411 }
355 if file_type.is_dir() { 412 if file_type.is_dir() {
356 if self.options.collect_traversed_dirs { 413 if self.options.collect_traversed_dirs {
357 self.outcome 414 self.outcome
358 .lock() 415 .lock()
379 )? 436 )?
380 } else { 437 } else {
381 if file_or_symlink && self.matcher.matches(hg_path) { 438 if file_or_symlink && self.matcher.matches(hg_path) {
382 if let Some(state) = dirstate_node.state()? { 439 if let Some(state) = dirstate_node.state()? {
383 match state { 440 match state {
384 EntryState::Added => self 441 EntryState::Added => {
385 .outcome 442 self.push_outcome(Outcome::Added, &dirstate_node)?
386 .lock() 443 }
387 .unwrap()
388 .added
389 .push(hg_path.detach_from_tree()),
390 EntryState::Removed => self 444 EntryState::Removed => self
391 .outcome 445 .push_outcome(Outcome::Removed, &dirstate_node)?,
392 .lock()
393 .unwrap()
394 .removed
395 .push(hg_path.detach_from_tree()),
396 EntryState::Merged => self 446 EntryState::Merged => self
397 .outcome 447 .push_outcome(Outcome::Modified, &dirstate_node)?,
398 .lock()
399 .unwrap()
400 .modified
401 .push(hg_path.detach_from_tree()),
402 EntryState::Normal => self 448 EntryState::Normal => self
403 .handle_normal_file(&dirstate_node, fs_metadata)?, 449 .handle_normal_file(&dirstate_node, fs_metadata)?,
404 } 450 }
405 } else { 451 } else {
406 // `node.entry.is_none()` indicates a "directory" 452 // `node.entry.is_none()` indicates a "directory"
508 } 554 }
509 555
510 let entry = dirstate_node 556 let entry = dirstate_node
511 .entry()? 557 .entry()?
512 .expect("handle_normal_file called with entry-less node"); 558 .expect("handle_normal_file called with entry-less node");
513 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
514 let mode_changed = 559 let mode_changed =
515 || self.options.check_exec && entry.mode_changed(fs_metadata); 560 || self.options.check_exec && entry.mode_changed(fs_metadata);
516 let size = entry.size(); 561 let size = entry.size();
517 let size_changed = size != truncate_u64(fs_metadata.len()); 562 let size_changed = size != truncate_u64(fs_metadata.len());
518 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() { 563 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
519 // issue6456: Size returned may be longer due to encryption 564 // issue6456: Size returned may be longer due to encryption
520 // on EXT-4 fscrypt. TODO maybe only do it on EXT4? 565 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
521 self.outcome 566 self.push_outcome(Outcome::Unsure, dirstate_node)?
522 .lock()
523 .unwrap()
524 .unsure
525 .push(hg_path.detach_from_tree())
526 } else if dirstate_node.has_copy_source() 567 } else if dirstate_node.has_copy_source()
527 || entry.is_from_other_parent() 568 || entry.is_from_other_parent()
528 || (size >= 0 && (size_changed || mode_changed())) 569 || (size >= 0 && (size_changed || mode_changed()))
529 { 570 {
530 self.outcome 571 self.push_outcome(Outcome::Modified, dirstate_node)?
531 .lock()
532 .unwrap()
533 .modified
534 .push(hg_path.detach_from_tree())
535 } else { 572 } else {
536 let mtime_looks_clean; 573 let mtime_looks_clean;
537 if let Some(dirstate_mtime) = entry.truncated_mtime() { 574 if let Some(dirstate_mtime) = entry.truncated_mtime() {
538 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata) 575 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata)
539 .expect("OS/libc does not support mtime?"); 576 .expect("OS/libc does not support mtime?");
546 } else { 583 } else {
547 // No mtime in the dirstate entry 584 // No mtime in the dirstate entry
548 mtime_looks_clean = false 585 mtime_looks_clean = false
549 }; 586 };
550 if !mtime_looks_clean { 587 if !mtime_looks_clean {
551 self.outcome 588 self.push_outcome(Outcome::Unsure, dirstate_node)?
552 .lock()
553 .unwrap()
554 .unsure
555 .push(hg_path.detach_from_tree())
556 } else if self.options.list_clean { 589 } else if self.options.list_clean {
557 self.outcome 590 self.push_outcome(Outcome::Clean, dirstate_node)?
558 .lock()
559 .unwrap()
560 .clean
561 .push(hg_path.detach_from_tree())
562 } 591 }
563 } 592 }
564 Ok(()) 593 Ok(())
565 } 594 }
566 595
568 fn traverse_dirstate_only( 597 fn traverse_dirstate_only(
569 &self, 598 &self,
570 dirstate_node: NodeRef<'tree, 'on_disk>, 599 dirstate_node: NodeRef<'tree, 'on_disk>,
571 ) -> Result<(), DirstateV2ParseError> { 600 ) -> Result<(), DirstateV2ParseError> {
572 self.check_for_outdated_directory_cache(&dirstate_node)?; 601 self.check_for_outdated_directory_cache(&dirstate_node)?;
573 self.mark_removed_or_deleted_if_file( 602 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
574 &dirstate_node.full_path_borrowed(self.dmap.on_disk)?,
575 dirstate_node.state()?,
576 );
577 dirstate_node 603 dirstate_node
578 .children(self.dmap.on_disk)? 604 .children(self.dmap.on_disk)?
579 .par_iter() 605 .par_iter()
580 .map(|child_node| self.traverse_dirstate_only(child_node)) 606 .map(|child_node| self.traverse_dirstate_only(child_node))
581 .collect() 607 .collect()
585 /// filesystem 611 /// filesystem
586 /// 612 ///
587 /// Does nothing on a "directory" node 613 /// Does nothing on a "directory" node
588 fn mark_removed_or_deleted_if_file( 614 fn mark_removed_or_deleted_if_file(
589 &self, 615 &self,
590 hg_path: &BorrowedPath<'tree, 'on_disk>, 616 dirstate_node: &NodeRef<'tree, 'on_disk>,
591 dirstate_node_state: Option<EntryState>, 617 ) -> Result<(), DirstateV2ParseError> {
592 ) { 618 if let Some(state) = dirstate_node.state()? {
593 if let Some(state) = dirstate_node_state { 619 let path = dirstate_node.full_path(self.dmap.on_disk)?;
594 if self.matcher.matches(hg_path) { 620 if self.matcher.matches(path) {
595 if let EntryState::Removed = state { 621 if let EntryState::Removed = state {
596 self.outcome 622 self.push_outcome(Outcome::Removed, dirstate_node)?
597 .lock()
598 .unwrap()
599 .removed
600 .push(hg_path.detach_from_tree())
601 } else { 623 } else {
602 self.outcome 624 self.push_outcome(Outcome::Deleted, &dirstate_node)?
603 .lock() 625 }
604 .unwrap() 626 }
605 .deleted 627 }
606 .push(hg_path.detach_from_tree()) 628 Ok(())
607 }
608 }
609 }
610 } 629 }
611 630
612 /// Something in the filesystem has no corresponding dirstate node 631 /// Something in the filesystem has no corresponding dirstate node
613 /// 632 ///
614 /// Returns whether that path is ignored 633 /// Returns whether that path is ignored
682 hg_path: &BorrowedPath<'_, 'on_disk>, 701 hg_path: &BorrowedPath<'_, 'on_disk>,
683 ) -> bool { 702 ) -> bool {
684 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path); 703 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
685 if is_ignored { 704 if is_ignored {
686 if self.options.list_ignored { 705 if self.options.list_ignored {
687 self.outcome 706 self.push_outcome_without_copy_source(
688 .lock() 707 Outcome::Ignored,
689 .unwrap() 708 hg_path,
690 .ignored 709 )
691 .push(hg_path.detach_from_tree())
692 } 710 }
693 } else { 711 } else {
694 if self.options.list_unknown { 712 if self.options.list_unknown {
695 self.outcome 713 self.push_outcome_without_copy_source(
696 .lock() 714 Outcome::Unknown,
697 .unwrap() 715 hg_path,
698 .unknown 716 )
699 .push(hg_path.detach_from_tree())
700 } 717 }
701 } 718 }
702 is_ignored 719 is_ignored
703 } 720 }
704 } 721 }