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