--- 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};
--- /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<dyn Matcher>,
+}
+
+fn manifest_for_rev(repo: &Repo, rev: Revision) -> Result<Manifest, HgError> {
+ 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<dyn Matcher>,
+) -> Result<StatusRevRev, HgError> {
+ 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<Item = Result<(&HgPath, DiffStatus), HgError>> {
+ 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
+ })
+ })
+ }
+}
--- 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<Vec<String>>,
+) -> Result<Option<(Revision, Revision)>, 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::<String>("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) => {
--- 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
--- 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