rhg: support "status FILE"
authorSpencer Baugh <sbaugh@janestreet.com>
Wed, 02 Aug 2023 10:46:47 -0400
changeset 50890 c112cc9effdc
parent 50889 f874342fa568
child 50891 788113f056d4
rhg: support "status FILE" This change adds a new [file] argument to rhg status, parses them as patterns, canonicalizes the paths, and constructs a new PatternMatcher to intersect with the existing matcher being passed to the status implementation. We also make filepatterns a public module so we can access the pattern-parsing functionality we need from commands/status.rs.
rust/hg-core/src/filepatterns.rs
rust/hg-core/src/lib.rs
rust/rhg/src/commands/status.rs
rust/rhg/src/error.rs
--- a/rust/hg-core/src/filepatterns.rs	Wed Aug 02 10:16:33 2023 -0400
+++ b/rust/hg-core/src/filepatterns.rs	Wed Aug 02 10:46:47 2023 -0400
@@ -366,6 +366,7 @@
     pattern: &[u8],
     source: &Path,
     default: PatternSyntax,
+    normalize: bool,
 ) -> IgnorePattern {
     let mut pattern_bytes: &[u8] = pattern;
     let mut syntax = default;
@@ -378,7 +379,19 @@
         }
     }
 
-    let pattern = pattern_bytes.to_vec();
+    let pattern = match syntax {
+        PatternSyntax::RootGlob
+        | PatternSyntax::Path
+        | PatternSyntax::Glob
+        | PatternSyntax::RelGlob
+        | PatternSyntax::RelPath
+        | PatternSyntax::RootFiles
+            if normalize =>
+        {
+            normalize_path_bytes(pattern_bytes)
+        }
+        _ => pattern_bytes.to_vec(),
+    };
 
     IgnorePattern {
         syntax,
@@ -438,6 +451,7 @@
                 line,
                 file_path,
                 current_syntax.clone(),
+                false,
             );
             inputs.push(if relativize {
                 pattern.to_relative()
@@ -449,6 +463,35 @@
     Ok((inputs, warnings))
 }
 
+pub fn parse_pattern_args(
+    patterns: Vec<Vec<u8>>,
+    cwd: &Path,
+    root: &Path,
+) -> Result<Vec<IgnorePattern>, HgPathError> {
+    let mut ignore_patterns: Vec<IgnorePattern> = Vec::new();
+    for pattern in patterns {
+        let pattern = parse_one_pattern(
+            &pattern,
+            Path::new("<args>"),
+            PatternSyntax::RelPath,
+            true,
+        );
+        match pattern.syntax {
+            PatternSyntax::RelGlob | PatternSyntax::RelPath => {
+                let name = get_path_from_bytes(&pattern.pattern);
+                let canon = canonical_path(root, cwd, name)?;
+                ignore_patterns.push(IgnorePattern {
+                    syntax: pattern.syntax,
+                    pattern: get_bytes_from_path(canon),
+                    source: pattern.source,
+                })
+            }
+            _ => ignore_patterns.push(pattern.to_owned()),
+        };
+    }
+    Ok(ignore_patterns)
+}
+
 pub fn read_pattern_file(
     file_path: &Path,
     warn: bool,
--- a/rust/hg-core/src/lib.rs	Wed Aug 02 10:16:33 2023 -0400
+++ b/rust/hg-core/src/lib.rs	Wed Aug 02 10:46:47 2023 -0400
@@ -25,7 +25,7 @@
     DirstateEntry, DirstateParents, EntryState,
 };
 pub mod copy_tracing;
-mod filepatterns;
+pub mod filepatterns;
 pub mod matchers;
 pub mod repo;
 pub mod revlog;
--- a/rust/rhg/src/commands/status.rs	Wed Aug 02 10:16:33 2023 -0400
+++ b/rust/rhg/src/commands/status.rs	Wed Aug 02 10:46:47 2023 -0400
@@ -18,13 +18,15 @@
 use hg::dirstate::status::StatusPath;
 use hg::dirstate::TruncatedTimestamp;
 use hg::errors::{HgError, IoResultExt};
+use hg::filepatterns::parse_pattern_args;
 use hg::lock::LockError;
 use hg::manifest::Manifest;
 use hg::matchers::{AlwaysMatcher, IntersectionMatcher};
 use hg::repo::Repo;
 use hg::utils::debug::debug_wait_for_file;
-use hg::utils::files::get_bytes_from_os_string;
-use hg::utils::files::get_path_from_bytes;
+use hg::utils::files::{
+    get_bytes_from_os_str, get_bytes_from_os_string, get_path_from_bytes,
+};
 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
 use hg::DirstateStatus;
 use hg::PatternFileWarning;
@@ -49,6 +51,12 @@
         .alias("st")
         .about(HELP_TEXT)
         .arg(
+            Arg::new("file")
+                .value_parser(clap::value_parser!(std::ffi::OsString))
+                .help("show only these files")
+                .action(clap::ArgAction::Append),
+        )
+        .arg(
             Arg::new("all")
                 .help("show status of all files")
                 .short('A')
@@ -367,10 +375,8 @@
             .expect("commands.status.relative should have a default value");
 
         let relativize_paths = relative_status || {
-            // TODO should be dependent on whether patterns are passed once
-            // we support those.
             // See in Python code with `getuipathfn` usage in `commands.py`.
-            let legacy_relative_behavior = false;
+            let legacy_relative_behavior = args.contains_id("file");
             match relative_paths(invocation.config)? {
                 RelativePaths::Legacy => legacy_relative_behavior,
                 RelativePaths::Bool(v) => v,
@@ -429,6 +435,29 @@
         (false, true) => sparse_matcher,
         (false, false) => Box::new(AlwaysMatcher),
     };
+    let matcher = match args.get_many::<std::ffi::OsString>("file") {
+        None => matcher,
+        Some(files) => {
+            let patterns: Vec<Vec<u8>> = files
+                .filter(|s| !s.is_empty())
+                .map(get_bytes_from_os_str)
+                .collect();
+            for file in &patterns {
+                if file.starts_with(b"set:") {
+                    return Err(CommandError::unsupported("fileset"));
+                }
+            }
+            let cwd = hg::utils::current_dir()?;
+            let root = repo.working_directory_path();
+            let ignore_patterns = parse_pattern_args(patterns, &cwd, root)?;
+            let files_matcher =
+                hg::matchers::PatternMatcher::new(ignore_patterns)?;
+            Box::new(IntersectionMatcher::new(
+                Box::new(files_matcher),
+                matcher,
+            ))
+        }
+    };
 
     print_narrow_sparse_warnings(
         &narrow_warnings,
--- a/rust/rhg/src/error.rs	Wed Aug 02 10:16:33 2023 -0400
+++ b/rust/rhg/src/error.rs	Wed Aug 02 10:46:47 2023 -0400
@@ -10,7 +10,8 @@
 use hg::revlog::RevlogError;
 use hg::sparse::SparseConfigError;
 use hg::utils::files::get_bytes_from_path;
-use hg::{DirstateError, DirstateMapError, StatusError};
+use hg::utils::hg_path::HgPathError;
+use hg::{DirstateError, DirstateMapError, PatternError, StatusError};
 use std::convert::From;
 
 /// The kind of command error
@@ -230,6 +231,18 @@
     }
 }
 
+impl From<HgPathError> for CommandError {
+    fn from(error: HgPathError) -> Self {
+        CommandError::unsupported(format!("{}", error))
+    }
+}
+
+impl From<PatternError> for CommandError {
+    fn from(error: PatternError) -> Self {
+        CommandError::unsupported(format!("{}", error))
+    }
+}
+
 impl From<DirstateMapError> for CommandError {
     fn from(error: DirstateMapError) -> Self {
         CommandError::abort(format!("{}", error))