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