Mercurial > public > mercurial-scm > hg
changeset 52398:bde718849153
rhg: support status --change, including --copies
It works by parsing copy information from filelog metadata headers. The
--rev --rev --copies case still falls back to Python since that will
require constructing a map like pathcopies does in copies.py.
As in Python, rhg by default only reports copies for newly added files. With
devel.copy-tracing.trace-all-files=True, it also does it for modified files.
author | Mitchell Kember <mkember@janestreet.com> |
---|---|
date | Tue, 03 Dec 2024 09:44:57 -0500 |
parents | 42bd36bbed67 |
children | 5ff6fba7c4c5 |
files | rust/hg-core/src/operations/mod.rs rust/hg-core/src/operations/status_rev_rev.rs rust/rhg/src/commands/status.rs |
diffstat | 3 files changed, 133 insertions(+), 40 deletions(-) [+] |
line wrap: on
line diff
--- a/rust/hg-core/src/operations/mod.rs Tue Dec 03 09:37:34 2024 -0500 +++ b/rust/hg-core/src/operations/mod.rs Tue Dec 03 09:44:57 2024 -0500 @@ -12,4 +12,7 @@ list_rev_tracked_files, list_revset_tracked_files, ExpandedManifestEntry, FilesForRev, }; -pub use status_rev_rev::{status_rev_rev_no_copies, DiffStatus, StatusRevRev}; +pub use status_rev_rev::{ + status_change, status_rev_rev_no_copies, DiffStatus, ListCopies, + StatusRevRev, +};
--- a/rust/hg-core/src/operations/status_rev_rev.rs Tue Dec 03 09:37:34 2024 -0500 +++ b/rust/hg-core/src/operations/status_rev_rev.rs Tue Dec 03 09:44:57 2024 -0500 @@ -1,12 +1,15 @@ +use std::borrow::Cow; + +use crate::dirstate::status::StatusPath; use crate::errors::HgError; use crate::matchers::Matcher; use crate::repo::Repo; -use crate::revlog::manifest::Manifest; +use crate::revlog::manifest::{Manifest, ManifestEntry}; use crate::utils::filter_map_results; -use crate::utils::hg_path::HgPath; +use crate::utils::hg_path::{HgPath, HgPathBuf}; use crate::utils::merge_join_results_by; -use crate::Revision; +use crate::{Revision, NULL_REVISION}; use itertools::EitherOrBoth; @@ -18,10 +21,27 @@ Modified, } -pub struct StatusRevRev { +/// What copy/rename information to report. +pub enum ListCopies { + /// Report copies only for added files. + Added, + /// Report copies for files that are added or modified. + AddedOrModified, +} + +/// Strategy for determining a file's copy source. +enum CopyStrategy<'a> { + /// Use the [`Repo`] to look up copy information in filelog metadata. + /// Assumes we are producing the status for a single changeset. + Change(&'a Repo), + // TODO: For --rev --rev --copies use a precomputed copy map +} + +pub struct StatusRevRev<'a> { manifest1: Manifest, manifest2: Manifest, narrow_matcher: Box<dyn Matcher>, + copies: Option<(ListCopies, CopyStrategy<'a>)>, } fn manifest_for_rev(repo: &Repo, rev: Revision) -> Result<Manifest, HgError> { @@ -39,19 +59,36 @@ rev2: Revision, narrow_matcher: Box<dyn Matcher>, ) -> Result<StatusRevRev, HgError> { - let manifest1 = manifest_for_rev(repo, rev1)?; - let manifest2 = manifest_for_rev(repo, rev2)?; Ok(StatusRevRev { - manifest1, - manifest2, + manifest1: manifest_for_rev(repo, rev1)?, + manifest2: manifest_for_rev(repo, rev2)?, narrow_matcher, + copies: None, }) } -impl StatusRevRev { +/// Computes the status of `rev` against its first parent. +pub fn status_change( + repo: &Repo, + rev: Revision, + narrow_matcher: Box<dyn Matcher>, + list_copies: Option<ListCopies>, +) -> Result<StatusRevRev, HgError> { + let parent = repo.changelog()?.revlog.get_entry(rev)?.p1(); + let parent = parent.unwrap_or(NULL_REVISION); + Ok(StatusRevRev { + manifest1: manifest_for_rev(repo, parent)?, + manifest2: manifest_for_rev(repo, rev)?, + narrow_matcher, + copies: list_copies.map(|list| (list, CopyStrategy::Change(repo))), + }) +} + +impl StatusRevRev<'_> { pub fn iter( &self, - ) -> impl Iterator<Item = Result<(&HgPath, DiffStatus), HgError>> { + ) -> impl Iterator<Item = Result<(StatusPath<'_>, DiffStatus), HgError>> + { let iter1 = self.manifest1.iter(); let iter2 = self.manifest2.iter(); @@ -59,15 +96,9 @@ merge_join_results_by(iter1, iter2, |i1, i2| i1.path.cmp(i2.path)); filter_map_results(merged, |entry| { - let (path, status) = match entry { - EitherOrBoth::Left(entry) => { - let path = entry.path; - (path, DiffStatus::Removed) - } - EitherOrBoth::Right(entry) => { - let path = entry.path; - (path, DiffStatus::Added) - } + let (path, status) = match &entry { + EitherOrBoth::Left(entry) => (entry.path, DiffStatus::Removed), + EitherOrBoth::Right(entry) => (entry.path, DiffStatus::Added), EitherOrBoth::Both(entry1, entry2) => { let path = entry1.path; if entry1.node_id().unwrap() == entry2.node_id().unwrap() @@ -79,11 +110,50 @@ } } }; - Ok(if self.narrow_matcher.matches(path) { - Some((path, status)) - } else { - None - }) + if !self.narrow_matcher.matches(path) { + return Ok(None); + } + let path = StatusPath { + path: Cow::Borrowed(path), + copy_source: self + .find_copy_source(path, status, entry.right().as_ref())? + .map(Cow::Owned), + }; + Ok(Some((path, status))) }) } + + /// Returns the path that a file was copied from, if it should be reported. + fn find_copy_source( + &self, + path: &HgPath, + status: DiffStatus, + entry: Option<&ManifestEntry>, + ) -> Result<Option<HgPathBuf>, HgError> { + let Some(entry) = entry else { return Ok(None) }; + let Some((list, strategy)) = &self.copies else { + return Ok(None); + }; + match (list, status) { + (ListCopies::Added, DiffStatus::Added) => {} + ( + ListCopies::AddedOrModified, + DiffStatus::Added | DiffStatus::Modified, + ) => {} + _ => return Ok(None), + } + match strategy { + CopyStrategy::Change(repo) => { + let data = repo + .filelog(path)? + .data_for_node(entry.node_id().unwrap())?; + if let Some(copy) = data.metadata()?.parse()?.copy { + if self.manifest1.find_by_path(copy)?.is_some() { + return Ok(Some(copy.to_owned())); + } + } + } + } + Ok(None) + } }
--- a/rust/rhg/src/commands/status.rs Tue Dec 03 09:37:34 2024 -0500 +++ b/rust/rhg/src/commands/status.rs Tue Dec 03 09:44:57 2024 -0500 @@ -35,7 +35,6 @@ use hg::{self, narrow, sparse}; use log::info; use rayon::prelude::*; -use std::borrow::Cow; use std::io; use std::mem::take; use std::path::PathBuf; @@ -156,6 +155,13 @@ .action(clap::ArgAction::Append) .value_name("REV"), ) + .arg( + Arg::new("change") + .help("list the changed files of a revision") + .long("change") + .value_name("REV") + .conflicts_with("rev"), + ) } fn parse_revpair( @@ -263,6 +269,7 @@ let args = invocation.subcommand_args; let revs = args.get_many::<String>("rev"); + let change = args.get_one::<String>("change"); let print0 = args.get_flag("print0"); let verbose = args.get_flag("verbose") || config.get_bool(b"ui", b"verbose")? @@ -302,6 +309,9 @@ let repo = invocation.repo?; let revpair = parse_revpair(repo, revs.map(|i| i.cloned().collect()))?; + let change = change + .map(|rev| hg::revset::resolve_single(rev, repo)) + .transpose()?; if verbose && has_unfinished_state(repo)? { return Err(CommandError::unsupported( @@ -490,23 +500,33 @@ repo, )?; - if let Some((rev1, rev2)) = revpair { + if revpair.is_some() || change.is_some() { let mut ds_status = DirstateStatus::default(); - if list_copies { - return Err(CommandError::unsupported( - "status --rev --rev with copy information is not implemented yet", - )); - } - - let stat = hg::operations::status_rev_rev_no_copies( - repo, rev1, rev2, matcher, - )?; + let list_copies = if list_copies { + if config.get_bool(b"devel", b"copy-tracing.trace-all-files")? { + Some(hg::operations::ListCopies::AddedOrModified) + } else { + Some(hg::operations::ListCopies::Added) + } + } else { + None + }; + let stat = if let Some((rev1, rev2)) = revpair { + if list_copies.is_some() { + return Err(CommandError::unsupported( + "status --rev --rev with copy information is not implemented yet", + )); + } + hg::operations::status_rev_rev_no_copies( + repo, rev1, rev2, matcher, + )? + } else if let Some(rev) = change { + hg::operations::status_change(repo, rev, matcher, list_copies)? + } else { + unreachable!(); + }; for entry in stat.iter() { let (path, status) = entry?; - let path = StatusPath { - path: Cow::Borrowed(path), - copy_source: None, - }; match status { hg::operations::DiffStatus::Removed => { if display_states.removed {