##// END OF EJS Templates
rhg: Fall back to Python for --version...
Simon Sapin -
r47480:eb14264b default
parent child Browse files
Show More
@@ -1,442 +1,443 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 .setting(AppSettings::SubcommandRequired)
33 .setting(AppSettings::SubcommandRequired)
33 .setting(AppSettings::VersionlessSubcommands)
34 .setting(AppSettings::VersionlessSubcommands)
34 .arg(
35 .arg(
35 Arg::with_name("repository")
36 Arg::with_name("repository")
36 .help("repository root directory")
37 .help("repository root directory")
37 .short("-R")
38 .short("-R")
38 .long("--repository")
39 .long("--repository")
39 .value_name("REPO")
40 .value_name("REPO")
40 .takes_value(true)
41 .takes_value(true)
41 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
42 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
42 .global(true),
43 .global(true),
43 )
44 )
44 .arg(
45 .arg(
45 Arg::with_name("config")
46 Arg::with_name("config")
46 .help("set/override config option (use 'section.name=value')")
47 .help("set/override config option (use 'section.name=value')")
47 .long("--config")
48 .long("--config")
48 .value_name("CONFIG")
49 .value_name("CONFIG")
49 .takes_value(true)
50 .takes_value(true)
50 .global(true)
51 .global(true)
51 // Ok: `--config section.key1=val --config section.key2=val2`
52 // Ok: `--config section.key1=val --config section.key2=val2`
52 .multiple(true)
53 .multiple(true)
53 // Not ok: `--config section.key1=val section.key2=val2`
54 // Not ok: `--config section.key1=val section.key2=val2`
54 .number_of_values(1),
55 .number_of_values(1),
55 )
56 )
56 .arg(
57 .arg(
57 Arg::with_name("cwd")
58 Arg::with_name("cwd")
58 .help("change working directory")
59 .help("change working directory")
59 .long("--cwd")
60 .long("--cwd")
60 .value_name("DIR")
61 .value_name("DIR")
61 .takes_value(true)
62 .takes_value(true)
62 .global(true),
63 .global(true),
63 )
64 )
64 .version("0.0.1");
65 .version("0.0.1");
65 let app = add_subcommand_args(app);
66 let app = add_subcommand_args(app);
66
67
67 let matches = app.clone().get_matches_safe()?;
68 let matches = app.clone().get_matches_safe()?;
68
69
69 let (subcommand_name, subcommand_matches) = matches.subcommand();
70 let (subcommand_name, subcommand_matches) = matches.subcommand();
70 let run = subcommand_run_fn(subcommand_name)
71 let run = subcommand_run_fn(subcommand_name)
71 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
72 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
72 let subcommand_args = subcommand_matches
73 let subcommand_args = subcommand_matches
73 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
74 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
74
75
75 let invocation = CliInvocation {
76 let invocation = CliInvocation {
76 ui,
77 ui,
77 subcommand_args,
78 subcommand_args,
78 config,
79 config,
79 repo,
80 repo,
80 };
81 };
81 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
82 let blackbox = blackbox::Blackbox::new(&invocation, process_start_time)?;
82 blackbox.log_command_start();
83 blackbox.log_command_start();
83 let result = run(&invocation);
84 let result = run(&invocation);
84 blackbox.log_command_end(exit_code(&result));
85 blackbox.log_command_end(exit_code(&result));
85 result
86 result
86 }
87 }
87
88
88 fn main() {
89 fn main() {
89 // Run this first, before we find out if the blackbox extension is even
90 // Run this first, before we find out if the blackbox extension is even
90 // enabled, in order to include everything in-between in the duration
91 // enabled, in order to include everything in-between in the duration
91 // measurements. Reading config files can be slow if they’re on NFS.
92 // measurements. Reading config files can be slow if they’re on NFS.
92 let process_start_time = blackbox::ProcessStartTime::now();
93 let process_start_time = blackbox::ProcessStartTime::now();
93
94
94 env_logger::init();
95 env_logger::init();
95 let ui = ui::Ui::new();
96 let ui = ui::Ui::new();
96
97
97 let early_args = EarlyArgs::parse(std::env::args_os());
98 let early_args = EarlyArgs::parse(std::env::args_os());
98
99
99 let initial_current_dir = early_args.cwd.map(|cwd| {
100 let initial_current_dir = early_args.cwd.map(|cwd| {
100 let cwd = get_path_from_bytes(&cwd);
101 let cwd = get_path_from_bytes(&cwd);
101 std::env::current_dir()
102 std::env::current_dir()
102 .and_then(|initial| {
103 .and_then(|initial| {
103 std::env::set_current_dir(cwd)?;
104 std::env::set_current_dir(cwd)?;
104 Ok(initial)
105 Ok(initial)
105 })
106 })
106 .unwrap_or_else(|error| {
107 .unwrap_or_else(|error| {
107 exit(
108 exit(
108 &None,
109 &None,
109 &ui,
110 &ui,
110 OnUnsupported::Abort,
111 OnUnsupported::Abort,
111 Err(CommandError::abort(format!(
112 Err(CommandError::abort(format!(
112 "abort: {}: '{}'",
113 "abort: {}: '{}'",
113 error,
114 error,
114 cwd.display()
115 cwd.display()
115 ))),
116 ))),
116 )
117 )
117 })
118 })
118 });
119 });
119
120
120 let non_repo_config =
121 let non_repo_config =
121 Config::load(early_args.config).unwrap_or_else(|error| {
122 Config::load(early_args.config).unwrap_or_else(|error| {
122 // Normally this is decided based on config, but we don’t have that
123 // Normally this is decided based on config, but we don’t have that
123 // available. As of this writing config loading never returns an
124 // available. As of this writing config loading never returns an
124 // "unsupported" error but that is not enforced by the type system.
125 // "unsupported" error but that is not enforced by the type system.
125 let on_unsupported = OnUnsupported::Abort;
126 let on_unsupported = OnUnsupported::Abort;
126
127
127 exit(&initial_current_dir, &ui, on_unsupported, Err(error.into()))
128 exit(&initial_current_dir, &ui, on_unsupported, Err(error.into()))
128 });
129 });
129
130
130 if let Some(repo_path_bytes) = &early_args.repo {
131 if let Some(repo_path_bytes) = &early_args.repo {
131 lazy_static::lazy_static! {
132 lazy_static::lazy_static! {
132 static ref SCHEME_RE: regex::bytes::Regex =
133 static ref SCHEME_RE: regex::bytes::Regex =
133 // Same as `_matchscheme` in `mercurial/util.py`
134 // Same as `_matchscheme` in `mercurial/util.py`
134 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
135 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
135 }
136 }
136 if SCHEME_RE.is_match(&repo_path_bytes) {
137 if SCHEME_RE.is_match(&repo_path_bytes) {
137 exit(
138 exit(
138 &initial_current_dir,
139 &initial_current_dir,
139 &ui,
140 &ui,
140 OnUnsupported::from_config(&non_repo_config),
141 OnUnsupported::from_config(&non_repo_config),
141 Err(CommandError::UnsupportedFeature {
142 Err(CommandError::UnsupportedFeature {
142 message: format_bytes!(
143 message: format_bytes!(
143 b"URL-like --repository {}",
144 b"URL-like --repository {}",
144 repo_path_bytes
145 repo_path_bytes
145 ),
146 ),
146 }),
147 }),
147 )
148 )
148 }
149 }
149 }
150 }
150 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
151 let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
151 let repo_result = match Repo::find(&non_repo_config, repo_path) {
152 let repo_result = match Repo::find(&non_repo_config, repo_path) {
152 Ok(repo) => Ok(repo),
153 Ok(repo) => Ok(repo),
153 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
154 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
154 // Not finding a repo is not fatal yet, if `-R` was not given
155 // Not finding a repo is not fatal yet, if `-R` was not given
155 Err(NoRepoInCwdError { cwd: at })
156 Err(NoRepoInCwdError { cwd: at })
156 }
157 }
157 Err(error) => exit(
158 Err(error) => exit(
158 &initial_current_dir,
159 &initial_current_dir,
159 &ui,
160 &ui,
160 OnUnsupported::from_config(&non_repo_config),
161 OnUnsupported::from_config(&non_repo_config),
161 Err(error.into()),
162 Err(error.into()),
162 ),
163 ),
163 };
164 };
164
165
165 let config = if let Ok(repo) = &repo_result {
166 let config = if let Ok(repo) = &repo_result {
166 repo.config()
167 repo.config()
167 } else {
168 } else {
168 &non_repo_config
169 &non_repo_config
169 };
170 };
170
171
171 let result = main_with_result(
172 let result = main_with_result(
172 &process_start_time,
173 &process_start_time,
173 &ui,
174 &ui,
174 repo_result.as_ref(),
175 repo_result.as_ref(),
175 config,
176 config,
176 );
177 );
177 exit(
178 exit(
178 &initial_current_dir,
179 &initial_current_dir,
179 &ui,
180 &ui,
180 OnUnsupported::from_config(config),
181 OnUnsupported::from_config(config),
181 result,
182 result,
182 )
183 )
183 }
184 }
184
185
185 fn exit_code(result: &Result<(), CommandError>) -> i32 {
186 fn exit_code(result: &Result<(), CommandError>) -> i32 {
186 match result {
187 match result {
187 Ok(()) => exitcode::OK,
188 Ok(()) => exitcode::OK,
188 Err(CommandError::Abort { .. }) => exitcode::ABORT,
189 Err(CommandError::Abort { .. }) => exitcode::ABORT,
189 Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL,
190 Err(CommandError::Unsuccessful) => exitcode::UNSUCCESSFUL,
190
191
191 // Exit with a specific code and no error message to let a potential
192 // Exit with a specific code and no error message to let a potential
192 // wrapper script fallback to Python-based Mercurial.
193 // wrapper script fallback to Python-based Mercurial.
193 Err(CommandError::UnsupportedFeature { .. }) => {
194 Err(CommandError::UnsupportedFeature { .. }) => {
194 exitcode::UNIMPLEMENTED
195 exitcode::UNIMPLEMENTED
195 }
196 }
196 }
197 }
197 }
198 }
198
199
199 fn exit(
200 fn exit(
200 initial_current_dir: &Option<PathBuf>,
201 initial_current_dir: &Option<PathBuf>,
201 ui: &Ui,
202 ui: &Ui,
202 mut on_unsupported: OnUnsupported,
203 mut on_unsupported: OnUnsupported,
203 result: Result<(), CommandError>,
204 result: Result<(), CommandError>,
204 ) -> ! {
205 ) -> ! {
205 if let (
206 if let (
206 OnUnsupported::Fallback { executable },
207 OnUnsupported::Fallback { executable },
207 Err(CommandError::UnsupportedFeature { .. }),
208 Err(CommandError::UnsupportedFeature { .. }),
208 ) = (&on_unsupported, &result)
209 ) = (&on_unsupported, &result)
209 {
210 {
210 let mut args = std::env::args_os();
211 let mut args = std::env::args_os();
211 let executable_path = get_path_from_bytes(&executable);
212 let executable_path = get_path_from_bytes(&executable);
212 let this_executable = args.next().expect("exepcted argv[0] to exist");
213 let this_executable = args.next().expect("exepcted argv[0] to exist");
213 if executable_path == &PathBuf::from(this_executable) {
214 if executable_path == &PathBuf::from(this_executable) {
214 // Avoid spawning infinitely many processes until resource
215 // Avoid spawning infinitely many processes until resource
215 // exhaustion.
216 // exhaustion.
216 let _ = ui.write_stderr(&format_bytes!(
217 let _ = ui.write_stderr(&format_bytes!(
217 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
218 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
218 points to `rhg` itself.\n",
219 points to `rhg` itself.\n",
219 executable
220 executable
220 ));
221 ));
221 on_unsupported = OnUnsupported::Abort
222 on_unsupported = OnUnsupported::Abort
222 } else {
223 } else {
223 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
224 // `args` is now `argv[1..]` since we’ve already consumed `argv[0]`
224 let mut command = Command::new(executable_path);
225 let mut command = Command::new(executable_path);
225 command.args(args);
226 command.args(args);
226 if let Some(initial) = initial_current_dir {
227 if let Some(initial) = initial_current_dir {
227 command.current_dir(initial);
228 command.current_dir(initial);
228 }
229 }
229 let result = command.status();
230 let result = command.status();
230 match result {
231 match result {
231 Ok(status) => std::process::exit(
232 Ok(status) => std::process::exit(
232 status.code().unwrap_or(exitcode::ABORT),
233 status.code().unwrap_or(exitcode::ABORT),
233 ),
234 ),
234 Err(error) => {
235 Err(error) => {
235 let _ = ui.write_stderr(&format_bytes!(
236 let _ = ui.write_stderr(&format_bytes!(
236 b"tried to fall back to a '{}' sub-process but got error {}\n",
237 b"tried to fall back to a '{}' sub-process but got error {}\n",
237 executable, format_bytes::Utf8(error)
238 executable, format_bytes::Utf8(error)
238 ));
239 ));
239 on_unsupported = OnUnsupported::Abort
240 on_unsupported = OnUnsupported::Abort
240 }
241 }
241 }
242 }
242 }
243 }
243 }
244 }
244 match &result {
245 match &result {
245 Ok(_) => {}
246 Ok(_) => {}
246 Err(CommandError::Unsuccessful) => {}
247 Err(CommandError::Unsuccessful) => {}
247 Err(CommandError::Abort { message }) => {
248 Err(CommandError::Abort { message }) => {
248 if !message.is_empty() {
249 if !message.is_empty() {
249 // Ignore errors when writing to stderr, we’re already exiting
250 // Ignore errors when writing to stderr, we’re already exiting
250 // with failure code so there’s not much more we can do.
251 // with failure code so there’s not much more we can do.
251 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
252 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
252 }
253 }
253 }
254 }
254 Err(CommandError::UnsupportedFeature { message }) => {
255 Err(CommandError::UnsupportedFeature { message }) => {
255 match on_unsupported {
256 match on_unsupported {
256 OnUnsupported::Abort => {
257 OnUnsupported::Abort => {
257 let _ = ui.write_stderr(&format_bytes!(
258 let _ = ui.write_stderr(&format_bytes!(
258 b"unsupported feature: {}\n",
259 b"unsupported feature: {}\n",
259 message
260 message
260 ));
261 ));
261 }
262 }
262 OnUnsupported::AbortSilent => {}
263 OnUnsupported::AbortSilent => {}
263 OnUnsupported::Fallback { .. } => unreachable!(),
264 OnUnsupported::Fallback { .. } => unreachable!(),
264 }
265 }
265 }
266 }
266 }
267 }
267 std::process::exit(exit_code(&result))
268 std::process::exit(exit_code(&result))
268 }
269 }
269
270
270 macro_rules! subcommands {
271 macro_rules! subcommands {
271 ($( $command: ident )+) => {
272 ($( $command: ident )+) => {
272 mod commands {
273 mod commands {
273 $(
274 $(
274 pub mod $command;
275 pub mod $command;
275 )+
276 )+
276 }
277 }
277
278
278 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
279 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
279 app
280 app
280 $(
281 $(
281 .subcommand(commands::$command::args())
282 .subcommand(commands::$command::args())
282 )+
283 )+
283 }
284 }
284
285
285 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
286 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
286
287
287 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
288 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
288 match name {
289 match name {
289 $(
290 $(
290 stringify!($command) => Some(commands::$command::run),
291 stringify!($command) => Some(commands::$command::run),
291 )+
292 )+
292 _ => None,
293 _ => None,
293 }
294 }
294 }
295 }
295 };
296 };
296 }
297 }
297
298
298 subcommands! {
299 subcommands! {
299 cat
300 cat
300 debugdata
301 debugdata
301 debugrequirements
302 debugrequirements
302 files
303 files
303 root
304 root
304 config
305 config
305 }
306 }
306 pub struct CliInvocation<'a> {
307 pub struct CliInvocation<'a> {
307 ui: &'a Ui,
308 ui: &'a Ui,
308 subcommand_args: &'a ArgMatches<'a>,
309 subcommand_args: &'a ArgMatches<'a>,
309 config: &'a Config,
310 config: &'a Config,
310 /// References inside `Result` is a bit peculiar but allow
311 /// References inside `Result` is a bit peculiar but allow
311 /// `invocation.repo?` to work out with `&CliInvocation` since this
312 /// `invocation.repo?` to work out with `&CliInvocation` since this
312 /// `Result` type is `Copy`.
313 /// `Result` type is `Copy`.
313 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
314 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
314 }
315 }
315
316
316 struct NoRepoInCwdError {
317 struct NoRepoInCwdError {
317 cwd: PathBuf,
318 cwd: PathBuf,
318 }
319 }
319
320
320 /// CLI arguments to be parsed "early" in order to be able to read
321 /// CLI arguments to be parsed "early" in order to be able to read
321 /// configuration before using Clap. Ideally we would also use Clap for this,
322 /// configuration before using Clap. Ideally we would also use Clap for this,
322 /// see <https://github.com/clap-rs/clap/discussions/2366>.
323 /// see <https://github.com/clap-rs/clap/discussions/2366>.
323 ///
324 ///
324 /// These arguments are still declared when we do use Clap later, so that Clap
325 /// These arguments are still declared when we do use Clap later, so that Clap
325 /// does not return an error for their presence.
326 /// does not return an error for their presence.
326 struct EarlyArgs {
327 struct EarlyArgs {
327 /// Values of all `--config` arguments. (Possibly none)
328 /// Values of all `--config` arguments. (Possibly none)
328 config: Vec<Vec<u8>>,
329 config: Vec<Vec<u8>>,
329 /// Value of the `-R` or `--repository` argument, if any.
330 /// Value of the `-R` or `--repository` argument, if any.
330 repo: Option<Vec<u8>>,
331 repo: Option<Vec<u8>>,
331 /// Value of the `--cwd` argument, if any.
332 /// Value of the `--cwd` argument, if any.
332 cwd: Option<Vec<u8>>,
333 cwd: Option<Vec<u8>>,
333 }
334 }
334
335
335 impl EarlyArgs {
336 impl EarlyArgs {
336 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
337 fn parse(args: impl IntoIterator<Item = OsString>) -> Self {
337 let mut args = args.into_iter().map(get_bytes_from_os_str);
338 let mut args = args.into_iter().map(get_bytes_from_os_str);
338 let mut config = Vec::new();
339 let mut config = Vec::new();
339 let mut repo = None;
340 let mut repo = None;
340 let mut cwd = None;
341 let mut cwd = None;
341 // Use `while let` instead of `for` so that we can also call
342 // Use `while let` instead of `for` so that we can also call
342 // `args.next()` inside the loop.
343 // `args.next()` inside the loop.
343 while let Some(arg) = args.next() {
344 while let Some(arg) = args.next() {
344 if arg == b"--config" {
345 if arg == b"--config" {
345 if let Some(value) = args.next() {
346 if let Some(value) = args.next() {
346 config.push(value)
347 config.push(value)
347 }
348 }
348 } else if let Some(value) = arg.drop_prefix(b"--config=") {
349 } else if let Some(value) = arg.drop_prefix(b"--config=") {
349 config.push(value.to_owned())
350 config.push(value.to_owned())
350 }
351 }
351
352
352 if arg == b"--cwd" {
353 if arg == b"--cwd" {
353 if let Some(value) = args.next() {
354 if let Some(value) = args.next() {
354 cwd = Some(value)
355 cwd = Some(value)
355 }
356 }
356 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
357 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
357 cwd = Some(value.to_owned())
358 cwd = Some(value.to_owned())
358 }
359 }
359
360
360 if arg == b"--repository" || arg == b"-R" {
361 if arg == b"--repository" || arg == b"-R" {
361 if let Some(value) = args.next() {
362 if let Some(value) = args.next() {
362 repo = Some(value)
363 repo = Some(value)
363 }
364 }
364 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
365 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
365 repo = Some(value.to_owned())
366 repo = Some(value.to_owned())
366 } else if let Some(value) = arg.drop_prefix(b"-R") {
367 } else if let Some(value) = arg.drop_prefix(b"-R") {
367 repo = Some(value.to_owned())
368 repo = Some(value.to_owned())
368 }
369 }
369 }
370 }
370 Self { config, repo, cwd }
371 Self { config, repo, cwd }
371 }
372 }
372 }
373 }
373
374
374 /// What to do when encountering some unsupported feature.
375 /// What to do when encountering some unsupported feature.
375 ///
376 ///
376 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
377 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
377 enum OnUnsupported {
378 enum OnUnsupported {
378 /// Print an error message describing what feature is not supported,
379 /// Print an error message describing what feature is not supported,
379 /// and exit with code 252.
380 /// and exit with code 252.
380 Abort,
381 Abort,
381 /// Silently exit with code 252.
382 /// Silently exit with code 252.
382 AbortSilent,
383 AbortSilent,
383 /// Try running a Python implementation
384 /// Try running a Python implementation
384 Fallback { executable: Vec<u8> },
385 Fallback { executable: Vec<u8> },
385 }
386 }
386
387
387 impl OnUnsupported {
388 impl OnUnsupported {
388 const DEFAULT: Self = OnUnsupported::Abort;
389 const DEFAULT: Self = OnUnsupported::Abort;
389 const DEFAULT_FALLBACK_EXECUTABLE: &'static [u8] = b"hg";
390 const DEFAULT_FALLBACK_EXECUTABLE: &'static [u8] = b"hg";
390
391
391 fn from_config(config: &Config) -> Self {
392 fn from_config(config: &Config) -> Self {
392 match config
393 match config
393 .get(b"rhg", b"on-unsupported")
394 .get(b"rhg", b"on-unsupported")
394 .map(|value| value.to_ascii_lowercase())
395 .map(|value| value.to_ascii_lowercase())
395 .as_deref()
396 .as_deref()
396 {
397 {
397 Some(b"abort") => OnUnsupported::Abort,
398 Some(b"abort") => OnUnsupported::Abort,
398 Some(b"abort-silent") => OnUnsupported::AbortSilent,
399 Some(b"abort-silent") => OnUnsupported::AbortSilent,
399 Some(b"fallback") => OnUnsupported::Fallback {
400 Some(b"fallback") => OnUnsupported::Fallback {
400 executable: config
401 executable: config
401 .get(b"rhg", b"fallback-executable")
402 .get(b"rhg", b"fallback-executable")
402 .unwrap_or(Self::DEFAULT_FALLBACK_EXECUTABLE)
403 .unwrap_or(Self::DEFAULT_FALLBACK_EXECUTABLE)
403 .to_owned(),
404 .to_owned(),
404 },
405 },
405 None => Self::DEFAULT,
406 None => Self::DEFAULT,
406 Some(_) => {
407 Some(_) => {
407 // TODO: warn about unknown config value
408 // TODO: warn about unknown config value
408 Self::DEFAULT
409 Self::DEFAULT
409 }
410 }
410 }
411 }
411 }
412 }
412 }
413 }
413
414
414 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
415 const SUPPORTED_EXTENSIONS: &[&[u8]] = &[b"blackbox", b"share"];
415
416
416 fn check_extensions(config: &Config) -> Result<(), CommandError> {
417 fn check_extensions(config: &Config) -> Result<(), CommandError> {
417 let enabled = config.get_section_keys(b"extensions");
418 let enabled = config.get_section_keys(b"extensions");
418
419
419 let mut unsupported = enabled;
420 let mut unsupported = enabled;
420 for supported in SUPPORTED_EXTENSIONS {
421 for supported in SUPPORTED_EXTENSIONS {
421 unsupported.remove(supported);
422 unsupported.remove(supported);
422 }
423 }
423
424
424 if let Some(ignored_list) =
425 if let Some(ignored_list) =
425 config.get_simple_list(b"rhg", b"ignored-extensions")
426 config.get_simple_list(b"rhg", b"ignored-extensions")
426 {
427 {
427 for ignored in ignored_list {
428 for ignored in ignored_list {
428 unsupported.remove(ignored);
429 unsupported.remove(ignored);
429 }
430 }
430 }
431 }
431
432
432 if unsupported.is_empty() {
433 if unsupported.is_empty() {
433 Ok(())
434 Ok(())
434 } else {
435 } else {
435 Err(CommandError::UnsupportedFeature {
436 Err(CommandError::UnsupportedFeature {
436 message: format_bytes!(
437 message: format_bytes!(
437 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
438 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
438 join(unsupported, b", ")
439 join(unsupported, b", ")
439 ),
440 ),
440 })
441 })
441 }
442 }
442 }
443 }
General Comments 0
You need to be logged in to leave comments. Login now