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