##// END OF EJS Templates
rhg: don't run `blackbox` if not activated...
Raphaël Gomès -
r49243:9ff246cd default
parent child Browse files
Show More
@@ -1,653 +1,658
1 extern crate log;
1 extern crate log;
2 use crate::error::CommandError;
2 use crate::error::CommandError;
3 use crate::ui::Ui;
3 use crate::ui::Ui;
4 use clap::App;
4 use clap::App;
5 use clap::AppSettings;
5 use clap::AppSettings;
6 use clap::Arg;
6 use clap::Arg;
7 use clap::ArgMatches;
7 use clap::ArgMatches;
8 use format_bytes::{format_bytes, join};
8 use format_bytes::{format_bytes, join};
9 use hg::config::{Config, ConfigSource};
9 use hg::config::{Config, ConfigSource};
10 use hg::exit_codes;
10 use hg::exit_codes;
11 use hg::repo::{Repo, RepoError};
11 use hg::repo::{Repo, RepoError};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
13 use hg::utils::SliceExt;
13 use hg::utils::SliceExt;
14 use std::ffi::OsString;
14 use std::ffi::OsString;
15 use std::path::PathBuf;
15 use std::path::PathBuf;
16 use std::process::Command;
16 use std::process::Command;
17
17
18 mod blackbox;
18 mod blackbox;
19 mod error;
19 mod error;
20 mod ui;
20 mod ui;
21 pub mod utils {
21 pub mod utils {
22 pub mod path_utils;
22 pub mod path_utils;
23 }
23 }
24
24
25 fn main_with_result(
25 fn main_with_result(
26 process_start_time: &blackbox::ProcessStartTime,
26 process_start_time: &blackbox::ProcessStartTime,
27 ui: &ui::Ui,
27 ui: &ui::Ui,
28 repo: Result<&Repo, &NoRepoInCwdError>,
28 repo: Result<&Repo, &NoRepoInCwdError>,
29 config: &Config,
29 config: &Config,
30 ) -> Result<(), CommandError> {
30 ) -> Result<(), CommandError> {
31 check_unsupported(config, ui)?;
31 check_unsupported(config, ui)?;
32
32
33 let app = App::new("rhg")
33 let app = App::new("rhg")
34 .global_setting(AppSettings::AllowInvalidUtf8)
34 .global_setting(AppSettings::AllowInvalidUtf8)
35 .global_setting(AppSettings::DisableVersion)
35 .global_setting(AppSettings::DisableVersion)
36 .setting(AppSettings::SubcommandRequired)
36 .setting(AppSettings::SubcommandRequired)
37 .setting(AppSettings::VersionlessSubcommands)
37 .setting(AppSettings::VersionlessSubcommands)
38 .arg(
38 .arg(
39 Arg::with_name("repository")
39 Arg::with_name("repository")
40 .help("repository root directory")
40 .help("repository root directory")
41 .short("-R")
41 .short("-R")
42 .long("--repository")
42 .long("--repository")
43 .value_name("REPO")
43 .value_name("REPO")
44 .takes_value(true)
44 .takes_value(true)
45 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
45 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
46 .global(true),
46 .global(true),
47 )
47 )
48 .arg(
48 .arg(
49 Arg::with_name("config")
49 Arg::with_name("config")
50 .help("set/override config option (use 'section.name=value')")
50 .help("set/override config option (use 'section.name=value')")
51 .long("--config")
51 .long("--config")
52 .value_name("CONFIG")
52 .value_name("CONFIG")
53 .takes_value(true)
53 .takes_value(true)
54 .global(true)
54 .global(true)
55 // Ok: `--config section.key1=val --config section.key2=val2`
55 // Ok: `--config section.key1=val --config section.key2=val2`
56 .multiple(true)
56 .multiple(true)
57 // Not ok: `--config section.key1=val section.key2=val2`
57 // Not ok: `--config section.key1=val section.key2=val2`
58 .number_of_values(1),
58 .number_of_values(1),
59 )
59 )
60 .arg(
60 .arg(
61 Arg::with_name("cwd")
61 Arg::with_name("cwd")
62 .help("change working directory")
62 .help("change working directory")
63 .long("--cwd")
63 .long("--cwd")
64 .value_name("DIR")
64 .value_name("DIR")
65 .takes_value(true)
65 .takes_value(true)
66 .global(true),
66 .global(true),
67 )
67 )
68 .version("0.0.1");
68 .version("0.0.1");
69 let app = add_subcommand_args(app);
69 let app = add_subcommand_args(app);
70
70
71 let matches = app.clone().get_matches_safe()?;
71 let matches = app.clone().get_matches_safe()?;
72
72
73 let (subcommand_name, subcommand_matches) = matches.subcommand();
73 let (subcommand_name, subcommand_matches) = matches.subcommand();
74
74
75 // Mercurial allows users to define "defaults" for commands, fallback
75 // Mercurial allows users to define "defaults" for commands, fallback
76 // if a default is detected for the current command
76 // if a default is detected for the current command
77 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
77 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
78 if defaults?.is_some() {
78 if defaults?.is_some() {
79 let msg = "`defaults` config set";
79 let msg = "`defaults` config set";
80 return Err(CommandError::unsupported(msg));
80 return Err(CommandError::unsupported(msg));
81 }
81 }
82
82
83 for prefix in ["pre", "post", "fail"].iter() {
83 for prefix in ["pre", "post", "fail"].iter() {
84 // Mercurial allows users to define generic hooks for commands,
84 // Mercurial allows users to define generic hooks for commands,
85 // fallback if any are detected
85 // fallback if any are detected
86 let item = format!("{}-{}", prefix, subcommand_name);
86 let item = format!("{}-{}", prefix, subcommand_name);
87 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
87 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
88 if hook_for_command.is_some() {
88 if hook_for_command.is_some() {
89 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
89 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
90 return Err(CommandError::unsupported(msg));
90 return Err(CommandError::unsupported(msg));
91 }
91 }
92 }
92 }
93 let run = subcommand_run_fn(subcommand_name)
93 let run = subcommand_run_fn(subcommand_name)
94 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
94 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
95 let subcommand_args = subcommand_matches
95 let subcommand_args = subcommand_matches
96 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
96 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
97
97
98 let invocation = CliInvocation {
98 let invocation = CliInvocation {
99 ui,
99 ui,
100 subcommand_args,
100 subcommand_args,
101 config,
101 config,
102 repo,
102 repo,
103 };
103 };
104
104
105 if let Ok(repo) = repo {
105 if let Ok(repo) = repo {
106 // We don't support subrepos, fallback if the subrepos file is present
106 // We don't support subrepos, fallback if the subrepos file is present
107 if repo.working_directory_vfs().join(".hgsub").exists() {
107 if repo.working_directory_vfs().join(".hgsub").exists() {
108 let msg = "subrepos (.hgsub is present)";
108 let msg = "subrepos (.hgsub is present)";
109 return Err(CommandError::unsupported(msg));
109 return Err(CommandError::unsupported(msg));
110 }
110 }
111 }
111 }
112
112
113 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
113 if config.is_extension_enabled(b"blackbox") {
114 let blackbox =
115 blackbox::Blackbox::new(&invocation, process_start_time)?;
114 blackbox.log_command_start();
116 blackbox.log_command_start();
115 let result = run(&invocation);
117 let result = run(&invocation);
116 blackbox.log_command_end(exit_code(
118 blackbox.log_command_end(exit_code(
117 &result,
119 &result,
118 // TODO: show a warning or combine with original error if `get_bool`
120 // TODO: show a warning or combine with original error if
119 // returns an error
121 // `get_bool` returns an error
120 config
122 config
121 .get_bool(b"ui", b"detailed-exit-code")
123 .get_bool(b"ui", b"detailed-exit-code")
122 .unwrap_or(false),
124 .unwrap_or(false),
123 ));
125 ));
124 result
126 result
127 } else {
128 run(&invocation)
129 }
125 }
130 }
126
131
127 fn main() {
132 fn main() {
128 // Run this first, before we find out if the blackbox extension is even
133 // Run this first, before we find out if the blackbox extension is even
129 // enabled, in order to include everything in-between in the duration
134 // enabled, in order to include everything in-between in the duration
130 // measurements. Reading config files can be slow if they’re on NFS.
135 // measurements. Reading config files can be slow if they’re on NFS.
131 let process_start_time = blackbox::ProcessStartTime::now();
136 let process_start_time = blackbox::ProcessStartTime::now();
132
137
133 env_logger::init();
138 env_logger::init();
134 let ui = ui::Ui::new();
139 let ui = ui::Ui::new();
135
140
136 let early_args = EarlyArgs::parse(std::env::args_os());
141 let early_args = EarlyArgs::parse(std::env::args_os());
137
142
138 let initial_current_dir = early_args.cwd.map(|cwd| {
143 let initial_current_dir = early_args.cwd.map(|cwd| {
139 let cwd = get_path_from_bytes(&cwd);
144 let cwd = get_path_from_bytes(&cwd);
140 std::env::current_dir()
145 std::env::current_dir()
141 .and_then(|initial| {
146 .and_then(|initial| {
142 std::env::set_current_dir(cwd)?;
147 std::env::set_current_dir(cwd)?;
143 Ok(initial)
148 Ok(initial)
144 })
149 })
145 .unwrap_or_else(|error| {
150 .unwrap_or_else(|error| {
146 exit(
151 exit(
147 &None,
152 &None,
148 &ui,
153 &ui,
149 OnUnsupported::Abort,
154 OnUnsupported::Abort,
150 Err(CommandError::abort(format!(
155 Err(CommandError::abort(format!(
151 "abort: {}: '{}'",
156 "abort: {}: '{}'",
152 error,
157 error,
153 cwd.display()
158 cwd.display()
154 ))),
159 ))),
155 false,
160 false,
156 )
161 )
157 })
162 })
158 });
163 });
159
164
160 let mut non_repo_config =
165 let mut non_repo_config =
161 Config::load_non_repo().unwrap_or_else(|error| {
166 Config::load_non_repo().unwrap_or_else(|error| {
162 // Normally this is decided based on config, but we don’t have that
167 // Normally this is decided based on config, but we don’t have that
163 // available. As of this writing config loading never returns an
168 // available. As of this writing config loading never returns an
164 // "unsupported" error but that is not enforced by the type system.
169 // "unsupported" error but that is not enforced by the type system.
165 let on_unsupported = OnUnsupported::Abort;
170 let on_unsupported = OnUnsupported::Abort;
166
171
167 exit(
172 exit(
168 &initial_current_dir,
173 &initial_current_dir,
169 &ui,
174 &ui,
170 on_unsupported,
175 on_unsupported,
171 Err(error.into()),
176 Err(error.into()),
172 false,
177 false,
173 )
178 )
174 });
179 });
175
180
176 non_repo_config
181 non_repo_config
177 .load_cli_args_config(early_args.config)
182 .load_cli_args_config(early_args.config)
178 .unwrap_or_else(|error| {
183 .unwrap_or_else(|error| {
179 exit(
184 exit(
180 &initial_current_dir,
185 &initial_current_dir,
181 &ui,
186 &ui,
182 OnUnsupported::from_config(&non_repo_config),
187 OnUnsupported::from_config(&non_repo_config),
183 Err(error.into()),
188 Err(error.into()),
184 non_repo_config
189 non_repo_config
185 .get_bool(b"ui", b"detailed-exit-code")
190 .get_bool(b"ui", b"detailed-exit-code")
186 .unwrap_or(false),
191 .unwrap_or(false),
187 )
192 )
188 });
193 });
189
194
190 if let Some(repo_path_bytes) = &early_args.repo {
195 if let Some(repo_path_bytes) = &early_args.repo {
191 lazy_static::lazy_static! {
196 lazy_static::lazy_static! {
192 static ref SCHEME_RE: regex::bytes::Regex =
197 static ref SCHEME_RE: regex::bytes::Regex =
193 // Same as `_matchscheme` in `mercurial/util.py`
198 // Same as `_matchscheme` in `mercurial/util.py`
194 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
199 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
195 }
200 }
196 if SCHEME_RE.is_match(&repo_path_bytes) {
201 if SCHEME_RE.is_match(&repo_path_bytes) {
197 exit(
202 exit(
198 &initial_current_dir,
203 &initial_current_dir,
199 &ui,
204 &ui,
200 OnUnsupported::from_config(&non_repo_config),
205 OnUnsupported::from_config(&non_repo_config),
201 Err(CommandError::UnsupportedFeature {
206 Err(CommandError::UnsupportedFeature {
202 message: format_bytes!(
207 message: format_bytes!(
203 b"URL-like --repository {}",
208 b"URL-like --repository {}",
204 repo_path_bytes
209 repo_path_bytes
205 ),
210 ),
206 }),
211 }),
207 // TODO: show a warning or combine with original error if
212 // TODO: show a warning or combine with original error if
208 // `get_bool` returns an error
213 // `get_bool` returns an error
209 non_repo_config
214 non_repo_config
210 .get_bool(b"ui", b"detailed-exit-code")
215 .get_bool(b"ui", b"detailed-exit-code")
211 .unwrap_or(false),
216 .unwrap_or(false),
212 )
217 )
213 }
218 }
214 }
219 }
215 let repo_arg = early_args.repo.unwrap_or(Vec::new());
220 let repo_arg = early_args.repo.unwrap_or(Vec::new());
216 let repo_path: Option<PathBuf> = {
221 let repo_path: Option<PathBuf> = {
217 if repo_arg.is_empty() {
222 if repo_arg.is_empty() {
218 None
223 None
219 } else {
224 } else {
220 let local_config = {
225 let local_config = {
221 if std::env::var_os("HGRCSKIPREPO").is_none() {
226 if std::env::var_os("HGRCSKIPREPO").is_none() {
222 // TODO: handle errors from find_repo_root
227 // TODO: handle errors from find_repo_root
223 if let Ok(current_dir_path) = Repo::find_repo_root() {
228 if let Ok(current_dir_path) = Repo::find_repo_root() {
224 let config_files = vec![
229 let config_files = vec![
225 ConfigSource::AbsPath(
230 ConfigSource::AbsPath(
226 current_dir_path.join(".hg/hgrc"),
231 current_dir_path.join(".hg/hgrc"),
227 ),
232 ),
228 ConfigSource::AbsPath(
233 ConfigSource::AbsPath(
229 current_dir_path.join(".hg/hgrc-not-shared"),
234 current_dir_path.join(".hg/hgrc-not-shared"),
230 ),
235 ),
231 ];
236 ];
232 // TODO: handle errors from
237 // TODO: handle errors from
233 // `load_from_explicit_sources`
238 // `load_from_explicit_sources`
234 Config::load_from_explicit_sources(config_files).ok()
239 Config::load_from_explicit_sources(config_files).ok()
235 } else {
240 } else {
236 None
241 None
237 }
242 }
238 } else {
243 } else {
239 None
244 None
240 }
245 }
241 };
246 };
242
247
243 let non_repo_config_val = {
248 let non_repo_config_val = {
244 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
249 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
245 match &non_repo_val {
250 match &non_repo_val {
246 Some(val) if val.len() > 0 => home::home_dir()
251 Some(val) if val.len() > 0 => home::home_dir()
247 .unwrap_or_else(|| PathBuf::from("~"))
252 .unwrap_or_else(|| PathBuf::from("~"))
248 .join(get_path_from_bytes(val))
253 .join(get_path_from_bytes(val))
249 .canonicalize()
254 .canonicalize()
250 // TODO: handle error and make it similar to python
255 // TODO: handle error and make it similar to python
251 // implementation maybe?
256 // implementation maybe?
252 .ok(),
257 .ok(),
253 _ => None,
258 _ => None,
254 }
259 }
255 };
260 };
256
261
257 let config_val = match &local_config {
262 let config_val = match &local_config {
258 None => non_repo_config_val,
263 None => non_repo_config_val,
259 Some(val) => {
264 Some(val) => {
260 let local_config_val = val.get(b"paths", &repo_arg);
265 let local_config_val = val.get(b"paths", &repo_arg);
261 match &local_config_val {
266 match &local_config_val {
262 Some(val) if val.len() > 0 => {
267 Some(val) if val.len() > 0 => {
263 // presence of a local_config assures that
268 // presence of a local_config assures that
264 // current_dir
269 // current_dir
265 // wont result in an Error
270 // wont result in an Error
266 let canpath = hg::utils::current_dir()
271 let canpath = hg::utils::current_dir()
267 .unwrap()
272 .unwrap()
268 .join(get_path_from_bytes(val))
273 .join(get_path_from_bytes(val))
269 .canonicalize();
274 .canonicalize();
270 canpath.ok().or(non_repo_config_val)
275 canpath.ok().or(non_repo_config_val)
271 }
276 }
272 _ => non_repo_config_val,
277 _ => non_repo_config_val,
273 }
278 }
274 }
279 }
275 };
280 };
276 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
281 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
277 }
282 }
278 };
283 };
279
284
280 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
285 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
281 {
286 {
282 Ok(repo) => Ok(repo),
287 Ok(repo) => Ok(repo),
283 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
288 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
284 // Not finding a repo is not fatal yet, if `-R` was not given
289 // Not finding a repo is not fatal yet, if `-R` was not given
285 Err(NoRepoInCwdError { cwd: at })
290 Err(NoRepoInCwdError { cwd: at })
286 }
291 }
287 Err(error) => exit(
292 Err(error) => exit(
288 &initial_current_dir,
293 &initial_current_dir,
289 &ui,
294 &ui,
290 OnUnsupported::from_config(&non_repo_config),
295 OnUnsupported::from_config(&non_repo_config),
291 Err(error.into()),
296 Err(error.into()),
292 // TODO: show a warning or combine with original error if
297 // TODO: show a warning or combine with original error if
293 // `get_bool` returns an error
298 // `get_bool` returns an error
294 non_repo_config
299 non_repo_config
295 .get_bool(b"ui", b"detailed-exit-code")
300 .get_bool(b"ui", b"detailed-exit-code")
296 .unwrap_or(false),
301 .unwrap_or(false),
297 ),
302 ),
298 };
303 };
299
304
300 let config = if let Ok(repo) = &repo_result {
305 let config = if let Ok(repo) = &repo_result {
301 repo.config()
306 repo.config()
302 } else {
307 } else {
303 &non_repo_config
308 &non_repo_config
304 };
309 };
305 let on_unsupported = OnUnsupported::from_config(config);
310 let on_unsupported = OnUnsupported::from_config(config);
306
311
307 let result = main_with_result(
312 let result = main_with_result(
308 &process_start_time,
313 &process_start_time,
309 &ui,
314 &ui,
310 repo_result.as_ref(),
315 repo_result.as_ref(),
311 config,
316 config,
312 );
317 );
313 exit(
318 exit(
314 &initial_current_dir,
319 &initial_current_dir,
315 &ui,
320 &ui,
316 on_unsupported,
321 on_unsupported,
317 result,
322 result,
318 // TODO: show a warning or combine with original error if `get_bool`
323 // TODO: show a warning or combine with original error if `get_bool`
319 // returns an error
324 // returns an error
320 config
325 config
321 .get_bool(b"ui", b"detailed-exit-code")
326 .get_bool(b"ui", b"detailed-exit-code")
322 .unwrap_or(false),
327 .unwrap_or(false),
323 )
328 )
324 }
329 }
325
330
326 fn exit_code(
331 fn exit_code(
327 result: &Result<(), CommandError>,
332 result: &Result<(), CommandError>,
328 use_detailed_exit_code: bool,
333 use_detailed_exit_code: bool,
329 ) -> i32 {
334 ) -> i32 {
330 match result {
335 match result {
331 Ok(()) => exit_codes::OK,
336 Ok(()) => exit_codes::OK,
332 Err(CommandError::Abort {
337 Err(CommandError::Abort {
333 message: _,
338 message: _,
334 detailed_exit_code,
339 detailed_exit_code,
335 }) => {
340 }) => {
336 if use_detailed_exit_code {
341 if use_detailed_exit_code {
337 *detailed_exit_code
342 *detailed_exit_code
338 } else {
343 } else {
339 exit_codes::ABORT
344 exit_codes::ABORT
340 }
345 }
341 }
346 }
342 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
347 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
343
348
344 // Exit with a specific code and no error message to let a potential
349 // Exit with a specific code and no error message to let a potential
345 // wrapper script fallback to Python-based Mercurial.
350 // wrapper script fallback to Python-based Mercurial.
346 Err(CommandError::UnsupportedFeature { .. }) => {
351 Err(CommandError::UnsupportedFeature { .. }) => {
347 exit_codes::UNIMPLEMENTED
352 exit_codes::UNIMPLEMENTED
348 }
353 }
349 }
354 }
350 }
355 }
351
356
352 fn exit(
357 fn exit(
353 initial_current_dir: &Option<PathBuf>,
358 initial_current_dir: &Option<PathBuf>,
354 ui: &Ui,
359 ui: &Ui,
355 mut on_unsupported: OnUnsupported,
360 mut on_unsupported: OnUnsupported,
356 result: Result<(), CommandError>,
361 result: Result<(), CommandError>,
357 use_detailed_exit_code: bool,
362 use_detailed_exit_code: bool,
358 ) -> ! {
363 ) -> ! {
359 if let (
364 if let (
360 OnUnsupported::Fallback { executable },
365 OnUnsupported::Fallback { executable },
361 Err(CommandError::UnsupportedFeature { .. }),
366 Err(CommandError::UnsupportedFeature { .. }),
362 ) = (&on_unsupported, &result)
367 ) = (&on_unsupported, &result)
363 {
368 {
364 let mut args = std::env::args_os();
369 let mut args = std::env::args_os();
365 let executable = match executable {
370 let executable = match executable {
366 None => {
371 None => {
367 exit_no_fallback(
372 exit_no_fallback(
368 ui,
373 ui,
369 OnUnsupported::Abort,
374 OnUnsupported::Abort,
370 Err(CommandError::abort(
375 Err(CommandError::abort(
371 "abort: 'rhg.on-unsupported=fallback' without \
376 "abort: 'rhg.on-unsupported=fallback' without \
372 'rhg.fallback-executable' set.",
377 'rhg.fallback-executable' set.",
373 )),
378 )),
374 false,
379 false,
375 );
380 );
376 }
381 }
377 Some(executable) => executable,
382 Some(executable) => executable,
378 };
383 };
379 let executable_path = get_path_from_bytes(&executable);
384 let executable_path = get_path_from_bytes(&executable);
380 let this_executable = args.next().expect("exepcted argv[0] to exist");
385 let this_executable = args.next().expect("exepcted argv[0] to exist");
381 if executable_path == &PathBuf::from(this_executable) {
386 if executable_path == &PathBuf::from(this_executable) {
382 // Avoid spawning infinitely many processes until resource
387 // Avoid spawning infinitely many processes until resource
383 // exhaustion.
388 // exhaustion.
384 let _ = ui.write_stderr(&format_bytes!(
389 let _ = ui.write_stderr(&format_bytes!(
385 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
390 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
386 points to `rhg` itself.\n",
391 points to `rhg` itself.\n",
387 executable
392 executable
388 ));
393 ));
389 on_unsupported = OnUnsupported::Abort
394 on_unsupported = OnUnsupported::Abort
390 } else {
395 } else {
391 // `args` is now `argv[1..]` since we’ve already consumed
396 // `args` is now `argv[1..]` since we’ve already consumed
392 // `argv[0]`
397 // `argv[0]`
393 let mut command = Command::new(executable_path);
398 let mut command = Command::new(executable_path);
394 command.args(args);
399 command.args(args);
395 if let Some(initial) = initial_current_dir {
400 if let Some(initial) = initial_current_dir {
396 command.current_dir(initial);
401 command.current_dir(initial);
397 }
402 }
398 let result = command.status();
403 let result = command.status();
399 match result {
404 match result {
400 Ok(status) => std::process::exit(
405 Ok(status) => std::process::exit(
401 status.code().unwrap_or(exit_codes::ABORT),
406 status.code().unwrap_or(exit_codes::ABORT),
402 ),
407 ),
403 Err(error) => {
408 Err(error) => {
404 let _ = ui.write_stderr(&format_bytes!(
409 let _ = ui.write_stderr(&format_bytes!(
405 b"tried to fall back to a '{}' sub-process but got error {}\n",
410 b"tried to fall back to a '{}' sub-process but got error {}\n",
406 executable, format_bytes::Utf8(error)
411 executable, format_bytes::Utf8(error)
407 ));
412 ));
408 on_unsupported = OnUnsupported::Abort
413 on_unsupported = OnUnsupported::Abort
409 }
414 }
410 }
415 }
411 }
416 }
412 }
417 }
413 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
418 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
414 }
419 }
415
420
416 fn exit_no_fallback(
421 fn exit_no_fallback(
417 ui: &Ui,
422 ui: &Ui,
418 on_unsupported: OnUnsupported,
423 on_unsupported: OnUnsupported,
419 result: Result<(), CommandError>,
424 result: Result<(), CommandError>,
420 use_detailed_exit_code: bool,
425 use_detailed_exit_code: bool,
421 ) -> ! {
426 ) -> ! {
422 match &result {
427 match &result {
423 Ok(_) => {}
428 Ok(_) => {}
424 Err(CommandError::Unsuccessful) => {}
429 Err(CommandError::Unsuccessful) => {}
425 Err(CommandError::Abort {
430 Err(CommandError::Abort {
426 message,
431 message,
427 detailed_exit_code: _,
432 detailed_exit_code: _,
428 }) => {
433 }) => {
429 if !message.is_empty() {
434 if !message.is_empty() {
430 // Ignore errors when writing to stderr, we’re already exiting
435 // Ignore errors when writing to stderr, we’re already exiting
431 // with failure code so there’s not much more we can do.
436 // with failure code so there’s not much more we can do.
432 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
437 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
433 }
438 }
434 }
439 }
435 Err(CommandError::UnsupportedFeature { message }) => {
440 Err(CommandError::UnsupportedFeature { message }) => {
436 match on_unsupported {
441 match on_unsupported {
437 OnUnsupported::Abort => {
442 OnUnsupported::Abort => {
438 let _ = ui.write_stderr(&format_bytes!(
443 let _ = ui.write_stderr(&format_bytes!(
439 b"unsupported feature: {}\n",
444 b"unsupported feature: {}\n",
440 message
445 message
441 ));
446 ));
442 }
447 }
443 OnUnsupported::AbortSilent => {}
448 OnUnsupported::AbortSilent => {}
444 OnUnsupported::Fallback { .. } => unreachable!(),
449 OnUnsupported::Fallback { .. } => unreachable!(),
445 }
450 }
446 }
451 }
447 }
452 }
448 std::process::exit(exit_code(&result, use_detailed_exit_code))
453 std::process::exit(exit_code(&result, use_detailed_exit_code))
449 }
454 }
450
455
451 macro_rules! subcommands {
456 macro_rules! subcommands {
452 ($( $command: ident )+) => {
457 ($( $command: ident )+) => {
453 mod commands {
458 mod commands {
454 $(
459 $(
455 pub mod $command;
460 pub mod $command;
456 )+
461 )+
457 }
462 }
458
463
459 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
464 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
460 app
465 app
461 $(
466 $(
462 .subcommand(commands::$command::args())
467 .subcommand(commands::$command::args())
463 )+
468 )+
464 }
469 }
465
470
466 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
471 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
467
472
468 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
473 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
469 match name {
474 match name {
470 $(
475 $(
471 stringify!($command) => Some(commands::$command::run),
476 stringify!($command) => Some(commands::$command::run),
472 )+
477 )+
473 _ => None,
478 _ => None,
474 }
479 }
475 }
480 }
476 };
481 };
477 }
482 }
478
483
479 subcommands! {
484 subcommands! {
480 cat
485 cat
481 debugdata
486 debugdata
482 debugrequirements
487 debugrequirements
483 debugignorerhg
488 debugignorerhg
484 files
489 files
485 root
490 root
486 config
491 config
487 status
492 status
488 }
493 }
489
494
490 pub struct CliInvocation<'a> {
495 pub struct CliInvocation<'a> {
491 ui: &'a Ui,
496 ui: &'a Ui,
492 subcommand_args: &'a ArgMatches<'a>,
497 subcommand_args: &'a ArgMatches<'a>,
493 config: &'a Config,
498 config: &'a Config,
494 /// References inside `Result` is a bit peculiar but allow
499 /// References inside `Result` is a bit peculiar but allow
495 /// `invocation.repo?` to work out with `&CliInvocation` since this
500 /// `invocation.repo?` to work out with `&CliInvocation` since this
496 /// `Result` type is `Copy`.
501 /// `Result` type is `Copy`.
497 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
502 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
498 }
503 }
499
504
500 struct NoRepoInCwdError {
505 struct NoRepoInCwdError {
501 cwd: PathBuf,
506 cwd: PathBuf,
502 }
507 }
503
508
504 /// CLI arguments to be parsed "early" in order to be able to read
509 /// CLI arguments to be parsed "early" in order to be able to read
505 /// configuration before using Clap. Ideally we would also use Clap for this,
510 /// configuration before using Clap. Ideally we would also use Clap for this,
506 /// see <https://github.com/clap-rs/clap/discussions/2366>.
511 /// see <https://github.com/clap-rs/clap/discussions/2366>.
507 ///
512 ///
508 /// These arguments are still declared when we do use Clap later, so that Clap
513 /// These arguments are still declared when we do use Clap later, so that Clap
509 /// does not return an error for their presence.
514 /// does not return an error for their presence.
510 struct EarlyArgs {
515 struct EarlyArgs {
511 /// Values of all `--config` arguments. (Possibly none)
516 /// Values of all `--config` arguments. (Possibly none)
512 config: Vec<Vec<u8>>,
517 config: Vec<Vec<u8>>,
513 /// Value of the `-R` or `--repository` argument, if any.
518 /// Value of the `-R` or `--repository` argument, if any.
514 repo: Option<Vec<u8>>,
519 repo: Option<Vec<u8>>,
515 /// Value of the `--cwd` argument, if any.
520 /// Value of the `--cwd` argument, if any.
516 cwd: Option<Vec<u8>>,
521 cwd: Option<Vec<u8>>,
517 }
522 }
518
523
519 impl EarlyArgs {
524 impl EarlyArgs {
520 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
525 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
521 let mut args = args.into_iter().map(get_bytes_from_os_str);
526 let mut args = args.into_iter().map(get_bytes_from_os_str);
522 let mut config = Vec::new();
527 let mut config = Vec::new();
523 let mut repo = None;
528 let mut repo = None;
524 let mut cwd = None;
529 let mut cwd = None;
525 // Use `while let` instead of `for` so that we can also call
530 // Use `while let` instead of `for` so that we can also call
526 // `args.next()` inside the loop.
531 // `args.next()` inside the loop.
527 while let Some(arg) = args.next() {
532 while let Some(arg) = args.next() {
528 if arg == b"--config" {
533 if arg == b"--config" {
529 if let Some(value) = args.next() {
534 if let Some(value) = args.next() {
530 config.push(value)
535 config.push(value)
531 }
536 }
532 } else if let Some(value) = arg.drop_prefix(b"--config=") {
537 } else if let Some(value) = arg.drop_prefix(b"--config=") {
533 config.push(value.to_owned())
538 config.push(value.to_owned())
534 }
539 }
535
540
536 if arg == b"--cwd" {
541 if arg == b"--cwd" {
537 if let Some(value) = args.next() {
542 if let Some(value) = args.next() {
538 cwd = Some(value)
543 cwd = Some(value)
539 }
544 }
540 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
545 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
541 cwd = Some(value.to_owned())
546 cwd = Some(value.to_owned())
542 }
547 }
543
548
544 if arg == b"--repository" || arg == b"-R" {
549 if arg == b"--repository" || arg == b"-R" {
545 if let Some(value) = args.next() {
550 if let Some(value) = args.next() {
546 repo = Some(value)
551 repo = Some(value)
547 }
552 }
548 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
553 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
549 repo = Some(value.to_owned())
554 repo = Some(value.to_owned())
550 } else if let Some(value) = arg.drop_prefix(b"-R") {
555 } else if let Some(value) = arg.drop_prefix(b"-R") {
551 repo = Some(value.to_owned())
556 repo = Some(value.to_owned())
552 }
557 }
553 }
558 }
554 Self { config, repo, cwd }
559 Self { config, repo, cwd }
555 }
560 }
556 }
561 }
557
562
558 /// What to do when encountering some unsupported feature.
563 /// What to do when encountering some unsupported feature.
559 ///
564 ///
560 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
565 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
561 enum OnUnsupported {
566 enum OnUnsupported {
562 /// Print an error message describing what feature is not supported,
567 /// Print an error message describing what feature is not supported,
563 /// and exit with code 252.
568 /// and exit with code 252.
564 Abort,
569 Abort,
565 /// Silently exit with code 252.
570 /// Silently exit with code 252.
566 AbortSilent,
571 AbortSilent,
567 /// Try running a Python implementation
572 /// Try running a Python implementation
568 Fallback { executable: Option<Vec<u8>> },
573 Fallback { executable: Option<Vec<u8>> },
569 }
574 }
570
575
571 impl OnUnsupported {
576 impl OnUnsupported {
572 const DEFAULT: Self = OnUnsupported::Abort;
577 const DEFAULT: Self = OnUnsupported::Abort;
573
578
574 fn from_config(config: &Config) -> Self {
579 fn from_config(config: &Config) -> Self {
575 match config
580 match config
576 .get(b"rhg", b"on-unsupported")
581 .get(b"rhg", b"on-unsupported")
577 .map(|value| value.to_ascii_lowercase())
582 .map(|value| value.to_ascii_lowercase())
578 .as_deref()
583 .as_deref()
579 {
584 {
580 Some(b"abort") => OnUnsupported::Abort,
585 Some(b"abort") => OnUnsupported::Abort,
581 Some(b"abort-silent") => OnUnsupported::AbortSilent,
586 Some(b"abort-silent") => OnUnsupported::AbortSilent,
582 Some(b"fallback") => OnUnsupported::Fallback {
587 Some(b"fallback") => OnUnsupported::Fallback {
583 executable: config
588 executable: config
584 .get(b"rhg", b"fallback-executable")
589 .get(b"rhg", b"fallback-executable")
585 .map(|x| x.to_owned()),
590 .map(|x| x.to_owned()),
586 },
591 },
587 None => Self::DEFAULT,
592 None => Self::DEFAULT,
588 Some(_) => {
593 Some(_) => {
589 // TODO: warn about unknown config value
594 // TODO: warn about unknown config value
590 Self::DEFAULT
595 Self::DEFAULT
591 }
596 }
592 }
597 }
593 }
598 }
594 }
599 }
595
600
596 const SUPPORTED_EXTENSIONS: &[&[u8]] =
601 const SUPPORTED_EXTENSIONS: &[&[u8]] =
597 &[b"blackbox", b"share", b"sparse", b"narrow"];
602 &[b"blackbox", b"share", b"sparse", b"narrow"];
598
603
599 fn check_extensions(config: &Config) -> Result<(), CommandError> {
604 fn check_extensions(config: &Config) -> Result<(), CommandError> {
600 let enabled = config.get_section_keys(b"extensions");
605 let enabled = config.get_section_keys(b"extensions");
601
606
602 let mut unsupported = enabled;
607 let mut unsupported = enabled;
603 for supported in SUPPORTED_EXTENSIONS {
608 for supported in SUPPORTED_EXTENSIONS {
604 unsupported.remove(supported);
609 unsupported.remove(supported);
605 }
610 }
606
611
607 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
612 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
608 {
613 {
609 for ignored in ignored_list {
614 for ignored in ignored_list {
610 unsupported.remove(ignored.as_slice());
615 unsupported.remove(ignored.as_slice());
611 }
616 }
612 }
617 }
613
618
614 if unsupported.is_empty() {
619 if unsupported.is_empty() {
615 Ok(())
620 Ok(())
616 } else {
621 } else {
617 Err(CommandError::UnsupportedFeature {
622 Err(CommandError::UnsupportedFeature {
618 message: format_bytes!(
623 message: format_bytes!(
619 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
624 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
620 join(unsupported, b", ")
625 join(unsupported, b", ")
621 ),
626 ),
622 })
627 })
623 }
628 }
624 }
629 }
625
630
626 fn check_unsupported(
631 fn check_unsupported(
627 config: &Config,
632 config: &Config,
628 ui: &ui::Ui,
633 ui: &ui::Ui,
629 ) -> Result<(), CommandError> {
634 ) -> Result<(), CommandError> {
630 check_extensions(config)?;
635 check_extensions(config)?;
631
636
632 if std::env::var_os("HG_PENDING").is_some() {
637 if std::env::var_os("HG_PENDING").is_some() {
633 // TODO: only if the value is `== repo.working_directory`?
638 // TODO: only if the value is `== repo.working_directory`?
634 // What about relative v.s. absolute paths?
639 // What about relative v.s. absolute paths?
635 Err(CommandError::unsupported("$HG_PENDING"))?
640 Err(CommandError::unsupported("$HG_PENDING"))?
636 }
641 }
637
642
638 if config.has_non_empty_section(b"encode") {
643 if config.has_non_empty_section(b"encode") {
639 Err(CommandError::unsupported("[encode] config"))?
644 Err(CommandError::unsupported("[encode] config"))?
640 }
645 }
641
646
642 if config.has_non_empty_section(b"decode") {
647 if config.has_non_empty_section(b"decode") {
643 Err(CommandError::unsupported("[decode] config"))?
648 Err(CommandError::unsupported("[decode] config"))?
644 }
649 }
645
650
646 if let Some(color) = config.get(b"ui", b"color") {
651 if let Some(color) = config.get(b"ui", b"color") {
647 if (color == b"always" || color == b"debug") && !ui.plain() {
652 if (color == b"always" || color == b"debug") && !ui.plain() {
648 Err(CommandError::unsupported("colored output"))?
653 Err(CommandError::unsupported("colored output"))?
649 }
654 }
650 }
655 }
651
656
652 Ok(())
657 Ok(())
653 }
658 }
General Comments 0
You need to be logged in to leave comments. Login now