just playing with tangled
at ig/vimdiffwarn 1663 lines 57 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::path::Path; 16 17use indoc::indoc; 18 19use crate::common::create_commit_with_files; 20use crate::common::CommandOutput; 21use crate::common::TestEnvironment; 22use crate::common::TestWorkDir; 23 24#[must_use] 25fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput { 26 work_dir.run_jj(["log", "-T", "bookmarks"]) 27} 28 29#[test] 30fn test_resolution() { 31 let mut test_env = TestEnvironment::default(); 32 let editor_script = test_env.set_up_fake_editor(); 33 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 34 let work_dir = test_env.work_dir("repo"); 35 36 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 37 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 38 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]); 39 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 40 // Test the setup 41 insta::assert_snapshot!(get_log_output(&work_dir), @r" 42 @ conflict 43 ├─╮ 44 │ ○ b 45 ○ │ a 46 ├─╯ 47 ○ base 48 49 [EOF] 50 "); 51 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 52 file 2-sided conflict 53 [EOF] 54 "); 55 insta::assert_snapshot!(work_dir.read_file("file"), @r" 56 <<<<<<< Conflict 1 of 1 57 %%%%%%% Changes from base to side #1 58 -base 59 +a 60 +++++++ Contents of side #2 61 b 62 >>>>>>> Conflict 1 of 1 ends 63 "); 64 65 // Check that output file starts out empty and resolve the conflict 66 std::fs::write( 67 &editor_script, 68 ["dump editor0", "write\nresolution\n"].join("\0"), 69 ) 70 .unwrap(); 71 let output = work_dir.run_jj(["resolve"]); 72 insta::assert_snapshot!(output, @r" 73 ------- stderr ------- 74 Resolving conflicts in: file 75 Working copy (@) now at: vruxwmqv e069f073 conflict | conflict 76 Parent commit (@-) : zsuskuln aa493daf a | a 77 Parent commit (@-) : royxmykx db6a4daf b | b 78 Added 0 files, modified 1 files, removed 0 files 79 [EOF] 80 "); 81 insta::assert_snapshot!( 82 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @""); 83 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 84 diff --git a/file b/file 85 index 0000000000..88425ec521 100644 86 --- a/file 87 +++ b/file 88 @@ -1,7 +1,1 @@ 89 -<<<<<<< Conflict 1 of 1 90 -%%%%%%% Changes from base to side #1 91 --base 92 -+a 93 -+++++++ Contents of side #2 94 -b 95 ->>>>>>> Conflict 1 of 1 ends 96 +resolution 97 [EOF] 98 "); 99 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 100 ------- stderr ------- 101 Error: No conflicts found at this revision 102 [EOF] 103 [exit status: 2] 104 "); 105 106 // Try again with --tool=<name> 107 work_dir.run_jj(["undo"]).success(); 108 std::fs::write(&editor_script, "write\nresolution\n").unwrap(); 109 let output = work_dir.run_jj([ 110 "resolve", 111 "--config=ui.merge-editor='false'", 112 "--tool=fake-editor", 113 ]); 114 insta::assert_snapshot!(output, @r" 115 ------- stderr ------- 116 Resolving conflicts in: file 117 Working copy (@) now at: vruxwmqv 1a70c7c6 conflict | conflict 118 Parent commit (@-) : zsuskuln aa493daf a | a 119 Parent commit (@-) : royxmykx db6a4daf b | b 120 Added 0 files, modified 1 files, removed 0 files 121 [EOF] 122 "); 123 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 124 diff --git a/file b/file 125 index 0000000000..88425ec521 100644 126 --- a/file 127 +++ b/file 128 @@ -1,7 +1,1 @@ 129 -<<<<<<< Conflict 1 of 1 130 -%%%%%%% Changes from base to side #1 131 --base 132 -+a 133 -+++++++ Contents of side #2 134 -b 135 ->>>>>>> Conflict 1 of 1 ends 136 +resolution 137 [EOF] 138 "); 139 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 140 ------- stderr ------- 141 Error: No conflicts found at this revision 142 [EOF] 143 [exit status: 2] 144 "); 145 146 // Check that the output file starts with conflict markers if 147 // `merge-tool-edits-conflict-markers=true` 148 work_dir.run_jj(["undo"]).success(); 149 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 150 std::fs::write( 151 &editor_script, 152 ["dump editor1", "write\nresolution\n"].join("\0"), 153 ) 154 .unwrap(); 155 work_dir 156 .run_jj([ 157 "resolve", 158 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 159 ]) 160 .success(); 161 insta::assert_snapshot!( 162 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r" 163 <<<<<<< Conflict 1 of 1 164 %%%%%%% Changes from base to side #1 165 -base 166 +a 167 +++++++ Contents of side #2 168 b 169 >>>>>>> Conflict 1 of 1 ends 170 "); 171 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 172 diff --git a/file b/file 173 index 0000000000..88425ec521 100644 174 --- a/file 175 +++ b/file 176 @@ -1,7 +1,1 @@ 177 -<<<<<<< Conflict 1 of 1 178 -%%%%%%% Changes from base to side #1 179 --base 180 -+a 181 -+++++++ Contents of side #2 182 -b 183 ->>>>>>> Conflict 1 of 1 ends 184 +resolution 185 [EOF] 186 "); 187 188 // Check that if merge tool leaves conflict markers in output file and 189 // `merge-tool-edits-conflict-markers=true`, these markers are properly parsed. 190 work_dir.run_jj(["undo"]).success(); 191 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 192 std::fs::write( 193 &editor_script, 194 [ 195 "dump editor2", 196 indoc! {" 197 write 198 <<<<<<< 199 %%%%%%% 200 -some 201 +fake 202 +++++++ 203 conflict 204 >>>>>>> 205 "}, 206 ] 207 .join("\0"), 208 ) 209 .unwrap(); 210 let output = work_dir.run_jj([ 211 "resolve", 212 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 213 ]); 214 insta::assert_snapshot!(output, @r" 215 ------- stderr ------- 216 Resolving conflicts in: file 217 Working copy (@) now at: vruxwmqv 608a2310 conflict | (conflict) conflict 218 Parent commit (@-) : zsuskuln aa493daf a | a 219 Parent commit (@-) : royxmykx db6a4daf b | b 220 Added 0 files, modified 1 files, removed 0 files 221 Warning: There are unresolved conflicts at these paths: 222 file 2-sided conflict 223 New conflicts appeared in 1 commits: 224 vruxwmqv 608a2310 conflict | (conflict) conflict 225 Hint: To resolve the conflicts, start by updating to it: 226 jj new vruxwmqv 227 Then use `jj resolve`, or edit the conflict markers in the file directly. 228 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 229 Then run `jj squash` to move the resolution into the conflicted commit. 230 [EOF] 231 "); 232 insta::assert_snapshot!( 233 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r" 234 <<<<<<< Conflict 1 of 1 235 %%%%%%% Changes from base to side #1 236 -base 237 +a 238 +++++++ Contents of side #2 239 b 240 >>>>>>> Conflict 1 of 1 ends 241 "); 242 // Note the "Modified" below 243 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 244 diff --git a/file b/file 245 --- a/file 246 +++ b/file 247 @@ -1,7 +1,7 @@ 248 <<<<<<< Conflict 1 of 1 249 %%%%%%% Changes from base to side #1 250 --base 251 -+a 252 +-some 253 ++fake 254 +++++++ Contents of side #2 255 -b 256 +conflict 257 >>>>>>> Conflict 1 of 1 ends 258 [EOF] 259 "); 260 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 261 file 2-sided conflict 262 [EOF] 263 "); 264 265 // Check that if merge tool leaves conflict markers in output file but 266 // `merge-tool-edits-conflict-markers=false` or is not specified, 267 // `jj` considers the conflict resolved. 268 work_dir.run_jj(["undo"]).success(); 269 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 270 std::fs::write( 271 &editor_script, 272 [ 273 "dump editor3", 274 indoc! {" 275 write 276 <<<<<<< 277 %%%%%%% 278 -some 279 +fake 280 +++++++ 281 conflict 282 >>>>>>> 283 "}, 284 ] 285 .join("\0"), 286 ) 287 .unwrap(); 288 let output = work_dir.run_jj(["resolve"]); 289 insta::assert_snapshot!(output, @r" 290 ------- stderr ------- 291 Resolving conflicts in: file 292 Working copy (@) now at: vruxwmqv 3166dfd2 conflict | conflict 293 Parent commit (@-) : zsuskuln aa493daf a | a 294 Parent commit (@-) : royxmykx db6a4daf b | b 295 Added 0 files, modified 1 files, removed 0 files 296 [EOF] 297 "); 298 insta::assert_snapshot!( 299 std::fs::read_to_string(test_env.env_root().join("editor3")).unwrap(), @""); 300 // Note the "Resolved" below 301 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 302 diff --git a/file b/file 303 index 0000000000..0610716cc1 100644 304 --- a/file 305 +++ b/file 306 @@ -1,7 +1,7 @@ 307 -<<<<<<< Conflict 1 of 1 308 -%%%%%%% Changes from base to side #1 309 --base 310 -+a 311 -+++++++ Contents of side #2 312 -b 313 ->>>>>>> Conflict 1 of 1 ends 314 +<<<<<<< 315 +%%%%%%% 316 +-some 317 ++fake 318 ++++++++ 319 +conflict 320 +>>>>>>> 321 [EOF] 322 "); 323 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 324 ------- stderr ------- 325 Error: No conflicts found at this revision 326 [EOF] 327 [exit status: 2] 328 "); 329 330 // Check that merge tool can override conflict marker style setting, and that 331 // the merge tool can output Git-style conflict markers 332 work_dir.run_jj(["undo"]).success(); 333 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 334 std::fs::write( 335 &editor_script, 336 [ 337 "dump editor4", 338 indoc! {" 339 write 340 <<<<<<< 341 some 342 ||||||| 343 fake 344 ======= 345 conflict 346 >>>>>>> 347 "}, 348 ] 349 .join("\0"), 350 ) 351 .unwrap(); 352 let output = work_dir.run_jj([ 353 "resolve", 354 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 355 "--config=merge-tools.fake-editor.conflict-marker-style=git", 356 ]); 357 insta::assert_snapshot!(output, @r" 358 ------- stderr ------- 359 Resolving conflicts in: file 360 Working copy (@) now at: vruxwmqv 8e03fefa conflict | (conflict) conflict 361 Parent commit (@-) : zsuskuln aa493daf a | a 362 Parent commit (@-) : royxmykx db6a4daf b | b 363 Added 0 files, modified 1 files, removed 0 files 364 Warning: There are unresolved conflicts at these paths: 365 file 2-sided conflict 366 New conflicts appeared in 1 commits: 367 vruxwmqv 8e03fefa conflict | (conflict) conflict 368 Hint: To resolve the conflicts, start by updating to it: 369 jj new vruxwmqv 370 Then use `jj resolve`, or edit the conflict markers in the file directly. 371 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 372 Then run `jj squash` to move the resolution into the conflicted commit. 373 [EOF] 374 "); 375 insta::assert_snapshot!( 376 std::fs::read_to_string(test_env.env_root().join("editor4")).unwrap(), @r" 377 <<<<<<< Side #1 (Conflict 1 of 1) 378 a 379 ||||||| Base 380 base 381 ======= 382 b 383 >>>>>>> Side #2 (Conflict 1 of 1 ends) 384 "); 385 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 386 diff --git a/file b/file 387 --- a/file 388 +++ b/file 389 @@ -1,7 +1,7 @@ 390 <<<<<<< Conflict 1 of 1 391 %%%%%%% Changes from base to side #1 392 --base 393 -+a 394 +-fake 395 ++some 396 +++++++ Contents of side #2 397 -b 398 +conflict 399 >>>>>>> Conflict 1 of 1 ends 400 [EOF] 401 "); 402 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 403 file 2-sided conflict 404 [EOF] 405 "); 406 407 // Check that merge tool can leave conflict markers by returning exit code 1 408 // when using `merge-conflict-exit-codes = [1]`. The Git "diff3" conflict 409 // markers should also be parsed correctly. 410 work_dir.run_jj(["undo"]).success(); 411 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 412 std::fs::write( 413 &editor_script, 414 [ 415 "dump editor5", 416 indoc! {" 417 write 418 <<<<<<< 419 some 420 ||||||| 421 fake 422 ======= 423 conflict 424 >>>>>>> 425 "}, 426 "fail", 427 ] 428 .join("\0"), 429 ) 430 .unwrap(); 431 let output = work_dir.run_jj([ 432 "resolve", 433 "--config=merge-tools.fake-editor.merge-conflict-exit-codes=[1]", 434 ]); 435 insta::assert_snapshot!(output, @r" 436 ------- stderr ------- 437 Resolving conflicts in: file 438 Working copy (@) now at: vruxwmqv a786ac2f conflict | (conflict) conflict 439 Parent commit (@-) : zsuskuln aa493daf a | a 440 Parent commit (@-) : royxmykx db6a4daf b | b 441 Added 0 files, modified 1 files, removed 0 files 442 Warning: There are unresolved conflicts at these paths: 443 file 2-sided conflict 444 New conflicts appeared in 1 commits: 445 vruxwmqv a786ac2f conflict | (conflict) conflict 446 Hint: To resolve the conflicts, start by updating to it: 447 jj new vruxwmqv 448 Then use `jj resolve`, or edit the conflict markers in the file directly. 449 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 450 Then run `jj squash` to move the resolution into the conflicted commit. 451 [EOF] 452 "); 453 insta::assert_snapshot!( 454 std::fs::read_to_string(test_env.env_root().join("editor5")).unwrap(), @""); 455 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 456 diff --git a/file b/file 457 --- a/file 458 +++ b/file 459 @@ -1,7 +1,7 @@ 460 <<<<<<< Conflict 1 of 1 461 %%%%%%% Changes from base to side #1 462 --base 463 -+a 464 +-fake 465 ++some 466 +++++++ Contents of side #2 467 -b 468 +conflict 469 >>>>>>> Conflict 1 of 1 ends 470 [EOF] 471 "); 472 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 473 file 2-sided conflict 474 [EOF] 475 "); 476 477 // Check that an error is reported if a merge tool indicated it would leave 478 // conflict markers, but the output file didn't contain valid conflict markers. 479 work_dir.run_jj(["undo"]).success(); 480 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 481 std::fs::write( 482 &editor_script, 483 [ 484 indoc! {" 485 write 486 <<<<<<< this isn't diff3 style! 487 some 488 ======= 489 conflict 490 >>>>>>> 491 "}, 492 "fail", 493 ] 494 .join("\0"), 495 ) 496 .unwrap(); 497 let output = work_dir.run_jj([ 498 "resolve", 499 "--config=merge-tools.fake-editor.merge-conflict-exit-codes=[1]", 500 ]); 501 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" 502 ------- stderr ------- 503 Resolving conflicts in: file 504 Error: Failed to resolve conflicts 505 Caused by: Tool exited with exit status: 1, but did not produce valid conflict markers (run with --debug to see the exact invocation) 506 [EOF] 507 [exit status: 1] 508 "); 509 510 // TODO: Check that running `jj new` and then `jj resolve -r conflict` works 511 // correctly. 512} 513 514fn check_resolve_produces_input_file( 515 test_env: &mut TestEnvironment, 516 root: impl AsRef<Path>, 517 filename: &str, 518 role: &str, 519 expected_content: &str, 520) { 521 let editor_script = test_env.set_up_fake_editor(); 522 let work_dir = test_env.work_dir(root); 523 std::fs::write(editor_script, format!("expect\n{expected_content}")).unwrap(); 524 525 let merge_arg_config = format!(r#"merge-tools.fake-editor.merge-args=["${role}"]"#); 526 // This error means that fake-editor exited successfully but did not modify the 527 // output file. 528 let output = work_dir.run_jj(["resolve", "--config", &merge_arg_config, filename]); 529 insta::allow_duplicates! { 530 insta::assert_snapshot!( 531 output.normalize_stderr_with(|s| s.replacen(filename, "$FILENAME", 1)), @r" 532 ------- stderr ------- 533 Resolving conflicts in: $FILENAME 534 Error: Failed to resolve conflicts 535 Caused by: The output file is either unchanged or empty after the editor quit (run with --debug to see the exact invocation). 536 [EOF] 537 [exit status: 1] 538 "); 539 } 540} 541 542#[test] 543fn test_normal_conflict_input_files() { 544 let mut test_env = TestEnvironment::default(); 545 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 546 let work_dir = test_env.work_dir("repo"); 547 548 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 549 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 550 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]); 551 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 552 // Test the setup 553 insta::assert_snapshot!(get_log_output(&work_dir), @r" 554 @ conflict 555 ├─╮ 556 │ ○ b 557 ○ │ a 558 ├─╯ 559 ○ base 560 561 [EOF] 562 "); 563 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 564 file 2-sided conflict 565 [EOF] 566 "); 567 insta::assert_snapshot!(work_dir.read_file("file"), @r" 568 <<<<<<< Conflict 1 of 1 569 %%%%%%% Changes from base to side #1 570 -base 571 +a 572 +++++++ Contents of side #2 573 b 574 >>>>>>> Conflict 1 of 1 ends 575 "); 576 577 check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n"); 578 check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n"); 579 check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", "b\n"); 580} 581 582#[test] 583fn test_baseless_conflict_input_files() { 584 let mut test_env = TestEnvironment::default(); 585 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 586 let work_dir = test_env.work_dir("repo"); 587 588 create_commit_with_files(&work_dir, "base", &[], &[]); 589 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 590 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]); 591 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 592 // Test the setup 593 insta::assert_snapshot!(get_log_output(&work_dir), @r" 594 @ conflict 595 ├─╮ 596 │ ○ b 597 ○ │ a 598 ├─╯ 599 ○ base 600 601 [EOF] 602 "); 603 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 604 file 2-sided conflict 605 [EOF] 606 "); 607 insta::assert_snapshot!(work_dir.read_file("file"), @r" 608 <<<<<<< Conflict 1 of 1 609 %%%%%%% Changes from base to side #1 610 +a 611 +++++++ Contents of side #2 612 b 613 >>>>>>> Conflict 1 of 1 ends 614 "); 615 616 check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", ""); 617 check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n"); 618 check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", "b\n"); 619} 620 621#[test] 622fn test_too_many_parents() { 623 let test_env = TestEnvironment::default(); 624 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 625 let work_dir = test_env.work_dir("repo"); 626 627 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 628 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 629 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]); 630 create_commit_with_files(&work_dir, "c", &["base"], &[("file", "c\n")]); 631 create_commit_with_files(&work_dir, "conflict", &["a", "b", "c"], &[]); 632 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 633 file 3-sided conflict 634 [EOF] 635 "); 636 // Test warning color 637 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r" 638 file 3-sided conflict 639 [EOF] 640 "); 641 642 let output = work_dir.run_jj(["resolve"]); 643 insta::assert_snapshot!(output, @r#" 644 ------- stderr ------- 645 Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message. 646 Error: Failed to resolve conflicts 647 Caused by: The conflict at "file" has 3 sides. At most 2 sides are supported. 648 [EOF] 649 [exit status: 1] 650 "#); 651} 652 653#[test] 654fn test_simplify_conflict_sides() { 655 let mut test_env = TestEnvironment::default(); 656 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 657 let work_dir = test_env.work_dir("repo"); 658 659 // Creates a 4-sided conflict, with fileA and fileB having different conflicts: 660 // fileA: A - B + C - B + B - B + B 661 // fileB: A - A + A - A + B - C + D 662 create_commit_with_files( 663 &work_dir, 664 "base", 665 &[], 666 &[("fileA", "base\n"), ("fileB", "base\n")], 667 ); 668 create_commit_with_files(&work_dir, "a1", &["base"], &[("fileA", "1\n")]); 669 create_commit_with_files(&work_dir, "a2", &["base"], &[("fileA", "2\n")]); 670 create_commit_with_files(&work_dir, "b1", &["base"], &[("fileB", "1\n")]); 671 create_commit_with_files(&work_dir, "b2", &["base"], &[("fileB", "2\n")]); 672 create_commit_with_files(&work_dir, "conflictA", &["a1", "a2"], &[]); 673 create_commit_with_files(&work_dir, "conflictB", &["b1", "b2"], &[]); 674 create_commit_with_files(&work_dir, "conflict", &["conflictA", "conflictB"], &[]); 675 676 // Even though the tree-level conflict is a 4-sided conflict, each file is 677 // materialized as a 2-sided conflict. 678 insta::assert_snapshot!(work_dir.run_jj(["debug", "tree"]), @r#" 679 fileA: Ok(Conflicted([Some(File { id: FileId("d00491fd7e5bb6fa28c517a0bb32b8b506539d4d"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("0cfbf08886fca9a91cb753ec8734c84fcbe52c9f"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })])) 680 fileB: Ok(Conflicted([Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("d00491fd7e5bb6fa28c517a0bb32b8b506539d4d"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("0cfbf08886fca9a91cb753ec8734c84fcbe52c9f"), executable: false })])) 681 [EOF] 682 "#); 683 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 684 fileA 2-sided conflict 685 fileB 2-sided conflict 686 [EOF] 687 "); 688 insta::assert_snapshot!(work_dir.read_file("fileA"), @r" 689 <<<<<<< Conflict 1 of 1 690 %%%%%%% Changes from base to side #1 691 -base 692 +1 693 +++++++ Contents of side #2 694 2 695 >>>>>>> Conflict 1 of 1 ends 696 "); 697 insta::assert_snapshot!(work_dir.read_file("fileB"), @r" 698 <<<<<<< Conflict 1 of 1 699 %%%%%%% Changes from base to side #1 700 -base 701 +1 702 +++++++ Contents of side #2 703 2 704 >>>>>>> Conflict 1 of 1 ends 705 "); 706 707 // Conflict should be simplified before being handled by external merge tool. 708 check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "base", "base\n"); 709 check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "left", "1\n"); 710 check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "right", "2\n"); 711 check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "base", "base\n"); 712 check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "left", "1\n"); 713 check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "right", "2\n"); 714 715 // Check that simplified conflicts are still parsed as conflicts after editing 716 // when `merge-tool-edits-conflict-markers=true`. 717 let editor_script = test_env.set_up_fake_editor(); 718 std::fs::write( 719 editor_script, 720 indoc! {" 721 write 722 <<<<<<< Conflict 1 of 1 723 %%%%%%% Changes from base to side #1 724 -base_edited 725 +1_edited 726 +++++++ Contents of side #2 727 2_edited 728 >>>>>>> Conflict 1 of 1 ends 729 "}, 730 ) 731 .unwrap(); 732 let work_dir = test_env.work_dir("repo"); 733 let output = work_dir.run_jj([ 734 "resolve", 735 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 736 "fileB", 737 ]); 738 insta::assert_snapshot!(output, @r" 739 ------- stderr ------- 740 Resolving conflicts in: fileB 741 Working copy (@) now at: nkmrtpmo 69cc0c2d conflict | (conflict) conflict 742 Parent commit (@-) : kmkuslsw 4601566f conflictA | (conflict) (empty) conflictA 743 Parent commit (@-) : lylxulpl 6f8d8381 conflictB | (conflict) (empty) conflictB 744 Added 0 files, modified 1 files, removed 0 files 745 Warning: There are unresolved conflicts at these paths: 746 fileA 2-sided conflict 747 fileB 2-sided conflict 748 New conflicts appeared in 1 commits: 749 nkmrtpmo 69cc0c2d conflict | (conflict) conflict 750 Hint: To resolve the conflicts, start by updating to it: 751 jj new nkmrtpmo 752 Then use `jj resolve`, or edit the conflict markers in the file directly. 753 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 754 Then run `jj squash` to move the resolution into the conflicted commit. 755 [EOF] 756 "); 757 insta::assert_snapshot!(work_dir.read_file("fileB"), @r" 758 <<<<<<< Conflict 1 of 1 759 %%%%%%% Changes from base to side #1 760 -base_edited 761 +1_edited 762 +++++++ Contents of side #2 763 2_edited 764 >>>>>>> Conflict 1 of 1 ends 765 "); 766 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 767 fileA 2-sided conflict 768 fileB 2-sided conflict 769 [EOF] 770 "); 771} 772 773#[test] 774fn test_edit_delete_conflict_input_files() { 775 let mut test_env = TestEnvironment::default(); 776 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 777 let work_dir = test_env.work_dir("repo"); 778 779 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 780 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 781 create_commit_with_files(&work_dir, "b", &["base"], &[]); 782 work_dir.remove_file("file"); 783 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 784 // Test the setup 785 insta::assert_snapshot!(get_log_output(&work_dir), @r" 786 @ conflict 787 ├─╮ 788 │ ○ b 789 ○ │ a 790 ├─╯ 791 ○ base 792 793 [EOF] 794 "); 795 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 796 file 2-sided conflict including 1 deletion 797 [EOF] 798 "); 799 insta::assert_snapshot!(work_dir.read_file("file"), @r" 800 <<<<<<< Conflict 1 of 1 801 +++++++ Contents of side #1 802 a 803 %%%%%%% Changes from base to side #2 804 -base 805 >>>>>>> Conflict 1 of 1 ends 806 "); 807 808 check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n"); 809 check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n"); 810 check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", ""); 811} 812 813#[test] 814fn test_file_vs_dir() { 815 let test_env = TestEnvironment::default(); 816 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 817 let work_dir = test_env.work_dir("repo"); 818 819 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 820 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 821 create_commit_with_files(&work_dir, "b", &["base"], &[]); 822 work_dir.remove_file("file"); 823 work_dir.create_dir("file"); 824 // Without a placeholder file, `jj` ignores an empty directory 825 work_dir.write_file("file/placeholder", ""); 826 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 827 insta::assert_snapshot!(get_log_output(&work_dir), @r" 828 @ conflict 829 ├─╮ 830 │ ○ b 831 ○ │ a 832 ├─╯ 833 ○ base 834 835 [EOF] 836 "); 837 838 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 839 file 2-sided conflict including a directory 840 [EOF] 841 "); 842 let output = work_dir.run_jj(["resolve"]); 843 insta::assert_snapshot!(output, @r#" 844 ------- stderr ------- 845 Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message. 846 Error: Failed to resolve conflicts 847 Caused by: Only conflicts that involve normal files (not symlinks, not executable, etc.) are supported. Conflict summary for "file": 848 Conflict: 849 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55 850 Adding file with id 78981922613b2afb6025042ff6bd878ac1994e85 851 Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877 852 853 [EOF] 854 [exit status: 1] 855 "#); 856} 857 858#[test] 859fn test_description_with_dir_and_deletion() { 860 let test_env = TestEnvironment::default(); 861 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 862 let work_dir = test_env.work_dir("repo"); 863 864 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 865 create_commit_with_files(&work_dir, "edit", &["base"], &[("file", "b\n")]); 866 create_commit_with_files(&work_dir, "dir", &["base"], &[]); 867 work_dir.remove_file("file"); 868 work_dir.create_dir("file"); 869 // Without a placeholder file, `jj` ignores an empty directory 870 work_dir.write_file("file/placeholder", ""); 871 create_commit_with_files(&work_dir, "del", &["base"], &[]); 872 work_dir.remove_file("file"); 873 create_commit_with_files(&work_dir, "conflict", &["edit", "dir", "del"], &[]); 874 insta::assert_snapshot!(get_log_output(&work_dir), @r" 875 @ conflict 876 ├─┬─╮ 877 │ │ ○ del 878 │ ○ │ dir 879 │ ├─╯ 880 ○ │ edit 881 ├─╯ 882 ○ base 883 884 [EOF] 885 "); 886 887 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 888 file 3-sided conflict including 1 deletion and a directory 889 [EOF] 890 "); 891 // Test warning color. The deletion is fine, so it's not highlighted 892 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r" 893 file 3-sided conflict including 1 deletion and a directory 894 [EOF] 895 "); 896 let output = work_dir.run_jj(["resolve"]); 897 insta::assert_snapshot!(output, @r#" 898 ------- stderr ------- 899 Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message. 900 Error: Failed to resolve conflicts 901 Caused by: Only conflicts that involve normal files (not symlinks, not executable, etc.) are supported. Conflict summary for "file": 902 Conflict: 903 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55 904 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55 905 Adding file with id 61780798228d17af2d34fce4cfbdf35556832472 906 Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877 907 908 [EOF] 909 [exit status: 1] 910 "#); 911} 912 913#[test] 914fn test_resolve_conflicts_with_executable() { 915 let mut test_env = TestEnvironment::default(); 916 let editor_script = test_env.set_up_fake_editor(); 917 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 918 let work_dir = test_env.work_dir("repo"); 919 920 // Create a conflict in "file1" where all 3 terms are executables, and create a 921 // conflict in "file2" where one side set the executable bit. 922 create_commit_with_files( 923 &work_dir, 924 "base", 925 &[], 926 &[("file1", "base1\n"), ("file2", "base2\n")], 927 ); 928 work_dir.run_jj(["file", "chmod", "x", "file1"]).success(); 929 create_commit_with_files( 930 &work_dir, 931 "a", 932 &["base"], 933 &[("file1", "a1\n"), ("file2", "a2\n")], 934 ); 935 create_commit_with_files( 936 &work_dir, 937 "b", 938 &["base"], 939 &[("file1", "b1\n"), ("file2", "b2\n")], 940 ); 941 work_dir.run_jj(["file", "chmod", "x", "file2"]).success(); 942 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 943 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 944 file1 2-sided conflict including an executable 945 file2 2-sided conflict including an executable 946 [EOF] 947 "); 948 insta::assert_snapshot!(work_dir.read_file("file1"), @r" 949 <<<<<<< Conflict 1 of 1 950 %%%%%%% Changes from base to side #1 951 -base1 952 +a1 953 +++++++ Contents of side #2 954 b1 955 >>>>>>> Conflict 1 of 1 ends 956 " 957 ); 958 insta::assert_snapshot!(work_dir.read_file("file2"), @r" 959 <<<<<<< Conflict 1 of 1 960 %%%%%%% Changes from base to side #1 961 -base2 962 +a2 963 +++++++ Contents of side #2 964 b2 965 >>>>>>> Conflict 1 of 1 ends 966 " 967 ); 968 969 // Test resolving the conflict in "file1", which should produce an executable 970 std::fs::write(&editor_script, b"write\nresolution1\n").unwrap(); 971 let output = work_dir.run_jj(["resolve", "file1"]); 972 insta::assert_snapshot!(output, @r" 973 ------- stderr ------- 974 Resolving conflicts in: file1 975 Working copy (@) now at: znkkpsqq eb159d56 conflict | (conflict) conflict 976 Parent commit (@-) : mzvwutvl 08932848 a | a 977 Parent commit (@-) : yqosqzyt b69b3de6 b | b 978 Added 0 files, modified 1 files, removed 0 files 979 Warning: There are unresolved conflicts at these paths: 980 file2 2-sided conflict including an executable 981 New conflicts appeared in 1 commits: 982 znkkpsqq eb159d56 conflict | (conflict) conflict 983 Hint: To resolve the conflicts, start by updating to it: 984 jj new znkkpsqq 985 Then use `jj resolve`, or edit the conflict markers in the file directly. 986 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 987 Then run `jj squash` to move the resolution into the conflicted commit. 988 [EOF] 989 "); 990 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 991 diff --git a/file1 b/file1 992 index 0000000000..95cc18629d 100755 993 --- a/file1 994 +++ b/file1 995 @@ -1,7 +1,1 @@ 996 -<<<<<<< Conflict 1 of 1 997 -%%%%%%% Changes from base to side #1 998 --base1 999 -+a1 1000 -+++++++ Contents of side #2 1001 -b1 1002 ->>>>>>> Conflict 1 of 1 ends 1003 +resolution1 1004 [EOF] 1005 "); 1006 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1007 file2 2-sided conflict including an executable 1008 [EOF] 1009 "); 1010 1011 // Test resolving the conflict in "file2", which should produce an executable 1012 work_dir.run_jj(["undo"]).success(); 1013 std::fs::write(&editor_script, b"write\nresolution2\n").unwrap(); 1014 let output = work_dir.run_jj(["resolve", "file2"]); 1015 insta::assert_snapshot!(output, @r" 1016 ------- stderr ------- 1017 Resolving conflicts in: file2 1018 Working copy (@) now at: znkkpsqq 4dccbb3c conflict | (conflict) conflict 1019 Parent commit (@-) : mzvwutvl 08932848 a | a 1020 Parent commit (@-) : yqosqzyt b69b3de6 b | b 1021 Added 0 files, modified 1 files, removed 0 files 1022 Warning: There are unresolved conflicts at these paths: 1023 file1 2-sided conflict including an executable 1024 New conflicts appeared in 1 commits: 1025 znkkpsqq 4dccbb3c conflict | (conflict) conflict 1026 Hint: To resolve the conflicts, start by updating to it: 1027 jj new znkkpsqq 1028 Then use `jj resolve`, or edit the conflict markers in the file directly. 1029 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 1030 Then run `jj squash` to move the resolution into the conflicted commit. 1031 [EOF] 1032 "); 1033 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1034 diff --git a/file2 b/file2 1035 index 0000000000..775f078581 100755 1036 --- a/file2 1037 +++ b/file2 1038 @@ -1,7 +1,1 @@ 1039 -<<<<<<< Conflict 1 of 1 1040 -%%%%%%% Changes from base to side #1 1041 --base2 1042 -+a2 1043 -+++++++ Contents of side #2 1044 -b2 1045 ->>>>>>> Conflict 1 of 1 ends 1046 +resolution2 1047 [EOF] 1048 "); 1049 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1050 file1 2-sided conflict including an executable 1051 [EOF] 1052 "); 1053} 1054 1055#[test] 1056fn test_resolve_long_conflict_markers() { 1057 let mut test_env = TestEnvironment::default(); 1058 let editor_script = test_env.set_up_fake_editor(); 1059 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1060 let work_dir = test_env.work_dir("repo"); 1061 1062 // Makes it easier to read the diffs between conflicts 1063 test_env.add_config("ui.conflict-marker-style = 'snapshot'"); 1064 1065 // Create a conflict which requires long conflict markers to be materialized 1066 create_commit_with_files(&work_dir, "base", &[], &[("file", "======= base\n")]); 1067 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "<<<<<<< a\n")]); 1068 create_commit_with_files(&work_dir, "b", &["base"], &[("file", ">>>>>>> b\n")]); 1069 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 1070 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1071 file 2-sided conflict 1072 [EOF] 1073 "); 1074 insta::assert_snapshot!(work_dir.read_file("file"), @r" 1075 <<<<<<<<<<< Conflict 1 of 1 1076 +++++++++++ Contents of side #1 1077 <<<<<<< a 1078 ----------- Contents of base 1079 ======= base 1080 +++++++++++ Contents of side #2 1081 >>>>>>> b 1082 >>>>>>>>>>> Conflict 1 of 1 ends 1083 " 1084 ); 1085 // Allow signaling that conflict markers were produced even if not editing 1086 // conflict markers materialized in the output file 1087 test_env.add_config("merge-tools.fake-editor.merge-conflict-exit-codes = [1]"); 1088 1089 // By default, conflict markers of length 7 or longer are parsed for 1090 // compatibility with Git merge tools 1091 std::fs::write( 1092 &editor_script, 1093 indoc! {b" 1094 write 1095 <<<<<<< 1096 A 1097 ||||||| 1098 BASE 1099 ======= 1100 B 1101 >>>>>>> 1102 \0fail 1103 "}, 1104 ) 1105 .unwrap(); 1106 let output = work_dir.run_jj(["resolve"]); 1107 insta::assert_snapshot!(output, @r" 1108 ------- stderr ------- 1109 Resolving conflicts in: file 1110 Working copy (@) now at: vruxwmqv 2b985546 conflict | (conflict) conflict 1111 Parent commit (@-) : zsuskuln 64177fd4 a | a 1112 Parent commit (@-) : royxmykx db442c1e b | b 1113 Added 0 files, modified 1 files, removed 0 files 1114 Warning: There are unresolved conflicts at these paths: 1115 file 2-sided conflict 1116 New conflicts appeared in 1 commits: 1117 vruxwmqv 2b985546 conflict | (conflict) conflict 1118 Hint: To resolve the conflicts, start by updating to it: 1119 jj new vruxwmqv 1120 Then use `jj resolve`, or edit the conflict markers in the file directly. 1121 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 1122 Then run `jj squash` to move the resolution into the conflicted commit. 1123 [EOF] 1124 "); 1125 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1126 diff --git a/file b/file 1127 --- a/file 1128 +++ b/file 1129 @@ -1,8 +1,8 @@ 1130 -<<<<<<<<<<< Conflict 1 of 1 1131 -+++++++++++ Contents of side #1 1132 -<<<<<<< a 1133 ------------ Contents of base 1134 -======= base 1135 -+++++++++++ Contents of side #2 1136 ->>>>>>> b 1137 ->>>>>>>>>>> Conflict 1 of 1 ends 1138 +<<<<<<< Conflict 1 of 1 1139 ++++++++ Contents of side #1 1140 +A 1141 +------- Contents of base 1142 +BASE 1143 ++++++++ Contents of side #2 1144 +B 1145 +>>>>>>> Conflict 1 of 1 ends 1146 [EOF] 1147 "); 1148 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1149 file 2-sided conflict 1150 [EOF] 1151 "); 1152 1153 // If the merge tool edits the output file with materialized markers, the 1154 // markers must match the length of the materialized markers to be parsed 1155 work_dir.run_jj(["undo"]).success(); 1156 std::fs::write( 1157 &editor_script, 1158 indoc! {b" 1159 dump editor 1160 \0write 1161 <<<<<<<<<<< 1162 <<<<<<< A 1163 ||||||||||| 1164 ======= BASE 1165 =========== 1166 >>>>>>> B 1167 >>>>>>>>>>> 1168 \0fail 1169 "}, 1170 ) 1171 .unwrap(); 1172 let output = work_dir.run_jj([ 1173 "resolve", 1174 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 1175 ]); 1176 insta::assert_snapshot!(output, @r" 1177 ------- stderr ------- 1178 Resolving conflicts in: file 1179 Working copy (@) now at: vruxwmqv fac9406d conflict | (conflict) conflict 1180 Parent commit (@-) : zsuskuln 64177fd4 a | a 1181 Parent commit (@-) : royxmykx db442c1e b | b 1182 Added 0 files, modified 1 files, removed 0 files 1183 Warning: There are unresolved conflicts at these paths: 1184 file 2-sided conflict 1185 New conflicts appeared in 1 commits: 1186 vruxwmqv fac9406d conflict | (conflict) conflict 1187 Hint: To resolve the conflicts, start by updating to it: 1188 jj new vruxwmqv 1189 Then use `jj resolve`, or edit the conflict markers in the file directly. 1190 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 1191 Then run `jj squash` to move the resolution into the conflicted commit. 1192 [EOF] 1193 "); 1194 insta::assert_snapshot!( 1195 std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r" 1196 <<<<<<<<<<< Conflict 1 of 1 1197 +++++++++++ Contents of side #1 1198 <<<<<<< a 1199 ----------- Contents of base 1200 ======= base 1201 +++++++++++ Contents of side #2 1202 >>>>>>> b 1203 >>>>>>>>>>> Conflict 1 of 1 ends 1204 "); 1205 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1206 diff --git a/file b/file 1207 --- a/file 1208 +++ b/file 1209 @@ -1,8 +1,8 @@ 1210 <<<<<<<<<<< Conflict 1 of 1 1211 +++++++++++ Contents of side #1 1212 -<<<<<<< a 1213 +<<<<<<< A 1214 ----------- Contents of base 1215 -======= base 1216 +======= BASE 1217 +++++++++++ Contents of side #2 1218 ->>>>>>> b 1219 +>>>>>>> B 1220 >>>>>>>>>>> Conflict 1 of 1 ends 1221 [EOF] 1222 "); 1223 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1224 file 2-sided conflict 1225 [EOF] 1226 "); 1227 1228 // If the merge tool accepts the marker length as an argument, then the conflict 1229 // markers should be at least as long as "$marker_length" 1230 work_dir.run_jj(["undo"]).success(); 1231 std::fs::write( 1232 &editor_script, 1233 indoc! {b" 1234 expect-arg 0 1235 11\0write 1236 <<<<<<<<<<< 1237 <<<<<<< A 1238 ||||||||||| 1239 ======= BASE 1240 =========== 1241 >>>>>>> B 1242 >>>>>>>>>>> 1243 \0fail 1244 "}, 1245 ) 1246 .unwrap(); 1247 let output = work_dir.run_jj([ 1248 "resolve", 1249 r#"--config=merge-tools.fake-editor.merge-args=["$output", "$marker_length"]"#, 1250 ]); 1251 insta::assert_snapshot!(output, @r" 1252 ------- stderr ------- 1253 Resolving conflicts in: file 1254 Working copy (@) now at: vruxwmqv 1b29631a conflict | (conflict) conflict 1255 Parent commit (@-) : zsuskuln 64177fd4 a | a 1256 Parent commit (@-) : royxmykx db442c1e b | b 1257 Added 0 files, modified 1 files, removed 0 files 1258 Warning: There are unresolved conflicts at these paths: 1259 file 2-sided conflict 1260 New conflicts appeared in 1 commits: 1261 vruxwmqv 1b29631a conflict | (conflict) conflict 1262 Hint: To resolve the conflicts, start by updating to it: 1263 jj new vruxwmqv 1264 Then use `jj resolve`, or edit the conflict markers in the file directly. 1265 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 1266 Then run `jj squash` to move the resolution into the conflicted commit. 1267 [EOF] 1268 "); 1269 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1270 diff --git a/file b/file 1271 --- a/file 1272 +++ b/file 1273 @@ -1,8 +1,8 @@ 1274 <<<<<<<<<<< Conflict 1 of 1 1275 +++++++++++ Contents of side #1 1276 -<<<<<<< a 1277 +<<<<<<< A 1278 ----------- Contents of base 1279 -======= base 1280 +======= BASE 1281 +++++++++++ Contents of side #2 1282 ->>>>>>> b 1283 +>>>>>>> B 1284 >>>>>>>>>>> Conflict 1 of 1 ends 1285 [EOF] 1286 "); 1287 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1288 file 2-sided conflict 1289 [EOF] 1290 "); 1291} 1292 1293#[test] 1294fn test_multiple_conflicts() { 1295 let mut test_env = TestEnvironment::default(); 1296 let editor_script = test_env.set_up_fake_editor(); 1297 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1298 let work_dir = test_env.work_dir("repo"); 1299 1300 create_commit_with_files( 1301 &work_dir, 1302 "base", 1303 &[], 1304 &[ 1305 ( 1306 "this_file_has_a_very_long_name_to_test_padding", 1307 "first base\n", 1308 ), 1309 ("another_file", "second base\n"), 1310 ], 1311 ); 1312 create_commit_with_files( 1313 &work_dir, 1314 "a", 1315 &["base"], 1316 &[ 1317 ( 1318 "this_file_has_a_very_long_name_to_test_padding", 1319 "first a\n", 1320 ), 1321 ("another_file", "second a\n"), 1322 ], 1323 ); 1324 create_commit_with_files( 1325 &work_dir, 1326 "b", 1327 &["base"], 1328 &[ 1329 ( 1330 "this_file_has_a_very_long_name_to_test_padding", 1331 "first b\n", 1332 ), 1333 ("another_file", "second b\n"), 1334 ], 1335 ); 1336 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 1337 // Test the setup 1338 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1339 @ conflict 1340 ├─╮ 1341 │ ○ b 1342 ○ │ a 1343 ├─╯ 1344 ○ base 13451346 [EOF] 1347 "); 1348 insta::assert_snapshot!( 1349 work_dir.read_file("this_file_has_a_very_long_name_to_test_padding"), @r" 1350 <<<<<<< Conflict 1 of 1 1351 %%%%%%% Changes from base to side #1 1352 -first base 1353 +first a 1354 +++++++ Contents of side #2 1355 first b 1356 >>>>>>> Conflict 1 of 1 ends 1357 "); 1358 insta::assert_snapshot!(work_dir.read_file("another_file"), @r" 1359 <<<<<<< Conflict 1 of 1 1360 %%%%%%% Changes from base to side #1 1361 -second base 1362 +second a 1363 +++++++ Contents of side #2 1364 second b 1365 >>>>>>> Conflict 1 of 1 ends 1366 "); 1367 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1368 another_file 2-sided conflict 1369 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1370 [EOF] 1371 "); 1372 // Test colors 1373 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r" 1374 another_file 2-sided conflict 1375 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1376 [EOF] 1377 "); 1378 1379 // Check that we can manually pick which of the conflicts to resolve first 1380 std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap(); 1381 let output = work_dir.run_jj(["resolve", "another_file"]); 1382 insta::assert_snapshot!(output, @r" 1383 ------- stderr ------- 1384 Resolving conflicts in: another_file 1385 Working copy (@) now at: vruxwmqv 309e981c conflict | (conflict) conflict 1386 Parent commit (@-) : zsuskuln de7553ef a | a 1387 Parent commit (@-) : royxmykx f68bc2f0 b | b 1388 Added 0 files, modified 1 files, removed 0 files 1389 Warning: There are unresolved conflicts at these paths: 1390 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1391 New conflicts appeared in 1 commits: 1392 vruxwmqv 309e981c conflict | (conflict) conflict 1393 Hint: To resolve the conflicts, start by updating to it: 1394 jj new vruxwmqv 1395 Then use `jj resolve`, or edit the conflict markers in the file directly. 1396 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 1397 Then run `jj squash` to move the resolution into the conflicted commit. 1398 [EOF] 1399 "); 1400 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1401 diff --git a/another_file b/another_file 1402 index 0000000000..a9fcc7d486 100644 1403 --- a/another_file 1404 +++ b/another_file 1405 @@ -1,7 +1,1 @@ 1406 -<<<<<<< Conflict 1 of 1 1407 -%%%%%%% Changes from base to side #1 1408 --second base 1409 -+second a 1410 -+++++++ Contents of side #2 1411 -second b 1412 ->>>>>>> Conflict 1 of 1 ends 1413 +resolution another_file 1414 [EOF] 1415 "); 1416 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1417 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1418 [EOF] 1419 "); 1420 1421 // Repeat the above with the `--quiet` option. 1422 work_dir.run_jj(["undo"]).success(); 1423 std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap(); 1424 let output = work_dir.run_jj(["resolve", "--quiet", "another_file"]); 1425 insta::assert_snapshot!(output, @""); 1426 1427 // Without a path, `jj resolve` should call the merge tool multiple times 1428 work_dir.run_jj(["undo"]).success(); 1429 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 1430 std::fs::write( 1431 &editor_script, 1432 [ 1433 "expect\n", 1434 "write\nfirst resolution for auto-chosen file\n", 1435 "next invocation\n", 1436 "expect\n", 1437 "write\nsecond resolution for auto-chosen file\n", 1438 ] 1439 .join("\0"), 1440 ) 1441 .unwrap(); 1442 work_dir.run_jj(["resolve"]).success(); 1443 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1444 diff --git a/another_file b/another_file 1445 index 0000000000..7903e1c1c7 100644 1446 --- a/another_file 1447 +++ b/another_file 1448 @@ -1,7 +1,1 @@ 1449 -<<<<<<< Conflict 1 of 1 1450 -%%%%%%% Changes from base to side #1 1451 --second base 1452 -+second a 1453 -+++++++ Contents of side #2 1454 -second b 1455 ->>>>>>> Conflict 1 of 1 ends 1456 +first resolution for auto-chosen file 1457 diff --git a/this_file_has_a_very_long_name_to_test_padding b/this_file_has_a_very_long_name_to_test_padding 1458 index 0000000000..f8c72adf17 100644 1459 --- a/this_file_has_a_very_long_name_to_test_padding 1460 +++ b/this_file_has_a_very_long_name_to_test_padding 1461 @@ -1,7 +1,1 @@ 1462 -<<<<<<< Conflict 1 of 1 1463 -%%%%%%% Changes from base to side #1 1464 --first base 1465 -+first a 1466 -+++++++ Contents of side #2 1467 -first b 1468 ->>>>>>> Conflict 1 of 1 ends 1469 +second resolution for auto-chosen file 1470 [EOF] 1471 "); 1472 1473 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1474 ------- stderr ------- 1475 Error: No conflicts found at this revision 1476 [EOF] 1477 [exit status: 2] 1478 "); 1479 insta::assert_snapshot!(work_dir.run_jj(["resolve"]), @r" 1480 ------- stderr ------- 1481 Error: No conflicts found at this revision 1482 [EOF] 1483 [exit status: 2] 1484 "); 1485} 1486 1487#[test] 1488fn test_multiple_conflicts_with_error() { 1489 let mut test_env = TestEnvironment::default(); 1490 let editor_script = test_env.set_up_fake_editor(); 1491 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1492 let work_dir = test_env.work_dir("repo"); 1493 1494 // Create two conflicted files, and one non-conflicted file 1495 create_commit_with_files( 1496 &work_dir, 1497 "base", 1498 &[], 1499 &[ 1500 ("file1", "base1\n"), 1501 ("file2", "base2\n"), 1502 ("file3", "base3\n"), 1503 ], 1504 ); 1505 create_commit_with_files( 1506 &work_dir, 1507 "a", 1508 &["base"], 1509 &[("file1", "a1\n"), ("file2", "a2\n")], 1510 ); 1511 create_commit_with_files( 1512 &work_dir, 1513 "b", 1514 &["base"], 1515 &[("file1", "b1\n"), ("file2", "b2\n")], 1516 ); 1517 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 1518 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1519 file1 2-sided conflict 1520 file2 2-sided conflict 1521 [EOF] 1522 "); 1523 insta::assert_snapshot!(work_dir.read_file("file1"), @r" 1524 <<<<<<< Conflict 1 of 1 1525 %%%%%%% Changes from base to side #1 1526 -base1 1527 +a1 1528 +++++++ Contents of side #2 1529 b1 1530 >>>>>>> Conflict 1 of 1 ends 1531 " 1532 ); 1533 insta::assert_snapshot!(work_dir.read_file("file2"), @r" 1534 <<<<<<< Conflict 1 of 1 1535 %%%%%%% Changes from base to side #1 1536 -base2 1537 +a2 1538 +++++++ Contents of side #2 1539 b2 1540 >>>>>>> Conflict 1 of 1 ends 1541 " 1542 ); 1543 1544 // Test resolving one conflict, then exiting without resolving the second one 1545 std::fs::write( 1546 &editor_script, 1547 ["write\nresolution1\n", "next invocation\n"].join("\0"), 1548 ) 1549 .unwrap(); 1550 let output = work_dir.run_jj(["resolve"]); 1551 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" 1552 ------- stderr ------- 1553 Resolving conflicts in: file1 1554 Resolving conflicts in: file2 1555 Working copy (@) now at: vruxwmqv d2f3f858 conflict | (conflict) conflict 1556 Parent commit (@-) : zsuskuln 9db7fdfb a | a 1557 Parent commit (@-) : royxmykx d67e26e4 b | b 1558 Added 0 files, modified 1 files, removed 0 files 1559 Warning: There are unresolved conflicts at these paths: 1560 file2 2-sided conflict 1561 New conflicts appeared in 1 commits: 1562 vruxwmqv d2f3f858 conflict | (conflict) conflict 1563 Hint: To resolve the conflicts, start by updating to it: 1564 jj new vruxwmqv 1565 Then use `jj resolve`, or edit the conflict markers in the file directly. 1566 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 1567 Then run `jj squash` to move the resolution into the conflicted commit. 1568 Error: Stopped due to error after resolving 1 conflicts 1569 Caused by: The output file is either unchanged or empty after the editor quit (run with --debug to see the exact invocation). 1570 [EOF] 1571 [exit status: 1] 1572 "); 1573 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1574 diff --git a/file1 b/file1 1575 index 0000000000..95cc18629d 100644 1576 --- a/file1 1577 +++ b/file1 1578 @@ -1,7 +1,1 @@ 1579 -<<<<<<< Conflict 1 of 1 1580 -%%%%%%% Changes from base to side #1 1581 --base1 1582 -+a1 1583 -+++++++ Contents of side #2 1584 -b1 1585 ->>>>>>> Conflict 1 of 1 ends 1586 +resolution1 1587 [EOF] 1588 "); 1589 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1590 file2 2-sided conflict 1591 [EOF] 1592 "); 1593 1594 // Test resolving one conflict, then failing during the second resolution 1595 work_dir.run_jj(["undo"]).success(); 1596 std::fs::write( 1597 &editor_script, 1598 ["write\nresolution1\n", "next invocation\n", "fail"].join("\0"), 1599 ) 1600 .unwrap(); 1601 let output = work_dir.run_jj(["resolve"]); 1602 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" 1603 ------- stderr ------- 1604 Resolving conflicts in: file1 1605 Resolving conflicts in: file2 1606 Working copy (@) now at: vruxwmqv 0a54e8ed conflict | (conflict) conflict 1607 Parent commit (@-) : zsuskuln 9db7fdfb a | a 1608 Parent commit (@-) : royxmykx d67e26e4 b | b 1609 Added 0 files, modified 1 files, removed 0 files 1610 Warning: There are unresolved conflicts at these paths: 1611 file2 2-sided conflict 1612 New conflicts appeared in 1 commits: 1613 vruxwmqv 0a54e8ed conflict | (conflict) conflict 1614 Hint: To resolve the conflicts, start by updating to it: 1615 jj new vruxwmqv 1616 Then use `jj resolve`, or edit the conflict markers in the file directly. 1617 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 1618 Then run `jj squash` to move the resolution into the conflicted commit. 1619 Error: Stopped due to error after resolving 1 conflicts 1620 Caused by: Tool exited with exit status: 1 (run with --debug to see the exact invocation) 1621 [EOF] 1622 [exit status: 1] 1623 "); 1624 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1625 diff --git a/file1 b/file1 1626 index 0000000000..95cc18629d 100644 1627 --- a/file1 1628 +++ b/file1 1629 @@ -1,7 +1,1 @@ 1630 -<<<<<<< Conflict 1 of 1 1631 -%%%%%%% Changes from base to side #1 1632 --base1 1633 -+a1 1634 -+++++++ Contents of side #2 1635 -b1 1636 ->>>>>>> Conflict 1 of 1 ends 1637 +resolution1 1638 [EOF] 1639 "); 1640 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1641 file2 2-sided conflict 1642 [EOF] 1643 "); 1644 1645 // Test immediately failing to resolve any conflict 1646 work_dir.run_jj(["undo"]).success(); 1647 std::fs::write(&editor_script, "fail").unwrap(); 1648 let output = work_dir.run_jj(["resolve"]); 1649 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" 1650 ------- stderr ------- 1651 Resolving conflicts in: file1 1652 Error: Failed to resolve conflicts 1653 Caused by: Tool exited with exit status: 1 (run with --debug to see the exact invocation) 1654 [EOF] 1655 [exit status: 1] 1656 "); 1657 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 1658 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1659 file1 2-sided conflict 1660 file2 2-sided conflict 1661 [EOF] 1662 "); 1663}