##// END OF EJS Templates
rhg: add a limited `rhg cat -r` subcommand...
Antoine Cezar -
r46113:33ded2d3 default
parent child Browse files
Show More
@@ -0,0 +1,99 b''
1 use crate::commands::Command;
2 use crate::error::{CommandError, CommandErrorKind};
3 use crate::ui::utf8_to_local;
4 use crate::ui::Ui;
5 use hg::operations::FindRoot;
6 use hg::operations::{CatRev, CatRevError, CatRevErrorKind};
7 use hg::utils::hg_path::HgPathBuf;
8 use micro_timer::timed;
9 use std::convert::TryFrom;
10
11 pub const HELP_TEXT: &str = "
12 Output the current or given revision of files
13 ";
14
15 pub struct CatCommand<'a> {
16 rev: Option<&'a str>,
17 files: Vec<&'a str>,
18 }
19
20 impl<'a> CatCommand<'a> {
21 pub fn new(rev: Option<&'a str>, files: Vec<&'a str>) -> Self {
22 Self { rev, files }
23 }
24
25 fn display(&self, ui: &Ui, data: &[u8]) -> Result<(), CommandError> {
26 ui.write_stdout(data)?;
27 Ok(())
28 }
29 }
30
31 impl<'a> Command for CatCommand<'a> {
32 #[timed]
33 fn run(&self, ui: &Ui) -> Result<(), CommandError> {
34 let root = FindRoot::new().run()?;
35 let cwd = std::env::current_dir()
36 .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?;
37
38 let mut files = vec![];
39 for file in self.files.iter() {
40 let normalized = cwd.join(&file);
41 let stripped = normalized
42 .strip_prefix(&root)
43 .map_err(|_| CommandErrorKind::Abort(None))?;
44 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
45 .map_err(|_| CommandErrorKind::Abort(None))?;
46 files.push(hg_file);
47 }
48
49 match self.rev {
50 Some(rev) => {
51 let mut operation = CatRev::new(&root, rev, &files)
52 .map_err(|e| map_rev_error(rev, e))?;
53 let data =
54 operation.run().map_err(|e| map_rev_error(rev, e))?;
55 self.display(ui, &data)
56 }
57 None => Err(CommandErrorKind::Unimplemented.into()),
58 }
59 }
60 }
61
62 /// Convert `CatRevErrorKind` to `CommandError`
63 fn map_rev_error(rev: &str, err: CatRevError) -> CommandError {
64 CommandError {
65 kind: match err.kind {
66 CatRevErrorKind::IoError(err) => CommandErrorKind::Abort(Some(
67 utf8_to_local(&format!("abort: {}\n", err)).into(),
68 )),
69 CatRevErrorKind::InvalidRevision => CommandErrorKind::Abort(Some(
70 utf8_to_local(&format!(
71 "abort: invalid revision identifier{}\n",
72 rev
73 ))
74 .into(),
75 )),
76 CatRevErrorKind::UnsuportedRevlogVersion(version) => {
77 CommandErrorKind::Abort(Some(
78 utf8_to_local(&format!(
79 "abort: unsupported revlog version {}\n",
80 version
81 ))
82 .into(),
83 ))
84 }
85 CatRevErrorKind::CorruptedRevlog => CommandErrorKind::Abort(Some(
86 "abort: corrupted revlog\n".into(),
87 )),
88 CatRevErrorKind::UnknowRevlogDataFormat(format) => {
89 CommandErrorKind::Abort(Some(
90 utf8_to_local(&format!(
91 "abort: unknow revlog dataformat {:?}\n",
92 format
93 ))
94 .into(),
95 ))
96 }
97 },
98 }
99 }
@@ -1,12 +1,13 b''
1 pub mod cat;
1 pub mod debugdata;
2 pub mod debugdata;
2 pub mod files;
3 pub mod files;
3 pub mod root;
4 pub mod root;
4 use crate::error::CommandError;
5 use crate::error::CommandError;
5 use crate::ui::Ui;
6 use crate::ui::Ui;
6
7
7 /// The common trait for rhg commands
8 /// The common trait for rhg commands
8 ///
9 ///
9 /// Normalize the interface of the commands provided by rhg
10 /// Normalize the interface of the commands provided by rhg
10 pub trait Command {
11 pub trait Command {
11 fn run(&self, ui: &Ui) -> Result<(), CommandError>;
12 fn run(&self, ui: &Ui) -> Result<(), CommandError>;
12 }
13 }
@@ -1,110 +1,113 b''
1 use crate::exitcode;
1 use crate::exitcode;
2 use crate::ui::UiError;
2 use crate::ui::UiError;
3 use hg::operations::{FindRootError, FindRootErrorKind};
3 use hg::operations::{FindRootError, FindRootErrorKind};
4 use hg::utils::files::get_bytes_from_path;
4 use hg::utils::files::get_bytes_from_path;
5 use std::convert::From;
5 use std::convert::From;
6 use std::path::PathBuf;
6 use std::path::PathBuf;
7
7
8 /// The kind of command error
8 /// The kind of command error
9 #[derive(Debug)]
9 #[derive(Debug)]
10 pub enum CommandErrorKind {
10 pub enum CommandErrorKind {
11 /// The root of the repository cannot be found
11 /// The root of the repository cannot be found
12 RootNotFound(PathBuf),
12 RootNotFound(PathBuf),
13 /// The current directory cannot be found
13 /// The current directory cannot be found
14 CurrentDirNotFound(std::io::Error),
14 CurrentDirNotFound(std::io::Error),
15 /// The standard output stream cannot be written to
15 /// The standard output stream cannot be written to
16 StdoutError,
16 StdoutError,
17 /// The standard error stream cannot be written to
17 /// The standard error stream cannot be written to
18 StderrError,
18 StderrError,
19 /// The command aborted
19 /// The command aborted
20 Abort(Option<Vec<u8>>),
20 Abort(Option<Vec<u8>>),
21 /// A mercurial capability as not been implemented.
22 Unimplemented,
21 }
23 }
22
24
23 impl CommandErrorKind {
25 impl CommandErrorKind {
24 pub fn get_exit_code(&self) -> exitcode::ExitCode {
26 pub fn get_exit_code(&self) -> exitcode::ExitCode {
25 match self {
27 match self {
26 CommandErrorKind::RootNotFound(_) => exitcode::ABORT,
28 CommandErrorKind::RootNotFound(_) => exitcode::ABORT,
27 CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT,
29 CommandErrorKind::CurrentDirNotFound(_) => exitcode::ABORT,
28 CommandErrorKind::StdoutError => exitcode::ABORT,
30 CommandErrorKind::StdoutError => exitcode::ABORT,
29 CommandErrorKind::StderrError => exitcode::ABORT,
31 CommandErrorKind::StderrError => exitcode::ABORT,
30 CommandErrorKind::Abort(_) => exitcode::ABORT,
32 CommandErrorKind::Abort(_) => exitcode::ABORT,
33 CommandErrorKind::Unimplemented => exitcode::UNIMPLEMENTED_COMMAND,
31 }
34 }
32 }
35 }
33
36
34 /// Return the message corresponding to the error kind if any
37 /// Return the message corresponding to the error kind if any
35 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
38 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
36 match self {
39 match self {
37 // TODO use formating macro
40 // TODO use formating macro
38 CommandErrorKind::RootNotFound(path) => {
41 CommandErrorKind::RootNotFound(path) => {
39 let bytes = get_bytes_from_path(path);
42 let bytes = get_bytes_from_path(path);
40 Some(
43 Some(
41 [
44 [
42 b"abort: no repository found in '",
45 b"abort: no repository found in '",
43 bytes.as_slice(),
46 bytes.as_slice(),
44 b"' (.hg not found)!\n",
47 b"' (.hg not found)!\n",
45 ]
48 ]
46 .concat(),
49 .concat(),
47 )
50 )
48 }
51 }
49 // TODO use formating macro
52 // TODO use formating macro
50 CommandErrorKind::CurrentDirNotFound(e) => Some(
53 CommandErrorKind::CurrentDirNotFound(e) => Some(
51 [
54 [
52 b"abort: error getting current working directory: ",
55 b"abort: error getting current working directory: ",
53 e.to_string().as_bytes(),
56 e.to_string().as_bytes(),
54 b"\n",
57 b"\n",
55 ]
58 ]
56 .concat(),
59 .concat(),
57 ),
60 ),
58 CommandErrorKind::Abort(message) => message.to_owned(),
61 CommandErrorKind::Abort(message) => message.to_owned(),
59 _ => None,
62 _ => None,
60 }
63 }
61 }
64 }
62 }
65 }
63
66
64 /// The error type for the Command trait
67 /// The error type for the Command trait
65 #[derive(Debug)]
68 #[derive(Debug)]
66 pub struct CommandError {
69 pub struct CommandError {
67 pub kind: CommandErrorKind,
70 pub kind: CommandErrorKind,
68 }
71 }
69
72
70 impl CommandError {
73 impl CommandError {
71 /// Exist the process with the corresponding exit code.
74 /// Exist the process with the corresponding exit code.
72 pub fn exit(&self) {
75 pub fn exit(&self) {
73 std::process::exit(self.kind.get_exit_code())
76 std::process::exit(self.kind.get_exit_code())
74 }
77 }
75
78
76 /// Return the message corresponding to the command error if any
79 /// Return the message corresponding to the command error if any
77 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
80 pub fn get_error_message_bytes(&self) -> Option<Vec<u8>> {
78 self.kind.get_error_message_bytes()
81 self.kind.get_error_message_bytes()
79 }
82 }
80 }
83 }
81
84
82 impl From<CommandErrorKind> for CommandError {
85 impl From<CommandErrorKind> for CommandError {
83 fn from(kind: CommandErrorKind) -> Self {
86 fn from(kind: CommandErrorKind) -> Self {
84 CommandError { kind }
87 CommandError { kind }
85 }
88 }
86 }
89 }
87
90
88 impl From<UiError> for CommandError {
91 impl From<UiError> for CommandError {
89 fn from(error: UiError) -> Self {
92 fn from(error: UiError) -> Self {
90 CommandError {
93 CommandError {
91 kind: match error {
94 kind: match error {
92 UiError::StdoutError(_) => CommandErrorKind::StdoutError,
95 UiError::StdoutError(_) => CommandErrorKind::StdoutError,
93 UiError::StderrError(_) => CommandErrorKind::StderrError,
96 UiError::StderrError(_) => CommandErrorKind::StderrError,
94 },
97 },
95 }
98 }
96 }
99 }
97 }
100 }
98
101
99 impl From<FindRootError> for CommandError {
102 impl From<FindRootError> for CommandError {
100 fn from(err: FindRootError) -> Self {
103 fn from(err: FindRootError) -> Self {
101 match err.kind {
104 match err.kind {
102 FindRootErrorKind::RootNotFound(path) => CommandError {
105 FindRootErrorKind::RootNotFound(path) => CommandError {
103 kind: CommandErrorKind::RootNotFound(path),
106 kind: CommandErrorKind::RootNotFound(path),
104 },
107 },
105 FindRootErrorKind::GetCurrentDirError(e) => CommandError {
108 FindRootErrorKind::GetCurrentDirError(e) => CommandError {
106 kind: CommandErrorKind::CurrentDirNotFound(e),
109 kind: CommandErrorKind::CurrentDirNotFound(e),
107 },
110 },
108 }
111 }
109 }
112 }
110 }
113 }
@@ -1,141 +1,177 b''
1 extern crate log;
1 extern crate log;
2 use clap::App;
2 use clap::App;
3 use clap::AppSettings;
3 use clap::AppSettings;
4 use clap::Arg;
4 use clap::Arg;
5 use clap::ArgGroup;
5 use clap::ArgGroup;
6 use clap::ArgMatches;
6 use clap::ArgMatches;
7 use clap::SubCommand;
7 use clap::SubCommand;
8 use hg::operations::DebugDataKind;
8 use hg::operations::DebugDataKind;
9 use std::convert::TryFrom;
9 use std::convert::TryFrom;
10
10
11 mod commands;
11 mod commands;
12 mod error;
12 mod error;
13 mod exitcode;
13 mod exitcode;
14 mod ui;
14 mod ui;
15 use commands::Command;
15 use commands::Command;
16 use error::CommandError;
16 use error::CommandError;
17
17
18 fn main() {
18 fn main() {
19 env_logger::init();
19 env_logger::init();
20 let app = App::new("rhg")
20 let app = App::new("rhg")
21 .setting(AppSettings::AllowInvalidUtf8)
21 .setting(AppSettings::AllowInvalidUtf8)
22 .setting(AppSettings::SubcommandRequired)
22 .setting(AppSettings::SubcommandRequired)
23 .setting(AppSettings::VersionlessSubcommands)
23 .setting(AppSettings::VersionlessSubcommands)
24 .version("0.0.1")
24 .version("0.0.1")
25 .subcommand(
25 .subcommand(
26 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
26 SubCommand::with_name("root").about(commands::root::HELP_TEXT),
27 )
27 )
28 .subcommand(
28 .subcommand(
29 SubCommand::with_name("files")
29 SubCommand::with_name("files")
30 .arg(
30 .arg(
31 Arg::with_name("rev")
31 Arg::with_name("rev")
32 .help("search the repository as it is in REV")
32 .help("search the repository as it is in REV")
33 .short("-r")
33 .short("-r")
34 .long("--revision")
34 .long("--revision")
35 .value_name("REV")
35 .value_name("REV")
36 .takes_value(true),
36 .takes_value(true),
37 )
37 )
38 .about(commands::files::HELP_TEXT),
38 .about(commands::files::HELP_TEXT),
39 )
39 )
40 .subcommand(
40 .subcommand(
41 SubCommand::with_name("cat")
42 .arg(
43 Arg::with_name("rev")
44 .help("search the repository as it is in REV")
45 .short("-r")
46 .long("--revision")
47 .value_name("REV")
48 .takes_value(true),
49 )
50 .arg(
51 clap::Arg::with_name("files")
52 .required(true)
53 .multiple(true)
54 .empty_values(false)
55 .value_name("FILE")
56 .help("Activity to start: activity@category"),
57 )
58 .about(commands::cat::HELP_TEXT),
59 )
60 .subcommand(
41 SubCommand::with_name("debugdata")
61 SubCommand::with_name("debugdata")
42 .about(commands::debugdata::HELP_TEXT)
62 .about(commands::debugdata::HELP_TEXT)
43 .arg(
63 .arg(
44 Arg::with_name("changelog")
64 Arg::with_name("changelog")
45 .help("open changelog")
65 .help("open changelog")
46 .short("-c")
66 .short("-c")
47 .long("--changelog"),
67 .long("--changelog"),
48 )
68 )
49 .arg(
69 .arg(
50 Arg::with_name("manifest")
70 Arg::with_name("manifest")
51 .help("open manifest")
71 .help("open manifest")
52 .short("-m")
72 .short("-m")
53 .long("--manifest"),
73 .long("--manifest"),
54 )
74 )
55 .group(
75 .group(
56 ArgGroup::with_name("")
76 ArgGroup::with_name("")
57 .args(&["changelog", "manifest"])
77 .args(&["changelog", "manifest"])
58 .required(true),
78 .required(true),
59 )
79 )
60 .arg(
80 .arg(
61 Arg::with_name("rev")
81 Arg::with_name("rev")
62 .help("revision")
82 .help("revision")
63 .required(true)
83 .required(true)
64 .value_name("REV"),
84 .value_name("REV"),
65 ),
85 ),
66 );
86 );
67
87
68 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
88 let matches = app.clone().get_matches_safe().unwrap_or_else(|err| {
69 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
89 let _ = ui::Ui::new().writeln_stderr_str(&err.message);
70 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
90 std::process::exit(exitcode::UNIMPLEMENTED_COMMAND)
71 });
91 });
72
92
73 let ui = ui::Ui::new();
93 let ui = ui::Ui::new();
74
94
75 let command_result = match_subcommand(matches, &ui);
95 let command_result = match_subcommand(matches, &ui);
76
96
77 match command_result {
97 match command_result {
78 Ok(_) => std::process::exit(exitcode::OK),
98 Ok(_) => std::process::exit(exitcode::OK),
79 Err(e) => {
99 Err(e) => {
80 let message = e.get_error_message_bytes();
100 let message = e.get_error_message_bytes();
81 if let Some(msg) = message {
101 if let Some(msg) = message {
82 match ui.write_stderr(&msg) {
102 match ui.write_stderr(&msg) {
83 Ok(_) => (),
103 Ok(_) => (),
84 Err(_) => std::process::exit(exitcode::ABORT),
104 Err(_) => std::process::exit(exitcode::ABORT),
85 };
105 };
86 };
106 };
87 e.exit()
107 e.exit()
88 }
108 }
89 }
109 }
90 }
110 }
91
111
92 fn match_subcommand(
112 fn match_subcommand(
93 matches: ArgMatches,
113 matches: ArgMatches,
94 ui: &ui::Ui,
114 ui: &ui::Ui,
95 ) -> Result<(), CommandError> {
115 ) -> Result<(), CommandError> {
96 match matches.subcommand() {
116 match matches.subcommand() {
97 ("root", _) => commands::root::RootCommand::new().run(&ui),
117 ("root", _) => commands::root::RootCommand::new().run(&ui),
98 ("files", Some(matches)) => {
118 ("files", Some(matches)) => {
99 commands::files::FilesCommand::try_from(matches)?.run(&ui)
119 commands::files::FilesCommand::try_from(matches)?.run(&ui)
100 }
120 }
121 ("cat", Some(matches)) => {
122 commands::cat::CatCommand::try_from(matches)?.run(&ui)
123 }
101 ("debugdata", Some(matches)) => {
124 ("debugdata", Some(matches)) => {
102 commands::debugdata::DebugDataCommand::try_from(matches)?.run(&ui)
125 commands::debugdata::DebugDataCommand::try_from(matches)?.run(&ui)
103 }
126 }
104 _ => unreachable!(), // Because of AppSettings::SubcommandRequired,
127 _ => unreachable!(), // Because of AppSettings::SubcommandRequired,
105 }
128 }
106 }
129 }
107
130
108 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::files::FilesCommand<'a> {
131 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::files::FilesCommand<'a> {
109 type Error = CommandError;
132 type Error = CommandError;
110
133
111 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
134 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
112 let rev = args.value_of("rev");
135 let rev = args.value_of("rev");
113 Ok(commands::files::FilesCommand::new(rev))
136 Ok(commands::files::FilesCommand::new(rev))
114 }
137 }
115 }
138 }
116
139
140 impl<'a> TryFrom<&'a ArgMatches<'_>> for commands::cat::CatCommand<'a> {
141 type Error = CommandError;
142
143 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
144 let rev = args.value_of("rev");
145 let files = match args.values_of("files") {
146 Some(files) => files.collect(),
147 None => vec![],
148 };
149 Ok(commands::cat::CatCommand::new(rev, files))
150 }
151 }
152
117 impl<'a> TryFrom<&'a ArgMatches<'_>>
153 impl<'a> TryFrom<&'a ArgMatches<'_>>
118 for commands::debugdata::DebugDataCommand<'a>
154 for commands::debugdata::DebugDataCommand<'a>
119 {
155 {
120 type Error = CommandError;
156 type Error = CommandError;
121
157
122 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
158 fn try_from(args: &'a ArgMatches) -> Result<Self, Self::Error> {
123 let rev = args
159 let rev = args
124 .value_of("rev")
160 .value_of("rev")
125 .expect("rev should be a required argument");
161 .expect("rev should be a required argument");
126 let kind = match (
162 let kind = match (
127 args.is_present("changelog"),
163 args.is_present("changelog"),
128 args.is_present("manifest"),
164 args.is_present("manifest"),
129 ) {
165 ) {
130 (true, false) => DebugDataKind::Changelog,
166 (true, false) => DebugDataKind::Changelog,
131 (false, true) => DebugDataKind::Manifest,
167 (false, true) => DebugDataKind::Manifest,
132 (true, true) => {
168 (true, true) => {
133 unreachable!("Should not happen since options are exclusive")
169 unreachable!("Should not happen since options are exclusive")
134 }
170 }
135 (false, false) => {
171 (false, false) => {
136 unreachable!("Should not happen since options are required")
172 unreachable!("Should not happen since options are required")
137 }
173 }
138 };
174 };
139 Ok(commands::debugdata::DebugDataCommand::new(rev, kind))
175 Ok(commands::debugdata::DebugDataCommand::new(rev, kind))
140 }
176 }
141 }
177 }
General Comments 0
You need to be logged in to leave comments. Login now