just playing with tangled
at gvimdiff 50 kB view raw
1// Copyright 2024 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 15#[cfg(unix)] 16use std::os::unix::fs::PermissionsExt as _; 17use std::path::PathBuf; 18 19use indoc::formatdoc; 20use indoc::indoc; 21use jj_lib::file_util::try_symlink; 22 23use crate::common::to_toml_value; 24use crate::common::TestEnvironment; 25 26fn set_up_fake_formatter(test_env: &TestEnvironment, args: &[&str]) { 27 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 28 assert!(formatter_path.is_file()); 29 test_env.add_config(formatdoc! {" 30 [fix.tools.fake-formatter] 31 command = {command} 32 patterns = ['all()'] 33 ", 34 command = toml_edit::Value::from_iter( 35 [formatter_path.to_str().unwrap()] 36 .iter() 37 .chain(args) 38 .copied() 39 ) 40 }); 41} 42 43#[test] 44fn test_config_no_tools() { 45 let test_env = TestEnvironment::default(); 46 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 47 let work_dir = test_env.work_dir("repo"); 48 49 work_dir.write_file("file", "content\n"); 50 let output = work_dir.run_jj(["fix"]); 51 insta::assert_snapshot!(output, @r" 52 ------- stderr ------- 53 Config error: No `fix.tools` are configured 54 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 55 [EOF] 56 [exit status: 1] 57 "); 58 59 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 60 insta::assert_snapshot!(output, @r" 61 content 62 [EOF] 63 "); 64} 65 66#[test] 67fn test_config_multiple_tools() { 68 let test_env = TestEnvironment::default(); 69 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 70 let work_dir = test_env.work_dir("repo"); 71 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 72 assert!(formatter_path.is_file()); 73 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 74 test_env.add_config(format!( 75 r###" 76 [fix.tools.tool-1] 77 command = [{formatter}, "--uppercase"] 78 patterns = ["foo"] 79 80 [fix.tools.tool-2] 81 command = [{formatter}, "--lowercase"] 82 patterns = ["bar"] 83 "###, 84 )); 85 86 work_dir.write_file("foo", "Foo\n"); 87 work_dir.write_file("bar", "Bar\n"); 88 work_dir.write_file("baz", "Baz\n"); 89 90 work_dir.run_jj(["fix"]).success(); 91 92 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]); 93 insta::assert_snapshot!(output, @r" 94 FOO 95 [EOF] 96 "); 97 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]); 98 insta::assert_snapshot!(output, @r" 99 bar 100 [EOF] 101 "); 102 let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]); 103 insta::assert_snapshot!(output, @r" 104 Baz 105 [EOF] 106 "); 107} 108 109#[test] 110fn test_config_multiple_tools_with_same_name() { 111 let mut test_env = TestEnvironment::default(); 112 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 113 let work_dir = test_env.work_dir("repo"); 114 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 115 assert!(formatter_path.is_file()); 116 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 117 118 // Multiple definitions with the same `name` are not allowed, because it is 119 // likely to be a mistake, and mistakes are risky when they rewrite files. 120 test_env.add_config(format!( 121 r###" 122 [fix.tools.my-tool] 123 command = [{formatter}, "--uppercase"] 124 patterns = ["foo"] 125 126 [fix.tools.my-tool] 127 command = [{formatter}, "--lowercase"] 128 patterns = ["bar"] 129 "###, 130 )); 131 132 work_dir.write_file("foo", "Foo\n"); 133 work_dir.write_file("bar", "Bar\n"); 134 135 let output = work_dir.run_jj(["fix"]); 136 insta::assert_snapshot!(output, @r" 137 ------- stderr ------- 138 Config error: Configuration cannot be parsed as TOML document 139 Caused by: TOML parse error at line 6, column 9 140 | 141 6 | [fix.tools.my-tool] 142 | ^ 143 invalid table header 144 duplicate key `my-tool` in table `fix.tools` 145 146 Hint: Check the config file: $TEST_ENV/config/config0002.toml 147 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 148 [EOF] 149 [exit status: 1] 150 "); 151 152 test_env.set_config_path("/dev/null"); 153 let work_dir = test_env.work_dir("repo"); 154 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]); 155 insta::assert_snapshot!(output, @r" 156 Foo 157 [EOF] 158 "); 159 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]); 160 insta::assert_snapshot!(output, @r" 161 Bar 162 [EOF] 163 "); 164} 165 166#[test] 167fn test_config_disabled_tools() { 168 let test_env = TestEnvironment::default(); 169 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 170 let work_dir = test_env.work_dir("repo"); 171 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 172 assert!(formatter_path.is_file()); 173 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 174 test_env.add_config(format!( 175 r###" 176 [fix.tools.tool-1] 177 # default is enabled 178 command = [{formatter}, "--uppercase"] 179 patterns = ["foo"] 180 181 [fix.tools.tool-2] 182 enabled = true 183 command = [{formatter}, "--lowercase"] 184 patterns = ["bar"] 185 186 [fix.tools.tool-3] 187 enabled = false 188 command = [{formatter}, "--lowercase"] 189 patterns = ["baz"] 190 "### 191 )); 192 193 work_dir.write_file("foo", "Foo\n"); 194 work_dir.write_file("bar", "Bar\n"); 195 work_dir.write_file("baz", "Baz\n"); 196 197 work_dir.run_jj(["fix"]).success(); 198 199 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]); 200 insta::assert_snapshot!(output, @r" 201 FOO 202 [EOF] 203 "); 204 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]); 205 insta::assert_snapshot!(output, @r" 206 bar 207 [EOF] 208 "); 209 let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]); 210 insta::assert_snapshot!(output, @r" 211 Baz 212 [EOF] 213 "); 214} 215 216#[test] 217fn test_config_disabled_tools_warning_when_all_tools_are_disabled() { 218 let test_env = TestEnvironment::default(); 219 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 220 let work_dir = test_env.work_dir("repo"); 221 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 222 assert!(formatter_path.is_file()); 223 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 224 test_env.add_config(format!( 225 r###" 226 [fix.tools.tool-2] 227 enabled = false 228 command = [{formatter}, "--lowercase"] 229 patterns = ["bar"] 230 "### 231 )); 232 233 work_dir.write_file("bar", "Bar\n"); 234 235 let output = work_dir.run_jj(["fix"]); 236 insta::assert_snapshot!(output, @r" 237 ------- stderr ------- 238 Config error: At least one entry of `fix.tools` must be enabled. 239 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 240 [EOF] 241 [exit status: 1] 242 "); 243} 244 245#[test] 246fn test_config_tables_overlapping_patterns() { 247 let test_env = TestEnvironment::default(); 248 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 249 let work_dir = test_env.work_dir("repo"); 250 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 251 assert!(formatter_path.is_file()); 252 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 253 254 test_env.add_config(format!( 255 r###" 256 [fix.tools.tool-1] 257 command = [{formatter}, "--append", "tool-1"] 258 patterns = ["foo", "bar"] 259 260 [fix.tools.tool-2] 261 command = [{formatter}, "--append", "tool-2"] 262 patterns = ["bar", "baz"] 263 "###, 264 )); 265 266 work_dir.write_file("foo", "foo\n"); 267 work_dir.write_file("bar", "bar\n"); 268 work_dir.write_file("baz", "baz\n"); 269 270 work_dir.run_jj(["fix"]).success(); 271 272 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]); 273 insta::assert_snapshot!(output, @r" 274 foo 275 tool-1[EOF] 276 "); 277 let output = work_dir.run_jj(["file", "show", "bar", "-r", "@"]); 278 insta::assert_snapshot!(output, @r" 279 bar 280 tool-1 281 tool-2[EOF] 282 "); 283 let output = work_dir.run_jj(["file", "show", "baz", "-r", "@"]); 284 insta::assert_snapshot!(output, @r" 285 baz 286 tool-2[EOF] 287 "); 288} 289 290#[test] 291fn test_config_tables_all_commands_missing() { 292 let test_env = TestEnvironment::default(); 293 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 294 let work_dir = test_env.work_dir("repo"); 295 test_env.add_config( 296 r###" 297 [fix.tools.my-tool-missing-command-1] 298 patterns = ["foo"] 299 300 [fix.tools.my-tool-missing-command-2] 301 patterns = ['glob:"ba*"'] 302 "###, 303 ); 304 305 work_dir.write_file("foo", "foo\n"); 306 307 let output = work_dir.run_jj(["fix"]); 308 insta::assert_snapshot!(output.normalize_backslash(), @r" 309 ------- stderr ------- 310 Config error: Invalid type or value for fix.tools.my-tool-missing-command-1 311 Caused by: missing field `command` 312 313 Hint: Check the config file: $TEST_ENV/config/config0002.toml 314 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 315 [EOF] 316 [exit status: 1] 317 "); 318 319 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]); 320 insta::assert_snapshot!(output, @r" 321 foo 322 [EOF] 323 "); 324} 325 326#[test] 327fn test_config_tables_some_commands_missing() { 328 let test_env = TestEnvironment::default(); 329 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 330 let work_dir = test_env.work_dir("repo"); 331 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 332 assert!(formatter_path.is_file()); 333 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 334 test_env.add_config(format!( 335 r###" 336 [fix.tools.tool-1] 337 command = [{formatter}, "--uppercase"] 338 patterns = ["foo"] 339 340 [fix.tools.my-tool-missing-command] 341 patterns = ['bar'] 342 "###, 343 )); 344 345 work_dir.write_file("foo", "foo\n"); 346 347 let output = work_dir.run_jj(["fix"]); 348 insta::assert_snapshot!(output.normalize_backslash(), @r" 349 ------- stderr ------- 350 Config error: Invalid type or value for fix.tools.my-tool-missing-command 351 Caused by: missing field `command` 352 353 Hint: Check the config file: $TEST_ENV/config/config0002.toml 354 For help, see https://jj-vcs.github.io/jj/latest/config/ or use `jj help -k config`. 355 [EOF] 356 [exit status: 1] 357 "); 358 359 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]); 360 insta::assert_snapshot!(output, @r" 361 foo 362 [EOF] 363 "); 364} 365 366#[test] 367fn test_config_tables_empty_patterns_list() { 368 let test_env = TestEnvironment::default(); 369 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 370 let work_dir = test_env.work_dir("repo"); 371 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 372 assert!(formatter_path.is_file()); 373 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 374 test_env.add_config(format!( 375 r###" 376 [fix.tools.my-tool-empty-patterns] 377 command = [{formatter}, "--uppercase"] 378 patterns = [] 379 "###, 380 )); 381 382 work_dir.write_file("foo", "foo\n"); 383 384 let output = work_dir.run_jj(["fix"]); 385 insta::assert_snapshot!(output, @r" 386 ------- stderr ------- 387 Fixed 0 commits of 1 checked. 388 Nothing changed. 389 [EOF] 390 "); 391 392 let output = work_dir.run_jj(["file", "show", "foo", "-r", "@"]); 393 insta::assert_snapshot!(output, @r" 394 foo 395 [EOF] 396 "); 397} 398 399#[test] 400fn test_config_filesets() { 401 let test_env = TestEnvironment::default(); 402 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 403 let work_dir = test_env.work_dir("repo"); 404 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 405 assert!(formatter_path.is_file()); 406 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 407 test_env.add_config(format!( 408 r###" 409 [fix.tools.my-tool-match-one] 410 command = [{formatter}, "--uppercase"] 411 patterns = ['glob:"a*"'] 412 413 [fix.tools.my-tool-match-two] 414 command = [{formatter}, "--reverse"] 415 patterns = ['glob:"b*"'] 416 417 [fix.tools.my-tool-match-none] 418 command = [{formatter}, "--append", "SHOULD NOT APPEAR"] 419 patterns = ['glob:"this-doesnt-match-anything-*"'] 420 "###, 421 )); 422 423 work_dir.write_file("a1", "a1\n"); 424 work_dir.write_file("b1", "b1\n"); 425 work_dir.write_file("b2", "b2\n"); 426 427 work_dir.run_jj(["fix"]).success(); 428 429 let output = work_dir.run_jj(["file", "show", "a1", "-r", "@"]); 430 insta::assert_snapshot!(output, @r" 431 A1 432 [EOF] 433 "); 434 let output = work_dir.run_jj(["file", "show", "b1", "-r", "@"]); 435 insta::assert_snapshot!(output, @r" 436 1b 437 [EOF] 438 "); 439 let output = work_dir.run_jj(["file", "show", "b2", "-r", "@"]); 440 insta::assert_snapshot!(output, @r" 441 2b 442 [EOF] 443 "); 444} 445 446#[test] 447fn test_relative_paths() { 448 let test_env = TestEnvironment::default(); 449 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 450 let work_dir = test_env.work_dir("repo"); 451 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 452 assert!(formatter_path.is_file()); 453 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 454 test_env.add_config(format!( 455 r###" 456 [fix.tools.tool] 457 command = [{formatter}, "--stdout", "Fixed!"] 458 patterns = ['glob:"foo*"'] 459 "###, 460 )); 461 462 let sub_dir = work_dir.create_dir("dir"); 463 work_dir.write_file("foo1", "unfixed\n"); 464 work_dir.write_file("foo2", "unfixed\n"); 465 work_dir.write_file("dir/foo3", "unfixed\n"); 466 467 // Positional arguments are cwd-relative, but the configured patterns are 468 // repo-relative, so this command fixes the empty intersection of those 469 // filesets. 470 sub_dir.run_jj(["fix", "foo3"]).success(); 471 let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]); 472 insta::assert_snapshot!(output, @r" 473 unfixed 474 [EOF] 475 "); 476 let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]); 477 insta::assert_snapshot!(output, @r" 478 unfixed 479 [EOF] 480 "); 481 let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]); 482 insta::assert_snapshot!(output, @r" 483 unfixed 484 [EOF] 485 "); 486 487 // Positional arguments can specify a subset of the configured fileset. 488 sub_dir.run_jj(["fix", "../foo1"]).success(); 489 let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]); 490 insta::assert_snapshot!(output, @"Fixed![EOF]"); 491 let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]); 492 insta::assert_snapshot!(output, @r" 493 unfixed 494 [EOF] 495 "); 496 let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]); 497 insta::assert_snapshot!(output, @r" 498 unfixed 499 [EOF] 500 "); 501 502 // The current directory does not change the interpretation of the config, so 503 // foo2 is fixed but not dir/foo3. 504 sub_dir.run_jj(["fix"]).success(); 505 let output = work_dir.run_jj(["file", "show", "foo1", "-r", "@"]); 506 insta::assert_snapshot!(output, @"Fixed![EOF]"); 507 let output = work_dir.run_jj(["file", "show", "foo2", "-r", "@"]); 508 insta::assert_snapshot!(output, @"Fixed![EOF]"); 509 let output = work_dir.run_jj(["file", "show", "dir/foo3", "-r", "@"]); 510 insta::assert_snapshot!(output, @r" 511 unfixed 512 [EOF] 513 "); 514} 515 516#[test] 517fn test_fix_empty_commit() { 518 let test_env = TestEnvironment::default(); 519 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 520 let work_dir = test_env.work_dir("repo"); 521 set_up_fake_formatter(&test_env, &["--uppercase"]); 522 let output = work_dir.run_jj(["fix", "-s", "@"]); 523 insta::assert_snapshot!(output, @r" 524 ------- stderr ------- 525 Fixed 0 commits of 1 checked. 526 Nothing changed. 527 [EOF] 528 "); 529} 530 531#[test] 532fn test_fix_leaf_commit() { 533 let test_env = TestEnvironment::default(); 534 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 535 let work_dir = test_env.work_dir("repo"); 536 set_up_fake_formatter(&test_env, &["--uppercase"]); 537 work_dir.write_file("file", "unaffected"); 538 work_dir.run_jj(["new"]).success(); 539 work_dir.write_file("file", "affected"); 540 541 let output = work_dir.run_jj(["fix", "-s", "@"]); 542 insta::assert_snapshot!(output, @r" 543 ------- stderr ------- 544 Fixed 1 commits of 1 checked. 545 Working copy (@) now at: rlvkpnrz 85ce8924 (no description set) 546 Parent commit (@-) : qpvuntsm b2ca2bc5 (no description set) 547 Added 0 files, modified 1 files, removed 0 files 548 [EOF] 549 "); 550 let output = work_dir.run_jj(["file", "show", "file", "-r", "@-"]); 551 insta::assert_snapshot!(output, @"unaffected[EOF]"); 552 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 553 insta::assert_snapshot!(output, @r" 554 AFFECTED 555 [EOF] 556 "); 557} 558 559#[test] 560fn test_fix_parent_commit() { 561 let test_env = TestEnvironment::default(); 562 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 563 let work_dir = test_env.work_dir("repo"); 564 set_up_fake_formatter(&test_env, &["--uppercase"]); 565 // Using one file name for all commits adds coverage of some possible bugs. 566 work_dir.write_file("file", "parent"); 567 work_dir 568 .run_jj(["bookmark", "create", "-r@", "parent"]) 569 .success(); 570 work_dir.run_jj(["new"]).success(); 571 work_dir.write_file("file", "child1"); 572 work_dir 573 .run_jj(["bookmark", "create", "-r@", "child1"]) 574 .success(); 575 work_dir.run_jj(["new", "-r", "parent"]).success(); 576 work_dir.write_file("file", "child2"); 577 work_dir 578 .run_jj(["bookmark", "create", "-r@", "child2"]) 579 .success(); 580 581 let output = work_dir.run_jj(["fix", "-s", "parent"]); 582 insta::assert_snapshot!(output, @r" 583 ------- stderr ------- 584 Fixed 3 commits of 3 checked. 585 Working copy (@) now at: mzvwutvl d30c8ae2 child2 | (no description set) 586 Parent commit (@-) : qpvuntsm 70a4dae2 parent | (no description set) 587 Added 0 files, modified 1 files, removed 0 files 588 [EOF] 589 "); 590 let output = work_dir.run_jj(["file", "show", "file", "-r", "parent"]); 591 insta::assert_snapshot!(output, @r" 592 PARENT 593 [EOF] 594 "); 595 let output = work_dir.run_jj(["file", "show", "file", "-r", "child1"]); 596 insta::assert_snapshot!(output, @r" 597 CHILD1 598 [EOF] 599 "); 600 let output = work_dir.run_jj(["file", "show", "file", "-r", "child2"]); 601 insta::assert_snapshot!(output, @r" 602 CHILD2 603 [EOF] 604 "); 605} 606 607#[test] 608fn test_fix_sibling_commit() { 609 let test_env = TestEnvironment::default(); 610 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 611 let work_dir = test_env.work_dir("repo"); 612 set_up_fake_formatter(&test_env, &["--uppercase"]); 613 work_dir.write_file("file", "parent"); 614 work_dir 615 .run_jj(["bookmark", "create", "-r@", "parent"]) 616 .success(); 617 work_dir.run_jj(["new"]).success(); 618 work_dir.write_file("file", "child1"); 619 work_dir 620 .run_jj(["bookmark", "create", "-r@", "child1"]) 621 .success(); 622 work_dir.run_jj(["new", "-r", "parent"]).success(); 623 work_dir.write_file("file", "child2"); 624 work_dir 625 .run_jj(["bookmark", "create", "-r@", "child2"]) 626 .success(); 627 628 let output = work_dir.run_jj(["fix", "-s", "child1"]); 629 insta::assert_snapshot!(output, @r" 630 ------- stderr ------- 631 Fixed 1 commits of 1 checked. 632 [EOF] 633 "); 634 let output = work_dir.run_jj(["file", "show", "file", "-r", "parent"]); 635 insta::assert_snapshot!(output, @"parent[EOF]"); 636 let output = work_dir.run_jj(["file", "show", "file", "-r", "child1"]); 637 insta::assert_snapshot!(output, @r" 638 CHILD1 639 [EOF] 640 "); 641 let output = work_dir.run_jj(["file", "show", "file", "-r", "child2"]); 642 insta::assert_snapshot!(output, @"child2[EOF]"); 643} 644 645#[test] 646fn test_default_revset() { 647 let test_env = TestEnvironment::default(); 648 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 649 let work_dir = test_env.work_dir("repo"); 650 set_up_fake_formatter(&test_env, &["--uppercase"]); 651 work_dir.write_file("file", "trunk1"); 652 work_dir 653 .run_jj(["bookmark", "create", "-r@", "trunk1"]) 654 .success(); 655 work_dir.run_jj(["new"]).success(); 656 work_dir.write_file("file", "trunk2"); 657 work_dir 658 .run_jj(["bookmark", "create", "-r@", "trunk2"]) 659 .success(); 660 work_dir.run_jj(["new", "trunk1"]).success(); 661 work_dir.write_file("file", "foo"); 662 work_dir 663 .run_jj(["bookmark", "create", "-r@", "foo"]) 664 .success(); 665 work_dir.run_jj(["new", "trunk1"]).success(); 666 work_dir.write_file("file", "bar1"); 667 work_dir 668 .run_jj(["bookmark", "create", "-r@", "bar1"]) 669 .success(); 670 work_dir.run_jj(["new"]).success(); 671 work_dir.write_file("file", "bar2"); 672 work_dir 673 .run_jj(["bookmark", "create", "-r@", "bar2"]) 674 .success(); 675 work_dir.run_jj(["new"]).success(); 676 work_dir.write_file("file", "bar3"); 677 work_dir 678 .run_jj(["bookmark", "create", "-r@", "bar3"]) 679 .success(); 680 work_dir.run_jj(["edit", "bar2"]).success(); 681 682 // With no args and no revset configuration, we fix `reachable(@, mutable())`, 683 // which includes bar{1,2,3} and excludes trunk{1,2} (which is immutable) and 684 // foo (which is mutable but not reachable). 685 test_env.add_config(r#"revset-aliases."immutable_heads()" = "trunk2""#); 686 let output = work_dir.run_jj(["fix"]); 687 insta::assert_snapshot!(output, @r" 688 ------- stderr ------- 689 Fixed 3 commits of 3 checked. 690 Working copy (@) now at: yostqsxw dabc47b2 bar2 | (no description set) 691 Parent commit (@-) : yqosqzyt 984b5924 bar1 | (no description set) 692 Added 0 files, modified 1 files, removed 0 files 693 [EOF] 694 "); 695 let output = work_dir.run_jj(["file", "show", "file", "-r", "trunk1"]); 696 insta::assert_snapshot!(output, @"trunk1[EOF]"); 697 let output = work_dir.run_jj(["file", "show", "file", "-r", "trunk2"]); 698 insta::assert_snapshot!(output, @"trunk2[EOF]"); 699 let output = work_dir.run_jj(["file", "show", "file", "-r", "foo"]); 700 insta::assert_snapshot!(output, @"foo[EOF]"); 701 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar1"]); 702 insta::assert_snapshot!(output, @r" 703 BAR1 704 [EOF] 705 "); 706 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar2"]); 707 insta::assert_snapshot!(output, @r" 708 BAR2 709 [EOF] 710 "); 711 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar3"]); 712 insta::assert_snapshot!(output, @r" 713 BAR3 714 [EOF] 715 "); 716} 717 718#[test] 719fn test_custom_default_revset() { 720 let test_env = TestEnvironment::default(); 721 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 722 let work_dir = test_env.work_dir("repo"); 723 set_up_fake_formatter(&test_env, &["--uppercase"]); 724 725 work_dir.write_file("file", "foo"); 726 work_dir 727 .run_jj(["bookmark", "create", "-r@", "foo"]) 728 .success(); 729 work_dir.run_jj(["new"]).success(); 730 work_dir.write_file("file", "bar"); 731 work_dir 732 .run_jj(["bookmark", "create", "-r@", "bar"]) 733 .success(); 734 735 // Check out a different commit so that the schema default `reachable(@, 736 // mutable())` would behave differently from our customized default. 737 work_dir.run_jj(["new", "-r", "foo"]).success(); 738 test_env.add_config(r#"revsets.fix = "bar""#); 739 740 let output = work_dir.run_jj(["fix"]); 741 insta::assert_snapshot!(output, @r" 742 ------- stderr ------- 743 Fixed 1 commits of 1 checked. 744 [EOF] 745 "); 746 let output = work_dir.run_jj(["file", "show", "file", "-r", "foo"]); 747 insta::assert_snapshot!(output, @"foo[EOF]"); 748 let output = work_dir.run_jj(["file", "show", "file", "-r", "bar"]); 749 insta::assert_snapshot!(output, @r" 750 BAR 751 [EOF] 752 "); 753} 754 755#[test] 756fn test_fix_immutable_commit() { 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 set_up_fake_formatter(&test_env, &["--uppercase"]); 761 work_dir.write_file("file", "immutable"); 762 work_dir 763 .run_jj(["bookmark", "create", "-r@", "immutable"]) 764 .success(); 765 work_dir.run_jj(["new"]).success(); 766 work_dir.write_file("file", "mutable"); 767 work_dir 768 .run_jj(["bookmark", "create", "-r@", "mutable"]) 769 .success(); 770 test_env.add_config(r#"revset-aliases."immutable_heads()" = "immutable""#); 771 772 let output = work_dir.run_jj(["fix", "-s", "immutable"]); 773 insta::assert_snapshot!(output, @r#" 774 ------- stderr ------- 775 Error: Commit e4b41a3ce243 is immutable 776 Hint: Could not modify commit: qpvuntsm e4b41a3c immutable | (no description set) 777 Hint: Immutable commits are used to protect shared history. 778 Hint: For more information, see: 779 - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits 780 - `jj help -k config`, "Set of immutable commits" 781 Hint: This operation would rewrite 1 immutable commits. 782 [EOF] 783 [exit status: 1] 784 "#); 785 let output = work_dir.run_jj(["file", "show", "file", "-r", "immutable"]); 786 insta::assert_snapshot!(output, @"immutable[EOF]"); 787 let output = work_dir.run_jj(["file", "show", "file", "-r", "mutable"]); 788 insta::assert_snapshot!(output, @"mutable[EOF]"); 789} 790 791#[test] 792fn test_fix_empty_file() { 793 let test_env = TestEnvironment::default(); 794 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 795 let work_dir = test_env.work_dir("repo"); 796 set_up_fake_formatter(&test_env, &["--uppercase"]); 797 work_dir.write_file("file", ""); 798 799 let output = work_dir.run_jj(["fix", "-s", "@"]); 800 insta::assert_snapshot!(output, @r" 801 ------- stderr ------- 802 Fixed 0 commits of 1 checked. 803 Nothing changed. 804 [EOF] 805 "); 806 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 807 insta::assert_snapshot!(output, @""); 808} 809 810#[test] 811fn test_fix_some_paths() { 812 let test_env = TestEnvironment::default(); 813 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 814 let work_dir = test_env.work_dir("repo"); 815 set_up_fake_formatter(&test_env, &["--uppercase"]); 816 work_dir.write_file("file1", "foo"); 817 work_dir.write_file("file2", "bar"); 818 819 let output = work_dir.run_jj(["fix", "-s", "@", "file1"]); 820 insta::assert_snapshot!(output, @r" 821 ------- stderr ------- 822 Fixed 1 commits of 1 checked. 823 Working copy (@) now at: qpvuntsm 54a90d2b (no description set) 824 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 825 Added 0 files, modified 1 files, removed 0 files 826 [EOF] 827 "); 828 let output = work_dir.run_jj(["file", "show", "file1"]); 829 insta::assert_snapshot!(output, @r" 830 FOO 831 [EOF] 832 "); 833 let output = work_dir.run_jj(["file", "show", "file2"]); 834 insta::assert_snapshot!(output, @"bar[EOF]"); 835} 836 837#[test] 838fn test_fix_cyclic() { 839 let test_env = TestEnvironment::default(); 840 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 841 let work_dir = test_env.work_dir("repo"); 842 set_up_fake_formatter(&test_env, &["--reverse"]); 843 work_dir.write_file("file", "content\n"); 844 845 let output = work_dir.run_jj(["fix"]); 846 insta::assert_snapshot!(output, @r" 847 ------- stderr ------- 848 Fixed 1 commits of 1 checked. 849 Working copy (@) now at: qpvuntsm bf5e6a5a (no description set) 850 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 851 Added 0 files, modified 1 files, removed 0 files 852 [EOF] 853 "); 854 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 855 insta::assert_snapshot!(output, @r" 856 tnetnoc 857 [EOF] 858 "); 859 860 let output = work_dir.run_jj(["fix"]); 861 insta::assert_snapshot!(output, @r" 862 ------- stderr ------- 863 Fixed 1 commits of 1 checked. 864 Working copy (@) now at: qpvuntsm 0e2d20d6 (no description set) 865 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 866 Added 0 files, modified 1 files, removed 0 files 867 [EOF] 868 "); 869 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 870 insta::assert_snapshot!(output, @r" 871 content 872 [EOF] 873 "); 874} 875 876#[test] 877fn test_deduplication() { 878 // Append all fixed content to a log file. Note that fix tools are always run 879 // from the workspace root, so this will always write to $root/$path-fixlog. 880 let test_env = TestEnvironment::default(); 881 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 882 let work_dir = test_env.work_dir("repo"); 883 set_up_fake_formatter(&test_env, &["--uppercase", "--tee", "$path-fixlog"]); 884 885 // There are at least two interesting cases: the content is repeated immediately 886 // in the child commit, or later in another descendant. 887 work_dir.write_file("file", "foo\n"); 888 work_dir 889 .run_jj(["bookmark", "create", "-r@", "a"]) 890 .success(); 891 work_dir.run_jj(["new"]).success(); 892 work_dir.write_file("file", "bar\n"); 893 work_dir 894 .run_jj(["bookmark", "create", "-r@", "b"]) 895 .success(); 896 work_dir.run_jj(["new"]).success(); 897 work_dir.write_file("file", "bar\n"); 898 work_dir 899 .run_jj(["bookmark", "create", "-r@", "c"]) 900 .success(); 901 work_dir.run_jj(["new"]).success(); 902 work_dir.write_file("file", "foo\n"); 903 work_dir 904 .run_jj(["bookmark", "create", "-r@", "d"]) 905 .success(); 906 907 let output = work_dir.run_jj(["fix", "-s", "a"]); 908 insta::assert_snapshot!(output, @r" 909 ------- stderr ------- 910 Fixed 4 commits of 4 checked. 911 Working copy (@) now at: yqosqzyt cf770245 d | (no description set) 912 Parent commit (@-) : mzvwutvl 370615a5 c | (empty) (no description set) 913 Added 0 files, modified 1 files, removed 0 files 914 [EOF] 915 "); 916 let output = work_dir.run_jj(["file", "show", "file", "-r", "a"]); 917 insta::assert_snapshot!(output, @r" 918 FOO 919 [EOF] 920 "); 921 let output = work_dir.run_jj(["file", "show", "file", "-r", "b"]); 922 insta::assert_snapshot!(output, @r" 923 BAR 924 [EOF] 925 "); 926 let output = work_dir.run_jj(["file", "show", "file", "-r", "c"]); 927 insta::assert_snapshot!(output, @r" 928 BAR 929 [EOF] 930 "); 931 let output = work_dir.run_jj(["file", "show", "file", "-r", "d"]); 932 insta::assert_snapshot!(output, @r" 933 FOO 934 [EOF] 935 "); 936 937 // Each new content string only appears once in the log, because all the other 938 // inputs (like file name) were identical, and so the results were reused. We 939 // sort the log because the order of execution inside `jj fix` is undefined. 940 insta::assert_snapshot!(sorted_lines(work_dir.root().join("file-fixlog")), @r" 941 BAR 942 FOO 943 "); 944} 945 946fn sorted_lines(path: PathBuf) -> String { 947 let mut log: Vec<_> = std::fs::read_to_string(path.as_os_str()) 948 .unwrap() 949 .lines() 950 .map(String::from) 951 .collect(); 952 log.sort(); 953 log.join("\n") 954} 955 956#[test] 957fn test_executed_but_nothing_changed() { 958 // Show that the tool ran by causing a side effect with --tee, and test that we 959 // do the right thing when the tool's output is exactly equal to its input. 960 let test_env = TestEnvironment::default(); 961 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 962 let work_dir = test_env.work_dir("repo"); 963 set_up_fake_formatter(&test_env, &["--tee", "$path-copy"]); 964 work_dir.write_file("file", "content\n"); 965 966 let output = work_dir.run_jj(["fix", "-s", "@"]); 967 insta::assert_snapshot!(output, @r" 968 ------- stderr ------- 969 Fixed 0 commits of 1 checked. 970 Nothing changed. 971 [EOF] 972 "); 973 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 974 insta::assert_snapshot!(output, @r" 975 content 976 [EOF] 977 "); 978 let copy_content = work_dir.read_file("file-copy"); 979 insta::assert_snapshot!(copy_content, @"content"); 980 981 // fix tools are always run from the workspace root, regardless of working 982 // directory at time of invocation. 983 let sub_dir = work_dir.create_dir("dir"); 984 let output = sub_dir.run_jj(["fix"]); 985 insta::assert_snapshot!(output, @r" 986 ------- stderr ------- 987 Fixed 0 commits of 1 checked. 988 Nothing changed. 989 [EOF] 990 "); 991 992 let copy_content = work_dir.read_file("file-copy"); 993 insta::assert_snapshot!(copy_content, @r" 994 content 995 content 996 "); 997 assert!(!sub_dir.root().join("file-copy").exists()); 998} 999 1000#[test] 1001fn test_failure() { 1002 let test_env = TestEnvironment::default(); 1003 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1004 let work_dir = test_env.work_dir("repo"); 1005 set_up_fake_formatter(&test_env, &["--fail"]); 1006 work_dir.write_file("file", "content"); 1007 1008 let output = work_dir.run_jj(["fix", "-s", "@"]); 1009 insta::assert_snapshot!(output, @r" 1010 ------- stderr ------- 1011 Fixed 0 commits of 1 checked. 1012 Nothing changed. 1013 [EOF] 1014 "); 1015 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 1016 insta::assert_snapshot!(output, @"content[EOF]"); 1017} 1018 1019#[test] 1020fn test_stderr_success() { 1021 let test_env = TestEnvironment::default(); 1022 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1023 let work_dir = test_env.work_dir("repo"); 1024 set_up_fake_formatter(&test_env, &["--stderr", "error", "--stdout", "new content"]); 1025 work_dir.write_file("file", "old content"); 1026 1027 // TODO: Associate the stderr lines with the relevant tool/file/commit instead 1028 // of passing it through directly. 1029 let output = work_dir.run_jj(["fix", "-s", "@"]); 1030 insta::assert_snapshot!(output, @r" 1031 ------- stderr ------- 1032 errorFixed 1 commits of 1 checked. 1033 Working copy (@) now at: qpvuntsm 487808ba (no description set) 1034 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 1035 Added 0 files, modified 1 files, removed 0 files 1036 [EOF] 1037 "); 1038 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 1039 insta::assert_snapshot!(output, @"new content[EOF]"); 1040} 1041 1042#[test] 1043fn test_stderr_failure() { 1044 let test_env = TestEnvironment::default(); 1045 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1046 let work_dir = test_env.work_dir("repo"); 1047 set_up_fake_formatter( 1048 &test_env, 1049 &["--stderr", "error", "--stdout", "new content", "--fail"], 1050 ); 1051 work_dir.write_file("file", "old content"); 1052 1053 let output = work_dir.run_jj(["fix", "-s", "@"]); 1054 insta::assert_snapshot!(output, @r" 1055 ------- stderr ------- 1056 errorFixed 0 commits of 1 checked. 1057 Nothing changed. 1058 [EOF] 1059 "); 1060 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 1061 insta::assert_snapshot!(output, @"old content[EOF]"); 1062} 1063 1064#[test] 1065fn test_missing_command() { 1066 let test_env = TestEnvironment::default(); 1067 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1068 let work_dir = test_env.work_dir("repo"); 1069 test_env.add_config(indoc! {" 1070 [fix.tools.bad-tool] 1071 command = ['this_executable_shouldnt_exist'] 1072 patterns = ['all()'] 1073 "}); 1074 // TODO: We should display a warning about invalid tool configurations. When we 1075 // support multiple tools, we should also keep going to see if any of the other 1076 // executions succeed. 1077 let output = work_dir.run_jj(["fix", "-s", "@"]); 1078 insta::assert_snapshot!(output, @r" 1079 ------- stderr ------- 1080 Fixed 0 commits of 1 checked. 1081 Nothing changed. 1082 [EOF] 1083 "); 1084} 1085 1086#[test] 1087fn test_fix_file_types() { 1088 let test_env = TestEnvironment::default(); 1089 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1090 let work_dir = test_env.work_dir("repo"); 1091 set_up_fake_formatter(&test_env, &["--uppercase"]); 1092 work_dir.write_file("file", "content"); 1093 work_dir.create_dir("dir"); 1094 try_symlink("file", work_dir.root().join("link")).unwrap(); 1095 1096 let output = work_dir.run_jj(["fix", "-s", "@"]); 1097 insta::assert_snapshot!(output, @r" 1098 ------- stderr ------- 1099 Fixed 1 commits of 1 checked. 1100 Working copy (@) now at: qpvuntsm 6836a9e4 (no description set) 1101 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 1102 Added 0 files, modified 1 files, removed 0 files 1103 [EOF] 1104 "); 1105 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 1106 insta::assert_snapshot!(output, @r" 1107 CONTENT 1108 [EOF] 1109 "); 1110} 1111 1112#[cfg(unix)] 1113#[test] 1114fn test_fix_executable() { 1115 let test_env = TestEnvironment::default(); 1116 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1117 let work_dir = test_env.work_dir("repo"); 1118 set_up_fake_formatter(&test_env, &["--uppercase"]); 1119 let path = work_dir.root().join("file"); 1120 work_dir.write_file("file", "content"); 1121 let mut permissions = std::fs::metadata(&path).unwrap().permissions(); 1122 permissions.set_mode(permissions.mode() | 0o111); 1123 std::fs::set_permissions(&path, permissions).unwrap(); 1124 1125 let output = work_dir.run_jj(["fix", "-s", "@"]); 1126 insta::assert_snapshot!(output, @r" 1127 ------- stderr ------- 1128 Fixed 1 commits of 1 checked. 1129 Working copy (@) now at: qpvuntsm fee78e99 (no description set) 1130 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 1131 Added 0 files, modified 1 files, removed 0 files 1132 [EOF] 1133 "); 1134 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 1135 insta::assert_snapshot!(output, @r" 1136 CONTENT 1137 [EOF] 1138 "); 1139 let executable = std::fs::metadata(&path).unwrap().permissions().mode() & 0o111; 1140 assert_eq!(executable, 0o111); 1141} 1142 1143#[test] 1144fn test_fix_trivial_merge_commit() { 1145 // All the changes are attributable to a parent, so none are fixed (in the same 1146 // way that none would be shown in `jj diff -r @`). 1147 let test_env = TestEnvironment::default(); 1148 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1149 let work_dir = test_env.work_dir("repo"); 1150 set_up_fake_formatter(&test_env, &["--uppercase"]); 1151 work_dir.write_file("file_a", "content a"); 1152 work_dir.write_file("file_c", "content c"); 1153 work_dir 1154 .run_jj(["bookmark", "create", "-r@", "a"]) 1155 .success(); 1156 work_dir.run_jj(["new", "@-"]).success(); 1157 work_dir.write_file("file_b", "content b"); 1158 work_dir.write_file("file_c", "content c"); 1159 work_dir 1160 .run_jj(["bookmark", "create", "-r@", "b"]) 1161 .success(); 1162 work_dir.run_jj(["new", "a", "b"]).success(); 1163 1164 let output = work_dir.run_jj(["fix", "-s", "@"]); 1165 insta::assert_snapshot!(output, @r" 1166 ------- stderr ------- 1167 Fixed 0 commits of 1 checked. 1168 Nothing changed. 1169 [EOF] 1170 "); 1171 let output = work_dir.run_jj(["file", "show", "file_a", "-r", "@"]); 1172 insta::assert_snapshot!(output, @"content a[EOF]"); 1173 let output = work_dir.run_jj(["file", "show", "file_b", "-r", "@"]); 1174 insta::assert_snapshot!(output, @"content b[EOF]"); 1175 let output = work_dir.run_jj(["file", "show", "file_c", "-r", "@"]); 1176 insta::assert_snapshot!(output, @"content c[EOF]"); 1177} 1178 1179#[test] 1180fn test_fix_adding_merge_commit() { 1181 // None of the changes are attributable to a parent, so they are all fixed (in 1182 // the same way that they would be shown in `jj diff -r @`). 1183 let test_env = TestEnvironment::default(); 1184 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1185 let work_dir = test_env.work_dir("repo"); 1186 set_up_fake_formatter(&test_env, &["--uppercase"]); 1187 work_dir.write_file("file_a", "content a"); 1188 work_dir.write_file("file_c", "content c"); 1189 work_dir 1190 .run_jj(["bookmark", "create", "-r@", "a"]) 1191 .success(); 1192 work_dir.run_jj(["new", "@-"]).success(); 1193 work_dir.write_file("file_b", "content b"); 1194 work_dir.write_file("file_c", "content c"); 1195 work_dir 1196 .run_jj(["bookmark", "create", "-r@", "b"]) 1197 .success(); 1198 work_dir.run_jj(["new", "a", "b"]).success(); 1199 work_dir.write_file("file_a", "change a"); 1200 work_dir.write_file("file_b", "change b"); 1201 work_dir.write_file("file_c", "change c"); 1202 work_dir.write_file("file_d", "change d"); 1203 1204 let output = work_dir.run_jj(["fix", "-s", "@"]); 1205 insta::assert_snapshot!(output, @r" 1206 ------- stderr ------- 1207 Fixed 1 commits of 1 checked. 1208 Working copy (@) now at: mzvwutvl f93eb5a9 (no description set) 1209 Parent commit (@-) : qpvuntsm 6e64e7a7 a | (no description set) 1210 Parent commit (@-) : kkmpptxz c536f264 b | (no description set) 1211 Added 0 files, modified 4 files, removed 0 files 1212 [EOF] 1213 "); 1214 let output = work_dir.run_jj(["file", "show", "file_a", "-r", "@"]); 1215 insta::assert_snapshot!(output, @r" 1216 CHANGE A 1217 [EOF] 1218 "); 1219 let output = work_dir.run_jj(["file", "show", "file_b", "-r", "@"]); 1220 insta::assert_snapshot!(output, @r" 1221 CHANGE B 1222 [EOF] 1223 "); 1224 let output = work_dir.run_jj(["file", "show", "file_c", "-r", "@"]); 1225 insta::assert_snapshot!(output, @r" 1226 CHANGE C 1227 [EOF] 1228 "); 1229 let output = work_dir.run_jj(["file", "show", "file_d", "-r", "@"]); 1230 insta::assert_snapshot!(output, @r" 1231 CHANGE D 1232 [EOF] 1233 "); 1234} 1235 1236#[test] 1237fn test_fix_both_sides_of_conflict() { 1238 let test_env = TestEnvironment::default(); 1239 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1240 let work_dir = test_env.work_dir("repo"); 1241 set_up_fake_formatter(&test_env, &["--uppercase"]); 1242 work_dir.write_file("file", "content a\n"); 1243 work_dir 1244 .run_jj(["bookmark", "create", "-r@", "a"]) 1245 .success(); 1246 work_dir.run_jj(["new", "@-"]).success(); 1247 work_dir.write_file("file", "content b\n"); 1248 work_dir 1249 .run_jj(["bookmark", "create", "-r@", "b"]) 1250 .success(); 1251 work_dir.run_jj(["new", "a", "b"]).success(); 1252 1253 // The conflicts are not different from the merged parent, so they would not be 1254 // fixed if we didn't fix the parents also. 1255 let output = work_dir.run_jj(["fix", "-s", "a", "-s", "b"]); 1256 insta::assert_snapshot!(output, @r" 1257 ------- stderr ------- 1258 Fixed 3 commits of 3 checked. 1259 Working copy (@) now at: mzvwutvl a55c6ec2 (conflict) (empty) (no description set) 1260 Parent commit (@-) : qpvuntsm 8e8aad69 a | (no description set) 1261 Parent commit (@-) : kkmpptxz 91f9b284 b | (no description set) 1262 Added 0 files, modified 1 files, removed 0 files 1263 Warning: There are unresolved conflicts at these paths: 1264 file 2-sided conflict 1265 [EOF] 1266 "); 1267 let output = work_dir.run_jj(["file", "show", "file", "-r", "a"]); 1268 insta::assert_snapshot!(output, @r" 1269 CONTENT A 1270 [EOF] 1271 "); 1272 let output = work_dir.run_jj(["file", "show", "file", "-r", "b"]); 1273 insta::assert_snapshot!(output, @r" 1274 CONTENT B 1275 [EOF] 1276 "); 1277 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 1278 insta::assert_snapshot!(output, @r" 1279 <<<<<<< Conflict 1 of 1 1280 %%%%%%% Changes from base to side #1 1281 +CONTENT A 1282 +++++++ Contents of side #2 1283 CONTENT B 1284 >>>>>>> Conflict 1 of 1 ends 1285 [EOF] 1286 "); 1287} 1288 1289#[test] 1290fn test_fix_resolve_conflict() { 1291 // If both sides of the conflict look the same after being fixed, the conflict 1292 // will be resolved. 1293 let test_env = TestEnvironment::default(); 1294 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1295 let work_dir = test_env.work_dir("repo"); 1296 set_up_fake_formatter(&test_env, &["--uppercase"]); 1297 work_dir.write_file("file", "Content\n"); 1298 work_dir 1299 .run_jj(["bookmark", "create", "-r@", "a"]) 1300 .success(); 1301 work_dir.run_jj(["new", "@-"]).success(); 1302 work_dir.write_file("file", "cOnTeNt\n"); 1303 work_dir 1304 .run_jj(["bookmark", "create", "-r@", "b"]) 1305 .success(); 1306 work_dir.run_jj(["new", "a", "b"]).success(); 1307 1308 // The conflicts are not different from the merged parent, so they would not be 1309 // fixed if we didn't fix the parents also. 1310 let output = work_dir.run_jj(["fix", "-s", "a", "-s", "b"]); 1311 insta::assert_snapshot!(output, @r" 1312 ------- stderr ------- 1313 Fixed 3 commits of 3 checked. 1314 Working copy (@) now at: mzvwutvl 50fd048d (empty) (no description set) 1315 Parent commit (@-) : qpvuntsm dd2721f1 a | (no description set) 1316 Parent commit (@-) : kkmpptxz 07c27a8e b | (no description set) 1317 Added 0 files, modified 1 files, removed 0 files 1318 [EOF] 1319 "); 1320 let output = work_dir.run_jj(["file", "show", "file", "-r", "@"]); 1321 insta::assert_snapshot!(output, @r" 1322 CONTENT 1323 [EOF] 1324 "); 1325} 1326 1327#[test] 1328fn test_all_files() { 1329 let test_env = TestEnvironment::default(); 1330 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1331 let work_dir = test_env.work_dir("repo"); 1332 let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter"); 1333 assert!(formatter_path.is_file()); 1334 let formatter = to_toml_value(formatter_path.to_str().unwrap()); 1335 1336 // Consider a few cases: 1337 // File A: in patterns, changed in child 1338 // File B: in patterns, NOT changed in child 1339 // File C: NOT in patterns, NOT changed in child 1340 // File D: NOT in patterns, changed in child 1341 // Some files will be in subdirectories to make sure we're covering that aspect 1342 // of matching. 1343 test_env.add_config(format!( 1344 r###" 1345 [fix.tools.tool] 1346 command = [{formatter}, "--append", "fixed"] 1347 patterns = ["a/a", "b/b"] 1348 "###, 1349 )); 1350 1351 work_dir.create_dir("a"); 1352 work_dir.create_dir("b"); 1353 work_dir.create_dir("c"); 1354 work_dir.write_file("a/a", "parent aaa\n"); 1355 work_dir.write_file("b/b", "parent bbb\n"); 1356 work_dir.write_file("c/c", "parent ccc\n"); 1357 work_dir.write_file("ddd", "parent ddd\n"); 1358 work_dir.run_jj(["commit", "-m", "parent"]).success(); 1359 1360 work_dir.write_file("a/a", "child aaa\n"); 1361 work_dir.write_file("ddd", "child ddd\n"); 1362 work_dir.run_jj(["describe", "-m", "child"]).success(); 1363 1364 // Specifying files means exactly those files will be fixed in each revision, 1365 // although some like file C won't have any tools configured to make changes to 1366 // them. Specified but unfixed files are silently skipped, whether they lack 1367 // configuration, are ignored, don't exist, aren't normal files, etc. 1368 let output = work_dir.run_jj([ 1369 "fix", 1370 "--include-unchanged-files", 1371 "b/b", 1372 "c/c", 1373 "does_not.exist", 1374 ]); 1375 insta::assert_snapshot!(output, @r" 1376 ------- stderr ------- 1377 Fixed 2 commits of 2 checked. 1378 Working copy (@) now at: rlvkpnrz c098d165 child 1379 Parent commit (@-) : qpvuntsm 0bb31627 parent 1380 Added 0 files, modified 1 files, removed 0 files 1381 [EOF] 1382 "); 1383 1384 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@-"]); 1385 insta::assert_snapshot!(output, @r" 1386 parent aaa 1387 [EOF] 1388 "); 1389 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@-"]); 1390 insta::assert_snapshot!(output, @r" 1391 parent bbb 1392 fixed[EOF] 1393 "); 1394 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@-"]); 1395 insta::assert_snapshot!(output, @r" 1396 parent ccc 1397 [EOF] 1398 "); 1399 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@-"]); 1400 insta::assert_snapshot!(output, @r" 1401 parent ddd 1402 [EOF] 1403 "); 1404 1405 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@"]); 1406 insta::assert_snapshot!(output, @r" 1407 child aaa 1408 [EOF] 1409 "); 1410 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@"]); 1411 insta::assert_snapshot!(output, @r" 1412 parent bbb 1413 fixed[EOF] 1414 "); 1415 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@"]); 1416 insta::assert_snapshot!(output, @r" 1417 parent ccc 1418 [EOF] 1419 "); 1420 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@"]); 1421 insta::assert_snapshot!(output, @r" 1422 child ddd 1423 [EOF] 1424 "); 1425 1426 // Not specifying files means all files will be fixed in each revision. 1427 let output = work_dir.run_jj(["fix", "--include-unchanged-files"]); 1428 insta::assert_snapshot!(output, @r" 1429 ------- stderr ------- 1430 Fixed 2 commits of 2 checked. 1431 Working copy (@) now at: rlvkpnrz c5d0aa1d child 1432 Parent commit (@-) : qpvuntsm b4d02ca9 parent 1433 Added 0 files, modified 2 files, removed 0 files 1434 [EOF] 1435 "); 1436 1437 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@-"]); 1438 insta::assert_snapshot!(output, @r" 1439 parent aaa 1440 fixed[EOF] 1441 "); 1442 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@-"]); 1443 insta::assert_snapshot!(output, @r" 1444 parent bbb 1445 fixed 1446 fixed[EOF] 1447 "); 1448 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@-"]); 1449 insta::assert_snapshot!(output, @r" 1450 parent ccc 1451 [EOF] 1452 "); 1453 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@-"]); 1454 insta::assert_snapshot!(output, @r" 1455 parent ddd 1456 [EOF] 1457 "); 1458 1459 let output = work_dir.run_jj(["file", "show", "a/a", "-r", "@"]); 1460 insta::assert_snapshot!(output, @r" 1461 child aaa 1462 fixed[EOF] 1463 "); 1464 let output = work_dir.run_jj(["file", "show", "b/b", "-r", "@"]); 1465 insta::assert_snapshot!(output, @r" 1466 parent bbb 1467 fixed 1468 fixed[EOF] 1469 "); 1470 let output = work_dir.run_jj(["file", "show", "c/c", "-r", "@"]); 1471 insta::assert_snapshot!(output, @r" 1472 parent ccc 1473 [EOF] 1474 "); 1475 let output = work_dir.run_jj(["file", "show", "ddd", "-r", "@"]); 1476 insta::assert_snapshot!(output, @r" 1477 child ddd 1478 [EOF] 1479 "); 1480}