just playing with tangled
at ig/vimdiffwarn 1477 lines 43 kB view raw
1// Copyright 2022 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15use std::env::join_paths; 16use std::path::PathBuf; 17 18use indoc::indoc; 19use regex::Regex; 20 21use crate::common::fake_editor_path; 22use crate::common::to_toml_value; 23use crate::common::TestEnvironment; 24 25#[test] 26fn test_config_list_single() { 27 let test_env = TestEnvironment::default(); 28 test_env.add_config( 29 r#" 30 [test-table] 31 somekey = "some value" 32 "#, 33 ); 34 35 let output = test_env.run_jj_in(".", ["config", "list", "test-table.somekey"]); 36 insta::assert_snapshot!(output, @r#" 37 test-table.somekey = "some value" 38 [EOF] 39 "#); 40 41 let output = test_env.run_jj_in( 42 ".", 43 ["config", "list", r#"-Tname ++ "\n""#, "test-table.somekey"], 44 ); 45 insta::assert_snapshot!(output, @r" 46 test-table.somekey 47 [EOF] 48 "); 49} 50 51#[test] 52fn test_config_list_nonexistent() { 53 let test_env = TestEnvironment::default(); 54 let output = test_env.run_jj_in(".", ["config", "list", "nonexistent-test-key"]); 55 insta::assert_snapshot!(output, @r" 56 ------- stderr ------- 57 Warning: No matching config key for nonexistent-test-key 58 [EOF] 59 "); 60} 61 62#[test] 63fn test_config_list_table() { 64 let test_env = TestEnvironment::default(); 65 test_env.add_config( 66 r#" 67 [test-table] 68 x = true 69 y.foo = "abc" 70 y.bar = 123 71 "z"."with space"."function()" = 5 72 "#, 73 ); 74 let output = test_env.run_jj_in(".", ["config", "list", "test-table"]); 75 insta::assert_snapshot!(output, @r#" 76 test-table.x = true 77 test-table.y.foo = "abc" 78 test-table.y.bar = 123 79 test-table.z."with space"."function()" = 5 80 [EOF] 81 "#); 82} 83 84#[test] 85fn test_config_list_inline_table() { 86 let test_env = TestEnvironment::default(); 87 test_env.add_config( 88 r#" 89 test-table = { x = true, y = 1 } 90 "#, 91 ); 92 // Inline tables are expanded 93 let output = test_env.run_jj_in(".", ["config", "list", "test-table"]); 94 insta::assert_snapshot!(output, @r" 95 test-table.x = true 96 test-table.y = 1 97 [EOF] 98 "); 99 // Inner value can also be addressed by a dotted name path 100 let output = test_env.run_jj_in(".", ["config", "list", "test-table.x"]); 101 insta::assert_snapshot!(output, @r" 102 test-table.x = true 103 [EOF] 104 "); 105} 106 107#[test] 108fn test_config_list_array() { 109 let test_env = TestEnvironment::default(); 110 test_env.add_config( 111 r#" 112 test-array = [1, "b", 3.4] 113 "#, 114 ); 115 let output = test_env.run_jj_in(".", ["config", "list", "test-array"]); 116 insta::assert_snapshot!(output, @r#" 117 test-array = [1, "b", 3.4] 118 [EOF] 119 "#); 120} 121 122#[test] 123fn test_config_list_array_of_tables() { 124 let test_env = TestEnvironment::default(); 125 test_env.add_config( 126 r#" 127 [[test-table]] 128 x = 1 129 [[test-table]] 130 y = ["z"] 131 z."key=with whitespace" = [] 132 "#, 133 ); 134 // Array is a value, so is array of tables 135 let output = test_env.run_jj_in(".", ["config", "list", "test-table"]); 136 insta::assert_snapshot!(output, @r#" 137 test-table = [{ x = 1 }, { y = ["z"], z = { "key=with whitespace" = [] } }] 138 [EOF] 139 "#); 140} 141 142#[test] 143fn test_config_list_all() { 144 let test_env = TestEnvironment::default(); 145 test_env.add_config( 146 r#" 147 test-val = [1, 2, 3] 148 [test-table] 149 x = true 150 y.foo = "abc" 151 y.bar = 123 152 "#, 153 ); 154 155 let output = test_env.run_jj_in(".", ["config", "list"]); 156 insta::assert_snapshot!( 157 output.normalize_stdout_with(|s| find_stdout_lines(r"(test-val|test-table\b[^=]*)", &s)), @r#" 158 test-val = [1, 2, 3] 159 test-table.x = true 160 test-table.y.foo = "abc" 161 test-table.y.bar = 123 162 [EOF] 163 "#); 164} 165 166#[test] 167fn test_config_list_multiline_string() { 168 let test_env = TestEnvironment::default(); 169 test_env.add_config( 170 r#" 171 multiline = ''' 172foo 173bar 174''' 175 "#, 176 ); 177 178 let output = test_env.run_jj_in(".", ["config", "list", "multiline"]); 179 insta::assert_snapshot!(output, @r" 180 multiline = ''' 181 foo 182 bar 183 ''' 184 [EOF] 185 "); 186 187 let output = test_env.run_jj_in( 188 ".", 189 [ 190 "config", 191 "list", 192 "multiline", 193 "--include-overridden", 194 "--config=multiline='single'", 195 ], 196 ); 197 insta::assert_snapshot!(output, @r" 198 # multiline = ''' 199 # foo 200 # bar 201 # ''' 202 multiline = 'single' 203 [EOF] 204 "); 205} 206 207#[test] 208fn test_config_list_layer() { 209 let mut test_env = TestEnvironment::default(); 210 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 211 // Test with fresh new config file 212 let user_config_path = test_env.config_path().join("config.toml"); 213 test_env.set_config_path(&user_config_path); 214 let work_dir = test_env.work_dir("repo"); 215 216 // User 217 work_dir 218 .run_jj(["config", "set", "--user", "test-key", "test-val"]) 219 .success(); 220 221 work_dir 222 .run_jj([ 223 "config", 224 "set", 225 "--user", 226 "test-layered-key", 227 "test-original-val", 228 ]) 229 .success(); 230 231 let output = work_dir.run_jj(["config", "list", "--user"]); 232 insta::assert_snapshot!(output, @r#" 233 test-key = "test-val" 234 test-layered-key = "test-original-val" 235 [EOF] 236 "#); 237 238 // Repo 239 work_dir 240 .run_jj([ 241 "config", 242 "set", 243 "--repo", 244 "test-layered-key", 245 "test-layered-val", 246 ]) 247 .success(); 248 249 let output = work_dir.run_jj(["config", "list", "--user"]); 250 insta::assert_snapshot!(output, @r#" 251 test-key = "test-val" 252 [EOF] 253 "#); 254 255 let output = work_dir.run_jj(["config", "list", "--repo"]); 256 insta::assert_snapshot!(output, @r#" 257 test-layered-key = "test-layered-val" 258 [EOF] 259 "#); 260} 261 262#[test] 263fn test_config_list_origin() { 264 let mut test_env = TestEnvironment::default(); 265 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 266 // Test with fresh new config file 267 let user_config_path = test_env.config_path().join("config.toml"); 268 test_env.set_config_path(&user_config_path); 269 let work_dir = test_env.work_dir("repo"); 270 271 // User 272 work_dir 273 .run_jj(["config", "set", "--user", "test-key", "test-val"]) 274 .success(); 275 276 work_dir 277 .run_jj([ 278 "config", 279 "set", 280 "--user", 281 "test-layered-key", 282 "test-original-val", 283 ]) 284 .success(); 285 286 // Repo 287 work_dir 288 .run_jj([ 289 "config", 290 "set", 291 "--repo", 292 "test-layered-key", 293 "test-layered-val", 294 ]) 295 .success(); 296 297 let output = work_dir.run_jj([ 298 "config", 299 "list", 300 "-Tbuiltin_config_list_detailed", 301 "--config", 302 "test-cli-key=test-cli-val", 303 ]); 304 insta::assert_snapshot!(output, @r#" 305 test-key = "test-val" # user $TEST_ENV/config/config.toml 306 test-layered-key = "test-layered-val" # repo $TEST_ENV/repo/.jj/repo/config.toml 307 user.name = "Test User" # env 308 user.email = "test.user@example.com" # env 309 debug.commit-timestamp = "2001-02-03T04:05:11+07:00" # env 310 debug.randomness-seed = 5 # env 311 debug.operation-timestamp = "2001-02-03T04:05:11+07:00" # env 312 operation.hostname = "host.example.com" # env 313 operation.username = "test-username" # env 314 test-cli-key = "test-cli-val" # cli 315 [EOF] 316 "#); 317 318 let output = work_dir.run_jj([ 319 "config", 320 "list", 321 "-Tbuiltin_config_list_detailed", 322 "--color=debug", 323 "--include-defaults", 324 "--include-overridden", 325 "--config=test-key=test-cli-val", 326 "test-key", 327 ]); 328 insta::assert_snapshot!(output, @r#" 329 <<config_list overridden name::# test-key>><<config_list overridden:: = >><<config_list overridden value::"test-val">><<config_list overridden:: # >><<config_list overridden source::user>><<config_list overridden:: >><<config_list overridden path::$TEST_ENV/config/config.toml>><<config_list overridden::>> 330 <<config_list name::test-key>><<config_list:: = >><<config_list value::"test-cli-val">><<config_list:: # >><<config_list source::cli>><<config_list::>> 331 [EOF] 332 "#); 333} 334 335#[test] 336fn test_config_layer_override_default() { 337 let test_env = TestEnvironment::default(); 338 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 339 let work_dir = test_env.work_dir("repo"); 340 let config_key = "merge-tools.vimdiff.program"; 341 342 // Default 343 let output = work_dir.run_jj(["config", "list", config_key, "--include-defaults"]); 344 insta::assert_snapshot!(output, @r#" 345 merge-tools.vimdiff.program = "vim" 346 [EOF] 347 "#); 348 349 // User 350 test_env.add_config(format!( 351 "{config_key} = {value}\n", 352 value = to_toml_value("user") 353 )); 354 let output = work_dir.run_jj(["config", "list", config_key]); 355 insta::assert_snapshot!(output, @r#" 356 merge-tools.vimdiff.program = "user" 357 [EOF] 358 "#); 359 360 // Repo 361 work_dir.write_file( 362 ".jj/repo/config.toml", 363 format!("{config_key} = {value}\n", value = to_toml_value("repo")), 364 ); 365 let output = work_dir.run_jj(["config", "list", config_key]); 366 insta::assert_snapshot!(output, @r#" 367 merge-tools.vimdiff.program = "repo" 368 [EOF] 369 "#); 370 371 // Command argument 372 let output = work_dir.run_jj([ 373 "config", 374 "list", 375 config_key, 376 "--config", 377 &format!("{config_key}={value}", value = to_toml_value("command-arg")), 378 ]); 379 insta::assert_snapshot!(output, @r#" 380 merge-tools.vimdiff.program = "command-arg" 381 [EOF] 382 "#); 383 384 // Allow printing overridden values 385 let output = work_dir.run_jj([ 386 "config", 387 "list", 388 config_key, 389 "--include-overridden", 390 "--config", 391 &format!("{config_key}={value}", value = to_toml_value("command-arg")), 392 ]); 393 insta::assert_snapshot!(output, @r##" 394 # merge-tools.vimdiff.program = "user" 395 # merge-tools.vimdiff.program = "repo" 396 merge-tools.vimdiff.program = "command-arg" 397 [EOF] 398 "##); 399 400 let output = work_dir.run_jj([ 401 "config", 402 "list", 403 "--color=always", 404 config_key, 405 "--include-overridden", 406 ]); 407 insta::assert_snapshot!(output, @r#" 408 # merge-tools.vimdiff.program = "user" 409 merge-tools.vimdiff.program = "repo" 410 [EOF] 411 "#); 412} 413 414#[test] 415fn test_config_layer_override_env() { 416 let mut test_env = TestEnvironment::default(); 417 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 418 let config_key = "ui.editor"; 419 420 // Environment base 421 test_env.add_env_var("EDITOR", "env-base"); 422 let work_dir = test_env.work_dir("repo"); 423 let output = work_dir.run_jj(["config", "list", config_key]); 424 insta::assert_snapshot!(output, @r#" 425 ui.editor = "env-base" 426 [EOF] 427 "#); 428 429 // User 430 test_env.add_config(format!( 431 "{config_key} = {value}\n", 432 value = to_toml_value("user") 433 )); 434 let output = work_dir.run_jj(["config", "list", config_key]); 435 insta::assert_snapshot!(output, @r#" 436 ui.editor = "user" 437 [EOF] 438 "#); 439 440 // Repo 441 work_dir.write_file( 442 ".jj/repo/config.toml", 443 format!("{config_key} = {value}\n", value = to_toml_value("repo")), 444 ); 445 let output = work_dir.run_jj(["config", "list", config_key]); 446 insta::assert_snapshot!(output, @r#" 447 ui.editor = "repo" 448 [EOF] 449 "#); 450 451 // Environment override 452 test_env.add_env_var("JJ_EDITOR", "env-override"); 453 let work_dir = test_env.work_dir("repo"); 454 let output = work_dir.run_jj(["config", "list", config_key]); 455 insta::assert_snapshot!(output, @r#" 456 ui.editor = "env-override" 457 [EOF] 458 "#); 459 460 // Command argument 461 let output = work_dir.run_jj([ 462 "config", 463 "list", 464 config_key, 465 "--config", 466 &format!("{config_key}={value}", value = to_toml_value("command-arg")), 467 ]); 468 insta::assert_snapshot!(output, @r#" 469 ui.editor = "command-arg" 470 [EOF] 471 "#); 472 473 // Allow printing overridden values 474 let output = work_dir.run_jj([ 475 "config", 476 "list", 477 config_key, 478 "--include-overridden", 479 "--config", 480 &format!("{config_key}={value}", value = to_toml_value("command-arg")), 481 ]); 482 insta::assert_snapshot!(output, @r##" 483 # ui.editor = "env-base" 484 # ui.editor = "user" 485 # ui.editor = "repo" 486 # ui.editor = "env-override" 487 ui.editor = "command-arg" 488 [EOF] 489 "##); 490} 491 492#[test] 493fn test_config_layer_workspace() { 494 let test_env = TestEnvironment::default(); 495 test_env.run_jj_in(".", ["git", "init", "main"]).success(); 496 let main_dir = test_env.work_dir("main"); 497 let secondary_dir = test_env.work_dir("secondary"); 498 let config_key = "ui.editor"; 499 500 main_dir.write_file("file", "contents"); 501 main_dir.run_jj(["new"]).success(); 502 main_dir 503 .run_jj(["workspace", "add", "--name", "second", "../secondary"]) 504 .success(); 505 506 // Repo 507 main_dir.write_file( 508 ".jj/repo/config.toml", 509 format!( 510 "{config_key} = {value}\n", 511 value = to_toml_value("main-repo") 512 ), 513 ); 514 let output = main_dir.run_jj(["config", "list", config_key]); 515 insta::assert_snapshot!(output, @r#" 516 ui.editor = "main-repo" 517 [EOF] 518 "#); 519 let output = secondary_dir.run_jj(["config", "list", config_key]); 520 insta::assert_snapshot!(output, @r#" 521 ui.editor = "main-repo" 522 [EOF] 523 "#); 524} 525 526#[test] 527fn test_config_set_bad_opts() { 528 let test_env = TestEnvironment::default(); 529 let output = test_env.run_jj_in(".", ["config", "set"]); 530 insta::assert_snapshot!(output, @r" 531 ------- stderr ------- 532 error: the following required arguments were not provided: 533 <--user|--repo> 534 <NAME> 535 <VALUE> 536 537 Usage: jj config set <--user|--repo> <NAME> <VALUE> 538 539 For more information, try '--help'. 540 [EOF] 541 [exit status: 2] 542 "); 543 544 let output = test_env.run_jj_in(".", ["config", "set", "--user", "", "x"]); 545 insta::assert_snapshot!(output, @r" 546 ------- stderr ------- 547 error: invalid value '' for '<NAME>': TOML parse error at line 1, column 1 548 | 549 1 | 550 | ^ 551 invalid key 552 553 554 For more information, try '--help'. 555 [EOF] 556 [exit status: 2] 557 "); 558 559 let output = test_env.run_jj_in(".", ["config", "set", "--user", "x", "['typo'}"]); 560 insta::assert_snapshot!(output, @r" 561 ------- stderr ------- 562 error: invalid value '['typo'}' for '<VALUE>': TOML parse error at line 1, column 8 563 | 564 1 | ['typo'} 565 | ^ 566 invalid array 567 expected `]` 568 569 570 For more information, try '--help'. 571 [EOF] 572 [exit status: 2] 573 "); 574} 575 576#[test] 577fn test_config_set_for_user() { 578 let mut test_env = TestEnvironment::default(); 579 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 580 // Test with fresh new config file 581 let user_config_path = test_env.config_path().join("config.toml"); 582 test_env.set_config_path(&user_config_path); 583 let work_dir = test_env.work_dir("repo"); 584 585 work_dir 586 .run_jj(["config", "set", "--user", "test-key", "test-val"]) 587 .success(); 588 work_dir 589 .run_jj(["config", "set", "--user", "test-table.foo", "true"]) 590 .success(); 591 work_dir 592 .run_jj(["config", "set", "--user", "test-table.'bar()'", "0"]) 593 .success(); 594 595 // Ensure test-key successfully written to user config. 596 let user_config_toml = std::fs::read_to_string(&user_config_path) 597 .unwrap_or_else(|_| panic!("Failed to read file {}", user_config_path.display())); 598 insta::assert_snapshot!(user_config_toml, @r#" 599 test-key = "test-val" 600 601 [test-table] 602 foo = true 603 'bar()' = 0 604 "#); 605} 606 607#[test] 608fn test_config_set_for_user_directory() { 609 let test_env = TestEnvironment::default(); 610 611 test_env 612 .run_jj_in(".", ["config", "set", "--user", "test-key", "test-val"]) 613 .success(); 614 insta::assert_snapshot!( 615 std::fs::read_to_string(test_env.last_config_file_path()).unwrap(), 616 @r#" 617 test-key = "test-val" 618 619 [template-aliases] 620 'format_time_range(time_range)' = 'time_range.start() ++ " - " ++ time_range.end()' 621 "#); 622 623 // Add one more config file to the directory 624 test_env.add_config(""); 625 let output = test_env.run_jj_in( 626 ".", 627 ["config", "set", "--user", "test-key", "test-other-val"], 628 ); 629 insta::assert_snapshot!(output, @r" 630 ------- stderr ------- 631 1: $TEST_ENV/config/config0001.toml 632 2: $TEST_ENV/config/config0002.toml 633 Choose a config file (default 1): 1 634 [EOF] 635 "); 636 637 insta::assert_snapshot!( 638 std::fs::read_to_string(test_env.first_config_file_path()).unwrap(), 639 @r#" 640 test-key = "test-other-val" 641 642 [template-aliases] 643 'format_time_range(time_range)' = 'time_range.start() ++ " - " ++ time_range.end()' 644 "#); 645 646 insta::assert_snapshot!( 647 std::fs::read_to_string(test_env.last_config_file_path()).unwrap(), 648 @""); 649} 650 651#[test] 652fn test_config_set_for_repo() { 653 let test_env = TestEnvironment::default(); 654 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 655 let work_dir = test_env.work_dir("repo"); 656 work_dir 657 .run_jj(["config", "set", "--repo", "test-key", "test-val"]) 658 .success(); 659 work_dir 660 .run_jj(["config", "set", "--repo", "test-table.foo", "true"]) 661 .success(); 662 // Ensure test-key successfully written to user config. 663 let repo_config_toml = work_dir.read_file(".jj/repo/config.toml"); 664 insta::assert_snapshot!(repo_config_toml, @r#" 665 test-key = "test-val" 666 667 [test-table] 668 foo = true 669 "#); 670} 671 672#[test] 673fn test_config_set_toml_types() { 674 let mut test_env = TestEnvironment::default(); 675 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 676 // Test with fresh new config file 677 let user_config_path = test_env.config_path().join("config.toml"); 678 test_env.set_config_path(&user_config_path); 679 let work_dir = test_env.work_dir("repo"); 680 681 let set_value = |key, value| { 682 work_dir 683 .run_jj(["config", "set", "--user", key, value]) 684 .success(); 685 }; 686 set_value("test-table.integer", "42"); 687 set_value("test-table.float", "3.14"); 688 set_value("test-table.array", r#"["one", "two"]"#); 689 set_value("test-table.boolean", "true"); 690 set_value("test-table.string", r#""foo""#); 691 set_value("test-table.invalid", r"a + b"); 692 insta::assert_snapshot!(std::fs::read_to_string(&user_config_path).unwrap(), @r#" 693 [test-table] 694 integer = 42 695 float = 3.14 696 array = ["one", "two"] 697 boolean = true 698 string = "foo" 699 invalid = "a + b" 700 "#); 701} 702 703#[test] 704fn test_config_set_type_mismatch() { 705 let test_env = TestEnvironment::default(); 706 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 707 let work_dir = test_env.work_dir("repo"); 708 709 work_dir 710 .run_jj(["config", "set", "--user", "test-table.foo", "test-val"]) 711 .success(); 712 let output = work_dir.run_jj(["config", "set", "--user", "test-table", "not-a-table"]); 713 insta::assert_snapshot!(output, @r" 714 ------- stderr ------- 715 Error: Failed to set test-table 716 Caused by: Would overwrite entire table test-table 717 [EOF] 718 [exit status: 1] 719 "); 720 721 // But it's fine to overwrite arrays and inline tables 722 work_dir 723 .run_jj(["config", "set", "--user", "test-table.array", "[1,2,3]"]) 724 .success(); 725 work_dir 726 .run_jj(["config", "set", "--user", "test-table.array", "[4,5,6]"]) 727 .success(); 728 work_dir 729 .run_jj(["config", "set", "--user", "test-table.inline", "{ x = 42}"]) 730 .success(); 731 work_dir 732 .run_jj(["config", "set", "--user", "test-table.inline", "42"]) 733 .success(); 734} 735 736#[test] 737fn test_config_set_nontable_parent() { 738 let test_env = TestEnvironment::default(); 739 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 740 let work_dir = test_env.work_dir("repo"); 741 742 work_dir 743 .run_jj(["config", "set", "--user", "test-nontable", "test-val"]) 744 .success(); 745 let output = work_dir.run_jj(["config", "set", "--user", "test-nontable.foo", "test-val"]); 746 insta::assert_snapshot!(output, @r" 747 ------- stderr ------- 748 Error: Failed to set test-nontable.foo 749 Caused by: Would overwrite non-table value with parent table test-nontable 750 [EOF] 751 [exit status: 1] 752 "); 753} 754 755#[test] 756fn test_config_unset_non_existent_key() { 757 let test_env = TestEnvironment::default(); 758 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 759 let work_dir = test_env.work_dir("repo"); 760 761 let output = work_dir.run_jj(["config", "unset", "--user", "nonexistent"]); 762 insta::assert_snapshot!(output, @r#" 763 ------- stderr ------- 764 Error: "nonexistent" doesn't exist 765 [EOF] 766 [exit status: 1] 767 "#); 768} 769 770#[test] 771fn test_config_unset_inline_table_key() { 772 let mut test_env = TestEnvironment::default(); 773 // Test with fresh new config file 774 let user_config_path = test_env.config_path().join("config.toml"); 775 test_env.set_config_path(&user_config_path); 776 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 777 let work_dir = test_env.work_dir("repo"); 778 779 work_dir 780 .run_jj(["config", "set", "--user", "inline-table", "{ foo = true }"]) 781 .success(); 782 work_dir 783 .run_jj(["config", "unset", "--user", "inline-table.foo"]) 784 .success(); 785 let user_config_toml = std::fs::read_to_string(&user_config_path).unwrap(); 786 insta::assert_snapshot!(user_config_toml, @"inline-table = {}"); 787} 788 789#[test] 790fn test_config_unset_table_like() { 791 let mut test_env = TestEnvironment::default(); 792 // Test with fresh new config file 793 let user_config_path = test_env.config_path().join("config.toml"); 794 test_env.set_config_path(&user_config_path); 795 796 std::fs::write( 797 &user_config_path, 798 indoc! {b" 799 inline-table = { foo = true } 800 [non-inline-table] 801 foo = true 802 "}, 803 ) 804 .unwrap(); 805 806 // Inline table is syntactically a "value", so it can be deleted. 807 test_env 808 .run_jj_in(".", ["config", "unset", "--user", "inline-table"]) 809 .success(); 810 // Non-inline table cannot be deleted. 811 let output = test_env.run_jj_in(".", ["config", "unset", "--user", "non-inline-table"]); 812 insta::assert_snapshot!(output, @r" 813 ------- stderr ------- 814 Error: Failed to unset non-inline-table 815 Caused by: Would delete entire table non-inline-table 816 [EOF] 817 [exit status: 1] 818 "); 819 820 let user_config_toml = std::fs::read_to_string(&user_config_path).unwrap(); 821 insta::assert_snapshot!(user_config_toml, @r" 822 [non-inline-table] 823 foo = true 824 "); 825} 826 827#[test] 828fn test_config_unset_for_user() { 829 let mut test_env = TestEnvironment::default(); 830 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 831 // Test with fresh new config file 832 let user_config_path = test_env.config_path().join("config.toml"); 833 test_env.set_config_path(&user_config_path); 834 let work_dir = test_env.work_dir("repo"); 835 836 work_dir 837 .run_jj(["config", "set", "--user", "foo", "true"]) 838 .success(); 839 work_dir 840 .run_jj(["config", "unset", "--user", "foo"]) 841 .success(); 842 843 work_dir 844 .run_jj(["config", "set", "--user", "table.foo", "true"]) 845 .success(); 846 work_dir 847 .run_jj(["config", "unset", "--user", "table.foo"]) 848 .success(); 849 850 work_dir 851 .run_jj(["config", "set", "--user", "table.inline", "{ foo = true }"]) 852 .success(); 853 work_dir 854 .run_jj(["config", "unset", "--user", "table.inline"]) 855 .success(); 856 857 let user_config_toml = std::fs::read_to_string(&user_config_path).unwrap(); 858 insta::assert_snapshot!(user_config_toml, @"[table]"); 859} 860 861#[test] 862fn test_config_unset_for_repo() { 863 let test_env = TestEnvironment::default(); 864 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 865 let work_dir = test_env.work_dir("repo"); 866 867 work_dir 868 .run_jj(["config", "set", "--repo", "test-key", "test-val"]) 869 .success(); 870 work_dir 871 .run_jj(["config", "unset", "--repo", "test-key"]) 872 .success(); 873 874 let repo_config_toml = work_dir.read_file(".jj/repo/config.toml"); 875 insta::assert_snapshot!(repo_config_toml, @""); 876} 877 878#[test] 879fn test_config_edit_missing_opt() { 880 let test_env = TestEnvironment::default(); 881 let output = test_env.run_jj_in(".", ["config", "edit"]); 882 insta::assert_snapshot!(output, @r" 883 ------- stderr ------- 884 error: the following required arguments were not provided: 885 <--user|--repo> 886 887 Usage: jj config edit <--user|--repo> 888 889 For more information, try '--help'. 890 [EOF] 891 [exit status: 2] 892 "); 893} 894 895#[test] 896fn test_config_edit_user() { 897 let mut test_env = TestEnvironment::default(); 898 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 899 // Remove one of the config file to disambiguate 900 std::fs::remove_file(test_env.last_config_file_path()).unwrap(); 901 let edit_script = test_env.set_up_fake_editor(); 902 let work_dir = test_env.work_dir("repo"); 903 904 std::fs::write(edit_script, "dump-path path").unwrap(); 905 work_dir.run_jj(["config", "edit", "--user"]).success(); 906 907 let edited_path = 908 PathBuf::from(std::fs::read_to_string(test_env.env_root().join("path")).unwrap()); 909 assert_eq!( 910 edited_path, 911 dunce::simplified(&test_env.last_config_file_path()) 912 ); 913} 914 915#[test] 916fn test_config_edit_user_new_file() { 917 let mut test_env = TestEnvironment::default(); 918 let user_config_path = test_env.config_path().join("config").join("file.toml"); 919 test_env.set_up_fake_editor(); // set $EDIT_SCRIPT, but added configuration is ignored 920 test_env.add_env_var("EDITOR", fake_editor_path()); 921 test_env.set_config_path(&user_config_path); 922 assert!(!user_config_path.exists()); 923 924 test_env 925 .run_jj_in(".", ["config", "edit", "--user"]) 926 .success(); 927 assert!( 928 user_config_path.exists(), 929 "new file and directory should be created" 930 ); 931} 932 933#[test] 934fn test_config_edit_repo() { 935 let mut test_env = TestEnvironment::default(); 936 let edit_script = test_env.set_up_fake_editor(); 937 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 938 let work_dir = test_env.work_dir("repo"); 939 let repo_config_path = work_dir 940 .root() 941 .join(PathBuf::from_iter([".jj", "repo", "config.toml"])); 942 assert!(!repo_config_path.exists()); 943 944 std::fs::write(edit_script, "dump-path path").unwrap(); 945 work_dir.run_jj(["config", "edit", "--repo"]).success(); 946 947 let edited_path = 948 PathBuf::from(std::fs::read_to_string(test_env.env_root().join("path")).unwrap()); 949 assert_eq!(edited_path, dunce::simplified(&repo_config_path)); 950 assert!(repo_config_path.exists(), "new file should be created"); 951} 952 953#[test] 954fn test_config_path() { 955 let mut test_env = TestEnvironment::default(); 956 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 957 let work_dir = test_env.work_dir("repo"); 958 959 let user_config_path = test_env.env_root().join("config.toml"); 960 let repo_config_path = work_dir 961 .root() 962 .join(PathBuf::from_iter([".jj", "repo", "config.toml"])); 963 test_env.set_config_path(&user_config_path); 964 let work_dir = test_env.work_dir("repo"); 965 966 insta::assert_snapshot!(work_dir.run_jj(["config", "path", "--user"]), @r" 967 $TEST_ENV/config.toml 968 [EOF] 969 "); 970 assert!( 971 !user_config_path.exists(), 972 "jj config path shouldn't create new file" 973 ); 974 975 insta::assert_snapshot!(work_dir.run_jj(["config", "path", "--repo"]), @r" 976 $TEST_ENV/repo/.jj/repo/config.toml 977 [EOF] 978 "); 979 assert!( 980 !repo_config_path.exists(), 981 "jj config path shouldn't create new file" 982 ); 983 984 insta::assert_snapshot!(test_env.run_jj_in(".", ["config", "path", "--repo"]), @r" 985 ------- stderr ------- 986 Error: No repo config path found 987 [EOF] 988 [exit status: 1] 989 "); 990} 991 992#[test] 993fn test_config_path_multiple() { 994 let mut test_env = TestEnvironment::default(); 995 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 996 let config_path = test_env.config_path().join("config.toml"); 997 let work_config_path = test_env.config_path().join("conf.d"); 998 let user_config_path = join_paths([config_path, work_config_path]).unwrap(); 999 test_env.set_config_path(&user_config_path); 1000 let work_dir = test_env.work_dir("repo"); 1001 insta::assert_snapshot!(work_dir.run_jj(["config", "path", "--user"]), @r" 1002 $TEST_ENV/config/config.toml 1003 $TEST_ENV/config/conf.d 1004 [EOF] 1005 "); 1006} 1007 1008#[test] 1009fn test_config_only_loads_toml_files() { 1010 let mut test_env = TestEnvironment::default(); 1011 test_env.set_up_fake_editor(); 1012 std::fs::File::create(test_env.config_path().join("is-not.loaded")).unwrap(); 1013 insta::assert_snapshot!(test_env.run_jj_in(".", ["config", "edit", "--user"]), @r" 1014 ------- stderr ------- 1015 1: $TEST_ENV/config/config0001.toml 1016 2: $TEST_ENV/config/config0002.toml 1017 Choose a config file (default 1): 1 1018 [EOF] 1019 "); 1020} 1021 1022#[test] 1023fn test_config_edit_repo_outside_repo() { 1024 let test_env = TestEnvironment::default(); 1025 let output = test_env.run_jj_in(".", ["config", "edit", "--repo"]); 1026 insta::assert_snapshot!(output, @r" 1027 ------- stderr ------- 1028 Error: No repo config path found to edit 1029 [EOF] 1030 [exit status: 1] 1031 "); 1032} 1033 1034#[test] 1035fn test_config_get() { 1036 let test_env = TestEnvironment::default(); 1037 test_env.add_config( 1038 r#" 1039 [table] 1040 string = "some value 1" 1041 int = 123 1042 list = ["list", "value"] 1043 overridden = "foo" 1044 "#, 1045 ); 1046 test_env.add_config( 1047 r#" 1048 [table] 1049 overridden = "bar" 1050 "#, 1051 ); 1052 1053 let output = test_env.run_jj_in(".", ["config", "get", "nonexistent"]); 1054 insta::assert_snapshot!(output, @r" 1055 ------- stderr ------- 1056 Config error: Value not found for nonexistent 1057 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 1058 [EOF] 1059 [exit status: 1] 1060 "); 1061 1062 let output = test_env.run_jj_in(".", ["config", "get", "table.string"]); 1063 insta::assert_snapshot!(output, @r" 1064 some value 1 1065 [EOF] 1066 "); 1067 1068 let output = test_env.run_jj_in(".", ["config", "get", "table.int"]); 1069 insta::assert_snapshot!(output, @r" 1070 123 1071 [EOF] 1072 "); 1073 1074 let output = test_env.run_jj_in(".", ["config", "get", "table.list"]); 1075 insta::assert_snapshot!(output, @r" 1076 ------- stderr ------- 1077 Config error: Invalid type or value for table.list 1078 Caused by: Expected a value convertible to a string, but is an array 1079 Hint: Check the config file: $TEST_ENV/config/config0002.toml 1080 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 1081 [EOF] 1082 [exit status: 1] 1083 "); 1084 1085 let output = test_env.run_jj_in(".", ["config", "get", "table"]); 1086 insta::assert_snapshot!(output, @r" 1087 ------- stderr ------- 1088 Config error: Invalid type or value for table 1089 Caused by: Expected a value convertible to a string, but is a table 1090 Hint: Check the config file: $TEST_ENV/config/config0003.toml 1091 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 1092 [EOF] 1093 [exit status: 1] 1094 "); 1095 1096 let output = test_env.run_jj_in(".", ["config", "get", "table.overridden"]); 1097 insta::assert_snapshot!(output, @r" 1098 bar 1099 [EOF] 1100 "); 1101} 1102 1103#[test] 1104fn test_config_path_syntax() { 1105 let test_env = TestEnvironment::default(); 1106 test_env.add_config( 1107 r#" 1108 a.'b()' = 0 1109 'b c'.d = 1 1110 'b c'.e.'f[]' = 2 1111 - = 3 1112 _ = 4 1113 '.' = 5 1114 "#, 1115 ); 1116 1117 let output = test_env.run_jj_in(".", ["config", "list", "a.'b()'"]); 1118 insta::assert_snapshot!(output, @r" 1119 a.'b()' = 0 1120 [EOF] 1121 "); 1122 let output = test_env.run_jj_in(".", ["config", "list", "'b c'"]); 1123 insta::assert_snapshot!(output, @r#" 1124 'b c'.d = 1 1125 'b c'.e."f[]" = 2 1126 [EOF] 1127 "#); 1128 let output = test_env.run_jj_in(".", ["config", "list", "'b c'.d"]); 1129 insta::assert_snapshot!(output, @r" 1130 'b c'.d = 1 1131 [EOF] 1132 "); 1133 let output = test_env.run_jj_in(".", ["config", "list", "'b c'.e.'f[]'"]); 1134 insta::assert_snapshot!(output, @r" 1135 'b c'.e.'f[]' = 2 1136 [EOF] 1137 "); 1138 let output = test_env.run_jj_in(".", ["config", "get", "'b c'.e.'f[]'"]); 1139 insta::assert_snapshot!(output, @r" 1140 2 1141 [EOF] 1142 "); 1143 1144 // Not a table 1145 let output = test_env.run_jj_in(".", ["config", "list", "a.'b()'.x"]); 1146 insta::assert_snapshot!(output, @r" 1147 ------- stderr ------- 1148 Warning: No matching config key for a.'b()'.x 1149 [EOF] 1150 "); 1151 let output = test_env.run_jj_in(".", ["config", "get", "a.'b()'.x"]); 1152 insta::assert_snapshot!(output, @r" 1153 ------- stderr ------- 1154 Config error: Value not found for a.'b()'.x 1155 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 1156 [EOF] 1157 [exit status: 1] 1158 "); 1159 1160 // "-" and "_" are valid TOML keys 1161 let output = test_env.run_jj_in(".", ["config", "list", "-"]); 1162 insta::assert_snapshot!(output, @r" 1163 - = 3 1164 [EOF] 1165 "); 1166 let output = test_env.run_jj_in(".", ["config", "list", "_"]); 1167 insta::assert_snapshot!(output, @r" 1168 _ = 4 1169 [EOF] 1170 "); 1171 1172 // "." requires quoting 1173 let output = test_env.run_jj_in(".", ["config", "list", "'.'"]); 1174 insta::assert_snapshot!(output, @r" 1175 '.' = 5 1176 [EOF] 1177 "); 1178 let output = test_env.run_jj_in(".", ["config", "get", "'.'"]); 1179 insta::assert_snapshot!(output, @r" 1180 5 1181 [EOF] 1182 "); 1183 let output = test_env.run_jj_in(".", ["config", "get", "."]); 1184 insta::assert_snapshot!(output, @r" 1185 ------- stderr ------- 1186 error: invalid value '.' for '<NAME>': TOML parse error at line 1, column 1 1187 | 1188 1 | . 1189 | ^ 1190 invalid key 1191 1192 1193 For more information, try '--help'. 1194 [EOF] 1195 [exit status: 2] 1196 "); 1197 1198 // Invalid TOML keys 1199 let output = test_env.run_jj_in(".", ["config", "list", "b c"]); 1200 insta::assert_snapshot!(output, @r" 1201 ------- stderr ------- 1202 error: invalid value 'b c' for '[NAME]': TOML parse error at line 1, column 3 1203 | 1204 1 | b c 1205 | ^ 1206 1207 1208 1209 For more information, try '--help'. 1210 [EOF] 1211 [exit status: 2] 1212 "); 1213 let output = test_env.run_jj_in(".", ["config", "list", ""]); 1214 insta::assert_snapshot!(output, @r" 1215 ------- stderr ------- 1216 error: invalid value '' for '[NAME]': TOML parse error at line 1, column 1 1217 | 1218 1 | 1219 | ^ 1220 invalid key 1221 1222 1223 For more information, try '--help'. 1224 [EOF] 1225 [exit status: 2] 1226 "); 1227} 1228 1229#[test] 1230#[cfg_attr(windows, ignore = "dirs::home_dir() can't be overridden by $HOME")] // TODO 1231fn test_config_conditional() { 1232 let mut test_env = TestEnvironment::default(); 1233 let home_dir = test_env.work_dir(test_env.home_dir()); 1234 home_dir.run_jj(["git", "init", "repo1"]).success(); 1235 home_dir.run_jj(["git", "init", "repo2"]).success(); 1236 // Test with fresh new config file 1237 let user_config_path = test_env.env_root().join("config.toml"); 1238 test_env.set_config_path(&user_config_path); 1239 std::fs::write( 1240 &user_config_path, 1241 indoc! {" 1242 foo = 'global' 1243 baz = 'global' 1244 qux = 'global' 1245 1246 [[--scope]] 1247 --when.repositories = ['~/repo1'] 1248 foo = 'repo1' 1249 [[--scope]] 1250 --when.repositories = ['~/repo2'] 1251 foo = 'repo2' 1252 1253 [[--scope]] 1254 --when.commands = ['config'] 1255 baz = 'config' 1256 [[--scope]] 1257 --when.commands = ['config get'] 1258 qux = 'get' 1259 [[--scope]] 1260 --when.commands = ['config list'] 1261 qux = 'list' 1262 "}, 1263 ) 1264 .unwrap(); 1265 let home_dir = test_env.work_dir(test_env.home_dir()); 1266 let work_dir1 = home_dir.dir("repo1"); 1267 let work_dir2 = home_dir.dir("repo2"); 1268 1269 // get and list should refer to the resolved config 1270 let output = test_env.run_jj_in(".", ["config", "get", "foo"]); 1271 insta::assert_snapshot!(output, @r" 1272 global 1273 [EOF] 1274 "); 1275 let output = work_dir1.run_jj(["config", "get", "foo"]); 1276 insta::assert_snapshot!(output, @r" 1277 repo1 1278 [EOF] 1279 "); 1280 // baz should be the same for `jj config get` and `jj config list` 1281 // qux should be different 1282 let output = work_dir1.run_jj(["config", "get", "baz"]); 1283 insta::assert_snapshot!(output, @r" 1284 config 1285 [EOF] 1286 "); 1287 let output = work_dir1.run_jj(["config", "get", "qux"]); 1288 insta::assert_snapshot!(output, @r" 1289 get 1290 [EOF] 1291 "); 1292 let output = test_env.run_jj_in(".", ["config", "list", "--user"]); 1293 insta::assert_snapshot!(output, @r" 1294 foo = 'global' 1295 baz = 'config' 1296 qux = 'list' 1297 [EOF] 1298 "); 1299 let output = work_dir1.run_jj(["config", "list", "--user"]); 1300 insta::assert_snapshot!(output, @r" 1301 foo = 'repo1' 1302 baz = 'config' 1303 qux = 'list' 1304 [EOF] 1305 "); 1306 let output = work_dir2.run_jj(["config", "list", "--user"]); 1307 insta::assert_snapshot!(output, @r" 1308 foo = 'repo2' 1309 baz = 'config' 1310 qux = 'list' 1311 [EOF] 1312 "); 1313 1314 // relative workspace path 1315 let output = work_dir2.run_jj(["config", "list", "--user", "-R../repo1"]); 1316 insta::assert_snapshot!(output, @r" 1317 foo = 'repo1' 1318 baz = 'config' 1319 qux = 'list' 1320 [EOF] 1321 "); 1322 1323 // set and unset should refer to the source config 1324 // (there's no option to update scoped table right now.) 1325 let output = test_env.run_jj_in(".", ["config", "set", "--user", "bar", "new value"]); 1326 insta::assert_snapshot!(output, @""); 1327 insta::assert_snapshot!(std::fs::read_to_string(&user_config_path).unwrap(), @r#" 1328 foo = 'global' 1329 baz = 'global' 1330 qux = 'global' 1331 bar = "new value" 1332 1333 [[--scope]] 1334 --when.repositories = ['~/repo1'] 1335 foo = 'repo1' 1336 [[--scope]] 1337 --when.repositories = ['~/repo2'] 1338 foo = 'repo2' 1339 1340 [[--scope]] 1341 --when.commands = ['config'] 1342 baz = 'config' 1343 [[--scope]] 1344 --when.commands = ['config get'] 1345 qux = 'get' 1346 [[--scope]] 1347 --when.commands = ['config list'] 1348 qux = 'list' 1349 "#); 1350 let output = work_dir1.run_jj(["config", "unset", "--user", "foo"]); 1351 insta::assert_snapshot!(output, @""); 1352 insta::assert_snapshot!(std::fs::read_to_string(&user_config_path).unwrap(), @r#" 1353 baz = 'global' 1354 qux = 'global' 1355 bar = "new value" 1356 1357 [[--scope]] 1358 --when.repositories = ['~/repo1'] 1359 foo = 'repo1' 1360 [[--scope]] 1361 --when.repositories = ['~/repo2'] 1362 foo = 'repo2' 1363 1364 [[--scope]] 1365 --when.commands = ['config'] 1366 baz = 'config' 1367 [[--scope]] 1368 --when.commands = ['config get'] 1369 qux = 'get' 1370 [[--scope]] 1371 --when.commands = ['config list'] 1372 qux = 'list' 1373 "#); 1374} 1375 1376// Minimal test for Windows where the home directory can't be switched. 1377// (Can be removed if test_config_conditional() is enabled on Windows.) 1378#[test] 1379fn test_config_conditional_without_home_dir() { 1380 let mut test_env = TestEnvironment::default(); 1381 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1382 // Test with fresh new config file 1383 let user_config_path = test_env.env_root().join("config.toml"); 1384 test_env.set_config_path(&user_config_path); 1385 let work_dir = test_env.work_dir("repo"); 1386 std::fs::write( 1387 &user_config_path, 1388 format!( 1389 indoc! {" 1390 foo = 'global' 1391 [[--scope]] 1392 --when.repositories = [{repo_path}] 1393 foo = 'repo' 1394 "}, 1395 // "\\?\" paths shouldn't be required on Windows 1396 repo_path = to_toml_value(dunce::simplified(work_dir.root()).to_str().unwrap()) 1397 ), 1398 ) 1399 .unwrap(); 1400 1401 let output = test_env.run_jj_in(".", ["config", "get", "foo"]); 1402 insta::assert_snapshot!(output, @r" 1403 global 1404 [EOF] 1405 "); 1406 let output = work_dir.run_jj(["config", "get", "foo"]); 1407 insta::assert_snapshot!(output, @r" 1408 repo 1409 [EOF] 1410 "); 1411} 1412 1413#[test] 1414fn test_config_show_paths() { 1415 let test_env = TestEnvironment::default(); 1416 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1417 let work_dir = test_env.work_dir("repo"); 1418 1419 work_dir 1420 .run_jj(["config", "set", "--user", "ui.paginate", ":builtin"]) 1421 .success(); 1422 let output = test_env.run_jj_in(".", ["st"]); 1423 insta::assert_snapshot!(output, @r" 1424 ------- stderr ------- 1425 Config error: Invalid type or value for ui.paginate 1426 Caused by: unknown variant `:builtin`, expected `never` or `auto` 1427 1428 Hint: Check the config file: $TEST_ENV/config/config0001.toml 1429 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 1430 [EOF] 1431 [exit status: 1] 1432 "); 1433} 1434 1435#[test] 1436fn test_config_author_change_warning() { 1437 let test_env = TestEnvironment::default(); 1438 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1439 let work_dir = test_env.work_dir("repo"); 1440 let output = work_dir.run_jj(["config", "set", "--repo", "user.email", "'Foo'"]); 1441 insta::assert_snapshot!(output, @r#" 1442 ------- stderr ------- 1443 Warning: This setting will only impact future commits. 1444 The author of the working copy will stay "Test User <test.user@example.com>". 1445 To change the working copy author, use "jj describe --reset-author --no-edit" 1446 [EOF] 1447 "#); 1448 1449 // test_env.run_jj*() resets state for every invocation 1450 // for this test, the state (user.email) is needed 1451 work_dir 1452 .run_jj_with(|cmd| { 1453 cmd.args(["describe", "--reset-author", "--no-edit"]) 1454 .env_remove("JJ_EMAIL") 1455 }) 1456 .success(); 1457 1458 let output = work_dir.run_jj(["log"]); 1459 insta::assert_snapshot!(output, @r" 1460 @ qpvuntsm Foo 2001-02-03 08:05:09 ed1febd8 1461 │ (empty) (no description set) 1462 ◆ zzzzzzzz root() 00000000 1463 [EOF] 1464 "); 1465} 1466 1467#[test] 1468fn test_config_author_change_warning_root_env() { 1469 let test_env = TestEnvironment::default(); 1470 let output = test_env.run_jj_in(".", ["config", "set", "--user", "user.email", "'Foo'"]); 1471 insta::assert_snapshot!(output, @""); 1472} 1473 1474fn find_stdout_lines(keyname_pattern: &str, stdout: &str) -> String { 1475 let key_line_re = Regex::new(&format!(r"(?m)^{keyname_pattern} = .*\n")).unwrap(); 1476 key_line_re.find_iter(stdout).map(|m| m.as_str()).collect() 1477}