diff --git a/rust/rhg/src/commands/cat.rs b/rust/rhg/src/commands/cat.rs --- a/rust/rhg/src/commands/cat.rs +++ b/rust/rhg/src/commands/cat.rs @@ -1,10 +1,9 @@ use crate::error::CommandError; +use crate::utils::path_utils::resolve_file_args; use clap::Arg; use format_bytes::format_bytes; use hg::operations::cat; -use hg::utils::hg_path::HgPathBuf; use std::ffi::OsString; -use std::os::unix::prelude::OsStrExt; pub const HELP_TEXT: &str = " Output the current or given revision of files @@ -40,52 +39,15 @@ pub fn run(invocation: &crate::CliInvoca )); } - let rev = invocation.subcommand_args.get_one::("rev"); - let file_args = - match invocation.subcommand_args.get_many::("files") { - Some(files) => files - .filter(|s| !s.is_empty()) - .map(|s| s.as_os_str()) - .collect(), - None => vec![], - }; - let repo = invocation.repo?; - let cwd = hg::utils::current_dir()?; - let working_directory = repo.working_directory_path(); - let working_directory = cwd.join(working_directory); // Make it absolute - - let mut files = vec![]; - for file in file_args { - if file.as_bytes().starts_with(b"set:") { - let message = "fileset"; - return Err(CommandError::unsupported(message)); - } - let normalized = cwd.join(file); - // TODO: actually normalize `..` path segments etc? - let dotted = normalized.components().any(|c| c.as_os_str() == ".."); - if file.as_bytes() == b"." || dotted { - let message = "`..` or `.` path segment"; - return Err(CommandError::unsupported(message)); - } - let relative_path = working_directory - .strip_prefix(&cwd) - .unwrap_or(&working_directory); - let stripped = normalized - .strip_prefix(&working_directory) - .map_err(|_| { - CommandError::abort(format!( - "abort: {} not under root '{}'\n(consider using '--cwd {}')", - String::from_utf8_lossy(file.as_bytes()), - working_directory.display(), - relative_path.display(), - )) - })?; - let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) - .map_err(|e| CommandError::abort(e.to_string()))?; - files.push(hg_file); - } + let rev = invocation.subcommand_args.get_one::("rev"); + let files = match invocation.subcommand_args.get_many::("files") + { + None => vec![], + Some(files) => resolve_file_args(repo, files)?, + }; + let files = files.iter().map(|file| file.as_ref()).collect(); // TODO probably move this to a util function like `repo.default_rev` or // something when it's used somewhere else diff --git a/rust/rhg/src/utils/path_utils.rs b/rust/rhg/src/utils/path_utils.rs --- a/rust/rhg/src/utils/path_utils.rs +++ b/rust/rhg/src/utils/path_utils.rs @@ -10,6 +10,9 @@ use hg::utils::files::{get_bytes_from_pa use hg::utils::hg_path::HgPath; use hg::utils::hg_path::HgPathBuf; use std::borrow::Cow; +use std::ffi::OsString; + +use crate::error::CommandError; pub struct RelativizePaths { repo_root: HgPathBuf, @@ -53,3 +56,41 @@ impl RelativizePaths { } } } + +/// Resolves `FILE ...` arguments to a list of paths in the repository. +pub fn resolve_file_args<'a>( + repo: &Repo, + file_args: impl Iterator, +) -> Result, CommandError> { + let cwd = hg::utils::current_dir()?; + let root = cwd.join(repo.working_directory_path()); + let mut result = Vec::new(); + for pattern in file_args { + // TODO: Support all the formats in `hg help patterns`. + if pattern.as_encoded_bytes().contains(&b':') { + return Err(CommandError::unsupported( + "rhg does not support file patterns", + )); + } + // TODO: use hg::utils::files::canonical_path (currently doesn't work). + let path = cwd.join(pattern); + let dotted = path.components().any(|c| c.as_os_str() == ".."); + if pattern.as_encoded_bytes() == b"." || dotted { + let message = "`..` or `.` path segment"; + return Err(CommandError::unsupported(message)); + } + let relative_path = root.strip_prefix(&cwd).unwrap_or(&root); + let stripped = path.strip_prefix(&root).map_err(|_| { + CommandError::abort(format!( + "abort: {} not under root '{}'\n(consider using '--cwd {}')", + String::from_utf8_lossy(pattern.as_encoded_bytes()), + root.display(), + relative_path.display(), + )) + })?; + let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) + .map_err(|e| CommandError::abort(e.to_string()))?; + result.push(hg_file); + } + Ok(result) +} diff --git a/tests/test-rhg.t b/tests/test-rhg.t --- a/tests/test-rhg.t +++ b/tests/test-rhg.t @@ -251,7 +251,7 @@ Fallback with shell path segments Fallback with filesets $ $NO_FALLBACK rhg cat "set:c or b" - unsupported feature: fileset + unsupported feature: rhg does not support file patterns [252] Fallback with generic hooks