##// END OF EJS Templates
rhg: add support for detailed exit code for ConfigParseError...
Pulkit Goyal -
r47576:821929d5 default
parent child Browse files
Show More
@@ -1,174 +1,192 b''
1 use crate::exitcode;
1 use crate::ui::utf8_to_local;
2 use crate::ui::utf8_to_local;
2 use crate::ui::UiError;
3 use crate::ui::UiError;
3 use crate::NoRepoInCwdError;
4 use crate::NoRepoInCwdError;
4 use format_bytes::format_bytes;
5 use format_bytes::format_bytes;
5 use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError};
6 use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError};
6 use hg::errors::HgError;
7 use hg::errors::HgError;
7 use hg::repo::RepoError;
8 use hg::repo::RepoError;
8 use hg::revlog::revlog::RevlogError;
9 use hg::revlog::revlog::RevlogError;
9 use hg::utils::files::get_bytes_from_path;
10 use hg::utils::files::get_bytes_from_path;
10 use hg::{DirstateError, DirstateMapError, StatusError};
11 use hg::{DirstateError, DirstateMapError, StatusError};
11 use std::convert::From;
12 use std::convert::From;
12
13
13 /// The kind of command error
14 /// The kind of command error
14 #[derive(Debug)]
15 #[derive(Debug)]
15 pub enum CommandError {
16 pub enum CommandError {
16 /// Exit with an error message and "standard" failure exit code.
17 /// Exit with an error message and "standard" failure exit code.
17 Abort { message: Vec<u8> },
18 Abort {
19 message: Vec<u8>,
20 detailed_exit_code: exitcode::ExitCode,
21 },
18
22
19 /// Exit with a failure exit code but no message.
23 /// Exit with a failure exit code but no message.
20 Unsuccessful,
24 Unsuccessful,
21
25
22 /// Encountered something (such as a CLI argument, repository layout, …)
26 /// Encountered something (such as a CLI argument, repository layout, …)
23 /// not supported by this version of `rhg`. Depending on configuration
27 /// not supported by this version of `rhg`. Depending on configuration
24 /// `rhg` may attempt to silently fall back to Python-based `hg`, which
28 /// `rhg` may attempt to silently fall back to Python-based `hg`, which
25 /// may or may not support this feature.
29 /// may or may not support this feature.
26 UnsupportedFeature { message: Vec<u8> },
30 UnsupportedFeature { message: Vec<u8> },
27 }
31 }
28
32
29 impl CommandError {
33 impl CommandError {
30 pub fn abort(message: impl AsRef<str>) -> Self {
34 pub fn abort(message: impl AsRef<str>) -> Self {
35 CommandError::abort_with_exit_code(message, exitcode::ABORT)
36 }
37
38 pub fn abort_with_exit_code(
39 message: impl AsRef<str>,
40 detailed_exit_code: exitcode::ExitCode,
41 ) -> Self {
31 CommandError::Abort {
42 CommandError::Abort {
32 // TODO: bytes-based (instead of Unicode-based) formatting
43 // TODO: bytes-based (instead of Unicode-based) formatting
33 // of error messages to handle non-UTF-8 filenames etc:
44 // of error messages to handle non-UTF-8 filenames etc:
34 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
45 // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output
35 message: utf8_to_local(message.as_ref()).into(),
46 message: utf8_to_local(message.as_ref()).into(),
47 detailed_exit_code: detailed_exit_code,
36 }
48 }
37 }
49 }
38
50
39 pub fn unsupported(message: impl AsRef<str>) -> Self {
51 pub fn unsupported(message: impl AsRef<str>) -> Self {
40 CommandError::UnsupportedFeature {
52 CommandError::UnsupportedFeature {
41 message: utf8_to_local(message.as_ref()).into(),
53 message: utf8_to_local(message.as_ref()).into(),
42 }
54 }
43 }
55 }
44 }
56 }
45
57
46 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
58 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
47 /// but not supported yet by `rhg`.
59 /// but not supported yet by `rhg`.
48 impl From<clap::Error> for CommandError {
60 impl From<clap::Error> for CommandError {
49 fn from(error: clap::Error) -> Self {
61 fn from(error: clap::Error) -> Self {
50 CommandError::unsupported(error.to_string())
62 CommandError::unsupported(error.to_string())
51 }
63 }
52 }
64 }
53
65
54 impl From<HgError> for CommandError {
66 impl From<HgError> for CommandError {
55 fn from(error: HgError) -> Self {
67 fn from(error: HgError) -> Self {
56 match error {
68 match error {
57 HgError::UnsupportedFeature(message) => {
69 HgError::UnsupportedFeature(message) => {
58 CommandError::unsupported(message)
70 CommandError::unsupported(message)
59 }
71 }
60 _ => CommandError::abort(error.to_string()),
72 _ => CommandError::abort(error.to_string()),
61 }
73 }
62 }
74 }
63 }
75 }
64
76
65 impl From<ConfigValueParseError> for CommandError {
77 impl From<ConfigValueParseError> for CommandError {
66 fn from(error: ConfigValueParseError) -> Self {
78 fn from(error: ConfigValueParseError) -> Self {
67 CommandError::abort(error.to_string())
79 CommandError::abort_with_exit_code(
80 error.to_string(),
81 exitcode::CONFIG_ERROR_ABORT,
82 )
68 }
83 }
69 }
84 }
70
85
71 impl From<UiError> for CommandError {
86 impl From<UiError> for CommandError {
72 fn from(_error: UiError) -> Self {
87 fn from(_error: UiError) -> Self {
73 // If we already failed writing to stdout or stderr,
88 // If we already failed writing to stdout or stderr,
74 // writing an error message to stderr about it would be likely to fail
89 // writing an error message to stderr about it would be likely to fail
75 // too.
90 // too.
76 CommandError::abort("")
91 CommandError::abort("")
77 }
92 }
78 }
93 }
79
94
80 impl From<RepoError> for CommandError {
95 impl From<RepoError> for CommandError {
81 fn from(error: RepoError) -> Self {
96 fn from(error: RepoError) -> Self {
82 match error {
97 match error {
83 RepoError::NotFound { at } => CommandError::Abort {
98 RepoError::NotFound { at } => CommandError::Abort {
84 message: format_bytes!(
99 message: format_bytes!(
85 b"abort: repository {} not found",
100 b"abort: repository {} not found",
86 get_bytes_from_path(at)
101 get_bytes_from_path(at)
87 ),
102 ),
103 detailed_exit_code: exitcode::ABORT,
88 },
104 },
89 RepoError::ConfigParseError(error) => error.into(),
105 RepoError::ConfigParseError(error) => error.into(),
90 RepoError::Other(error) => error.into(),
106 RepoError::Other(error) => error.into(),
91 }
107 }
92 }
108 }
93 }
109 }
94
110
95 impl<'a> From<&'a NoRepoInCwdError> for CommandError {
111 impl<'a> From<&'a NoRepoInCwdError> for CommandError {
96 fn from(error: &'a NoRepoInCwdError) -> Self {
112 fn from(error: &'a NoRepoInCwdError) -> Self {
97 let NoRepoInCwdError { cwd } = error;
113 let NoRepoInCwdError { cwd } = error;
98 CommandError::Abort {
114 CommandError::Abort {
99 message: format_bytes!(
115 message: format_bytes!(
100 b"abort: no repository found in '{}' (.hg not found)!",
116 b"abort: no repository found in '{}' (.hg not found)!",
101 get_bytes_from_path(cwd)
117 get_bytes_from_path(cwd)
102 ),
118 ),
119 detailed_exit_code: exitcode::ABORT,
103 }
120 }
104 }
121 }
105 }
122 }
106
123
107 impl From<ConfigError> for CommandError {
124 impl From<ConfigError> for CommandError {
108 fn from(error: ConfigError) -> Self {
125 fn from(error: ConfigError) -> Self {
109 match error {
126 match error {
110 ConfigError::Parse(error) => error.into(),
127 ConfigError::Parse(error) => error.into(),
111 ConfigError::Other(error) => error.into(),
128 ConfigError::Other(error) => error.into(),
112 }
129 }
113 }
130 }
114 }
131 }
115
132
116 impl From<ConfigParseError> for CommandError {
133 impl From<ConfigParseError> for CommandError {
117 fn from(error: ConfigParseError) -> Self {
134 fn from(error: ConfigParseError) -> Self {
118 let ConfigParseError {
135 let ConfigParseError {
119 origin,
136 origin,
120 line,
137 line,
121 message,
138 message,
122 } = error;
139 } = error;
123 let line_message = if let Some(line_number) = line {
140 let line_message = if let Some(line_number) = line {
124 format_bytes!(b":{}", line_number.to_string().into_bytes())
141 format_bytes!(b":{}", line_number.to_string().into_bytes())
125 } else {
142 } else {
126 Vec::new()
143 Vec::new()
127 };
144 };
128 CommandError::Abort {
145 CommandError::Abort {
129 message: format_bytes!(
146 message: format_bytes!(
130 b"config error at {}{}: {}",
147 b"config error at {}{}: {}",
131 origin,
148 origin,
132 line_message,
149 line_message,
133 message
150 message
134 ),
151 ),
152 detailed_exit_code: exitcode::CONFIG_ERROR_ABORT,
135 }
153 }
136 }
154 }
137 }
155 }
138
156
139 impl From<(RevlogError, &str)> for CommandError {
157 impl From<(RevlogError, &str)> for CommandError {
140 fn from((err, rev): (RevlogError, &str)) -> CommandError {
158 fn from((err, rev): (RevlogError, &str)) -> CommandError {
141 match err {
159 match err {
142 RevlogError::InvalidRevision => CommandError::abort(format!(
160 RevlogError::InvalidRevision => CommandError::abort(format!(
143 "abort: invalid revision identifier: {}",
161 "abort: invalid revision identifier: {}",
144 rev
162 rev
145 )),
163 )),
146 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
164 RevlogError::AmbiguousPrefix => CommandError::abort(format!(
147 "abort: ambiguous revision identifier: {}",
165 "abort: ambiguous revision identifier: {}",
148 rev
166 rev
149 )),
167 )),
150 RevlogError::Other(error) => error.into(),
168 RevlogError::Other(error) => error.into(),
151 }
169 }
152 }
170 }
153 }
171 }
154
172
155 impl From<StatusError> for CommandError {
173 impl From<StatusError> for CommandError {
156 fn from(error: StatusError) -> Self {
174 fn from(error: StatusError) -> Self {
157 CommandError::abort(format!("{}", error))
175 CommandError::abort(format!("{}", error))
158 }
176 }
159 }
177 }
160
178
161 impl From<DirstateMapError> for CommandError {
179 impl From<DirstateMapError> for CommandError {
162 fn from(error: DirstateMapError) -> Self {
180 fn from(error: DirstateMapError) -> Self {
163 CommandError::abort(format!("{}", error))
181 CommandError::abort(format!("{}", error))
164 }
182 }
165 }
183 }
166
184
167 impl From<DirstateError> for CommandError {
185 impl From<DirstateError> for CommandError {
168 fn from(error: DirstateError) -> Self {
186 fn from(error: DirstateError) -> Self {
169 match error {
187 match error {
170 DirstateError::Common(error) => error.into(),
188 DirstateError::Common(error) => error.into(),
171 DirstateError::Map(error) => error.into(),
189 DirstateError::Map(error) => error.into(),
172 }
190 }
173 }
191 }
174 }
192 }
@@ -1,13 +1,16 b''
1 pub type ExitCode = i32;
1 pub type ExitCode = i32;
2
2
3 /// Successful exit
3 /// Successful exit
4 pub const OK: ExitCode = 0;
4 pub const OK: ExitCode = 0;
5
5
6 /// Generic abort
6 /// Generic abort
7 pub const ABORT: ExitCode = 255;
7 pub const ABORT: ExitCode = 255;
8
8
9 // Abort when there is a config related error
10 pub const CONFIG_ERROR_ABORT: ExitCode = 30;
11
9 /// Generic something completed but did not succeed
12 /// Generic something completed but did not succeed
10 pub const UNSUCCESSFUL: ExitCode = 1;
13 pub const UNSUCCESSFUL: ExitCode = 1;
11
14
12 /// Command or feature not implemented by rhg
15 /// Command or feature not implemented by rhg
13 pub const UNIMPLEMENTED: ExitCode = 252;
16 pub const UNIMPLEMENTED: ExitCode = 252;
@@ -1,455 +1,507 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, join};
7 use format_bytes::{format_bytes, join};
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 use std::process::Command;
14 use std::process::Command;
15
15
16 mod blackbox;
16 mod blackbox;
17 mod error;
17 mod error;
18 mod exitcode;
18 mod exitcode;
19 mod ui;
19 mod ui;
20 use error::CommandError;
20 use error::CommandError;
21
21
22 fn main_with_result(
22 fn main_with_result(
23 process_start_time: &blackbox::ProcessStartTime,
23 process_start_time: &blackbox::ProcessStartTime,
24 ui: &ui::Ui,
24 ui: &ui::Ui,
25 repo: Result<&Repo, &NoRepoInCwdError>,
25 repo: Result<&Repo, &NoRepoInCwdError>,
26 config: &Config,
26 config: &Config,
27 ) -> Result<(), CommandError> {
27 ) -> Result<(), CommandError> {
28 check_extensions(config)?;
28 check_extensions(config)?;
29
29
30 let app = App::new("rhg")
30 let app = App::new("rhg")
31 .global_setting(AppSettings::AllowInvalidUtf8)
31 .global_setting(AppSettings::AllowInvalidUtf8)
32 .global_setting(AppSettings::DisableVersion)
32 .global_setting(AppSettings::DisableVersion)
33 .setting(AppSettings::SubcommandRequired)
33 .setting(AppSettings::SubcommandRequired)
34 .setting(AppSettings::VersionlessSubcommands)
34 .setting(AppSettings::VersionlessSubcommands)
35 .arg(
35 .arg(
36 Arg::with_name("repository")
36 Arg::with_name("repository")
37 .help("repository root directory")
37 .help("repository root directory")
38 .short("-R")
38 .short("-R")
39 .long("--repository")
39 .long("--repository")
40 .value_name("REPO")
40 .value_name("REPO")
41 .takes_value(true)
41 .takes_value(true)
42 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
42 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
43 .global(true),
43 .global(true),
44 )
44 )
45 .arg(
45 .arg(
46 Arg::with_name("config")
46 Arg::with_name("config")
47 .help("set/override config option (use 'section.name=value')")
47 .help("set/override config option (use 'section.name=value')")
48 .long("--config")
48 .long("--config")
49 .value_name("CONFIG")
49 .value_name("CONFIG")
50 .takes_value(true)
50 .takes_value(true)
51 .global(true)
51 .global(true)
52 // Ok: `--config section.key1=val --config section.key2=val2`
52 // Ok: `--config section.key1=val --config section.key2=val2`
53 .multiple(true)
53 .multiple(true)
54 // Not ok: `--config section.key1=val section.key2=val2`
54 // Not ok: `--config section.key1=val section.key2=val2`
55 .number_of_values(1),
55 .number_of_values(1),
56 )
56 )
57 .arg(
57 .arg(
58 Arg::with_name("cwd")
58 Arg::with_name("cwd")
59 .help("change working directory")
59 .help("change working directory")
60 .long("--cwd")
60 .long("--cwd")
61 .value_name("DIR")
61 .value_name("DIR")
62 .takes_value(true)
62 .takes_value(true)
63 .global(true),
63 .global(true),
64 )
64 )
65 .version("0.0.1");
65 .version("0.0.1");
66 let app = add_subcommand_args(app);
66 let app = add_subcommand_args(app);
67
67
68 let matches = app.clone().get_matches_safe()?;
68 let matches = app.clone().get_matches_safe()?;
69
69
70 let (subcommand_name, subcommand_matches) = matches.subcommand();
70 let (subcommand_name, subcommand_matches) = matches.subcommand();
71 let run = subcommand_run_fn(subcommand_name)
71 let run = subcommand_run_fn(subcommand_name)
72 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
72 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
73 let subcommand_args = subcommand_matches
73 let subcommand_args = subcommand_matches
74 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
74 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
75
75
76 let invocation = CliInvocation {
76 let invocation = CliInvocation {
77 ui,
77 ui,
78 subcommand_args,
78 subcommand_args,
79 config,
79 config,
80 repo,
80 repo,
81 };
81 };
82 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
82 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
83 blackbox.log_command_start();
83 blackbox.log_command_start();
84 let result = run(&invocation);
84 let result = run(&invocation);
85 blackbox.log_command_end(exit_code(&result));
85 blackbox.log_command_end(exit_code(
86 &result,
87 // TODO: show a warning or combine with original error if `get_bool`
88 // returns an error
89 config
90 .get_bool(b"ui", b"detailed-exit-code")
91 .unwrap_or(false),
92 ));
86 result
93 result
87 }
94 }
88
95
89 fn main() {
96 fn main() {
90 // Run this first, before we find out if the blackbox extension is even
97 // Run this first, before we find out if the blackbox extension is even
91 // enabled, in order to include everything in-between in the duration
98 // enabled, in order to include everything in-between in the duration
92 // measurements. Reading config files can be slow if they’re on NFS.
99 // measurements. Reading config files can be slow if they’re on NFS.
93 let process_start_time = blackbox::ProcessStartTime::now();
100 let process_start_time = blackbox::ProcessStartTime::now();
94
101
95 env_logger::init();
102 env_logger::init();
96 let ui = ui::Ui::new();
103 let ui = ui::Ui::new();
97
104
98 let early_args = EarlyArgs::parse(std::env::args_os());
105 let early_args = EarlyArgs::parse(std::env::args_os());
99
106
100 let initial_current_dir = early_args.cwd.map(|cwd| {
107 let initial_current_dir = early_args.cwd.map(|cwd| {
101 let cwd = get_path_from_bytes(&cwd);
108 let cwd = get_path_from_bytes(&cwd);
102 std::env::current_dir()
109 std::env::current_dir()
103 .and_then(|initial| {
110 .and_then(|initial| {
104 std::env::set_current_dir(cwd)?;
111 std::env::set_current_dir(cwd)?;
105 Ok(initial)
112 Ok(initial)
106 })
113 })
107 .unwrap_or_else(|error| {
114 .unwrap_or_else(|error| {
108 exit(
115 exit(
109 &None,
116 &None,
110 &ui,
117 &ui,
111 OnUnsupported::Abort,
118 OnUnsupported::Abort,
112 Err(CommandError::abort(format!(
119 Err(CommandError::abort(format!(
113 "abort: {}: '{}'",
120 "abort: {}: '{}'",
114 error,
121 error,
115 cwd.display()
122 cwd.display()
116 ))),
123 ))),
124 false,
117 )
125 )
118 })
126 })
119 });
127 });
120
128
121 let non_repo_config =
129 let non_repo_config =
122 Config::load(early_args.config).unwrap_or_else(|error| {
130 Config::load(early_args.config).unwrap_or_else(|error| {
123 // Normally this is decided based on config, but we don’t have that
131 // Normally this is decided based on config, but we don’t have that
124 // available. As of this writing config loading never returns an
132 // available. As of this writing config loading never returns an
125 // "unsupported" error but that is not enforced by the type system.
133 // "unsupported" error but that is not enforced by the type system.
126 let on_unsupported = OnUnsupported::Abort;
134 let on_unsupported = OnUnsupported::Abort;
127
135
128 exit(&initial_current_dir, &ui, on_unsupported, Err(error.into()))
136 exit(
137 &initial_current_dir,
138 &ui,
139 on_unsupported,
140 Err(error.into()),
141 false,
142 )
129 });
143 });
130
144
131 if let Some(repo_path_bytes) = &early_args.repo {
145 if let Some(repo_path_bytes) = &early_args.repo {
132 lazy_static::lazy_static! {
146 lazy_static::lazy_static! {
133 static ref SCHEME_RE: regex::bytes::Regex =
147 static ref SCHEME_RE: regex::bytes::Regex =
134 // Same as `_matchscheme` in `mercurial/util.py`
148 // Same as `_matchscheme` in `mercurial/util.py`
135 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
149 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
136 }
150 }
137 if SCHEME_RE.is_match(&repo_path_bytes) {
151 if SCHEME_RE.is_match(&repo_path_bytes) {
138 exit(
152 exit(
139 &initial_current_dir,
153 &initial_current_dir,
140 &ui,
154 &ui,
141 OnUnsupported::from_config(&ui, &non_repo_config),
155 OnUnsupported::from_config(&ui, &non_repo_config),
142 Err(CommandError::UnsupportedFeature {
156 Err(CommandError::UnsupportedFeature {
143 message: format_bytes!(
157 message: format_bytes!(
144 b"URL-like --repository {}",
158 b"URL-like --repository {}",
145 repo_path_bytes
159 repo_path_bytes
146 ),
160 ),
147 }),
161 }),
162 // TODO: show a warning or combine with original error if
163 // `get_bool` returns an error
164 non_repo_config
165 .get_bool(b"ui", b"detailed-exit-code")
166 .unwrap_or(false),
148 )
167 )
149 }
168 }
150 }
169 }
151 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
170 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
152 let repo_result = match Repo::find(&non_repo_config, repo_path) {
171 let repo_result = match Repo::find(&non_repo_config, repo_path) {
153 Ok(repo) => Ok(repo),
172 Ok(repo) => Ok(repo),
154 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
173 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
155 // Not finding a repo is not fatal yet, if `-R` was not given
174 // Not finding a repo is not fatal yet, if `-R` was not given
156 Err(NoRepoInCwdError { cwd: at })
175 Err(NoRepoInCwdError { cwd: at })
157 }
176 }
158 Err(error) => exit(
177 Err(error) => exit(
159 &initial_current_dir,
178 &initial_current_dir,
160 &ui,
179 &ui,
161 OnUnsupported::from_config(&ui, &non_repo_config),
180 OnUnsupported::from_config(&ui, &non_repo_config),
162 Err(error.into()),
181 Err(error.into()),
182 // TODO: show a warning or combine with original error if
183 // `get_bool` returns an error
184 non_repo_config
185 .get_bool(b"ui", b"detailed-exit-code")
186 .unwrap_or(false),
163 ),
187 ),
164 };
188 };
165
189
166 let config = if let Ok(repo) = &repo_result {
190 let config = if let Ok(repo) = &repo_result {
167 repo.config()
191 repo.config()
168 } else {
192 } else {
169 &non_repo_config
193 &non_repo_config
170 };
194 };
171 let on_unsupported = OnUnsupported::from_config(&ui, config);
195 let on_unsupported = OnUnsupported::from_config(&ui, config);
172
196
173 let result = main_with_result(
197 let result = main_with_result(
174 &process_start_time,
198 &process_start_time,
175 &ui,
199 &ui,
176 repo_result.as_ref(),
200 repo_result.as_ref(),
177 config,
201 config,
178 );
202 );
179 exit(&initial_current_dir, &ui, on_unsupported, result)
203 exit(
204 &initial_current_dir,
205 &ui,
206 on_unsupported,
207 result,
208 // TODO: show a warning or combine with original error if `get_bool`
209 // returns an error
210 config
211 .get_bool(b"ui", b"detailed-exit-code")
212 .unwrap_or(false),
213 )
180 }
214 }
181
215
182 fn exit_code(result: &Result<(), CommandError>) -> i32 {
216 fn exit_code(
217 result: &Result<(), CommandError>,
218 use_detailed_exit_code: bool,
219 ) -> i32 {
183 match result {
220 match result {
184 Ok(()) => exitcode::OK,
221 Ok(()) => exitcode::OK,
185 Err(CommandError::Abort { .. }) => exitcode::ABORT,
222 Err(CommandError::Abort {
223 message: _,
224 detailed_exit_code,
225 }) => {
226 if use_detailed_exit_code {
227 *detailed_exit_code
228 } else {
229 exitcode::ABORT
230 }
231 }
186 Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL,
232 Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL,
187
233
188 // Exit with a specific code and no error message to let a potential
234 // Exit with a specific code and no error message to let a potential
189 // wrapper script fallback to Python-based Mercurial.
235 // wrapper script fallback to Python-based Mercurial.
190 Err(CommandError::UnsupportedFeature { .. }) => {
236 Err(CommandError::UnsupportedFeature { .. }) => {
191 exitcode::UNIMPLEMENTED
237 exitcode::UNIMPLEMENTED
192 }
238 }
193 }
239 }
194 }
240 }
195
241
196 fn exit(
242 fn exit(
197 initial_current_dir: &Option<PathBuf>,
243 initial_current_dir: &Option<PathBuf>,
198 ui: &Ui,
244 ui: &Ui,
199 mut on_unsupported: OnUnsupported,
245 mut on_unsupported: OnUnsupported,
200 result: Result<(), CommandError>,
246 result: Result<(), CommandError>,
247 use_detailed_exit_code: bool,
201 ) -> ! {
248 ) -> ! {
202 if let (
249 if let (
203 OnUnsupported::Fallback { executable },
250 OnUnsupported::Fallback { executable },
204 Err(CommandError::UnsupportedFeature { .. }),
251 Err(CommandError::UnsupportedFeature { .. }),
205 ) = (&on_unsupported, &result)
252 ) = (&on_unsupported, &result)
206 {
253 {
207 let mut args = std::env::args_os();
254 let mut args = std::env::args_os();
208 let executable_path = get_path_from_bytes(&executable);
255 let executable_path = get_path_from_bytes(&executable);
209 let this_executable = args.next().expect("exepcted argv[0] to exist");
256 let this_executable = args.next().expect("exepcted argv[0] to exist");
210 if executable_path == &PathBuf::from(this_executable) {
257 if executable_path == &PathBuf::from(this_executable) {
211 // Avoid spawning infinitely many processes until resource
258 // Avoid spawning infinitely many processes until resource
212 // exhaustion.
259 // exhaustion.
213 let _ = ui.write_stderr(&format_bytes!(
260 let _ = ui.write_stderr(&format_bytes!(
214 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
261 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
215 points to `rhg` itself.\n",
262 points to `rhg` itself.\n",
216 executable
263 executable
217 ));
264 ));
218 on_unsupported = OnUnsupported::Abort
265 on_unsupported = OnUnsupported::Abort
219 } else {
266 } else {
220 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
267 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
221 let mut command = Command::new(executable_path);
268 let mut command = Command::new(executable_path);
222 command.args(args);
269 command.args(args);
223 if let Some(initial) = initial_current_dir {
270 if let Some(initial) = initial_current_dir {
224 command.current_dir(initial);
271 command.current_dir(initial);
225 }
272 }
226 let result = command.status();
273 let result = command.status();
227 match result {
274 match result {
228 Ok(status) => std::process::exit(
275 Ok(status) => std::process::exit(
229 status.code().unwrap_or(exitcode::ABORT),
276 status.code().unwrap_or(exitcode::ABORT),
230 ),
277 ),
231 Err(error) => {
278 Err(error) => {
232 let _ = ui.write_stderr(&format_bytes!(
279 let _ = ui.write_stderr(&format_bytes!(
233 b"tried to fall back to a '{}' sub-process but got error {}\n",
280 b"tried to fall back to a '{}' sub-process but got error {}\n",
234 executable, format_bytes::Utf8(error)
281 executable, format_bytes::Utf8(error)
235 ));
282 ));
236 on_unsupported = OnUnsupported::Abort
283 on_unsupported = OnUnsupported::Abort
237 }
284 }
238 }
285 }
239 }
286 }
240 }
287 }
241 exit_no_fallback(ui, on_unsupported, result)
288 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
242 }
289 }
243
290
244 fn exit_no_fallback(
291 fn exit_no_fallback(
245 ui: &Ui,
292 ui: &Ui,
246 on_unsupported: OnUnsupported,
293 on_unsupported: OnUnsupported,
247 result: Result<(), CommandError>,
294 result: Result<(), CommandError>,
295 use_detailed_exit_code: bool,
248 ) -> ! {
296 ) -> ! {
249 match &result {
297 match &result {
250 Ok(_) => {}
298 Ok(_) => {}
251 Err(CommandError::Unsuccessful) => {}
299 Err(CommandError::Unsuccessful) => {}
252 Err(CommandError::Abort { message }) => {
300 Err(CommandError::Abort {
301 message,
302 detailed_exit_code: _,
303 }) => {
253 if !message.is_empty() {
304 if !message.is_empty() {
254 // Ignore errors when writing to stderr, we’re already exiting
305 // Ignore errors when writing to stderr, we’re already exiting
255 // with failure code so there’s not much more we can do.
306 // with failure code so there’s not much more we can do.
256 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
307 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
257 }
308 }
258 }
309 }
259 Err(CommandError::UnsupportedFeature { message }) => {
310 Err(CommandError::UnsupportedFeature { message }) => {
260 match on_unsupported {
311 match on_unsupported {
261 OnUnsupported::Abort => {
312 OnUnsupported::Abort => {
262 let _ = ui.write_stderr(&format_bytes!(
313 let _ = ui.write_stderr(&format_bytes!(
263 b"unsupported feature: {}\n",
314 b"unsupported feature: {}\n",
264 message
315 message
265 ));
316 ));
266 }
317 }
267 OnUnsupported::AbortSilent => {}
318 OnUnsupported::AbortSilent => {}
268 OnUnsupported::Fallback { .. } => unreachable!(),
319 OnUnsupported::Fallback { .. } => unreachable!(),
269 }
320 }
270 }
321 }
271 }
322 }
272 std::process::exit(exit_code(&result))
323 std::process::exit(exit_code(&result, use_detailed_exit_code))
273 }
324 }
274
325
275 macro_rules! subcommands {
326 macro_rules! subcommands {
276 ($( $command: ident )+) => {
327 ($( $command: ident )+) => {
277 mod commands {
328 mod commands {
278 $(
329 $(
279 pub mod $command;
330 pub mod $command;
280 )+
331 )+
281 }
332 }
282
333
283 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
334 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
284 app
335 app
285 $(
336 $(
286 .subcommand(commands::$command::args())
337 .subcommand(commands::$command::args())
287 )+
338 )+
288 }
339 }
289
340
290 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
341 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
291
342
292 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
343 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
293 match name {
344 match name {
294 $(
345 $(
295 stringify!($command) => Some(commands::$command::run),
346 stringify!($command) => Some(commands::$command::run),
296 )+
347 )+
297 _ => None,
348 _ => None,
298 }
349 }
299 }
350 }
300 };
351 };
301 }
352 }
302
353
303 subcommands! {
354 subcommands! {
304 cat
355 cat
305 debugdata
356 debugdata
306 debugrequirements
357 debugrequirements
307 files
358 files
308 root
359 root
309 config
360 config
310 }
361 }
311 pub struct CliInvocation<'a> {
362 pub struct CliInvocation<'a> {
312 ui: &'a Ui,
363 ui: &'a Ui,
313 subcommand_args: &'a ArgMatches<'a>,
364 subcommand_args: &'a ArgMatches<'a>,
314 config: &'a Config,
365 config: &'a Config,
315 /// References inside `Result` is a bit peculiar but allow
366 /// References inside `Result` is a bit peculiar but allow
316 /// `invocation.repo?` to work out with `&CliInvocation` since this
367 /// `invocation.repo?` to work out with `&CliInvocation` since this
317 /// `Result` type is `Copy`.
368 /// `Result` type is `Copy`.
318 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
369 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
319 }
370 }
320
371
321 struct NoRepoInCwdError {
372 struct NoRepoInCwdError {
322 cwd: PathBuf,
373 cwd: PathBuf,
323 }
374 }
324
375
325 /// CLI arguments to be parsed "early" in order to be able to read
376 /// CLI arguments to be parsed "early" in order to be able to read
326 /// configuration before using Clap. Ideally we would also use Clap for this,
377 /// configuration before using Clap. Ideally we would also use Clap for this,
327 /// see <https://github.com/clap-rs/clap/discussions/2366>.
378 /// see <https://github.com/clap-rs/clap/discussions/2366>.
328 ///
379 ///
329 /// These arguments are still declared when we do use Clap later, so that Clap
380 /// These arguments are still declared when we do use Clap later, so that Clap
330 /// does not return an error for their presence.
381 /// does not return an error for their presence.
331 struct EarlyArgs {
382 struct EarlyArgs {
332 /// Values of all `--config` arguments. (Possibly none)
383 /// Values of all `--config` arguments. (Possibly none)
333 config: Vec<Vec<u8>>,
384 config: Vec<Vec<u8>>,
334 /// Value of the `-R` or `--repository` argument, if any.
385 /// Value of the `-R` or `--repository` argument, if any.
335 repo: Option<Vec<u8>>,
386 repo: Option<Vec<u8>>,
336 /// Value of the `--cwd` argument, if any.
387 /// Value of the `--cwd` argument, if any.
337 cwd: Option<Vec<u8>>,
388 cwd: Option<Vec<u8>>,
338 }
389 }
339
390
340 impl EarlyArgs {
391 impl EarlyArgs {
341 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
392 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
342 let mut args = args.into_iter().map(get_bytes_from_os_str);
393 let mut args = args.into_iter().map(get_bytes_from_os_str);
343 let mut config = Vec::new();
394 let mut config = Vec::new();
344 let mut repo = None;
395 let mut repo = None;
345 let mut cwd = None;
396 let mut cwd = None;
346 // Use `while let` instead of `for` so that we can also call
397 // Use `while let` instead of `for` so that we can also call
347 // `args.next()` inside the loop.
398 // `args.next()` inside the loop.
348 while let Some(arg) = args.next() {
399 while let Some(arg) = args.next() {
349 if arg == b"--config" {
400 if arg == b"--config" {
350 if let Some(value) = args.next() {
401 if let Some(value) = args.next() {
351 config.push(value)
402 config.push(value)
352 }
403 }
353 } else if let Some(value) = arg.drop_prefix(b"--config=") {
404 } else if let Some(value) = arg.drop_prefix(b"--config=") {
354 config.push(value.to_owned())
405 config.push(value.to_owned())
355 }
406 }
356
407
357 if arg == b"--cwd" {
408 if arg == b"--cwd" {
358 if let Some(value) = args.next() {
409 if let Some(value) = args.next() {
359 cwd = Some(value)
410 cwd = Some(value)
360 }
411 }
361 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
412 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
362 cwd = Some(value.to_owned())
413 cwd = Some(value.to_owned())
363 }
414 }
364
415
365 if arg == b"--repository" || arg == b"-R" {
416 if arg == b"--repository" || arg == b"-R" {
366 if let Some(value) = args.next() {
417 if let Some(value) = args.next() {
367 repo = Some(value)
418 repo = Some(value)
368 }
419 }
369 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
420 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
370 repo = Some(value.to_owned())
421 repo = Some(value.to_owned())
371 } else if let Some(value) = arg.drop_prefix(b"-R") {
422 } else if let Some(value) = arg.drop_prefix(b"-R") {
372 repo = Some(value.to_owned())
423 repo = Some(value.to_owned())
373 }
424 }
374 }
425 }
375 Self { config, repo, cwd }
426 Self { config, repo, cwd }
376 }
427 }
377 }
428 }
378
429
379 /// What to do when encountering some unsupported feature.
430 /// What to do when encountering some unsupported feature.
380 ///
431 ///
381 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
432 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
382 enum OnUnsupported {
433 enum OnUnsupported {
383 /// Print an error message describing what feature is not supported,
434 /// Print an error message describing what feature is not supported,
384 /// and exit with code 252.
435 /// and exit with code 252.
385 Abort,
436 Abort,
386 /// Silently exit with code 252.
437 /// Silently exit with code 252.
387 AbortSilent,
438 AbortSilent,
388 /// Try running a Python implementation
439 /// Try running a Python implementation
389 Fallback { executable: Vec<u8> },
440 Fallback { executable: Vec<u8> },
390 }
441 }
391
442
392 impl OnUnsupported {
443 impl OnUnsupported {
393 const DEFAULT: Self = OnUnsupported::Abort;
444 const DEFAULT: Self = OnUnsupported::Abort;
394
445
395 fn from_config(ui: &Ui, config: &Config) -> Self {
446 fn from_config(ui: &Ui, config: &Config) -> Self {
396 match config
447 match config
397 .get(b"rhg", b"on-unsupported")
448 .get(b"rhg", b"on-unsupported")
398 .map(|value| value.to_ascii_lowercase())
449 .map(|value| value.to_ascii_lowercase())
399 .as_deref()
450 .as_deref()
400 {
451 {
401 Some(b"abort") => OnUnsupported::Abort,
452 Some(b"abort") => OnUnsupported::Abort,
402 Some(b"abort-silent") => OnUnsupported::AbortSilent,
453 Some(b"abort-silent") => OnUnsupported::AbortSilent,
403 Some(b"fallback") => OnUnsupported::Fallback {
454 Some(b"fallback") => OnUnsupported::Fallback {
404 executable: config
455 executable: config
405 .get(b"rhg", b"fallback-executable")
456 .get(b"rhg", b"fallback-executable")
406 .unwrap_or_else(|| {
457 .unwrap_or_else(|| {
407 exit_no_fallback(
458 exit_no_fallback(
408 ui,
459 ui,
409 Self::Abort,
460 Self::Abort,
410 Err(CommandError::abort(
461 Err(CommandError::abort(
411 "abort: 'rhg.on-unsupported=fallback' without \
462 "abort: 'rhg.on-unsupported=fallback' without \
412 'rhg.fallback-executable' set."
463 'rhg.fallback-executable' set."
413 )),
464 )),
465 false,
414 )
466 )
415 })
467 })
416 .to_owned(),
468 .to_owned(),
417 },
469 },
418 None => Self::DEFAULT,
470 None => Self::DEFAULT,
419 Some(_) => {
471 Some(_) => {
420 // TODO: warn about unknown config value
472 // TODO: warn about unknown config value
421 Self::DEFAULT
473 Self::DEFAULT
422 }
474 }
423 }
475 }
424 }
476 }
425 }
477 }
426
478
427 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
479 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
428
480
429 fn check_extensions(config: &Config) -> Result<(), CommandError> {
481 fn check_extensions(config: &Config) -> Result<(), CommandError> {
430 let enabled = config.get_section_keys(b"extensions");
482 let enabled = config.get_section_keys(b"extensions");
431
483
432 let mut unsupported = enabled;
484 let mut unsupported = enabled;
433 for supported in SUPPORTED_EXTENSIONS {
485 for supported in SUPPORTED_EXTENSIONS {
434 unsupported.remove(supported);
486 unsupported.remove(supported);
435 }
487 }
436
488
437 if let Some(ignored_list) =
489 if let Some(ignored_list) =
438 config.get_simple_list(b"rhg", b"ignored-extensions")
490 config.get_simple_list(b"rhg", b"ignored-extensions")
439 {
491 {
440 for ignored in ignored_list {
492 for ignored in ignored_list {
441 unsupported.remove(ignored);
493 unsupported.remove(ignored);
442 }
494 }
443 }
495 }
444
496
445 if unsupported.is_empty() {
497 if unsupported.is_empty() {
446 Ok(())
498 Ok(())
447 } else {
499 } else {
448 Err(CommandError::UnsupportedFeature {
500 Err(CommandError::UnsupportedFeature {
449 message: format_bytes!(
501 message: format_bytes!(
450 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
502 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
451 join(unsupported, b", ")
503 join(unsupported, b", ")
452 ),
504 ),
453 })
505 })
454 }
506 }
455 }
507 }
@@ -1,504 +1,501 b''
1 hide outer repo
1 hide outer repo
2 $ hg init
2 $ hg init
3
3
4 Invalid syntax: no value
4 Invalid syntax: no value
5
5
6 TODO: add rhg support for detailed exit codes
7 #if no-rhg
8 $ cat > .hg/hgrc << EOF
6 $ cat > .hg/hgrc << EOF
9 > novaluekey
7 > novaluekey
10 > EOF
8 > EOF
11 $ hg showconfig
9 $ hg showconfig
12 config error at $TESTTMP/.hg/hgrc:1: novaluekey
10 config error at $TESTTMP/.hg/hgrc:1: novaluekey
13 [30]
11 [30]
14
12
15 Invalid syntax: no key
13 Invalid syntax: no key
16
14
17 $ cat > .hg/hgrc << EOF
15 $ cat > .hg/hgrc << EOF
18 > =nokeyvalue
16 > =nokeyvalue
19 > EOF
17 > EOF
20 $ hg showconfig
18 $ hg showconfig
21 config error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
19 config error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
22 [30]
20 [30]
23
21
24 Test hint about invalid syntax from leading white space
22 Test hint about invalid syntax from leading white space
25
23
26 $ cat > .hg/hgrc << EOF
24 $ cat > .hg/hgrc << EOF
27 > key=value
25 > key=value
28 > EOF
26 > EOF
29 $ hg showconfig
27 $ hg showconfig
30 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
28 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
31 [30]
29 [30]
32
30
33 $ cat > .hg/hgrc << EOF
31 $ cat > .hg/hgrc << EOF
34 > [section]
32 > [section]
35 > key=value
33 > key=value
36 > EOF
34 > EOF
37 $ hg showconfig
35 $ hg showconfig
38 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
36 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
39 [30]
37 [30]
40 #endif
41
38
42 Reset hgrc
39 Reset hgrc
43
40
44 $ echo > .hg/hgrc
41 $ echo > .hg/hgrc
45
42
46 Test case sensitive configuration
43 Test case sensitive configuration
47
44
48 $ cat <<EOF >> $HGRCPATH
45 $ cat <<EOF >> $HGRCPATH
49 > [Section]
46 > [Section]
50 > KeY = Case Sensitive
47 > KeY = Case Sensitive
51 > key = lower case
48 > key = lower case
52 > EOF
49 > EOF
53
50
54 $ hg showconfig Section
51 $ hg showconfig Section
55 Section.KeY=Case Sensitive
52 Section.KeY=Case Sensitive
56 Section.key=lower case
53 Section.key=lower case
57
54
58 $ hg showconfig Section -Tjson
55 $ hg showconfig Section -Tjson
59 [
56 [
60 {
57 {
61 "defaultvalue": null,
58 "defaultvalue": null,
62 "name": "Section.KeY",
59 "name": "Section.KeY",
63 "source": "*.hgrc:*", (glob)
60 "source": "*.hgrc:*", (glob)
64 "value": "Case Sensitive"
61 "value": "Case Sensitive"
65 },
62 },
66 {
63 {
67 "defaultvalue": null,
64 "defaultvalue": null,
68 "name": "Section.key",
65 "name": "Section.key",
69 "source": "*.hgrc:*", (glob)
66 "source": "*.hgrc:*", (glob)
70 "value": "lower case"
67 "value": "lower case"
71 }
68 }
72 ]
69 ]
73 $ hg showconfig Section.KeY -Tjson
70 $ hg showconfig Section.KeY -Tjson
74 [
71 [
75 {
72 {
76 "defaultvalue": null,
73 "defaultvalue": null,
77 "name": "Section.KeY",
74 "name": "Section.KeY",
78 "source": "*.hgrc:*", (glob)
75 "source": "*.hgrc:*", (glob)
79 "value": "Case Sensitive"
76 "value": "Case Sensitive"
80 }
77 }
81 ]
78 ]
82 $ hg showconfig -Tjson | tail -7
79 $ hg showconfig -Tjson | tail -7
83 {
80 {
84 "defaultvalue": null,
81 "defaultvalue": null,
85 "name": "*", (glob)
82 "name": "*", (glob)
86 "source": "*", (glob)
83 "source": "*", (glob)
87 "value": "*" (glob)
84 "value": "*" (glob)
88 }
85 }
89 ]
86 ]
90
87
91 Test config default of various types:
88 Test config default of various types:
92
89
93 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
90 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
94 how the templater works. Unknown keywords are evaluated to "".
91 how the templater works. Unknown keywords are evaluated to "".
95
92
96 dynamicdefault
93 dynamicdefault
97
94
98 $ hg config --config alias.foo= alias -Tjson
95 $ hg config --config alias.foo= alias -Tjson
99 [
96 [
100 {
97 {
101 "name": "alias.foo",
98 "name": "alias.foo",
102 "source": "--config",
99 "source": "--config",
103 "value": ""
100 "value": ""
104 }
101 }
105 ]
102 ]
106 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
103 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
107 [
104 [
108 {"defaultvalue": ""}
105 {"defaultvalue": ""}
109 ]
106 ]
110 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
107 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
111
108
112
109
113 null
110 null
114
111
115 $ hg config --config auth.cookiefile= auth -Tjson
112 $ hg config --config auth.cookiefile= auth -Tjson
116 [
113 [
117 {
114 {
118 "defaultvalue": null,
115 "defaultvalue": null,
119 "name": "auth.cookiefile",
116 "name": "auth.cookiefile",
120 "source": "--config",
117 "source": "--config",
121 "value": ""
118 "value": ""
122 }
119 }
123 ]
120 ]
124 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
121 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
125 [
122 [
126 {"defaultvalue": null}
123 {"defaultvalue": null}
127 ]
124 ]
128 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
125 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
129
126
130
127
131 false
128 false
132
129
133 $ hg config --config commands.commit.post-status= commands -Tjson
130 $ hg config --config commands.commit.post-status= commands -Tjson
134 [
131 [
135 {
132 {
136 "defaultvalue": false,
133 "defaultvalue": false,
137 "name": "commands.commit.post-status",
134 "name": "commands.commit.post-status",
138 "source": "--config",
135 "source": "--config",
139 "value": ""
136 "value": ""
140 }
137 }
141 ]
138 ]
142 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
139 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
143 [
140 [
144 {"defaultvalue": false}
141 {"defaultvalue": false}
145 ]
142 ]
146 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
143 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
147 False
144 False
148
145
149 true
146 true
150
147
151 $ hg config --config format.dotencode= format -Tjson
148 $ hg config --config format.dotencode= format -Tjson
152 [
149 [
153 {
150 {
154 "defaultvalue": true,
151 "defaultvalue": true,
155 "name": "format.dotencode",
152 "name": "format.dotencode",
156 "source": "--config",
153 "source": "--config",
157 "value": ""
154 "value": ""
158 }
155 }
159 ]
156 ]
160 $ hg config --config format.dotencode= format -T'json(defaultvalue)'
157 $ hg config --config format.dotencode= format -T'json(defaultvalue)'
161 [
158 [
162 {"defaultvalue": true}
159 {"defaultvalue": true}
163 ]
160 ]
164 $ hg config --config format.dotencode= format -T'{defaultvalue}\n'
161 $ hg config --config format.dotencode= format -T'{defaultvalue}\n'
165 True
162 True
166
163
167 bytes
164 bytes
168
165
169 $ hg config --config commands.resolve.mark-check= commands -Tjson
166 $ hg config --config commands.resolve.mark-check= commands -Tjson
170 [
167 [
171 {
168 {
172 "defaultvalue": "none",
169 "defaultvalue": "none",
173 "name": "commands.resolve.mark-check",
170 "name": "commands.resolve.mark-check",
174 "source": "--config",
171 "source": "--config",
175 "value": ""
172 "value": ""
176 }
173 }
177 ]
174 ]
178 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
175 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
179 [
176 [
180 {"defaultvalue": "none"}
177 {"defaultvalue": "none"}
181 ]
178 ]
182 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
179 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
183 none
180 none
184
181
185 empty list
182 empty list
186
183
187 $ hg config --config commands.show.aliasprefix= commands -Tjson
184 $ hg config --config commands.show.aliasprefix= commands -Tjson
188 [
185 [
189 {
186 {
190 "defaultvalue": [],
187 "defaultvalue": [],
191 "name": "commands.show.aliasprefix",
188 "name": "commands.show.aliasprefix",
192 "source": "--config",
189 "source": "--config",
193 "value": ""
190 "value": ""
194 }
191 }
195 ]
192 ]
196 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
193 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
197 [
194 [
198 {"defaultvalue": []}
195 {"defaultvalue": []}
199 ]
196 ]
200 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
197 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
201
198
202
199
203 nonempty list
200 nonempty list
204
201
205 $ hg config --config progress.format= progress -Tjson
202 $ hg config --config progress.format= progress -Tjson
206 [
203 [
207 {
204 {
208 "defaultvalue": ["topic", "bar", "number", "estimate"],
205 "defaultvalue": ["topic", "bar", "number", "estimate"],
209 "name": "progress.format",
206 "name": "progress.format",
210 "source": "--config",
207 "source": "--config",
211 "value": ""
208 "value": ""
212 }
209 }
213 ]
210 ]
214 $ hg config --config progress.format= progress -T'json(defaultvalue)'
211 $ hg config --config progress.format= progress -T'json(defaultvalue)'
215 [
212 [
216 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
213 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
217 ]
214 ]
218 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
215 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
219 topic bar number estimate
216 topic bar number estimate
220
217
221 int
218 int
222
219
223 $ hg config --config profiling.freq= profiling -Tjson
220 $ hg config --config profiling.freq= profiling -Tjson
224 [
221 [
225 {
222 {
226 "defaultvalue": 1000,
223 "defaultvalue": 1000,
227 "name": "profiling.freq",
224 "name": "profiling.freq",
228 "source": "--config",
225 "source": "--config",
229 "value": ""
226 "value": ""
230 }
227 }
231 ]
228 ]
232 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
229 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
233 [
230 [
234 {"defaultvalue": 1000}
231 {"defaultvalue": 1000}
235 ]
232 ]
236 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
233 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
237 1000
234 1000
238
235
239 float
236 float
240
237
241 $ hg config --config profiling.showmax= profiling -Tjson
238 $ hg config --config profiling.showmax= profiling -Tjson
242 [
239 [
243 {
240 {
244 "defaultvalue": 0.999,
241 "defaultvalue": 0.999,
245 "name": "profiling.showmax",
242 "name": "profiling.showmax",
246 "source": "--config",
243 "source": "--config",
247 "value": ""
244 "value": ""
248 }
245 }
249 ]
246 ]
250 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
247 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
251 [
248 [
252 {"defaultvalue": 0.999}
249 {"defaultvalue": 0.999}
253 ]
250 ]
254 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
251 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
255 0.999
252 0.999
256
253
257 Test empty config source:
254 Test empty config source:
258
255
259 $ cat <<EOF > emptysource.py
256 $ cat <<EOF > emptysource.py
260 > def reposetup(ui, repo):
257 > def reposetup(ui, repo):
261 > ui.setconfig(b'empty', b'source', b'value')
258 > ui.setconfig(b'empty', b'source', b'value')
262 > EOF
259 > EOF
263 $ cp .hg/hgrc .hg/hgrc.orig
260 $ cp .hg/hgrc .hg/hgrc.orig
264 $ cat <<EOF >> .hg/hgrc
261 $ cat <<EOF >> .hg/hgrc
265 > [extensions]
262 > [extensions]
266 > emptysource = `pwd`/emptysource.py
263 > emptysource = `pwd`/emptysource.py
267 > EOF
264 > EOF
268
265
269 $ hg config --debug empty.source
266 $ hg config --debug empty.source
270 read config from: * (glob)
267 read config from: * (glob)
271 none: value
268 none: value
272 $ hg config empty.source -Tjson
269 $ hg config empty.source -Tjson
273 [
270 [
274 {
271 {
275 "defaultvalue": null,
272 "defaultvalue": null,
276 "name": "empty.source",
273 "name": "empty.source",
277 "source": "",
274 "source": "",
278 "value": "value"
275 "value": "value"
279 }
276 }
280 ]
277 ]
281
278
282 $ cp .hg/hgrc.orig .hg/hgrc
279 $ cp .hg/hgrc.orig .hg/hgrc
283
280
284 Test "%unset"
281 Test "%unset"
285
282
286 $ cat >> $HGRCPATH <<EOF
283 $ cat >> $HGRCPATH <<EOF
287 > [unsettest]
284 > [unsettest]
288 > local-hgrcpath = should be unset (HGRCPATH)
285 > local-hgrcpath = should be unset (HGRCPATH)
289 > %unset local-hgrcpath
286 > %unset local-hgrcpath
290 >
287 >
291 > global = should be unset (HGRCPATH)
288 > global = should be unset (HGRCPATH)
292 >
289 >
293 > both = should be unset (HGRCPATH)
290 > both = should be unset (HGRCPATH)
294 >
291 >
295 > set-after-unset = should be unset (HGRCPATH)
292 > set-after-unset = should be unset (HGRCPATH)
296 > EOF
293 > EOF
297
294
298 $ cat >> .hg/hgrc <<EOF
295 $ cat >> .hg/hgrc <<EOF
299 > [unsettest]
296 > [unsettest]
300 > local-hgrc = should be unset (.hg/hgrc)
297 > local-hgrc = should be unset (.hg/hgrc)
301 > %unset local-hgrc
298 > %unset local-hgrc
302 >
299 >
303 > %unset global
300 > %unset global
304 >
301 >
305 > both = should be unset (.hg/hgrc)
302 > both = should be unset (.hg/hgrc)
306 > %unset both
303 > %unset both
307 >
304 >
308 > set-after-unset = should be unset (.hg/hgrc)
305 > set-after-unset = should be unset (.hg/hgrc)
309 > %unset set-after-unset
306 > %unset set-after-unset
310 > set-after-unset = should be set (.hg/hgrc)
307 > set-after-unset = should be set (.hg/hgrc)
311 > EOF
308 > EOF
312
309
313 $ hg showconfig unsettest
310 $ hg showconfig unsettest
314 unsettest.set-after-unset=should be set (.hg/hgrc)
311 unsettest.set-after-unset=should be set (.hg/hgrc)
315
312
316 Test exit code when no config matches
313 Test exit code when no config matches
317
314
318 $ hg config Section.idontexist
315 $ hg config Section.idontexist
319 [1]
316 [1]
320
317
321 sub-options in [paths] aren't expanded
318 sub-options in [paths] aren't expanded
322
319
323 $ cat > .hg/hgrc << EOF
320 $ cat > .hg/hgrc << EOF
324 > [paths]
321 > [paths]
325 > foo = ~/foo
322 > foo = ~/foo
326 > foo:suboption = ~/foo
323 > foo:suboption = ~/foo
327 > EOF
324 > EOF
328
325
329 $ hg showconfig paths
326 $ hg showconfig paths
330 paths.foo:suboption=~/foo
327 paths.foo:suboption=~/foo
331 paths.foo=$TESTTMP/foo
328 paths.foo=$TESTTMP/foo
332
329
333 edit failure
330 edit failure
334
331
335 $ HGEDITOR=false hg config --edit
332 $ HGEDITOR=false hg config --edit
336 abort: edit failed: false exited with status 1
333 abort: edit failed: false exited with status 1
337 [10]
334 [10]
338
335
339 config affected by environment variables
336 config affected by environment variables
340
337
341 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
338 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
342 $VISUAL: ui.editor=e2
339 $VISUAL: ui.editor=e2
343
340
344 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
341 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
345 --config: ui.editor=e3
342 --config: ui.editor=e3
346
343
347 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
344 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
348 $PAGER: pager.pager=p1
345 $PAGER: pager.pager=p1
349
346
350 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
347 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
351 --config: pager.pager=p2
348 --config: pager.pager=p2
352
349
353 verify that aliases are evaluated as well
350 verify that aliases are evaluated as well
354
351
355 $ hg init aliastest
352 $ hg init aliastest
356 $ cd aliastest
353 $ cd aliastest
357 $ cat > .hg/hgrc << EOF
354 $ cat > .hg/hgrc << EOF
358 > [ui]
355 > [ui]
359 > user = repo user
356 > user = repo user
360 > EOF
357 > EOF
361 $ touch index
358 $ touch index
362 $ unset HGUSER
359 $ unset HGUSER
363 $ hg ci -Am test
360 $ hg ci -Am test
364 adding index
361 adding index
365 $ hg log --template '{author}\n'
362 $ hg log --template '{author}\n'
366 repo user
363 repo user
367 $ cd ..
364 $ cd ..
368
365
369 alias has lower priority
366 alias has lower priority
370
367
371 $ hg init aliaspriority
368 $ hg init aliaspriority
372 $ cd aliaspriority
369 $ cd aliaspriority
373 $ cat > .hg/hgrc << EOF
370 $ cat > .hg/hgrc << EOF
374 > [ui]
371 > [ui]
375 > user = alias user
372 > user = alias user
376 > username = repo user
373 > username = repo user
377 > EOF
374 > EOF
378 $ touch index
375 $ touch index
379 $ unset HGUSER
376 $ unset HGUSER
380 $ hg ci -Am test
377 $ hg ci -Am test
381 adding index
378 adding index
382 $ hg log --template '{author}\n'
379 $ hg log --template '{author}\n'
383 repo user
380 repo user
384 $ cd ..
381 $ cd ..
385
382
386 configs should be read in lexicographical order
383 configs should be read in lexicographical order
387
384
388 $ mkdir configs
385 $ mkdir configs
389 $ for i in `$TESTDIR/seq.py 10 99`; do
386 $ for i in `$TESTDIR/seq.py 10 99`; do
390 > printf "[section]\nkey=$i" > configs/$i.rc
387 > printf "[section]\nkey=$i" > configs/$i.rc
391 > done
388 > done
392 $ HGRCPATH=configs hg config section.key
389 $ HGRCPATH=configs hg config section.key
393 99
390 99
394
391
395 Configuration priority
392 Configuration priority
396 ======================
393 ======================
397
394
398 setup necessary file
395 setup necessary file
399
396
400 $ cat > file-A.rc << EOF
397 $ cat > file-A.rc << EOF
401 > [config-test]
398 > [config-test]
402 > basic = value-A
399 > basic = value-A
403 > pre-include= value-A
400 > pre-include= value-A
404 > %include ./included.rc
401 > %include ./included.rc
405 > post-include= value-A
402 > post-include= value-A
406 > [command-templates]
403 > [command-templates]
407 > log = "value-A\n"
404 > log = "value-A\n"
408 > EOF
405 > EOF
409
406
410 $ cat > file-B.rc << EOF
407 $ cat > file-B.rc << EOF
411 > [config-test]
408 > [config-test]
412 > basic = value-B
409 > basic = value-B
413 > [ui]
410 > [ui]
414 > logtemplate = "value-B\n"
411 > logtemplate = "value-B\n"
415 > EOF
412 > EOF
416
413
417
414
418 $ cat > included.rc << EOF
415 $ cat > included.rc << EOF
419 > [config-test]
416 > [config-test]
420 > pre-include= value-included
417 > pre-include= value-included
421 > post-include= value-included
418 > post-include= value-included
422 > EOF
419 > EOF
423
420
424 $ cat > file-C.rc << EOF
421 $ cat > file-C.rc << EOF
425 > %include ./included-alias-C.rc
422 > %include ./included-alias-C.rc
426 > [ui]
423 > [ui]
427 > logtemplate = "value-C\n"
424 > logtemplate = "value-C\n"
428 > EOF
425 > EOF
429
426
430 $ cat > included-alias-C.rc << EOF
427 $ cat > included-alias-C.rc << EOF
431 > [command-templates]
428 > [command-templates]
432 > log = "value-included\n"
429 > log = "value-included\n"
433 > EOF
430 > EOF
434
431
435
432
436 $ cat > file-D.rc << EOF
433 $ cat > file-D.rc << EOF
437 > [command-templates]
434 > [command-templates]
438 > log = "value-D\n"
435 > log = "value-D\n"
439 > %include ./included-alias-D.rc
436 > %include ./included-alias-D.rc
440 > EOF
437 > EOF
441
438
442 $ cat > included-alias-D.rc << EOF
439 $ cat > included-alias-D.rc << EOF
443 > [ui]
440 > [ui]
444 > logtemplate = "value-included\n"
441 > logtemplate = "value-included\n"
445 > EOF
442 > EOF
446
443
447 Simple order checking
444 Simple order checking
448 ---------------------
445 ---------------------
449
446
450 If file B is read after file A, value from B overwrite value from A.
447 If file B is read after file A, value from B overwrite value from A.
451
448
452 $ HGRCPATH="file-A.rc:file-B.rc" hg config config-test.basic
449 $ HGRCPATH="file-A.rc:file-B.rc" hg config config-test.basic
453 value-B
450 value-B
454
451
455 Ordering from include
452 Ordering from include
456 ---------------------
453 ---------------------
457
454
458 value from an include overwrite value defined before the include, but not the one defined after the include
455 value from an include overwrite value defined before the include, but not the one defined after the include
459
456
460 $ HGRCPATH="file-A.rc" hg config config-test.pre-include
457 $ HGRCPATH="file-A.rc" hg config config-test.pre-include
461 value-included
458 value-included
462 $ HGRCPATH="file-A.rc" hg config config-test.post-include
459 $ HGRCPATH="file-A.rc" hg config config-test.post-include
463 value-A
460 value-A
464
461
465 command line override
462 command line override
466 ---------------------
463 ---------------------
467
464
468 $ HGRCPATH="file-A.rc:file-B.rc" hg config config-test.basic --config config-test.basic=value-CLI
465 $ HGRCPATH="file-A.rc:file-B.rc" hg config config-test.basic --config config-test.basic=value-CLI
469 value-CLI
466 value-CLI
470
467
471 Alias ordering
468 Alias ordering
472 --------------
469 --------------
473
470
474 The official config is now `command-templates.log`, the historical
471 The official config is now `command-templates.log`, the historical
475 `ui.logtemplate` is a valid alternative for it.
472 `ui.logtemplate` is a valid alternative for it.
476
473
477 When both are defined, The config value read the last "win", this should keep
474 When both are defined, The config value read the last "win", this should keep
478 being true if the config have other alias. In other word, the config value read
475 being true if the config have other alias. In other word, the config value read
479 earlier will be considered "lower level" and the config read later would be
476 earlier will be considered "lower level" and the config read later would be
480 considered "higher level". And higher level values wins.
477 considered "higher level". And higher level values wins.
481
478
482 $ HGRCPATH="file-A.rc" hg log -r .
479 $ HGRCPATH="file-A.rc" hg log -r .
483 value-A
480 value-A
484 $ HGRCPATH="file-B.rc" hg log -r .
481 $ HGRCPATH="file-B.rc" hg log -r .
485 value-B
482 value-B
486 $ HGRCPATH="file-A.rc:file-B.rc" hg log -r .
483 $ HGRCPATH="file-A.rc:file-B.rc" hg log -r .
487 value-B
484 value-B
488
485
489 Alias and include
486 Alias and include
490 -----------------
487 -----------------
491
488
492 The pre/post include priority should also apply when tie-breaking alternatives.
489 The pre/post include priority should also apply when tie-breaking alternatives.
493 See the case above for details about the two config options used.
490 See the case above for details about the two config options used.
494
491
495 $ HGRCPATH="file-C.rc" hg log -r .
492 $ HGRCPATH="file-C.rc" hg log -r .
496 value-C
493 value-C
497 $ HGRCPATH="file-D.rc" hg log -r .
494 $ HGRCPATH="file-D.rc" hg log -r .
498 value-included
495 value-included
499
496
500 command line override
497 command line override
501 ---------------------
498 ---------------------
502
499
503 $ HGRCPATH="file-A.rc:file-B.rc" hg log -r . --config ui.logtemplate="value-CLI\n"
500 $ HGRCPATH="file-A.rc:file-B.rc" hg log -r . --config ui.logtemplate="value-CLI\n"
504 value-CLI
501 value-CLI
@@ -1,224 +1,221 b''
1 test command parsing and dispatch
1 test command parsing and dispatch
2
2
3 $ hg init a
3 $ hg init a
4 $ cd a
4 $ cd a
5
5
6 Redundant options used to crash (issue436):
6 Redundant options used to crash (issue436):
7 $ hg -v log -v
7 $ hg -v log -v
8 $ hg -v log -v x
8 $ hg -v log -v x
9
9
10 $ echo a > a
10 $ echo a > a
11 $ hg ci -Ama
11 $ hg ci -Ama
12 adding a
12 adding a
13
13
14 Missing arg:
14 Missing arg:
15
15
16 $ hg cat
16 $ hg cat
17 hg cat: invalid arguments
17 hg cat: invalid arguments
18 hg cat [OPTION]... FILE...
18 hg cat [OPTION]... FILE...
19
19
20 output the current or given revision of files
20 output the current or given revision of files
21
21
22 options ([+] can be repeated):
22 options ([+] can be repeated):
23
23
24 -o --output FORMAT print output to file with formatted name
24 -o --output FORMAT print output to file with formatted name
25 -r --rev REV print the given revision
25 -r --rev REV print the given revision
26 --decode apply any matching decode filter
26 --decode apply any matching decode filter
27 -I --include PATTERN [+] include names matching the given patterns
27 -I --include PATTERN [+] include names matching the given patterns
28 -X --exclude PATTERN [+] exclude names matching the given patterns
28 -X --exclude PATTERN [+] exclude names matching the given patterns
29 -T --template TEMPLATE display with template
29 -T --template TEMPLATE display with template
30
30
31 (use 'hg cat -h' to show more help)
31 (use 'hg cat -h' to show more help)
32 [10]
32 [10]
33
33
34 Missing parameter for early option:
34 Missing parameter for early option:
35
35
36 $ hg log -R 2>&1 | grep 'hg log'
36 $ hg log -R 2>&1 | grep 'hg log'
37 hg log: option -R requires argument
37 hg log: option -R requires argument
38 hg log [OPTION]... [FILE]
38 hg log [OPTION]... [FILE]
39 (use 'hg log -h' to show more help)
39 (use 'hg log -h' to show more help)
40
40
41 "--" may be an option value:
41 "--" may be an option value:
42
42
43 $ hg -R -- log
43 $ hg -R -- log
44 abort: repository -- not found
44 abort: repository -- not found
45 [255]
45 [255]
46 $ hg log -R --
46 $ hg log -R --
47 abort: repository -- not found
47 abort: repository -- not found
48 [255]
48 [255]
49 $ hg log -T --
49 $ hg log -T --
50 -- (no-eol)
50 -- (no-eol)
51 $ hg log -T -- -k nomatch
51 $ hg log -T -- -k nomatch
52
52
53 Parsing of early options should stop at "--":
53 Parsing of early options should stop at "--":
54
54
55 $ hg cat -- --config=hooks.pre-cat=false
55 $ hg cat -- --config=hooks.pre-cat=false
56 --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
56 --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
57 [1]
57 [1]
58 $ hg cat -- --debugger
58 $ hg cat -- --debugger
59 --debugger: no such file in rev cb9a9f314b8b
59 --debugger: no such file in rev cb9a9f314b8b
60 [1]
60 [1]
61
61
62 Unparsable form of early options:
62 Unparsable form of early options:
63
63
64 $ hg cat --debugg
64 $ hg cat --debugg
65 abort: option --debugger may not be abbreviated
65 abort: option --debugger may not be abbreviated
66 [10]
66 [10]
67
67
68 Parsing failure of early options should be detected before executing the
68 Parsing failure of early options should be detected before executing the
69 command:
69 command:
70
70
71 $ hg log -b '--config=hooks.pre-log=false' default
71 $ hg log -b '--config=hooks.pre-log=false' default
72 abort: option --config may not be abbreviated
72 abort: option --config may not be abbreviated
73 [10]
73 [10]
74 $ hg log -b -R. default
74 $ hg log -b -R. default
75 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo
75 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo
76 [10]
76 [10]
77 $ hg log --cwd .. -b --cwd=. default
77 $ hg log --cwd .. -b --cwd=. default
78 abort: option --cwd may not be abbreviated
78 abort: option --cwd may not be abbreviated
79 [10]
79 [10]
80
80
81 However, we can't prevent it from loading extensions and configs:
81 However, we can't prevent it from loading extensions and configs:
82
82
83 $ cat <<EOF > bad.py
83 $ cat <<EOF > bad.py
84 > raise Exception('bad')
84 > raise Exception('bad')
85 > EOF
85 > EOF
86 $ hg log -b '--config=extensions.bad=bad.py' default
86 $ hg log -b '--config=extensions.bad=bad.py' default
87 *** failed to import extension bad from bad.py: bad
87 *** failed to import extension bad from bad.py: bad
88 abort: option --config may not be abbreviated
88 abort: option --config may not be abbreviated
89 [10]
89 [10]
90
90
91 $ mkdir -p badrepo/.hg
91 $ mkdir -p badrepo/.hg
92 $ echo 'invalid-syntax' > badrepo/.hg/hgrc
92 $ echo 'invalid-syntax' > badrepo/.hg/hgrc
93 TODO: add rhg support for detailed exit codes
94 #if no-rhg
95 $ hg log -b -Rbadrepo default
93 $ hg log -b -Rbadrepo default
96 config error at badrepo/.hg/hgrc:1: invalid-syntax
94 config error at badrepo/.hg/hgrc:1: invalid-syntax
97 [30]
95 [30]
98 #endif
99
96
100 $ hg log -b --cwd=inexistent default
97 $ hg log -b --cwd=inexistent default
101 abort: $ENOENT$: 'inexistent'
98 abort: $ENOENT$: 'inexistent'
102 [255]
99 [255]
103
100
104 $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
101 $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
105 Traceback (most recent call last):
102 Traceback (most recent call last):
106 $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
103 $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
107 Sample count: .*|No samples recorded\. (re)
104 Sample count: .*|No samples recorded\. (re)
108
105
109 Early options can't be specified in [aliases] and [defaults] because they are
106 Early options can't be specified in [aliases] and [defaults] because they are
110 applied before the command name is resolved:
107 applied before the command name is resolved:
111
108
112 $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
109 $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
113 hg log: option -b not recognized
110 hg log: option -b not recognized
114 error in definition for alias 'log': --config may only be given on the command
111 error in definition for alias 'log': --config may only be given on the command
115 line
112 line
116 [10]
113 [10]
117
114
118 $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
115 $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
119 abort: option --config may not be abbreviated
116 abort: option --config may not be abbreviated
120 [10]
117 [10]
121
118
122 Shell aliases bypass any command parsing rules but for the early one:
119 Shell aliases bypass any command parsing rules but for the early one:
123
120
124 $ hg log -b '--config=alias.log=!echo howdy'
121 $ hg log -b '--config=alias.log=!echo howdy'
125 howdy
122 howdy
126
123
127 Early options must come first if HGPLAIN=+strictflags is specified:
124 Early options must come first if HGPLAIN=+strictflags is specified:
128 (BUG: chg cherry-picks early options to pass them as a server command)
125 (BUG: chg cherry-picks early options to pass them as a server command)
129
126
130 #if no-chg
127 #if no-chg
131 $ HGPLAIN=+strictflags hg log -b --config='hooks.pre-log=false' default
128 $ HGPLAIN=+strictflags hg log -b --config='hooks.pre-log=false' default
132 abort: unknown revision '--config=hooks.pre-log=false'
129 abort: unknown revision '--config=hooks.pre-log=false'
133 [255]
130 [255]
134 $ HGPLAIN=+strictflags hg log -b -R. default
131 $ HGPLAIN=+strictflags hg log -b -R. default
135 abort: unknown revision '-R.'
132 abort: unknown revision '-R.'
136 [255]
133 [255]
137 $ HGPLAIN=+strictflags hg log -b --cwd=. default
134 $ HGPLAIN=+strictflags hg log -b --cwd=. default
138 abort: unknown revision '--cwd=.'
135 abort: unknown revision '--cwd=.'
139 [255]
136 [255]
140 #endif
137 #endif
141 $ HGPLAIN=+strictflags hg log -b --debugger default
138 $ HGPLAIN=+strictflags hg log -b --debugger default
142 abort: unknown revision '--debugger'
139 abort: unknown revision '--debugger'
143 [255]
140 [255]
144 $ HGPLAIN=+strictflags hg log -b --config='alias.log=!echo pwned' default
141 $ HGPLAIN=+strictflags hg log -b --config='alias.log=!echo pwned' default
145 abort: unknown revision '--config=alias.log=!echo pwned'
142 abort: unknown revision '--config=alias.log=!echo pwned'
146 [255]
143 [255]
147
144
148 $ HGPLAIN=+strictflags hg log --config='hooks.pre-log=false' -b default
145 $ HGPLAIN=+strictflags hg log --config='hooks.pre-log=false' -b default
149 abort: option --config may not be abbreviated
146 abort: option --config may not be abbreviated
150 [10]
147 [10]
151 $ HGPLAIN=+strictflags hg log -q --cwd=.. -b default
148 $ HGPLAIN=+strictflags hg log -q --cwd=.. -b default
152 abort: option --cwd may not be abbreviated
149 abort: option --cwd may not be abbreviated
153 [10]
150 [10]
154 $ HGPLAIN=+strictflags hg log -q -R . -b default
151 $ HGPLAIN=+strictflags hg log -q -R . -b default
155 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo
152 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo
156 [10]
153 [10]
157
154
158 $ HGPLAIN=+strictflags hg --config='hooks.pre-log=false' log -b default
155 $ HGPLAIN=+strictflags hg --config='hooks.pre-log=false' log -b default
159 abort: pre-log hook exited with status 1
156 abort: pre-log hook exited with status 1
160 [40]
157 [40]
161 $ HGPLAIN=+strictflags hg --cwd .. -q -Ra log -b default
158 $ HGPLAIN=+strictflags hg --cwd .. -q -Ra log -b default
162 0:cb9a9f314b8b
159 0:cb9a9f314b8b
163 $ HGPLAIN=+strictflags hg --cwd .. -q --repository a log -b default
160 $ HGPLAIN=+strictflags hg --cwd .. -q --repository a log -b default
164 0:cb9a9f314b8b
161 0:cb9a9f314b8b
165 $ HGPLAIN=+strictflags hg --cwd .. -q --repo a log -b default
162 $ HGPLAIN=+strictflags hg --cwd .. -q --repo a log -b default
166 0:cb9a9f314b8b
163 0:cb9a9f314b8b
167
164
168 For compatibility reasons, HGPLAIN=+strictflags is not enabled by plain HGPLAIN:
165 For compatibility reasons, HGPLAIN=+strictflags is not enabled by plain HGPLAIN:
169
166
170 $ HGPLAIN= hg log --config='hooks.pre-log=false' -b default
167 $ HGPLAIN= hg log --config='hooks.pre-log=false' -b default
171 abort: pre-log hook exited with status 1
168 abort: pre-log hook exited with status 1
172 [40]
169 [40]
173 $ HGPLAINEXCEPT= hg log --cwd .. -q -Ra -b default
170 $ HGPLAINEXCEPT= hg log --cwd .. -q -Ra -b default
174 0:cb9a9f314b8b
171 0:cb9a9f314b8b
175
172
176 [defaults]
173 [defaults]
177
174
178 $ hg cat a
175 $ hg cat a
179 a
176 a
180 $ cat >> $HGRCPATH <<EOF
177 $ cat >> $HGRCPATH <<EOF
181 > [defaults]
178 > [defaults]
182 > cat = -r null
179 > cat = -r null
183 > EOF
180 > EOF
184 $ hg cat a
181 $ hg cat a
185 a: no such file in rev 000000000000
182 a: no such file in rev 000000000000
186 [1]
183 [1]
187
184
188 $ cd "$TESTTMP"
185 $ cd "$TESTTMP"
189
186
190 OSError "No such file or directory" / "The system cannot find the path
187 OSError "No such file or directory" / "The system cannot find the path
191 specified" should include filename even when it is empty
188 specified" should include filename even when it is empty
192
189
193 $ hg -R a archive ''
190 $ hg -R a archive ''
194 abort: $ENOENT$: '' (no-windows !)
191 abort: $ENOENT$: '' (no-windows !)
195 abort: $ENOTDIR$: '' (windows !)
192 abort: $ENOTDIR$: '' (windows !)
196 [255]
193 [255]
197
194
198 #if no-outer-repo
195 #if no-outer-repo
199
196
200 No repo:
197 No repo:
201
198
202 $ hg cat
199 $ hg cat
203 abort: no repository found in '$TESTTMP' (.hg not found)
200 abort: no repository found in '$TESTTMP' (.hg not found)
204 [10]
201 [10]
205
202
206 #endif
203 #endif
207
204
208 #if rmcwd
205 #if rmcwd
209
206
210 Current directory removed:
207 Current directory removed:
211
208
212 $ mkdir $TESTTMP/repo1
209 $ mkdir $TESTTMP/repo1
213 $ cd $TESTTMP/repo1
210 $ cd $TESTTMP/repo1
214 $ rm -rf $TESTTMP/repo1
211 $ rm -rf $TESTTMP/repo1
215
212
216 The output could be one of the following and something else:
213 The output could be one of the following and something else:
217 chg: abort: failed to getcwd (errno = *) (glob)
214 chg: abort: failed to getcwd (errno = *) (glob)
218 abort: error getting current working directory: * (glob)
215 abort: error getting current working directory: * (glob)
219 sh: 0: getcwd() failed: $ENOENT$
216 sh: 0: getcwd() failed: $ENOENT$
220 Since the exact behavior depends on the shell, only check it returns non-zero.
217 Since the exact behavior depends on the shell, only check it returns non-zero.
221 $ HGDEMANDIMPORT=disable hg version -q 2>/dev/null || false
218 $ HGDEMANDIMPORT=disable hg version -q 2>/dev/null || false
222 [1]
219 [1]
223
220
224 #endif
221 #endif
General Comments 0
You need to be logged in to leave comments. Login now