Mercurial > public > mercurial-scm > hg
diff 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 |
line wrap: on
line diff
--- a/rust/hg-core/src/dirstate_tree/status.rs Fri Dec 10 16:57:39 2021 +0100 +++ b/rust/hg-core/src/dirstate_tree/status.rs Fri Dec 10 16:18:58 2021 +0100 @@ -1,5 +1,6 @@ use crate::dirstate::entry::TruncatedTimestamp; use crate::dirstate::status::IgnoreFnType; +use crate::dirstate::status::StatusPath; use crate::dirstate_tree::dirstate_map::BorrowedPath; use crate::dirstate_tree::dirstate_map::ChildNodesRef; use crate::dirstate_tree::dirstate_map::DirstateMap; @@ -15,6 +16,7 @@ use crate::DirstateStatus; use crate::EntryState; use crate::HgPathBuf; +use crate::HgPathCow; use crate::PatternFileWarning; use crate::StatusError; use crate::StatusOptions; @@ -146,7 +148,65 @@ filesystem_time_at_status_start: Option<SystemTime>, } +enum Outcome { + Modified, + Added, + Removed, + Deleted, + Clean, + Ignored, + Unknown, + Unsure, +} + impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> { + fn push_outcome( + &self, + which: Outcome, + dirstate_node: &NodeRef<'tree, 'on_disk>, + ) -> Result<(), DirstateV2ParseError> { + let path = dirstate_node + .full_path_borrowed(self.dmap.on_disk)? + .detach_from_tree(); + let copy_source = if self.options.list_copies { + dirstate_node + .copy_source_borrowed(self.dmap.on_disk)? + .map(|source| source.detach_from_tree()) + } else { + None + }; + self.push_outcome_common(which, path, copy_source); + Ok(()) + } + + fn push_outcome_without_copy_source( + &self, + which: Outcome, + path: &BorrowedPath<'_, 'on_disk>, + ) { + self.push_outcome_common(which, path.detach_from_tree(), None) + } + + fn push_outcome_common( + &self, + which: Outcome, + path: HgPathCow<'on_disk>, + copy_source: Option<HgPathCow<'on_disk>>, + ) { + let mut outcome = self.outcome.lock().unwrap(); + let vec = match which { + Outcome::Modified => &mut outcome.modified, + Outcome::Added => &mut outcome.added, + Outcome::Removed => &mut outcome.removed, + Outcome::Deleted => &mut outcome.deleted, + Outcome::Clean => &mut outcome.clean, + Outcome::Ignored => &mut outcome.ignored, + Outcome::Unknown => &mut outcome.unknown, + Outcome::Unsure => &mut outcome.unsure, + }; + vec.push(StatusPath { path, copy_source }); + } + fn read_dir( &self, hg_path: &HgPath, @@ -347,10 +407,7 @@ // If we previously had a file here, it was removed (with // `hg rm` or similar) or deleted before it could be // replaced by a directory or something else. - self.mark_removed_or_deleted_if_file( - &hg_path, - dirstate_node.state()?, - ); + self.mark_removed_or_deleted_if_file(&dirstate_node)?; } if file_type.is_dir() { if self.options.collect_traversed_dirs { @@ -381,24 +438,13 @@ if file_or_symlink && self.matcher.matches(hg_path) { if let Some(state) = dirstate_node.state()? { match state { - EntryState::Added => self - .outcome - .lock() - .unwrap() - .added - .push(hg_path.detach_from_tree()), + EntryState::Added => { + self.push_outcome(Outcome::Added, &dirstate_node)? + } EntryState::Removed => self - .outcome - .lock() - .unwrap() - .removed - .push(hg_path.detach_from_tree()), + .push_outcome(Outcome::Removed, &dirstate_node)?, EntryState::Merged => self - .outcome - .lock() - .unwrap() - .modified - .push(hg_path.detach_from_tree()), + .push_outcome(Outcome::Modified, &dirstate_node)?, EntryState::Normal => self .handle_normal_file(&dirstate_node, fs_metadata)?, } @@ -510,7 +556,6 @@ let entry = dirstate_node .entry()? .expect("handle_normal_file called with entry-less node"); - let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?; let mode_changed = || self.options.check_exec && entry.mode_changed(fs_metadata); let size = entry.size(); @@ -518,20 +563,12 @@ if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() { // issue6456: Size returned may be longer due to encryption // on EXT-4 fscrypt. TODO maybe only do it on EXT4? - self.outcome - .lock() - .unwrap() - .unsure - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Unsure, dirstate_node)? } else if dirstate_node.has_copy_source() || entry.is_from_other_parent() || (size >= 0 && (size_changed || mode_changed())) { - self.outcome - .lock() - .unwrap() - .modified - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Modified, dirstate_node)? } else { let mtime_looks_clean; if let Some(dirstate_mtime) = entry.truncated_mtime() { @@ -548,17 +585,9 @@ mtime_looks_clean = false }; if !mtime_looks_clean { - self.outcome - .lock() - .unwrap() - .unsure - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Unsure, dirstate_node)? } else if self.options.list_clean { - self.outcome - .lock() - .unwrap() - .clean - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Clean, dirstate_node)? } } Ok(()) @@ -570,10 +599,7 @@ dirstate_node: NodeRef<'tree, 'on_disk>, ) -> Result<(), DirstateV2ParseError> { self.check_for_outdated_directory_cache(&dirstate_node)?; - self.mark_removed_or_deleted_if_file( - &dirstate_node.full_path_borrowed(self.dmap.on_disk)?, - dirstate_node.state()?, - ); + self.mark_removed_or_deleted_if_file(&dirstate_node)?; dirstate_node .children(self.dmap.on_disk)? .par_iter() @@ -587,26 +613,19 @@ /// Does nothing on a "directory" node fn mark_removed_or_deleted_if_file( &self, - hg_path: &BorrowedPath<'tree, 'on_disk>, - dirstate_node_state: Option<EntryState>, - ) { - if let Some(state) = dirstate_node_state { - if self.matcher.matches(hg_path) { + dirstate_node: &NodeRef<'tree, 'on_disk>, + ) -> Result<(), DirstateV2ParseError> { + if let Some(state) = dirstate_node.state()? { + let path = dirstate_node.full_path(self.dmap.on_disk)?; + if self.matcher.matches(path) { if let EntryState::Removed = state { - self.outcome - .lock() - .unwrap() - .removed - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Removed, dirstate_node)? } else { - self.outcome - .lock() - .unwrap() - .deleted - .push(hg_path.detach_from_tree()) + self.push_outcome(Outcome::Deleted, &dirstate_node)? } } } + Ok(()) } /// Something in the filesystem has no corresponding dirstate node @@ -684,19 +703,17 @@ let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path); if is_ignored { if self.options.list_ignored { - self.outcome - .lock() - .unwrap() - .ignored - .push(hg_path.detach_from_tree()) + self.push_outcome_without_copy_source( + Outcome::Ignored, + hg_path, + ) } } else { if self.options.list_unknown { - self.outcome - .lock() - .unwrap() - .unknown - .push(hg_path.detach_from_tree()) + self.push_outcome_without_copy_source( + Outcome::Unknown, + hg_path, + ) } } is_ignored