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