##// END OF EJS Templates
rhg: Add a `rhg.on-unsupported` configuration key...
Simon Sapin -
r47424:33f2d56a default
parent child Browse files
Show More
@@ -1,65 +1,67 b''
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use clap::Arg;
2 use clap::Arg;
3 use hg::operations::cat;
3 use hg::operations::cat;
4 use hg::utils::hg_path::HgPathBuf;
4 use hg::utils::hg_path::HgPathBuf;
5 use micro_timer::timed;
5 use micro_timer::timed;
6 use std::convert::TryFrom;
6 use std::convert::TryFrom;
7
7
8 pub const HELP_TEXT: &str = "
8 pub const HELP_TEXT: &str = "
9 Output the current or given revision of files
9 Output the current or given revision of files
10 ";
10 ";
11
11
12 pub fn args() -> clap::App<'static, 'static> {
12 pub fn args() -> clap::App<'static, 'static> {
13 clap::SubCommand::with_name("cat")
13 clap::SubCommand::with_name("cat")
14 .arg(
14 .arg(
15 Arg::with_name("rev")
15 Arg::with_name("rev")
16 .help("search the repository as it is in REV")
16 .help("search the repository as it is in REV")
17 .short("-r")
17 .short("-r")
18 .long("--revision")
18 .long("--revision")
19 .value_name("REV")
19 .value_name("REV")
20 .takes_value(true),
20 .takes_value(true),
21 )
21 )
22 .arg(
22 .arg(
23 clap::Arg::with_name("files")
23 clap::Arg::with_name("files")
24 .required(true)
24 .required(true)
25 .multiple(true)
25 .multiple(true)
26 .empty_values(false)
26 .empty_values(false)
27 .value_name("FILE")
27 .value_name("FILE")
28 .help("Activity to start: activity@category"),
28 .help("Activity to start: activity@category"),
29 )
29 )
30 .about(HELP_TEXT)
30 .about(HELP_TEXT)
31 }
31 }
32
32
33 #[timed]
33 #[timed]
34 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
34 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
35 let rev = invocation.subcommand_args.value_of("rev");
35 let rev = invocation.subcommand_args.value_of("rev");
36 let file_args = match invocation.subcommand_args.values_of("files") {
36 let file_args = match invocation.subcommand_args.values_of("files") {
37 Some(files) => files.collect(),
37 Some(files) => files.collect(),
38 None => vec![],
38 None => vec![],
39 };
39 };
40
40
41 let repo = invocation.repo?;
41 let repo = invocation.repo?;
42 let cwd = hg::utils::current_dir()?;
42 let cwd = hg::utils::current_dir()?;
43
43
44 let mut files = vec![];
44 let mut files = vec![];
45 for file in file_args.iter() {
45 for file in file_args.iter() {
46 // TODO: actually normalize `..` path segments etc?
46 // TODO: actually normalize `..` path segments etc?
47 let normalized = cwd.join(&file);
47 let normalized = cwd.join(&file);
48 let stripped = normalized
48 let stripped = normalized
49 .strip_prefix(&repo.working_directory_path())
49 .strip_prefix(&repo.working_directory_path())
50 // TODO: error message for path arguments outside of the repo
50 // TODO: error message for path arguments outside of the repo
51 .map_err(|_| CommandError::abort(""))?;
51 .map_err(|_| CommandError::abort(""))?;
52 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
52 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
53 .map_err(|e| CommandError::abort(e.to_string()))?;
53 .map_err(|e| CommandError::abort(e.to_string()))?;
54 files.push(hg_file);
54 files.push(hg_file);
55 }
55 }
56
56
57 match rev {
57 match rev {
58 Some(rev) => {
58 Some(rev) => {
59 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
59 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
60 invocation.ui.write_stdout(&data)?;
60 invocation.ui.write_stdout(&data)?;
61 Ok(())
61 Ok(())
62 }
62 }
63 None => Err(CommandError::Unimplemented.into()),
63 None => Err(CommandError::unsupported(
64 "`rhg cat` without `--rev` / `-r`",
65 )),
64 }
66 }
65 }
67 }
@@ -1,136 +1,143 b''
1 use crate::ui::utf8_to_local;
1 use crate::ui::utf8_to_local;
2 use crate::ui::UiError;
2 use crate::ui::UiError;
3 use crate::NoRepoInCwdError;
3 use crate::NoRepoInCwdError;
4 use format_bytes::format_bytes;
4 use format_bytes::format_bytes;
5 use hg::config::{ConfigError, ConfigParseError};
5 use hg::config::{ConfigError, ConfigParseError};
6 use hg::errors::HgError;
6 use hg::errors::HgError;
7 use hg::repo::RepoError;
7 use hg::repo::RepoError;
8 use hg::revlog::revlog::RevlogError;
8 use hg::revlog::revlog::RevlogError;
9 use hg::utils::files::get_bytes_from_path;
9 use hg::utils::files::get_bytes_from_path;
10 use std::convert::From;
10 use std::convert::From;
11
11
12 /// The kind of command error
12 /// The kind of command error
13 #[derive(Debug)]
13 #[derive(Debug)]
14 pub enum CommandError {
14 pub enum CommandError {
15 /// Exit with an error message and "standard" failure exit code.
15 /// Exit with an error message and "standard" failure exit code.
16 Abort { message: Vec<u8> },
16 Abort { message: Vec<u8> },
17
17
18 /// A mercurial capability as not been implemented.
18 /// Encountered something (such as a CLI argument, repository layout, …)
19 ///
19 /// not supported by this version of `rhg`. Depending on configuration
20 /// There is no error message printed in this case.
20 /// `rhg` may attempt to silently fall back to Python-based `hg`, which
21 /// Instead, we exit with a specic status code and a wrapper script may
21 /// may or may not support this feature.
22 /// fallback to Python-based Mercurial.
22 UnsupportedFeature { message: Vec<u8> },
23 Unimplemented,
24 }
23 }
25
24
26 impl CommandError {
25 impl CommandError {
27 pub fn abort(message: impl AsRef<str>) -> Self {
26 pub fn abort(message: impl AsRef<str>) -> Self {
28 CommandError::Abort {
27 CommandError::Abort {
29 // TODO: bytes-based (instead of Unicode-based) formatting
28 // TODO: bytes-based (instead of Unicode-based) formatting
30 // of error messages to handle non-UTF-8 filenames etc:
29 // of error messages to handle non-UTF-8 filenames etc:
31 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
30 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
32 message: utf8_to_local(message.as_ref()).into(),
31 message: utf8_to_local(message.as_ref()).into(),
33 }
32 }
34 }
33 }
34
35 pub fn unsupported(message: impl AsRef<str>) -> Self {
36 CommandError::UnsupportedFeature {
37 message: utf8_to_local(message.as_ref()).into(),
38 }
39 }
35 }
40 }
36
41
37 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
42 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
38 /// but not supported yet by `rhg`.
43 /// but not supported yet by `rhg`.
39 impl From<clap::Error> for CommandError {
44 impl From<clap::Error> for CommandError {
40 fn from(_: clap::Error) -> Self {
45 fn from(error: clap::Error) -> Self {
41 CommandError::Unimplemented
46 CommandError::unsupported(error.to_string())
42 }
47 }
43 }
48 }
44
49
45 impl From<HgError> for CommandError {
50 impl From<HgError> for CommandError {
46 fn from(error: HgError) -> Self {
51 fn from(error: HgError) -> Self {
47 match error {
52 match error {
48 HgError::UnsupportedFeature(_) => CommandError::Unimplemented,
53 HgError::UnsupportedFeature(message) => {
54 CommandError::unsupported(message)
55 }
49 _ => CommandError::abort(error.to_string()),
56 _ => CommandError::abort(error.to_string()),
50 }
57 }
51 }
58 }
52 }
59 }
53
60
54 impl From<UiError> for CommandError {
61 impl From<UiError> for CommandError {
55 fn from(_error: UiError) -> Self {
62 fn from(_error: UiError) -> Self {
56 // If we already failed writing to stdout or stderr,
63 // If we already failed writing to stdout or stderr,
57 // writing an error message to stderr about it would be likely to fail
64 // writing an error message to stderr about it would be likely to fail
58 // too.
65 // too.
59 CommandError::abort("")
66 CommandError::abort("")
60 }
67 }
61 }
68 }
62
69
63 impl From<RepoError> for CommandError {
70 impl From<RepoError> for CommandError {
64 fn from(error: RepoError) -> Self {
71 fn from(error: RepoError) -> Self {
65 match error {
72 match error {
66 RepoError::NotFound { at } => CommandError::Abort {
73 RepoError::NotFound { at } => CommandError::Abort {
67 message: format_bytes!(
74 message: format_bytes!(
68 b"repository {} not found",
75 b"repository {} not found",
69 get_bytes_from_path(at)
76 get_bytes_from_path(at)
70 ),
77 ),
71 },
78 },
72 RepoError::ConfigParseError(error) => error.into(),
79 RepoError::ConfigParseError(error) => error.into(),
73 RepoError::Other(error) => error.into(),
80 RepoError::Other(error) => error.into(),
74 }
81 }
75 }
82 }
76 }
83 }
77
84
78 impl<'a> From<&'a NoRepoInCwdError> for CommandError {
85 impl<'a> From<&'a NoRepoInCwdError> for CommandError {
79 fn from(error: &'a NoRepoInCwdError) -> Self {
86 fn from(error: &'a NoRepoInCwdError) -> Self {
80 let NoRepoInCwdError { cwd } = error;
87 let NoRepoInCwdError { cwd } = error;
81 CommandError::Abort {
88 CommandError::Abort {
82 message: format_bytes!(
89 message: format_bytes!(
83 b"no repository found in '{}' (.hg not found)!",
90 b"no repository found in '{}' (.hg not found)!",
84 get_bytes_from_path(cwd)
91 get_bytes_from_path(cwd)
85 ),
92 ),
86 }
93 }
87 }
94 }
88 }
95 }
89
96
90 impl From<ConfigError> for CommandError {
97 impl From<ConfigError> for CommandError {
91 fn from(error: ConfigError) -> Self {
98 fn from(error: ConfigError) -> Self {
92 match error {
99 match error {
93 ConfigError::Parse(error) => error.into(),
100 ConfigError::Parse(error) => error.into(),
94 ConfigError::Other(error) => error.into(),
101 ConfigError::Other(error) => error.into(),
95 }
102 }
96 }
103 }
97 }
104 }
98
105
99 impl From<ConfigParseError> for CommandError {
106 impl From<ConfigParseError> for CommandError {
100 fn from(error: ConfigParseError) -> Self {
107 fn from(error: ConfigParseError) -> Self {
101 let ConfigParseError {
108 let ConfigParseError {
102 origin,
109 origin,
103 line,
110 line,
104 bytes,
111 bytes,
105 } = error;
112 } = error;
106 let line_message = if let Some(line_number) = line {
113 let line_message = if let Some(line_number) = line {
107 format_bytes!(b" at line {}", line_number.to_string().into_bytes())
114 format_bytes!(b" at line {}", line_number.to_string().into_bytes())
108 } else {
115 } else {
109 Vec::new()
116 Vec::new()
110 };
117 };
111 CommandError::Abort {
118 CommandError::Abort {
112 message: format_bytes!(
119 message: format_bytes!(
113 b"config parse error in {}{}: '{}'",
120 b"config parse error in {}{}: '{}'",
114 origin,
121 origin,
115 line_message,
122 line_message,
116 bytes
123 bytes
117 ),
124 ),
118 }
125 }
119 }
126 }
120 }
127 }
121
128
122 impl From<(RevlogError, &str)> for CommandError {
129 impl From<(RevlogError, &str)> for CommandError {
123 fn from((err, rev): (RevlogError, &str)) -> CommandError {
130 fn from((err, rev): (RevlogError, &str)) -> CommandError {
124 match err {
131 match err {
125 RevlogError::InvalidRevision => CommandError::abort(format!(
132 RevlogError::InvalidRevision => CommandError::abort(format!(
126 "invalid revision identifier {}",
133 "invalid revision identifier {}",
127 rev
134 rev
128 )),
135 )),
129 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
136 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
130 "ambiguous revision identifier {}",
137 "ambiguous revision identifier {}",
131 rev
138 rev
132 )),
139 )),
133 RevlogError::Other(error) => error.into(),
140 RevlogError::Other(error) => error.into(),
134 }
141 }
135 }
142 }
136 }
143 }
@@ -1,228 +1,286 b''
1 extern crate log;
1 extern crate log;
2 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::App;
3 use clap::App;
4 use clap::AppSettings;
4 use clap::AppSettings;
5 use clap::Arg;
5 use clap::Arg;
6 use clap::ArgMatches;
6 use clap::ArgMatches;
7 use format_bytes::format_bytes;
7 use format_bytes::format_bytes;
8 use hg::config::Config;
8 use hg::config::Config;
9 use hg::repo::{Repo, RepoError};
9 use hg::repo::{Repo, RepoError};
10 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
10 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
11 use hg::utils::SliceExt;
11 use hg::utils::SliceExt;
12 use std::ffi::OsString;
12 use std::ffi::OsString;
13 use std::path::PathBuf;
13 use std::path::PathBuf;
14
14
15 mod blackbox;
15 mod blackbox;
16 mod error;
16 mod error;
17 mod exitcode;
17 mod exitcode;
18 mod ui;
18 mod ui;
19 use error::CommandError;
19 use error::CommandError;
20
20
21 fn main_with_result(
21 fn main_with_result(
22 process_start_time: &blackbox::ProcessStartTime,
22 process_start_time: &blackbox::ProcessStartTime,
23 ui: &ui::Ui,
23 ui: &ui::Ui,
24 repo: Result<&Repo, &NoRepoInCwdError>,
24 repo: Result<&Repo, &NoRepoInCwdError>,
25 config: &Config,
25 config: &Config,
26 ) -> Result<(), CommandError> {
26 ) -> Result<(), CommandError> {
27 let app = App::new("rhg")
27 let app = App::new("rhg")
28 .global_setting(AppSettings::AllowInvalidUtf8)
28 .global_setting(AppSettings::AllowInvalidUtf8)
29 .setting(AppSettings::SubcommandRequired)
29 .setting(AppSettings::SubcommandRequired)
30 .setting(AppSettings::VersionlessSubcommands)
30 .setting(AppSettings::VersionlessSubcommands)
31 .arg(
31 .arg(
32 Arg::with_name("repository")
32 Arg::with_name("repository")
33 .help("repository root directory")
33 .help("repository root directory")
34 .short("-R")
34 .short("-R")
35 .long("--repository")
35 .long("--repository")
36 .value_name("REPO")
36 .value_name("REPO")
37 .takes_value(true)
37 .takes_value(true)
38 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
38 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
39 .global(true),
39 .global(true),
40 )
40 )
41 .arg(
41 .arg(
42 Arg::with_name("config")
42 Arg::with_name("config")
43 .help("set/override config option (use 'section.name=value')")
43 .help("set/override config option (use 'section.name=value')")
44 .long("--config")
44 .long("--config")
45 .value_name("CONFIG")
45 .value_name("CONFIG")
46 .takes_value(true)
46 .takes_value(true)
47 .global(true)
47 .global(true)
48 // Ok: `--config section.key1=val --config section.key2=val2`
48 // Ok: `--config section.key1=val --config section.key2=val2`
49 .multiple(true)
49 .multiple(true)
50 // Not ok: `--config section.key1=val section.key2=val2`
50 // Not ok: `--config section.key1=val section.key2=val2`
51 .number_of_values(1),
51 .number_of_values(1),
52 )
52 )
53 .version("0.0.1");
53 .version("0.0.1");
54 let app = add_subcommand_args(app);
54 let app = add_subcommand_args(app);
55
55
56 let matches = app.clone().get_matches_safe()?;
56 let matches = app.clone().get_matches_safe()?;
57
57
58 let (subcommand_name, subcommand_matches) = matches.subcommand();
58 let (subcommand_name, subcommand_matches) = matches.subcommand();
59 let run = subcommand_run_fn(subcommand_name)
59 let run = subcommand_run_fn(subcommand_name)
60 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
60 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
61 let subcommand_args = subcommand_matches
61 let subcommand_args = subcommand_matches
62 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
62 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
63
63
64 let invocation = CliInvocation {
64 let invocation = CliInvocation {
65 ui,
65 ui,
66 subcommand_args,
66 subcommand_args,
67 config,
67 config,
68 repo,
68 repo,
69 };
69 };
70 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
70 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
71 blackbox.log_command_start();
71 blackbox.log_command_start();
72 let result = run(&invocation);
72 let result = run(&invocation);
73 blackbox.log_command_end(exit_code(&result));
73 blackbox.log_command_end(exit_code(&result));
74 result
74 result
75 }
75 }
76
76
77 fn main() {
77 fn main() {
78 // Run this first, before we find out if the blackbox extension is even
78 // Run this first, before we find out if the blackbox extension is even
79 // enabled, in order to include everything in-between in the duration
79 // enabled, in order to include everything in-between in the duration
80 // measurements. Reading config files can be slow if they’re on NFS.
80 // measurements. Reading config files can be slow if they’re on NFS.
81 let process_start_time = blackbox::ProcessStartTime::now();
81 let process_start_time = blackbox::ProcessStartTime::now();
82
82
83 env_logger::init();
83 env_logger::init();
84 let ui = ui::Ui::new();
84 let ui = ui::Ui::new();
85
85
86 let early_args = EarlyArgs::parse(std::env::args_os());
86 let early_args = EarlyArgs::parse(std::env::args_os());
87 let non_repo_config = Config::load(early_args.config)
87 let non_repo_config =
88 .unwrap_or_else(|error| exit(&ui, Err(error.into())));
88 Config::load(early_args.config).unwrap_or_else(|error| {
89 // Normally this is decided based on config, but we don’t have that
90 // available. As of this writing config loading never returns an
91 // "unsupported" error but that is not enforced by the type system.
92 let on_unsupported = OnUnsupported::Abort;
93
94 exit(&ui, on_unsupported, Err(error.into()))
95 });
89
96
90 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
97 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
91 let repo_result = match Repo::find(&non_repo_config, repo_path) {
98 let repo_result = match Repo::find(&non_repo_config, repo_path) {
92 Ok(repo) => Ok(repo),
99 Ok(repo) => Ok(repo),
93 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
100 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
94 // Not finding a repo is not fatal yet, if `-R` was not given
101 // Not finding a repo is not fatal yet, if `-R` was not given
95 Err(NoRepoInCwdError { cwd: at })
102 Err(NoRepoInCwdError { cwd: at })
96 }
103 }
97 Err(error) => exit(&ui, Err(error.into())),
104 Err(error) => exit(
105 &ui,
106 OnUnsupported::from_config(&non_repo_config),
107 Err(error.into()),
108 ),
98 };
109 };
99
110
100 let config = if let Ok(repo) = &repo_result {
111 let config = if let Ok(repo) = &repo_result {
101 repo.config()
112 repo.config()
102 } else {
113 } else {
103 &non_repo_config
114 &non_repo_config
104 };
115 };
105
116
106 let result = main_with_result(
117 let result = main_with_result(
107 &process_start_time,
118 &process_start_time,
108 &ui,
119 &ui,
109 repo_result.as_ref(),
120 repo_result.as_ref(),
110 config,
121 config,
111 );
122 );
112 exit(&ui, result)
123 exit(&ui, OnUnsupported::from_config(config), result)
113 }
124 }
114
125
115 fn exit_code(result: &Result<(), CommandError>) -> i32 {
126 fn exit_code(result: &Result<(), CommandError>) -> i32 {
116 match result {
127 match result {
117 Ok(()) => exitcode::OK,
128 Ok(()) => exitcode::OK,
118 Err(CommandError::Abort { .. }) => exitcode::ABORT,
129 Err(CommandError::Abort { .. }) => exitcode::ABORT,
119
130
120 // Exit with a specific code and no error message to let a potential
131 // Exit with a specific code and no error message to let a potential
121 // wrapper script fallback to Python-based Mercurial.
132 // wrapper script fallback to Python-based Mercurial.
122 Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
133 Err(CommandError::UnsupportedFeature { .. }) => {
134 exitcode::UNIMPLEMENTED
135 }
123 }
136 }
124 }
137 }
125
138
126 fn exit(ui: &Ui, result: Result<(), CommandError>) -> ! {
139 fn exit(
127 if let Err(CommandError::Abort { message }) = &result {
140 ui: &Ui,
128 if !message.is_empty() {
141 on_unsupported: OnUnsupported,
129 // Ignore errors when writing to stderr, we’re already exiting
142 result: Result<(), CommandError>,
130 // with failure code so there’s not much more we can do.
143 ) -> ! {
131 let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
144 match &result {
145 Ok(_) => {}
146 Err(CommandError::Abort { message }) => {
147 if !message.is_empty() {
148 // Ignore errors when writing to stderr, we’re already exiting
149 // with failure code so there’s not much more we can do.
150 let _ =
151 ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
152 }
153 }
154 Err(CommandError::UnsupportedFeature { message }) => {
155 match on_unsupported {
156 OnUnsupported::Abort => {
157 let _ = ui.write_stderr(&format_bytes!(
158 b"unsupported feature: {}\n",
159 message
160 ));
161 }
162 OnUnsupported::AbortSilent => {}
163 }
132 }
164 }
133 }
165 }
134 std::process::exit(exit_code(&result))
166 std::process::exit(exit_code(&result))
135 }
167 }
136
168
137 macro_rules! subcommands {
169 macro_rules! subcommands {
138 ($( $command: ident )+) => {
170 ($( $command: ident )+) => {
139 mod commands {
171 mod commands {
140 $(
172 $(
141 pub mod $command;
173 pub mod $command;
142 )+
174 )+
143 }
175 }
144
176
145 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
177 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
146 app
178 app
147 $(
179 $(
148 .subcommand(commands::$command::args())
180 .subcommand(commands::$command::args())
149 )+
181 )+
150 }
182 }
151
183
152 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
184 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
153
185
154 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
186 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
155 match name {
187 match name {
156 $(
188 $(
157 stringify!($command) => Some(commands::$command::run),
189 stringify!($command) => Some(commands::$command::run),
158 )+
190 )+
159 _ => None,
191 _ => None,
160 }
192 }
161 }
193 }
162 };
194 };
163 }
195 }
164
196
165 subcommands! {
197 subcommands! {
166 cat
198 cat
167 debugdata
199 debugdata
168 debugrequirements
200 debugrequirements
169 files
201 files
170 root
202 root
171 config
203 config
172 }
204 }
173 pub struct CliInvocation<'a> {
205 pub struct CliInvocation<'a> {
174 ui: &'a Ui,
206 ui: &'a Ui,
175 subcommand_args: &'a ArgMatches<'a>,
207 subcommand_args: &'a ArgMatches<'a>,
176 config: &'a Config,
208 config: &'a Config,
177 /// References inside `Result` is a bit peculiar but allow
209 /// References inside `Result` is a bit peculiar but allow
178 /// `invocation.repo?` to work out with `&CliInvocation` since this
210 /// `invocation.repo?` to work out with `&CliInvocation` since this
179 /// `Result` type is `Copy`.
211 /// `Result` type is `Copy`.
180 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
212 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
181 }
213 }
182
214
183 struct NoRepoInCwdError {
215 struct NoRepoInCwdError {
184 cwd: PathBuf,
216 cwd: PathBuf,
185 }
217 }
186
218
187 /// CLI arguments to be parsed "early" in order to be able to read
219 /// CLI arguments to be parsed "early" in order to be able to read
188 /// configuration before using Clap. Ideally we would also use Clap for this,
220 /// configuration before using Clap. Ideally we would also use Clap for this,
189 /// see <https://github.com/clap-rs/clap/discussions/2366>.
221 /// see <https://github.com/clap-rs/clap/discussions/2366>.
190 ///
222 ///
191 /// These arguments are still declared when we do use Clap later, so that Clap
223 /// These arguments are still declared when we do use Clap later, so that Clap
192 /// does not return an error for their presence.
224 /// does not return an error for their presence.
193 struct EarlyArgs {
225 struct EarlyArgs {
194 /// Values of all `--config` arguments. (Possibly none)
226 /// Values of all `--config` arguments. (Possibly none)
195 config: Vec<Vec<u8>>,
227 config: Vec<Vec<u8>>,
196 /// Value of the `-R` or `--repository` argument, if any.
228 /// Value of the `-R` or `--repository` argument, if any.
197 repo: Option<Vec<u8>>,
229 repo: Option<Vec<u8>>,
198 }
230 }
199
231
200 impl EarlyArgs {
232 impl EarlyArgs {
201 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
233 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
202 let mut args = args.into_iter().map(get_bytes_from_os_str);
234 let mut args = args.into_iter().map(get_bytes_from_os_str);
203 let mut config = Vec::new();
235 let mut config = Vec::new();
204 let mut repo = None;
236 let mut repo = None;
205 // Use `while let` instead of `for` so that we can also call
237 // Use `while let` instead of `for` so that we can also call
206 // `args.next()` inside the loop.
238 // `args.next()` inside the loop.
207 while let Some(arg) = args.next() {
239 while let Some(arg) = args.next() {
208 if arg == b"--config" {
240 if arg == b"--config" {
209 if let Some(value) = args.next() {
241 if let Some(value) = args.next() {
210 config.push(value)
242 config.push(value)
211 }
243 }
212 } else if let Some(value) = arg.drop_prefix(b"--config=") {
244 } else if let Some(value) = arg.drop_prefix(b"--config=") {
213 config.push(value.to_owned())
245 config.push(value.to_owned())
214 }
246 }
215
247
216 if arg == b"--repository" || arg == b"-R" {
248 if arg == b"--repository" || arg == b"-R" {
217 if let Some(value) = args.next() {
249 if let Some(value) = args.next() {
218 repo = Some(value)
250 repo = Some(value)
219 }
251 }
220 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
252 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
221 repo = Some(value.to_owned())
253 repo = Some(value.to_owned())
222 } else if let Some(value) = arg.drop_prefix(b"-R") {
254 } else if let Some(value) = arg.drop_prefix(b"-R") {
223 repo = Some(value.to_owned())
255 repo = Some(value.to_owned())
224 }
256 }
225 }
257 }
226 Self { config, repo }
258 Self { config, repo }
227 }
259 }
228 }
260 }
261
262 /// What to do when encountering some unsupported feature.
263 ///
264 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
265 enum OnUnsupported {
266 /// Print an error message describing what feature is not supported,
267 /// and exit with code 252.
268 Abort,
269 /// Silently exit with code 252.
270 AbortSilent,
271 }
272
273 impl OnUnsupported {
274 fn from_config(config: &Config) -> Self {
275 let default = OnUnsupported::Abort;
276 match config.get(b"rhg", b"on-unsupported") {
277 Some(b"abort") => OnUnsupported::Abort,
278 Some(b"abort-silent") => OnUnsupported::AbortSilent,
279 None => default,
280 Some(_) => {
281 // TODO: warn about unknown config value
282 default
283 }
284 }
285 }
286 }
@@ -1,275 +1,287 b''
1 #require rust
1 #require rust
2
2
3 Define an rhg function that will only run if rhg exists
3 Define an rhg function that will only run if rhg exists
4 $ rhg() {
4 $ rhg() {
5 > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then
5 > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then
6 > "$RUNTESTDIR/../rust/target/release/rhg" "$@"
6 > "$RUNTESTDIR/../rust/target/release/rhg" "$@"
7 > else
7 > else
8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
8 > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg."
9 > exit 80
9 > exit 80
10 > fi
10 > fi
11 > }
11 > }
12
12
13 Unimplemented command
13 Unimplemented command
14 $ rhg unimplemented-command
14 $ rhg unimplemented-command
15 unsupported feature: error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
16
17 USAGE:
18 rhg [OPTIONS] <SUBCOMMAND>
19
20 For more information try --help
21
22 [252]
23 $ rhg unimplemented-command --config rhg.on-unsupported=abort-silent
15 [252]
24 [252]
16
25
17 Finding root
26 Finding root
18 $ rhg root
27 $ rhg root
19 abort: no repository found in '$TESTTMP' (.hg not found)!
28 abort: no repository found in '$TESTTMP' (.hg not found)!
20 [255]
29 [255]
21
30
22 $ hg init repository
31 $ hg init repository
23 $ cd repository
32 $ cd repository
24 $ rhg root
33 $ rhg root
25 $TESTTMP/repository
34 $TESTTMP/repository
26
35
27 Reading and setting configuration
36 Reading and setting configuration
28 $ echo "[ui]" >> $HGRCPATH
37 $ echo "[ui]" >> $HGRCPATH
29 $ echo "username = user1" >> $HGRCPATH
38 $ echo "username = user1" >> $HGRCPATH
30 $ rhg config ui.username
39 $ rhg config ui.username
31 user1
40 user1
32 $ echo "[ui]" >> .hg/hgrc
41 $ echo "[ui]" >> .hg/hgrc
33 $ echo "username = user2" >> .hg/hgrc
42 $ echo "username = user2" >> .hg/hgrc
34 $ rhg config ui.username
43 $ rhg config ui.username
35 user2
44 user2
36 $ rhg --config ui.username=user3 config ui.username
45 $ rhg --config ui.username=user3 config ui.username
37 user3
46 user3
38
47
39 Unwritable file descriptor
48 Unwritable file descriptor
40 $ rhg root > /dev/full
49 $ rhg root > /dev/full
41 abort: No space left on device (os error 28)
50 abort: No space left on device (os error 28)
42 [255]
51 [255]
43
52
44 Deleted repository
53 Deleted repository
45 $ rm -rf `pwd`
54 $ rm -rf `pwd`
46 $ rhg root
55 $ rhg root
47 abort: $ENOENT$: current directory
56 abort: $ENOENT$: current directory
48 [255]
57 [255]
49
58
50 Listing tracked files
59 Listing tracked files
51 $ cd $TESTTMP
60 $ cd $TESTTMP
52 $ hg init repository
61 $ hg init repository
53 $ cd repository
62 $ cd repository
54 $ for i in 1 2 3; do
63 $ for i in 1 2 3; do
55 > echo $i >> file$i
64 > echo $i >> file$i
56 > hg add file$i
65 > hg add file$i
57 > done
66 > done
58 > hg commit -m "commit $i" -q
67 > hg commit -m "commit $i" -q
59
68
60 Listing tracked files from root
69 Listing tracked files from root
61 $ rhg files
70 $ rhg files
62 file1
71 file1
63 file2
72 file2
64 file3
73 file3
65
74
66 Listing tracked files from subdirectory
75 Listing tracked files from subdirectory
67 $ mkdir -p path/to/directory
76 $ mkdir -p path/to/directory
68 $ cd path/to/directory
77 $ cd path/to/directory
69 $ rhg files
78 $ rhg files
70 ../../../file1
79 ../../../file1
71 ../../../file2
80 ../../../file2
72 ../../../file3
81 ../../../file3
73
82
74 Listing tracked files through broken pipe
83 Listing tracked files through broken pipe
75 $ rhg files | head -n 1
84 $ rhg files | head -n 1
76 ../../../file1
85 ../../../file1
77
86
78 Debuging data in inline index
87 Debuging data in inline index
79 $ cd $TESTTMP
88 $ cd $TESTTMP
80 $ rm -rf repository
89 $ rm -rf repository
81 $ hg init repository
90 $ hg init repository
82 $ cd repository
91 $ cd repository
83 $ for i in 1 2 3 4 5 6; do
92 $ for i in 1 2 3 4 5 6; do
84 > echo $i >> file-$i
93 > echo $i >> file-$i
85 > hg add file-$i
94 > hg add file-$i
86 > hg commit -m "Commit $i" -q
95 > hg commit -m "Commit $i" -q
87 > done
96 > done
88 $ rhg debugdata -c 2
97 $ rhg debugdata -c 2
89 8d0267cb034247ebfa5ee58ce59e22e57a492297
98 8d0267cb034247ebfa5ee58ce59e22e57a492297
90 test
99 test
91 0 0
100 0 0
92 file-3
101 file-3
93
102
94 Commit 3 (no-eol)
103 Commit 3 (no-eol)
95 $ rhg debugdata -m 2
104 $ rhg debugdata -m 2
96 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
105 file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
97 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
106 file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc)
98 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
107 file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc)
99
108
100 Debuging with full node id
109 Debuging with full node id
101 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
110 $ rhg debugdata -c `hg log -r 0 -T '{node}'`
102 d1d1c679d3053e8926061b6f45ca52009f011e3f
111 d1d1c679d3053e8926061b6f45ca52009f011e3f
103 test
112 test
104 0 0
113 0 0
105 file-1
114 file-1
106
115
107 Commit 1 (no-eol)
116 Commit 1 (no-eol)
108
117
109 Specifying revisions by changeset ID
118 Specifying revisions by changeset ID
110 $ hg log -T '{node}\n'
119 $ hg log -T '{node}\n'
111 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
120 c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b
112 d654274993d0149eecc3cc03214f598320211900
121 d654274993d0149eecc3cc03214f598320211900
113 f646af7e96481d3a5470b695cf30ad8e3ab6c575
122 f646af7e96481d3a5470b695cf30ad8e3ab6c575
114 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
123 cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7
115 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
124 91c6f6e73e39318534dc415ea4e8a09c99cd74d6
116 6ae9681c6d30389694d8701faf24b583cf3ccafe
125 6ae9681c6d30389694d8701faf24b583cf3ccafe
117 $ rhg files -r cf8b83
126 $ rhg files -r cf8b83
118 file-1
127 file-1
119 file-2
128 file-2
120 file-3
129 file-3
121 $ rhg cat -r cf8b83 file-2
130 $ rhg cat -r cf8b83 file-2
122 2
131 2
123 $ rhg cat -r c file-2
132 $ rhg cat -r c file-2
124 abort: ambiguous revision identifier c
133 abort: ambiguous revision identifier c
125 [255]
134 [255]
126 $ rhg cat -r d file-2
135 $ rhg cat -r d file-2
127 2
136 2
128
137
129 Cat files
138 Cat files
130 $ cd $TESTTMP
139 $ cd $TESTTMP
131 $ rm -rf repository
140 $ rm -rf repository
132 $ hg init repository
141 $ hg init repository
133 $ cd repository
142 $ cd repository
134 $ echo "original content" > original
143 $ echo "original content" > original
135 $ hg add original
144 $ hg add original
136 $ hg commit -m "add original" original
145 $ hg commit -m "add original" original
137 $ rhg cat -r 0 original
146 $ rhg cat -r 0 original
138 original content
147 original content
139 Cat copied file should not display copy metadata
148 Cat copied file should not display copy metadata
140 $ hg copy original copy_of_original
149 $ hg copy original copy_of_original
141 $ hg commit -m "add copy of original"
150 $ hg commit -m "add copy of original"
142 $ rhg cat -r 1 copy_of_original
151 $ rhg cat -r 1 copy_of_original
143 original content
152 original content
144
153
145 Requirements
154 Requirements
146 $ rhg debugrequirements
155 $ rhg debugrequirements
147 dotencode
156 dotencode
148 fncache
157 fncache
149 generaldelta
158 generaldelta
150 revlogv1
159 revlogv1
151 sparserevlog
160 sparserevlog
152 store
161 store
153
162
154 $ echo indoor-pool >> .hg/requires
163 $ echo indoor-pool >> .hg/requires
155 $ rhg files
164 $ rhg files
165 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
156 [252]
166 [252]
157
167
158 $ rhg cat -r 1 copy_of_original
168 $ rhg cat -r 1 copy_of_original
169 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
159 [252]
170 [252]
160
171
161 $ rhg debugrequirements
172 $ rhg debugrequirements
173 unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
162 [252]
174 [252]
163
175
164 $ echo -e '\xFF' >> .hg/requires
176 $ echo -e '\xFF' >> .hg/requires
165 $ rhg debugrequirements
177 $ rhg debugrequirements
166 abort: corrupted repository: parse error in 'requires' file
178 abort: corrupted repository: parse error in 'requires' file
167 [255]
179 [255]
168
180
169 Persistent nodemap
181 Persistent nodemap
170 $ cd $TESTTMP
182 $ cd $TESTTMP
171 $ rm -rf repository
183 $ rm -rf repository
172 $ hg init repository
184 $ hg init repository
173 $ cd repository
185 $ cd repository
174 $ rhg debugrequirements | grep nodemap
186 $ rhg debugrequirements | grep nodemap
175 [1]
187 [1]
176 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
188 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
177 $ hg id -r tip
189 $ hg id -r tip
178 c3ae8dec9fad tip
190 c3ae8dec9fad tip
179 $ ls .hg/store/00changelog*
191 $ ls .hg/store/00changelog*
180 .hg/store/00changelog.d
192 .hg/store/00changelog.d
181 .hg/store/00changelog.i
193 .hg/store/00changelog.i
182 $ rhg files -r c3ae8dec9fad
194 $ rhg files -r c3ae8dec9fad
183 of
195 of
184
196
185 $ cd $TESTTMP
197 $ cd $TESTTMP
186 $ rm -rf repository
198 $ rm -rf repository
187 $ hg --config format.use-persistent-nodemap=True init repository
199 $ hg --config format.use-persistent-nodemap=True init repository
188 $ cd repository
200 $ cd repository
189 $ rhg debugrequirements | grep nodemap
201 $ rhg debugrequirements | grep nodemap
190 persistent-nodemap
202 persistent-nodemap
191 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
203 $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn"
192 $ hg id -r tip
204 $ hg id -r tip
193 c3ae8dec9fad tip
205 c3ae8dec9fad tip
194 $ ls .hg/store/00changelog*
206 $ ls .hg/store/00changelog*
195 .hg/store/00changelog-*.nd (glob)
207 .hg/store/00changelog-*.nd (glob)
196 .hg/store/00changelog.d
208 .hg/store/00changelog.d
197 .hg/store/00changelog.i
209 .hg/store/00changelog.i
198 .hg/store/00changelog.n
210 .hg/store/00changelog.n
199
211
200 Specifying revisions by changeset ID
212 Specifying revisions by changeset ID
201 $ rhg files -r c3ae8dec9fad
213 $ rhg files -r c3ae8dec9fad
202 of
214 of
203 $ rhg cat -r c3ae8dec9fad of
215 $ rhg cat -r c3ae8dec9fad of
204 r5000
216 r5000
205
217
206 Crate a shared repository
218 Crate a shared repository
207
219
208 $ echo "[extensions]" >> $HGRCPATH
220 $ echo "[extensions]" >> $HGRCPATH
209 $ echo "share = " >> $HGRCPATH
221 $ echo "share = " >> $HGRCPATH
210
222
211 $ cd $TESTTMP
223 $ cd $TESTTMP
212 $ hg init repo1
224 $ hg init repo1
213 $ echo a > repo1/a
225 $ echo a > repo1/a
214 $ hg -R repo1 commit -A -m'init'
226 $ hg -R repo1 commit -A -m'init'
215 adding a
227 adding a
216
228
217 $ hg share repo1 repo2
229 $ hg share repo1 repo2
218 updating working directory
230 updating working directory
219 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
231 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
220
232
221 And check that basic rhg commands work with sharing
233 And check that basic rhg commands work with sharing
222
234
223 $ rhg files -R repo2
235 $ rhg files -R repo2
224 repo2/a
236 repo2/a
225 $ rhg -R repo2 cat -r 0 repo2/a
237 $ rhg -R repo2 cat -r 0 repo2/a
226 a
238 a
227
239
228 Same with relative sharing
240 Same with relative sharing
229
241
230 $ hg share repo2 repo3 --relative
242 $ hg share repo2 repo3 --relative
231 updating working directory
243 updating working directory
232 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
233
245
234 $ rhg files -R repo3
246 $ rhg files -R repo3
235 repo3/a
247 repo3/a
236 $ rhg -R repo3 cat -r 0 repo3/a
248 $ rhg -R repo3 cat -r 0 repo3/a
237 a
249 a
238
250
239 Same with share-safe
251 Same with share-safe
240
252
241 $ echo "[format]" >> $HGRCPATH
253 $ echo "[format]" >> $HGRCPATH
242 $ echo "use-share-safe = True" >> $HGRCPATH
254 $ echo "use-share-safe = True" >> $HGRCPATH
243
255
244 $ cd $TESTTMP
256 $ cd $TESTTMP
245 $ hg init repo4
257 $ hg init repo4
246 $ cd repo4
258 $ cd repo4
247 $ echo a > a
259 $ echo a > a
248 $ hg commit -A -m'init'
260 $ hg commit -A -m'init'
249 adding a
261 adding a
250
262
251 $ cd ..
263 $ cd ..
252 $ hg share repo4 repo5
264 $ hg share repo4 repo5
253 updating working directory
265 updating working directory
254 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
255
267
256 And check that basic rhg commands work with sharing
268 And check that basic rhg commands work with sharing
257
269
258 $ cd repo5
270 $ cd repo5
259 $ rhg files
271 $ rhg files
260 a
272 a
261 $ rhg cat -r 0 a
273 $ rhg cat -r 0 a
262 a
274 a
263
275
264 The blackbox extension is supported
276 The blackbox extension is supported
265
277
266 $ echo "[extensions]" >> $HGRCPATH
278 $ echo "[extensions]" >> $HGRCPATH
267 $ echo "blackbox =" >> $HGRCPATH
279 $ echo "blackbox =" >> $HGRCPATH
268 $ echo "[blackbox]" >> $HGRCPATH
280 $ echo "[blackbox]" >> $HGRCPATH
269 $ echo "maxsize = 1" >> $HGRCPATH
281 $ echo "maxsize = 1" >> $HGRCPATH
270 $ rhg files > /dev/null
282 $ rhg files > /dev/null
271 $ cat .hg/blackbox.log
283 $ cat .hg/blackbox.log
272 ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files exited 0 after 0.??? seconds (glob)
284 ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files exited 0 after 0.??? seconds (glob)
273 $ cat .hg/blackbox.log.1
285 $ cat .hg/blackbox.log.1
274 ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files (glob)
286 ????/??/?? ??:??:??.??? * @d3873e73d99ef67873dac33fbcc66268d5d2b6f4 (*)> (rust) files (glob)
275
287
General Comments 0
You need to be logged in to leave comments. Login now