rhg: add a limited `rhg cat -r` subcommand
authorAntoine Cezar <antoine.cezar@octobus.net>
Tue, 15 Sep 2020 16:51:11 +0200
changeset 45542 33ded2d3f4fc
parent 45541 522ec3dc44b9
child 45543 68906595016c
rhg: add a limited `rhg cat -r` subcommand It only supports revision specification (rev or full hash) and the list of files to cat. Differential Revision: https://phab.mercurial-scm.org/D9052
rust/rhg/src/commands.rs
rust/rhg/src/commands/cat.rs
rust/rhg/src/error.rs
rust/rhg/src/main.rs
--- a/rust/rhg/src/commands.rs	Fri Sep 11 17:32:53 2020 +0200
+++ b/rust/rhg/src/commands.rs	Tue Sep 15 16:51:11 2020 +0200
@@ -1,3 +1,4 @@
+pub mod cat;
 pub mod debugdata;
 pub mod files;
 pub mod root;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/rhg/src/commands/cat.rs	Tue Sep 15 16:51:11 2020 +0200
@@ -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(),
+                ))
+            }
+        },
+    }
+}
--- a/rust/rhg/src/error.rs	Fri Sep 11 17:32:53 2020 +0200
+++ b/rust/rhg/src/error.rs	Tue Sep 15 16:51:11 2020 +0200
@@ -18,6 +18,8 @@
     StderrError,
     /// The command aborted
     Abort(Option<Vec<u8>>),
+    /// A mercurial capability as not been implemented.
+    Unimplemented,
 }
 
 impl CommandErrorKind {
@@ -28,6 +30,7 @@
             CommandErrorKind::StdoutError => exitcode::ABORT,
             CommandErrorKind::StderrError => exitcode::ABORT,
             CommandErrorKind::Abort(_) => exitcode::ABORT,
+            CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
         }
     }
 
--- a/rust/rhg/src/main.rs	Fri Sep 11 17:32:53 2020 +0200
+++ b/rust/rhg/src/main.rs	Tue Sep 15 16:51:11 2020 +0200
@@ -38,6 +38,26 @@
                 .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 @@
         ("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 commands::cat::CatCommand<'a> {
+    type Error = CommandError;
+
+    fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
+        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>
 {