Mercurial > public > mercurial-scm > hg
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 } |