diff --git a/rust/rhg/src/commands.rs b/rust/rhg/src/commands.rs --- a/rust/rhg/src/commands.rs +++ b/rust/rhg/src/commands.rs @@ -1,3 +1,4 @@ +pub mod cat; pub mod debugdata; pub mod files; pub mod root; diff --git a/rust/rhg/src/commands/cat.rs b/rust/rhg/src/commands/cat.rs new file mode 100644 --- /dev/null +++ b/rust/rhg/src/commands/cat.rs @@ -0,0 +1,99 @@ +use crate::commands::Command; +use crate::error::{CommandError, CommandErrorKind}; +use crate::ui::utf8_to_local; +use crate::ui::Ui; +use hg::operations::FindRoot; +use hg::operations::{CatRev, CatRevError, CatRevErrorKind}; +use hg::utils::hg_path::HgPathBuf; +use micro_timer::timed; +use std::convert::TryFrom; + +pub const HELP_TEXT: &str = " +Output the current or given revision of files +"; + +pub struct CatCommand<'a> { + rev: Option<&'a str>, + files: Vec<&'a str>, +} + +impl<'a> CatCommand<'a> { + pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self { + Self { rev, files } + } + + fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> { + ui.write_stdout(data)?; + Ok(()) + } +} + +impl<'a> Command for CatCommand<'a> { + #[timed] + fn run(&self, ui: &Ui) -> Result<(), CommandError> { + let root = FindRoot::new().run()?; + let cwd = std::env::current_dir() + .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?; + + let mut files = vec![]; + for file in self.files.iter() { + let normalized = cwd.join(&file); + let stripped = normalized + .strip_prefix(&root) + .map_err(|_| CommandErrorKind::Abort(None))?; + let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) + .map_err(|_| CommandErrorKind::Abort(None))?; + files.push(hg_file); + } + + match self.rev { + Some(rev) => { + let mut operation = CatRev::new(&root, rev, &files) + .map_err(|e| map_rev_error(rev, e))?; + let data = + operation.run().map_err(|e| map_rev_error(rev, e))?; + self.display(ui, &data) + } + None => Err(CommandErrorKind::Unimplemented.into()), + } + } +} + +/// Convert `CatRevErrorKind` to `CommandError` +fn map_rev_error(rev: &str, err: CatRevError) -> CommandError { + CommandError { + kind: match err.kind { + CatRevErrorKind::IoError(err) => CommandErrorKind::Abort(Some( + utf8_to_local(&format!("abort: {}\n", err)).into(), + )), + CatRevErrorKind::InvalidRevision => CommandErrorKind::Abort(Some( + utf8_to_local(&format!( + "abort: invalid revision identifier{}\n", + rev + )) + .into(), + )), + CatRevErrorKind::UnsuportedRevlogVersion(version) => { + CommandErrorKind::Abort(Some( + utf8_to_local(&format!( + "abort: unsupported revlog version {}\n", + version + )) + .into(), + )) + } + CatRevErrorKind::CorruptedRevlog => CommandErrorKind::Abort(Some( + "abort: corrupted revlog\n".into(), + )), + CatRevErrorKind::UnknowRevlogDataFormat(format) => { + CommandErrorKind::Abort(Some( + utf8_to_local(&format!( + "abort: unknow revlog dataformat {:?}\n", + format + )) + .into(), + )) + } + }, + } +} diff --git a/rust/rhg/src/error.rs b/rust/rhg/src/error.rs --- a/rust/rhg/src/error.rs +++ b/rust/rhg/src/error.rs @@ -18,6 +18,8 @@ pub enum CommandErrorKind { StderrError, /// The command aborted Abort(Option>), + /// A mercurial capability as not been implemented. + Unimplemented, } impl CommandErrorKind { @@ -28,6 +30,7 @@ impl CommandErrorKind { CommandErrorKind::StdoutError => exitcode::ABORT, CommandErrorKind::StderrError => exitcode::ABORT, CommandErrorKind::Abort(_) => exitcode::ABORT, + CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND, } } diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs --- a/rust/rhg/src/main.rs +++ b/rust/rhg/src/main.rs @@ -38,6 +38,26 @@ fn main() { .about(commands::files::HELP_TEXT), ) .subcommand( + SubCommand::with_name("cat") + .arg( + Arg::with_name("rev") + .help("search the repository as it is in REV") + .short("-r") + .long("--revision") + .value_name("REV") + .takes_value(true), + ) + .arg( + clap::Arg::with_name("files") + .required(true) + .multiple(true) + .empty_values(false) + .value_name("FILE") + .help("Activity to start: activity@category"), + ) + .about(commands::cat::HELP_TEXT), + ) + .subcommand( SubCommand::with_name("debugdata") .about(commands::debugdata::HELP_TEXT) .arg( @@ -98,6 +118,9 @@ fn match_subcommand( ("files", Some(matches)) => { commands::files::FilesCommand::try_from(matches)?.run(&ui) } + ("cat", Some(matches)) => { + commands::cat::CatCommand::try_from(matches)?.run(&ui) + } ("debugdata", Some(matches)) => { commands::debugdata::DebugDataCommand::try_from(matches)?.run(&ui) } @@ -114,6 +137,19 @@ impl<'a> TryFrom<&'a ArgMatches<'_>> for } } +impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::cat::CatCommand<'a> { + type Error = CommandError; + + fn try_from(args: &'a ArgMatches) -> Result { + let rev = args.value_of("rev"); + let files = match args.values_of("files") { + Some(files) => files.collect(), + None => vec![], + }; + Ok(commands::cat::CatCommand::new(rev, files)) + } +} + impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::debugdata::DebugDataCommand<'a> {