# HG changeset patch # User Arseniy Alekseyev # Date 1700073783 0 # Node ID ac3859a8b79604f0039cfc700170a50f0595322f # Parent aba622c7dc7ea424e37c011b13c7003700960cfd rhg: support rhg status --rev --rev diff -r aba622c7dc7e -r ac3859a8b796 rust/hg-core/src/operations/mod.rs --- a/rust/hg-core/src/operations/mod.rs Wed Nov 15 18:41:33 2023 +0000 +++ b/rust/hg-core/src/operations/mod.rs Wed Nov 15 18:43:03 2023 +0000 @@ -5,6 +5,8 @@ mod cat; mod debugdata; mod list_tracked_files; +mod status_rev_rev; pub use cat::{cat, CatOutput}; pub use debugdata::{debug_data, DebugDataKind}; pub use list_tracked_files::{list_rev_tracked_files, FilesForRev}; +pub use status_rev_rev::{status_rev_rev_no_copies, DiffStatus, StatusRevRev}; diff -r aba622c7dc7e -r ac3859a8b796 rust/hg-core/src/operations/status_rev_rev.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/operations/status_rev_rev.rs Wed Nov 15 18:43:03 2023 +0000 @@ -0,0 +1,89 @@ +use crate::errors::HgError; +use crate::matchers::Matcher; +use crate::repo::Repo; +use crate::revlog::manifest::Manifest; +use crate::utils::filter_map_results; +use crate::utils::hg_path::HgPath; +use crate::utils::merge_join_results_by; + +use crate::Revision; + +use itertools::EitherOrBoth; + +#[derive(Debug, Copy, Clone)] +pub enum DiffStatus { + Removed, + Added, + Matching, + Modified, +} + +pub struct StatusRevRev { + manifest1: Manifest, + manifest2: Manifest, + narrow_matcher: Box, +} + +fn manifest_for_rev(repo: &Repo, rev: Revision) -> Result { + repo.manifest_for_rev(rev.into()).map_err(|e| { + HgError::corrupted(format!( + "manifest lookup failed for revision {}: {}", + rev, e + )) + }) +} + +pub fn status_rev_rev_no_copies( + repo: &Repo, + rev1: Revision, + rev2: Revision, + narrow_matcher: Box, +) -> Result { + let manifest1 = manifest_for_rev(repo, rev1)?; + let manifest2 = manifest_for_rev(repo, rev2)?; + Ok(StatusRevRev { + manifest1, + manifest2, + narrow_matcher, + }) +} + +impl StatusRevRev { + pub fn iter( + &self, + ) -> impl Iterator> { + let iter1 = self.manifest1.iter(); + let iter2 = self.manifest2.iter(); + + let merged = + 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) + } + EitherOrBoth::Both(entry1, entry2) => { + let path = entry1.path; + if entry1.node_id().unwrap() == entry2.node_id().unwrap() + && entry1.flags == entry2.flags + { + (path, DiffStatus::Matching) + } else { + (path, DiffStatus::Modified) + } + } + }; + Ok(if self.narrow_matcher.matches(path) { + Some((path, status)) + } else { + None + }) + }) + } +} diff -r aba622c7dc7e -r ac3859a8b796 rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs Wed Nov 15 18:41:33 2023 +0000 +++ b/rust/rhg/src/commands/status.rs Wed Nov 15 18:43:03 2023 +0000 @@ -30,11 +30,13 @@ use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; use hg::DirstateStatus; use hg::PatternFileWarning; +use hg::Revision; use hg::StatusError; use hg::StatusOptions; 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; @@ -141,6 +143,38 @@ .action(clap::ArgAction::SetTrue) .long("verbose"), ) + .arg( + Arg::new("rev") + .help("show difference from/to revision") + .long("rev") + .num_args(1) + .action(clap::ArgAction::Append) + .value_name("REV"), + ) +} + +fn parse_revpair( + repo: &Repo, + revs: Option>, +) -> Result, CommandError> { + let revs = match revs { + None => return Ok(None), + Some(revs) => revs, + }; + if revs.is_empty() { + return Ok(None); + } + if revs.len() != 2 { + return Err(CommandError::unsupported("expected 0 or 2 --rev flags")); + } + + let rev1 = &revs[0]; + let rev2 = &revs[1]; + let rev1 = hg::revset::resolve_single(rev1, repo) + .map_err(|e| (e, rev1.as_str()))?; + let rev2 = hg::revset::resolve_single(rev2, repo) + .map_err(|e| (e, rev2.as_str()))?; + Ok(Some((rev1, rev2))) } /// Pure data type allowing the caller to specify file states to display @@ -230,6 +264,7 @@ let config = invocation.config; let args = invocation.subcommand_args; + let revs = args.get_many::("rev"); let print0 = args.get_flag("print0"); let verbose = args.get_flag("verbose") || config.get_bool(b"ui", b"verbose")? @@ -263,6 +298,7 @@ || config.get_bool(b"ui", b"statuscopies")?; let repo = invocation.repo?; + let revpair = parse_revpair(repo, revs.map(|i| i.cloned().collect()))?; if verbose && has_unfinished_state(repo)? { return Err(CommandError::unsupported( @@ -407,6 +443,57 @@ )) }; let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?; + + match revpair { + Some((rev1, rev2)) => { + 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, + narrow_matcher, + )?; + 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 { + ds_status.removed.push(path) + } + } + hg::operations::DiffStatus::Added => { + if display_states.added { + ds_status.added.push(path) + } + } + hg::operations::DiffStatus::Modified => { + if display_states.modified { + ds_status.modified.push(path) + } + } + hg::operations::DiffStatus::Matching => { + if display_states.clean { + ds_status.clean.push(path) + } + } + } + } + output.output(display_states, ds_status)?; + return Ok(()); + } + None => (), + } + let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?; let matcher = match (repo.has_narrow(), repo.has_sparse()) { (true, true) => { diff -r aba622c7dc7e -r ac3859a8b796 rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs Wed Nov 15 18:41:33 2023 +0000 +++ b/rust/rhg/src/main.rs Wed Nov 15 18:43:03 2023 +0000 @@ -524,13 +524,20 @@ std::process::exit(exit_code(&result, use_detailed_exit_code)) } +mod commands { + pub mod cat; + pub mod config; + pub mod debugdata; + pub mod debugignorerhg; + pub mod debugrequirements; + pub mod debugrhgsparse; + pub mod files; + pub mod root; + pub mod status; +} + macro_rules! subcommands { ($( $command: ident )+) => { - mod commands { - $( - pub mod $command; - )+ - } fn add_subcommand_args(app: clap::Command) -> clap::Command { app diff -r aba622c7dc7e -r ac3859a8b796 tests/test-status-rev.t --- a/tests/test-status-rev.t Wed Nov 15 18:41:33 2023 +0000 +++ b/tests/test-status-rev.t Wed Nov 15 18:43:03 2023 +0000 @@ -88,6 +88,33 @@ Status between first and second commit. Should ignore dirstate status. + $ hg status -marc --rev 0 --rev 1 --config rhg.on-unsupported=abort + M content1_content2_content1-tracked + M content1_content2_content1-untracked + M content1_content2_content2-tracked + M content1_content2_content2-untracked + M content1_content2_content3-tracked + M content1_content2_content3-untracked + M content1_content2_missing-tracked + M content1_content2_missing-untracked + A missing_content2_content2-tracked + A missing_content2_content2-untracked + A missing_content2_content3-tracked + A missing_content2_content3-untracked + A missing_content2_missing-tracked + A missing_content2_missing-untracked + R content1_missing_content1-tracked + R content1_missing_content1-untracked + R content1_missing_content3-tracked + R content1_missing_content3-untracked + R content1_missing_missing-tracked + R content1_missing_missing-untracked + C content1_content1_content1-tracked + C content1_content1_content1-untracked + C content1_content1_content3-tracked + C content1_content1_content3-untracked + C content1_content1_missing-tracked + C content1_content1_missing-untracked $ hg status -A --rev 0:1 'glob:content1_content2_*' M content1_content2_content1-tracked M content1_content2_content1-untracked