just playing with tangled
at main 2093 lines 72 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 741263c9 conflict | conflict 76 Parent commit (@-) : zsuskuln 45537d53 a | a 77 Parent commit (@-) : royxmykx 89d1b299 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 1f8a36f7 conflict | conflict 118 Parent commit (@-) : zsuskuln 45537d53 a | a 119 Parent commit (@-) : royxmykx 89d1b299 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 0d40d2b8 conflict | (conflict) conflict 218 Parent commit (@-) : zsuskuln 45537d53 a | a 219 Parent commit (@-) : royxmykx 89d1b299 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 0d40d2b8 conflict | (conflict) conflict 225 Hint: To resolve the conflicts, start by creating a commit on top of 226 the conflicted commit: 227 jj new vruxwmqv 228 Then use `jj resolve`, or edit the conflict markers in the file directly. 229 Once the conflicts are resolved, you can inspect the result with `jj diff`. 230 Then run `jj squash` to move the resolution into the conflicted commit. 231 [EOF] 232 "###); 233 insta::assert_snapshot!( 234 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r" 235 <<<<<<< Conflict 1 of 1 236 %%%%%%% Changes from base to side #1 237 -base 238 +a 239 +++++++ Contents of side #2 240 b 241 >>>>>>> Conflict 1 of 1 ends 242 "); 243 // Note the "Modified" below 244 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 245 diff --git a/file b/file 246 --- a/file 247 +++ b/file 248 @@ -1,7 +1,7 @@ 249 <<<<<<< Conflict 1 of 1 250 %%%%%%% Changes from base to side #1 251 --base 252 -+a 253 +-some 254 ++fake 255 +++++++ Contents of side #2 256 -b 257 +conflict 258 >>>>>>> Conflict 1 of 1 ends 259 [EOF] 260 "); 261 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 262 file 2-sided conflict 263 [EOF] 264 "); 265 266 // Check that if merge tool leaves conflict markers in output file but 267 // `merge-tool-edits-conflict-markers=false` or is not specified, 268 // `jj` considers the conflict resolved. 269 work_dir.run_jj(["undo"]).success(); 270 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 271 std::fs::write( 272 &editor_script, 273 [ 274 "dump editor3", 275 indoc! {" 276 write 277 <<<<<<< 278 %%%%%%% 279 -some 280 +fake 281 +++++++ 282 conflict 283 >>>>>>> 284 "}, 285 ] 286 .join("\0"), 287 ) 288 .unwrap(); 289 let output = work_dir.run_jj(["resolve"]); 290 insta::assert_snapshot!(output, @r" 291 ------- stderr ------- 292 Resolving conflicts in: file 293 Working copy (@) now at: vruxwmqv 2cc7f5e3 conflict | conflict 294 Parent commit (@-) : zsuskuln 45537d53 a | a 295 Parent commit (@-) : royxmykx 89d1b299 b | b 296 Added 0 files, modified 1 files, removed 0 files 297 [EOF] 298 "); 299 insta::assert_snapshot!( 300 std::fs::read_to_string(test_env.env_root().join("editor3")).unwrap(), @""); 301 // Note the "Resolved" below 302 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 303 diff --git a/file b/file 304 index 0000000000..0610716cc1 100644 305 --- a/file 306 +++ b/file 307 @@ -1,7 +1,7 @@ 308 -<<<<<<< Conflict 1 of 1 309 -%%%%%%% Changes from base to side #1 310 --base 311 -+a 312 -+++++++ Contents of side #2 313 -b 314 ->>>>>>> Conflict 1 of 1 ends 315 +<<<<<<< 316 +%%%%%%% 317 +-some 318 ++fake 319 ++++++++ 320 +conflict 321 +>>>>>>> 322 [EOF] 323 "); 324 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 325 ------- stderr ------- 326 Error: No conflicts found at this revision 327 [EOF] 328 [exit status: 2] 329 "); 330 331 // Check that merge tool can override conflict marker style setting, and that 332 // the merge tool can output Git-style conflict markers 333 work_dir.run_jj(["undo"]).success(); 334 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 335 std::fs::write( 336 &editor_script, 337 [ 338 "dump editor4", 339 indoc! {" 340 write 341 <<<<<<< 342 some 343 ||||||| 344 fake 345 ======= 346 conflict 347 >>>>>>> 348 "}, 349 ] 350 .join("\0"), 351 ) 352 .unwrap(); 353 let output = work_dir.run_jj([ 354 "resolve", 355 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 356 "--config=merge-tools.fake-editor.conflict-marker-style=git", 357 ]); 358 insta::assert_snapshot!(output, @r###" 359 ------- stderr ------- 360 Resolving conflicts in: file 361 Working copy (@) now at: vruxwmqv d5f058ec conflict | (conflict) conflict 362 Parent commit (@-) : zsuskuln 45537d53 a | a 363 Parent commit (@-) : royxmykx 89d1b299 b | b 364 Added 0 files, modified 1 files, removed 0 files 365 Warning: There are unresolved conflicts at these paths: 366 file 2-sided conflict 367 New conflicts appeared in 1 commits: 368 vruxwmqv d5f058ec conflict | (conflict) conflict 369 Hint: To resolve the conflicts, start by creating a commit on top of 370 the conflicted commit: 371 jj new vruxwmqv 372 Then use `jj resolve`, or edit the conflict markers in the file directly. 373 Once the conflicts are resolved, you can inspect the result with `jj diff`. 374 Then run `jj squash` to move the resolution into the conflicted commit. 375 [EOF] 376 "###); 377 insta::assert_snapshot!( 378 std::fs::read_to_string(test_env.env_root().join("editor4")).unwrap(), @r" 379 <<<<<<< Side #1 (Conflict 1 of 1) 380 a 381 ||||||| Base 382 base 383 ======= 384 b 385 >>>>>>> Side #2 (Conflict 1 of 1 ends) 386 "); 387 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 388 diff --git a/file b/file 389 --- a/file 390 +++ b/file 391 @@ -1,7 +1,7 @@ 392 <<<<<<< Conflict 1 of 1 393 %%%%%%% Changes from base to side #1 394 --base 395 -+a 396 +-fake 397 ++some 398 +++++++ Contents of side #2 399 -b 400 +conflict 401 >>>>>>> Conflict 1 of 1 ends 402 [EOF] 403 "); 404 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 405 file 2-sided conflict 406 [EOF] 407 "); 408 409 // Check that merge tool can leave conflict markers by returning exit code 1 410 // when using `merge-conflict-exit-codes = [1]`. The Git "diff3" conflict 411 // markers should also be parsed correctly. 412 work_dir.run_jj(["undo"]).success(); 413 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 414 std::fs::write( 415 &editor_script, 416 [ 417 "dump editor5", 418 indoc! {" 419 write 420 <<<<<<< 421 some 422 ||||||| 423 fake 424 ======= 425 conflict 426 >>>>>>> 427 "}, 428 "fail", 429 ] 430 .join("\0"), 431 ) 432 .unwrap(); 433 let output = work_dir.run_jj([ 434 "resolve", 435 "--config=merge-tools.fake-editor.merge-conflict-exit-codes=[1]", 436 ]); 437 insta::assert_snapshot!(output, @r###" 438 ------- stderr ------- 439 Resolving conflicts in: file 440 Working copy (@) now at: vruxwmqv 6c205356 conflict | (conflict) conflict 441 Parent commit (@-) : zsuskuln 45537d53 a | a 442 Parent commit (@-) : royxmykx 89d1b299 b | b 443 Added 0 files, modified 1 files, removed 0 files 444 Warning: There are unresolved conflicts at these paths: 445 file 2-sided conflict 446 New conflicts appeared in 1 commits: 447 vruxwmqv 6c205356 conflict | (conflict) conflict 448 Hint: To resolve the conflicts, start by creating a commit on top of 449 the conflicted commit: 450 jj new vruxwmqv 451 Then use `jj resolve`, or edit the conflict markers in the file directly. 452 Once the conflicts are resolved, you can inspect the result with `jj diff`. 453 Then run `jj squash` to move the resolution into the conflicted commit. 454 [EOF] 455 "###); 456 insta::assert_snapshot!( 457 std::fs::read_to_string(test_env.env_root().join("editor5")).unwrap(), @""); 458 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 459 diff --git a/file b/file 460 --- a/file 461 +++ b/file 462 @@ -1,7 +1,7 @@ 463 <<<<<<< Conflict 1 of 1 464 %%%%%%% Changes from base to side #1 465 --base 466 -+a 467 +-fake 468 ++some 469 +++++++ Contents of side #2 470 -b 471 +conflict 472 >>>>>>> Conflict 1 of 1 ends 473 [EOF] 474 "); 475 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 476 file 2-sided conflict 477 [EOF] 478 "); 479 480 // Check that an error is reported if a merge tool indicated it would leave 481 // conflict markers, but the output file didn't contain valid conflict markers. 482 work_dir.run_jj(["undo"]).success(); 483 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 484 std::fs::write( 485 &editor_script, 486 [ 487 indoc! {" 488 write 489 <<<<<<< this isn't diff3 style! 490 some 491 ======= 492 conflict 493 >>>>>>> 494 "}, 495 "fail", 496 ] 497 .join("\0"), 498 ) 499 .unwrap(); 500 let output = work_dir.run_jj([ 501 "resolve", 502 "--config=merge-tools.fake-editor.merge-conflict-exit-codes=[1]", 503 ]); 504 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" 505 ------- stderr ------- 506 Resolving conflicts in: file 507 Error: Failed to resolve conflicts 508 Caused by: Tool exited with exit status: 1, but did not produce valid conflict markers (run with --debug to see the exact invocation) 509 [EOF] 510 [exit status: 1] 511 "); 512 513 // TODO: Check that running `jj new` and then `jj resolve -r conflict` works 514 // correctly. 515} 516 517fn check_resolve_produces_input_file( 518 test_env: &mut TestEnvironment, 519 root: impl AsRef<Path>, 520 filename: &str, 521 role: &str, 522 expected_content: &str, 523) { 524 let editor_script = test_env.set_up_fake_editor(); 525 let work_dir = test_env.work_dir(root); 526 std::fs::write(editor_script, format!("expect\n{expected_content}")).unwrap(); 527 528 let merge_arg_config = format!(r#"merge-tools.fake-editor.merge-args=["${role}"]"#); 529 // This error means that fake-editor exited successfully but did not modify the 530 // output file. 531 let output = work_dir.run_jj(["resolve", "--config", &merge_arg_config, filename]); 532 insta::allow_duplicates! { 533 insta::assert_snapshot!( 534 output.normalize_stderr_with(|s| s.replacen(filename, "$FILENAME", 1)), @r" 535 ------- stderr ------- 536 Resolving conflicts in: $FILENAME 537 Error: Failed to resolve conflicts 538 Caused by: The output file is either unchanged or empty after the editor quit (run with --debug to see the exact invocation). 539 [EOF] 540 [exit status: 1] 541 "); 542 } 543} 544 545#[test] 546fn test_normal_conflict_input_files() { 547 let mut test_env = TestEnvironment::default(); 548 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 549 let work_dir = test_env.work_dir("repo"); 550 551 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 552 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 553 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]); 554 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 555 // Test the setup 556 insta::assert_snapshot!(get_log_output(&work_dir), @r" 557 @ conflict 558 ├─╮ 559 │ ○ b 560 ○ │ a 561 ├─╯ 562 ○ base 563 564 [EOF] 565 "); 566 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 567 file 2-sided conflict 568 [EOF] 569 "); 570 insta::assert_snapshot!(work_dir.read_file("file"), @r" 571 <<<<<<< Conflict 1 of 1 572 %%%%%%% Changes from base to side #1 573 -base 574 +a 575 +++++++ Contents of side #2 576 b 577 >>>>>>> Conflict 1 of 1 ends 578 "); 579 580 check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n"); 581 check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n"); 582 check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", "b\n"); 583} 584 585#[test] 586fn test_baseless_conflict_input_files() { 587 let mut test_env = TestEnvironment::default(); 588 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 589 let work_dir = test_env.work_dir("repo"); 590 591 create_commit_with_files(&work_dir, "base", &[], &[]); 592 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 593 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]); 594 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 595 // Test the setup 596 insta::assert_snapshot!(get_log_output(&work_dir), @r" 597 @ conflict 598 ├─╮ 599 │ ○ b 600 ○ │ a 601 ├─╯ 602 ○ base 603 604 [EOF] 605 "); 606 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 607 file 2-sided conflict 608 [EOF] 609 "); 610 insta::assert_snapshot!(work_dir.read_file("file"), @r" 611 <<<<<<< Conflict 1 of 1 612 %%%%%%% Changes from base to side #1 613 +a 614 +++++++ Contents of side #2 615 b 616 >>>>>>> Conflict 1 of 1 ends 617 "); 618 619 check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", ""); 620 check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n"); 621 check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", "b\n"); 622} 623 624#[test] 625fn test_too_many_parents() { 626 let test_env = TestEnvironment::default(); 627 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 628 let work_dir = test_env.work_dir("repo"); 629 630 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 631 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 632 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]); 633 create_commit_with_files(&work_dir, "c", &["base"], &[("file", "c\n")]); 634 create_commit_with_files(&work_dir, "conflict", &["a", "b", "c"], &[]); 635 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 636 file 3-sided conflict 637 [EOF] 638 "); 639 // Test warning color 640 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r" 641 file 3-sided conflict 642 [EOF] 643 "); 644 645 let output = work_dir.run_jj(["resolve"]); 646 insta::assert_snapshot!(output, @r#" 647 ------- stderr ------- 648 Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message. 649 Error: Failed to resolve conflicts 650 Caused by: The conflict at "file" has 3 sides. At most 2 sides are supported. 651 Hint: Edit the conflict markers manually to resolve this. 652 [EOF] 653 [exit status: 1] 654 "#); 655} 656 657#[test] 658fn test_simplify_conflict_sides() { 659 let mut test_env = TestEnvironment::default(); 660 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 661 let work_dir = test_env.work_dir("repo"); 662 663 // Creates a 4-sided conflict, with fileA and fileB having different conflicts: 664 // fileA: A - B + C - B + B - B + B 665 // fileB: A - A + A - A + B - C + D 666 create_commit_with_files( 667 &work_dir, 668 "base", 669 &[], 670 &[("fileA", "base\n"), ("fileB", "base\n")], 671 ); 672 create_commit_with_files(&work_dir, "a1", &["base"], &[("fileA", "1\n")]); 673 create_commit_with_files(&work_dir, "a2", &["base"], &[("fileA", "2\n")]); 674 create_commit_with_files(&work_dir, "b1", &["base"], &[("fileB", "1\n")]); 675 create_commit_with_files(&work_dir, "b2", &["base"], &[("fileB", "2\n")]); 676 create_commit_with_files(&work_dir, "conflictA", &["a1", "a2"], &[]); 677 create_commit_with_files(&work_dir, "conflictB", &["b1", "b2"], &[]); 678 create_commit_with_files(&work_dir, "conflict", &["conflictA", "conflictB"], &[]); 679 680 // Even though the tree-level conflict is a 4-sided conflict, each file is 681 // materialized as a 2-sided conflict. 682 insta::assert_snapshot!(work_dir.run_jj(["debug", "tree"]), @r#" 683 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 })])) 684 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 })])) 685 [EOF] 686 "#); 687 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 688 fileA 2-sided conflict 689 fileB 2-sided conflict 690 [EOF] 691 "); 692 insta::assert_snapshot!(work_dir.read_file("fileA"), @r" 693 <<<<<<< Conflict 1 of 1 694 %%%%%%% Changes from base to side #1 695 -base 696 +1 697 +++++++ Contents of side #2 698 2 699 >>>>>>> Conflict 1 of 1 ends 700 "); 701 insta::assert_snapshot!(work_dir.read_file("fileB"), @r" 702 <<<<<<< Conflict 1 of 1 703 %%%%%%% Changes from base to side #1 704 -base 705 +1 706 +++++++ Contents of side #2 707 2 708 >>>>>>> Conflict 1 of 1 ends 709 "); 710 711 // Conflict should be simplified before being handled by external merge tool. 712 check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "base", "base\n"); 713 check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "left", "1\n"); 714 check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "right", "2\n"); 715 check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "base", "base\n"); 716 check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "left", "1\n"); 717 check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "right", "2\n"); 718 719 // Check that simplified conflicts are still parsed as conflicts after editing 720 // when `merge-tool-edits-conflict-markers=true`. 721 let editor_script = test_env.set_up_fake_editor(); 722 std::fs::write( 723 editor_script, 724 indoc! {" 725 write 726 <<<<<<< Conflict 1 of 1 727 %%%%%%% Changes from base to side #1 728 -base_edited 729 +1_edited 730 +++++++ Contents of side #2 731 2_edited 732 >>>>>>> Conflict 1 of 1 ends 733 "}, 734 ) 735 .unwrap(); 736 let work_dir = test_env.work_dir("repo"); 737 let output = work_dir.run_jj([ 738 "resolve", 739 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 740 "fileB", 741 ]); 742 insta::assert_snapshot!(output, @r###" 743 ------- stderr ------- 744 Resolving conflicts in: fileB 745 Working copy (@) now at: nkmrtpmo 25c5dd0b conflict | (conflict) conflict 746 Parent commit (@-) : kmkuslsw ccb05364 conflictA | (conflict) (empty) conflictA 747 Parent commit (@-) : lylxulpl d9bc60cb conflictB | (conflict) (empty) conflictB 748 Added 0 files, modified 1 files, removed 0 files 749 Warning: There are unresolved conflicts at these paths: 750 fileA 2-sided conflict 751 fileB 2-sided conflict 752 New conflicts appeared in 1 commits: 753 nkmrtpmo 25c5dd0b conflict | (conflict) conflict 754 Hint: To resolve the conflicts, start by creating a commit on top of 755 the conflicted commit: 756 jj new nkmrtpmo 757 Then use `jj resolve`, or edit the conflict markers in the file directly. 758 Once the conflicts are resolved, you can inspect the result with `jj diff`. 759 Then run `jj squash` to move the resolution into the conflicted commit. 760 [EOF] 761 "###); 762 insta::assert_snapshot!(work_dir.read_file("fileB"), @r" 763 <<<<<<< Conflict 1 of 1 764 %%%%%%% Changes from base to side #1 765 -base_edited 766 +1_edited 767 +++++++ Contents of side #2 768 2_edited 769 >>>>>>> Conflict 1 of 1 ends 770 "); 771 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 772 fileA 2-sided conflict 773 fileB 2-sided conflict 774 [EOF] 775 "); 776} 777 778#[test] 779fn test_edit_delete_conflict_input_files() { 780 let mut test_env = TestEnvironment::default(); 781 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 782 let work_dir = test_env.work_dir("repo"); 783 784 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 785 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 786 create_commit_with_files(&work_dir, "b", &["base"], &[]); 787 work_dir.remove_file("file"); 788 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 789 // Test the setup 790 insta::assert_snapshot!(get_log_output(&work_dir), @r" 791 @ conflict 792 ├─╮ 793 │ ○ b 794 ○ │ a 795 ├─╯ 796 ○ base 797 798 [EOF] 799 "); 800 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 801 file 2-sided conflict including 1 deletion 802 [EOF] 803 "); 804 insta::assert_snapshot!(work_dir.read_file("file"), @r" 805 <<<<<<< Conflict 1 of 1 806 +++++++ Contents of side #1 807 a 808 %%%%%%% Changes from base to side #2 809 -base 810 >>>>>>> Conflict 1 of 1 ends 811 "); 812 813 check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n"); 814 check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n"); 815 check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", ""); 816} 817 818#[test] 819fn test_file_vs_dir() { 820 let test_env = TestEnvironment::default(); 821 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 822 let work_dir = test_env.work_dir("repo"); 823 824 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 825 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]); 826 create_commit_with_files(&work_dir, "b", &["base"], &[]); 827 work_dir.remove_file("file"); 828 work_dir.create_dir("file"); 829 // Without a placeholder file, `jj` ignores an empty directory 830 work_dir.write_file("file/placeholder", ""); 831 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 832 insta::assert_snapshot!(get_log_output(&work_dir), @r" 833 @ conflict 834 ├─╮ 835 │ ○ b 836 ○ │ a 837 ├─╯ 838 ○ base 839 840 [EOF] 841 "); 842 843 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 844 file 2-sided conflict including a directory 845 [EOF] 846 "); 847 let output = work_dir.run_jj(["resolve"]); 848 insta::assert_snapshot!(output, @r#" 849 ------- stderr ------- 850 Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message. 851 Error: Failed to resolve conflicts 852 Caused by: Only conflicts that involve normal files (not symlinks, etc.) are supported. Conflict summary for "file": 853 Conflict: 854 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55 855 Adding file with id 78981922613b2afb6025042ff6bd878ac1994e85 856 Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877 857 [EOF] 858 [exit status: 1] 859 "#); 860} 861 862#[test] 863fn test_description_with_dir_and_deletion() { 864 let test_env = TestEnvironment::default(); 865 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 866 let work_dir = test_env.work_dir("repo"); 867 868 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]); 869 create_commit_with_files(&work_dir, "edit", &["base"], &[("file", "b\n")]); 870 create_commit_with_files(&work_dir, "dir", &["base"], &[]); 871 work_dir.remove_file("file"); 872 work_dir.create_dir("file"); 873 // Without a placeholder file, `jj` ignores an empty directory 874 work_dir.write_file("file/placeholder", ""); 875 create_commit_with_files(&work_dir, "del", &["base"], &[]); 876 work_dir.remove_file("file"); 877 create_commit_with_files(&work_dir, "conflict", &["edit", "dir", "del"], &[]); 878 insta::assert_snapshot!(get_log_output(&work_dir), @r" 879 @ conflict 880 ├─┬─╮ 881 │ │ ○ del 882 │ ○ │ dir 883 │ ├─╯ 884 ○ │ edit 885 ├─╯ 886 ○ base 887 888 [EOF] 889 "); 890 891 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 892 file 3-sided conflict including 1 deletion and a directory 893 [EOF] 894 "); 895 // Test warning color. The deletion is fine, so it's not highlighted 896 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r" 897 file 3-sided conflict including 1 deletion and a directory 898 [EOF] 899 "); 900 let output = work_dir.run_jj(["resolve"]); 901 insta::assert_snapshot!(output, @r#" 902 ------- stderr ------- 903 Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message. 904 Error: Failed to resolve conflicts 905 Caused by: Only conflicts that involve normal files (not symlinks, etc.) are supported. Conflict summary for "file": 906 Conflict: 907 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55 908 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55 909 Adding file with id 61780798228d17af2d34fce4cfbdf35556832472 910 Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877 911 [EOF] 912 [exit status: 1] 913 "#); 914} 915 916#[test] 917fn test_resolve_conflicts_with_executable() { 918 let mut test_env = TestEnvironment::default(); 919 let editor_script = test_env.set_up_fake_editor(); 920 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 921 let work_dir = test_env.work_dir("repo"); 922 923 // Create a conflict in "file1" where all 3 terms are executables, and create a 924 // conflict in "file2" where one side set the executable bit. 925 create_commit_with_files( 926 &work_dir, 927 "base", 928 &[], 929 &[("file1", "base1\n"), ("file2", "base2\n")], 930 ); 931 work_dir.run_jj(["file", "chmod", "x", "file1"]).success(); 932 create_commit_with_files( 933 &work_dir, 934 "a", 935 &["base"], 936 &[("file1", "a1\n"), ("file2", "a2\n")], 937 ); 938 create_commit_with_files( 939 &work_dir, 940 "b", 941 &["base"], 942 &[("file1", "b1\n"), ("file2", "b2\n")], 943 ); 944 work_dir.run_jj(["file", "chmod", "x", "file2"]).success(); 945 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 946 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 947 file1 2-sided conflict including an executable 948 file2 2-sided conflict including an executable 949 [EOF] 950 "); 951 insta::assert_snapshot!(work_dir.read_file("file1"), @r" 952 <<<<<<< Conflict 1 of 1 953 %%%%%%% Changes from base to side #1 954 -base1 955 +a1 956 +++++++ Contents of side #2 957 b1 958 >>>>>>> Conflict 1 of 1 ends 959 " 960 ); 961 insta::assert_snapshot!(work_dir.read_file("file2"), @r" 962 <<<<<<< Conflict 1 of 1 963 %%%%%%% Changes from base to side #1 964 -base2 965 +a2 966 +++++++ Contents of side #2 967 b2 968 >>>>>>> Conflict 1 of 1 ends 969 " 970 ); 971 972 // Test resolving the conflict in "file1", which should produce an executable 973 std::fs::write(&editor_script, b"write\nresolution1\n").unwrap(); 974 let output = work_dir.run_jj(["resolve", "file1"]); 975 insta::assert_snapshot!(output, @r###" 976 ------- stderr ------- 977 Resolving conflicts in: file1 978 Working copy (@) now at: znkkpsqq 8ab9c54e conflict | (conflict) conflict 979 Parent commit (@-) : mzvwutvl 86f7f0e3 a | a 980 Parent commit (@-) : yqosqzyt 36361412 b | b 981 Added 0 files, modified 1 files, removed 0 files 982 Warning: There are unresolved conflicts at these paths: 983 file2 2-sided conflict including an executable 984 New conflicts appeared in 1 commits: 985 znkkpsqq 8ab9c54e conflict | (conflict) conflict 986 Hint: To resolve the conflicts, start by creating a commit on top of 987 the conflicted commit: 988 jj new znkkpsqq 989 Then use `jj resolve`, or edit the conflict markers in the file directly. 990 Once the conflicts are resolved, you can inspect the result with `jj diff`. 991 Then run `jj squash` to move the resolution into the conflicted commit. 992 [EOF] 993 "###); 994 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 995 diff --git a/file1 b/file1 996 index 0000000000..95cc18629d 100755 997 --- a/file1 998 +++ b/file1 999 @@ -1,7 +1,1 @@ 1000 -<<<<<<< Conflict 1 of 1 1001 -%%%%%%% Changes from base to side #1 1002 --base1 1003 -+a1 1004 -+++++++ Contents of side #2 1005 -b1 1006 ->>>>>>> Conflict 1 of 1 ends 1007 +resolution1 1008 [EOF] 1009 "); 1010 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1011 file2 2-sided conflict including an executable 1012 [EOF] 1013 "); 1014 1015 // Test resolving the conflict in "file2", which should produce an executable 1016 work_dir.run_jj(["undo"]).success(); 1017 std::fs::write(&editor_script, b"write\nresolution2\n").unwrap(); 1018 let output = work_dir.run_jj(["resolve", "file2"]); 1019 insta::assert_snapshot!(output, @r###" 1020 ------- stderr ------- 1021 Resolving conflicts in: file2 1022 Working copy (@) now at: znkkpsqq d47830a6 conflict | (conflict) conflict 1023 Parent commit (@-) : mzvwutvl 86f7f0e3 a | a 1024 Parent commit (@-) : yqosqzyt 36361412 b | b 1025 Added 0 files, modified 1 files, removed 0 files 1026 Warning: There are unresolved conflicts at these paths: 1027 file1 2-sided conflict including an executable 1028 New conflicts appeared in 1 commits: 1029 znkkpsqq d47830a6 conflict | (conflict) conflict 1030 Hint: To resolve the conflicts, start by creating a commit on top of 1031 the conflicted commit: 1032 jj new znkkpsqq 1033 Then use `jj resolve`, or edit the conflict markers in the file directly. 1034 Once the conflicts are resolved, you can inspect the result with `jj diff`. 1035 Then run `jj squash` to move the resolution into the conflicted commit. 1036 [EOF] 1037 "###); 1038 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1039 diff --git a/file2 b/file2 1040 index 0000000000..775f078581 100755 1041 --- a/file2 1042 +++ b/file2 1043 @@ -1,7 +1,1 @@ 1044 -<<<<<<< Conflict 1 of 1 1045 -%%%%%%% Changes from base to side #1 1046 --base2 1047 -+a2 1048 -+++++++ Contents of side #2 1049 -b2 1050 ->>>>>>> Conflict 1 of 1 ends 1051 +resolution2 1052 [EOF] 1053 "); 1054 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1055 file1 2-sided conflict including an executable 1056 [EOF] 1057 "); 1058 1059 // Pick "our" contents, but merges executable bits 1060 work_dir.run_jj(["undo"]).success(); 1061 let output = work_dir.run_jj(["resolve", "--tool=:ours"]); 1062 insta::assert_snapshot!(output, @r" 1063 ------- stderr ------- 1064 Working copy (@) now at: znkkpsqq d902c14b conflict | conflict 1065 Parent commit (@-) : mzvwutvl 86f7f0e3 a | a 1066 Parent commit (@-) : yqosqzyt 36361412 b | b 1067 Added 0 files, modified 2 files, removed 0 files 1068 [EOF] 1069 "); 1070 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1071 diff --git a/file1 b/file1 1072 index 0000000000..da0f8ed91a 100755 1073 --- a/file1 1074 +++ b/file1 1075 @@ -1,7 +1,1 @@ 1076 -<<<<<<< Conflict 1 of 1 1077 -%%%%%%% Changes from base to side #1 1078 --base1 1079 -+a1 1080 -+++++++ Contents of side #2 1081 -b1 1082 ->>>>>>> Conflict 1 of 1 ends 1083 +a1 1084 diff --git a/file2 b/file2 1085 index 0000000000..c1827f07e1 100755 1086 --- a/file2 1087 +++ b/file2 1088 @@ -1,7 +1,1 @@ 1089 -<<<<<<< Conflict 1 of 1 1090 -%%%%%%% Changes from base to side #1 1091 --base2 1092 -+a2 1093 -+++++++ Contents of side #2 1094 -b2 1095 ->>>>>>> Conflict 1 of 1 ends 1096 +a2 1097 [EOF] 1098 "); 1099 1100 // Pick "their" contents, but merges executable bits 1101 work_dir.run_jj(["undo"]).success(); 1102 let output = work_dir.run_jj(["resolve", "--tool=:theirs"]); 1103 insta::assert_snapshot!(output, @r" 1104 ------- stderr ------- 1105 Working copy (@) now at: znkkpsqq a340ca5f conflict | conflict 1106 Parent commit (@-) : mzvwutvl 86f7f0e3 a | a 1107 Parent commit (@-) : yqosqzyt 36361412 b | b 1108 Added 0 files, modified 2 files, removed 0 files 1109 [EOF] 1110 "); 1111 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1112 diff --git a/file1 b/file1 1113 index 0000000000..c9c6af7f78 100755 1114 --- a/file1 1115 +++ b/file1 1116 @@ -1,7 +1,1 @@ 1117 -<<<<<<< Conflict 1 of 1 1118 -%%%%%%% Changes from base to side #1 1119 --base1 1120 -+a1 1121 -+++++++ Contents of side #2 1122 b1 1123 ->>>>>>> Conflict 1 of 1 ends 1124 diff --git a/file2 b/file2 1125 index 0000000000..e6bfff5c1d 100755 1126 --- a/file2 1127 +++ b/file2 1128 @@ -1,7 +1,1 @@ 1129 -<<<<<<< Conflict 1 of 1 1130 -%%%%%%% Changes from base to side #1 1131 --base2 1132 -+a2 1133 -+++++++ Contents of side #2 1134 b2 1135 ->>>>>>> Conflict 1 of 1 ends 1136 [EOF] 1137 "); 1138} 1139 1140#[test] 1141fn test_resolve_change_delete_executable() { 1142 let mut test_env = TestEnvironment::default(); 1143 let editor_script = test_env.set_up_fake_editor(); 1144 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1145 let work_dir = test_env.work_dir("repo"); 1146 1147 let file_template = 1148 r#"separate(' ', path, if(conflict, "c", "-"), if(executable, "x", "-")) ++ "\n""#; 1149 let file_list = |path: &str| work_dir.run_jj(["file", "list", "-T", file_template, path]); 1150 1151 // base a b 1152 // file1: normal -> { exec, absent } 1153 // file2: exec -> { absent, normal } (with content change) 1154 // file3: absent -> { normal, exec } 1155 // file4: normal -> { normal, absent } (with content change) 1156 // file5: exec -> { absent, exec } (with content change) 1157 create_commit_with_files( 1158 &work_dir, 1159 "base", 1160 &[], 1161 &[("file1", ""), ("file2", ""), ("file4", ""), ("file5", "")], 1162 ); 1163 work_dir 1164 .run_jj(["file", "chmod", "x", "file2", "file5"]) 1165 .success(); 1166 create_commit_with_files( 1167 &work_dir, 1168 "a", 1169 &["base"], 1170 &[("file1", ""), ("file3", ""), ("file4", "a4\n")], 1171 ); 1172 work_dir.remove_file("file2"); 1173 work_dir.remove_file("file5"); 1174 work_dir.run_jj(["file", "chmod", "x", "file1"]).success(); 1175 create_commit_with_files( 1176 &work_dir, 1177 "b", 1178 &["base"], 1179 &[("file2", "b2\n"), ("file3", ""), ("file5", "b5\n")], 1180 ); 1181 work_dir.remove_file("file1"); 1182 work_dir.remove_file("file4"); 1183 work_dir.run_jj(["file", "chmod", "n", "file2"]).success(); 1184 work_dir.run_jj(["file", "chmod", "x", "file3"]).success(); 1185 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 1186 1187 // Test the setup 1188 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1189 file1 2-sided conflict including 1 deletion and an executable 1190 file2 2-sided conflict including 1 deletion and an executable 1191 file3 2-sided conflict including an executable 1192 file4 2-sided conflict including 1 deletion 1193 file5 2-sided conflict including 1 deletion and an executable 1194 [EOF] 1195 "); 1196 insta::assert_snapshot!(file_list("all()"), @r" 1197 file1 c - 1198 file2 c - 1199 file3 c - 1200 file4 c - 1201 file5 c x 1202 [EOF] 1203 "); 1204 insta::assert_snapshot!(work_dir.run_jj(["log", "--git"]), @r" 1205 @ kmkuslsw test.user@example.com 2001-02-03 08:05:18 conflict 7a7ac759 conflict 1206 ├─╮ (empty) conflict 1207 │ ○ vruxwmqv test.user@example.com 2001-02-03 08:05:17 b 888b6cc3 1208 │ │ b 1209 │ │ diff --git a/file1 b/file1 1210 │ │ deleted file mode 100644 1211 │ │ index e69de29bb2..0000000000 1212 │ │ diff --git a/file2 b/file2 1213 │ │ old mode 100755 1214 │ │ new mode 100644 1215 │ │ index e69de29bb2..e6bfff5c1d 1216 │ │ --- a/file2 1217 │ │ +++ b/file2 1218 │ │ @@ -0,0 +1,1 @@ 1219 │ │ +b2 1220 │ │ diff --git a/file3 b/file3 1221 │ │ new file mode 100755 1222 │ │ index 0000000000..e69de29bb2 1223 │ │ diff --git a/file4 b/file4 1224 │ │ deleted file mode 100644 1225 │ │ index e69de29bb2..0000000000 1226 │ │ diff --git a/file5 b/file5 1227 │ │ index e69de29bb2..90a5159bf0 100755 1228 │ │ --- a/file5 1229 │ │ +++ b/file5 1230 │ │ @@ -0,0 +1,1 @@ 1231 │ │ +b5 1232 ○ │ mzvwutvl test.user@example.com 2001-02-03 08:05:13 a e2d3924b 1233 ├─╯ a 1234 │ diff --git a/file1 b/file1 1235 │ old mode 100644 1236 │ new mode 100755 1237 │ diff --git a/file2 b/file2 1238 │ deleted file mode 100755 1239 │ index e69de29bb2..0000000000 1240 │ diff --git a/file3 b/file3 1241 │ new file mode 100644 1242 │ index 0000000000..e69de29bb2 1243 │ diff --git a/file4 b/file4 1244 │ index e69de29bb2..88ba23dca8 100644 1245 │ --- a/file4 1246 │ +++ b/file4 1247 │ @@ -0,0 +1,1 @@ 1248 │ +a4 1249 │ diff --git a/file5 b/file5 1250 │ deleted file mode 100755 1251 │ index e69de29bb2..0000000000 1252 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 base f747aa1f 1253 │ base 1254 │ diff --git a/file1 b/file1 1255 │ new file mode 100644 1256 │ index 0000000000..e69de29bb2 1257 │ diff --git a/file2 b/file2 1258 │ new file mode 100755 1259 │ index 0000000000..e69de29bb2 1260 │ diff --git a/file4 b/file4 1261 │ new file mode 100644 1262 │ index 0000000000..e69de29bb2 1263 │ diff --git a/file5 b/file5 1264 │ new file mode 100755 1265 │ index 0000000000..e69de29bb2 1266 ◆ zzzzzzzz root() 00000000 1267 [EOF] 1268 "); 1269 1270 // Exec bit conflict can be resolved by chmod 1271 let output = work_dir.run_jj(["resolve", "file1"]); 1272 insta::assert_snapshot!(output, @r#" 1273 ------- stderr ------- 1274 Error: Failed to resolve conflicts 1275 Caused by: "file1" has conflicts in executable bit 1276 Conflict: 1277 Removing file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1278 Adding executable file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1279 Hint: Use `jj file chmod` to update the executable bit. 1280 [EOF] 1281 [exit status: 1] 1282 "#); 1283 let output = work_dir.run_jj(["file", "chmod", "--quiet", "x", "file1"]); 1284 insta::assert_snapshot!(output, @""); 1285 1286 // Exec bit conflict can be resolved by chmod, then content conflict 1287 let output = work_dir.run_jj(["resolve", "file2"]); 1288 insta::assert_snapshot!(output, @r#" 1289 ------- stderr ------- 1290 Error: Failed to resolve conflicts 1291 Caused by: "file2" has conflicts in executable bit 1292 Conflict: 1293 Removing executable file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1294 Adding file with id e6bfff5c1d0f0ecd501552b43a1e13d8008abc31 1295 Hint: Use `jj file chmod` to update the executable bit. 1296 [EOF] 1297 [exit status: 1] 1298 "#); 1299 let output = work_dir.run_jj(["file", "chmod", "--quiet", "n", "file2"]); 1300 insta::assert_snapshot!(output, @""); 1301 std::fs::write(&editor_script, "write\nresolved\n").unwrap(); 1302 let output = work_dir.run_jj(["resolve", "file2"]); 1303 insta::assert_snapshot!(output, @r" 1304 ------- stderr ------- 1305 Resolving conflicts in: file2 1306 Working copy (@) now at: kmkuslsw 1323520e conflict | (conflict) conflict 1307 Parent commit (@-) : mzvwutvl e2d3924b a | a 1308 Parent commit (@-) : vruxwmqv 888b6cc3 b | b 1309 Added 0 files, modified 1 files, removed 0 files 1310 Warning: There are unresolved conflicts at these paths: 1311 file3 2-sided conflict including an executable 1312 file4 2-sided conflict including 1 deletion 1313 file5 2-sided conflict including 1 deletion and an executable 1314 [EOF] 1315 "); 1316 1317 // Exec bit conflict can be resolved by chmod 1318 let output = work_dir.run_jj(["resolve", "file3"]); 1319 insta::assert_snapshot!(output, @r#" 1320 ------- stderr ------- 1321 Error: Failed to resolve conflicts 1322 Caused by: "file3" has conflicts in executable bit 1323 Conflict: 1324 Adding file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1325 Adding executable file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1326 Hint: Use `jj file chmod` to update the executable bit. 1327 [EOF] 1328 [exit status: 1] 1329 "#); 1330 let output = work_dir.run_jj(["file", "chmod", "--quiet", "x", "file3"]); 1331 insta::assert_snapshot!(output, @""); 1332 1333 // Take modified content, the executable bit should be kept as "-" 1334 let output = work_dir.run_jj(["resolve", "file4", "--tool=:ours"]); 1335 insta::assert_snapshot!(output, @r" 1336 ------- stderr ------- 1337 Working copy (@) now at: kmkuslsw 630e8689 conflict | (conflict) conflict 1338 Parent commit (@-) : mzvwutvl e2d3924b a | a 1339 Parent commit (@-) : vruxwmqv 888b6cc3 b | b 1340 Added 0 files, modified 1 files, removed 0 files 1341 Warning: There are unresolved conflicts at these paths: 1342 file5 2-sided conflict including 1 deletion and an executable 1343 [EOF] 1344 "); 1345 1346 // Take modified content, the executable bit should be kept as "x" 1347 let output = work_dir.run_jj(["resolve", "file5", "--tool=:theirs"]); 1348 insta::assert_snapshot!(output, @r" 1349 ------- stderr ------- 1350 Working copy (@) now at: kmkuslsw 7337267a conflict | conflict 1351 Parent commit (@-) : mzvwutvl e2d3924b a | a 1352 Parent commit (@-) : vruxwmqv 888b6cc3 b | b 1353 Added 0 files, modified 1 files, removed 0 files 1354 Existing conflicts were resolved or abandoned from 1 commits. 1355 [EOF] 1356 "); 1357 1358 insta::assert_snapshot!(file_list("all()"), @r" 1359 file2 - - 1360 file3 - x 1361 file4 - - 1362 file5 - x 1363 [EOF] 1364 "); 1365} 1366 1367#[test] 1368fn test_resolve_long_conflict_markers() { 1369 let mut test_env = TestEnvironment::default(); 1370 let editor_script = test_env.set_up_fake_editor(); 1371 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1372 let work_dir = test_env.work_dir("repo"); 1373 1374 // Makes it easier to read the diffs between conflicts 1375 test_env.add_config("ui.conflict-marker-style = 'snapshot'"); 1376 1377 // Create a conflict which requires long conflict markers to be materialized 1378 create_commit_with_files(&work_dir, "base", &[], &[("file", "======= base\n")]); 1379 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "<<<<<<< a\n")]); 1380 create_commit_with_files(&work_dir, "b", &["base"], &[("file", ">>>>>>> b\n")]); 1381 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 1382 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1383 file 2-sided conflict 1384 [EOF] 1385 "); 1386 insta::assert_snapshot!(work_dir.read_file("file"), @r" 1387 <<<<<<<<<<< Conflict 1 of 1 1388 +++++++++++ Contents of side #1 1389 <<<<<<< a 1390 ----------- Contents of base 1391 ======= base 1392 +++++++++++ Contents of side #2 1393 >>>>>>> b 1394 >>>>>>>>>>> Conflict 1 of 1 ends 1395 " 1396 ); 1397 // Allow signaling that conflict markers were produced even if not editing 1398 // conflict markers materialized in the output file 1399 test_env.add_config("merge-tools.fake-editor.merge-conflict-exit-codes = [1]"); 1400 1401 // By default, conflict markers of length 7 or longer are parsed for 1402 // compatibility with Git merge tools 1403 std::fs::write( 1404 &editor_script, 1405 indoc! {b" 1406 write 1407 <<<<<<< 1408 A 1409 ||||||| 1410 BASE 1411 ======= 1412 B 1413 >>>>>>> 1414 \0fail 1415 "}, 1416 ) 1417 .unwrap(); 1418 let output = work_dir.run_jj(["resolve"]); 1419 insta::assert_snapshot!(output, @r###" 1420 ------- stderr ------- 1421 Resolving conflicts in: file 1422 Working copy (@) now at: vruxwmqv 1e254ee3 conflict | (conflict) conflict 1423 Parent commit (@-) : zsuskuln 10d994ef a | a 1424 Parent commit (@-) : royxmykx 7f215575 b | b 1425 Added 0 files, modified 1 files, removed 0 files 1426 Warning: There are unresolved conflicts at these paths: 1427 file 2-sided conflict 1428 New conflicts appeared in 1 commits: 1429 vruxwmqv 1e254ee3 conflict | (conflict) conflict 1430 Hint: To resolve the conflicts, start by creating a commit on top of 1431 the conflicted commit: 1432 jj new vruxwmqv 1433 Then use `jj resolve`, or edit the conflict markers in the file directly. 1434 Once the conflicts are resolved, you can inspect the result with `jj diff`. 1435 Then run `jj squash` to move the resolution into the conflicted commit. 1436 [EOF] 1437 "###); 1438 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1439 diff --git a/file b/file 1440 --- a/file 1441 +++ b/file 1442 @@ -1,8 +1,8 @@ 1443 -<<<<<<<<<<< Conflict 1 of 1 1444 -+++++++++++ Contents of side #1 1445 -<<<<<<< a 1446 ------------ Contents of base 1447 -======= base 1448 -+++++++++++ Contents of side #2 1449 ->>>>>>> b 1450 ->>>>>>>>>>> Conflict 1 of 1 ends 1451 +<<<<<<< Conflict 1 of 1 1452 ++++++++ Contents of side #1 1453 +A 1454 +------- Contents of base 1455 +BASE 1456 ++++++++ Contents of side #2 1457 +B 1458 +>>>>>>> Conflict 1 of 1 ends 1459 [EOF] 1460 "); 1461 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1462 file 2-sided conflict 1463 [EOF] 1464 "); 1465 1466 // If the merge tool edits the output file with materialized markers, the 1467 // markers must match the length of the materialized markers to be parsed 1468 work_dir.run_jj(["undo"]).success(); 1469 std::fs::write( 1470 &editor_script, 1471 indoc! {b" 1472 dump editor 1473 \0write 1474 <<<<<<<<<<< 1475 <<<<<<< A 1476 ||||||||||| 1477 ======= BASE 1478 =========== 1479 >>>>>>> B 1480 >>>>>>>>>>> 1481 \0fail 1482 "}, 1483 ) 1484 .unwrap(); 1485 let output = work_dir.run_jj([ 1486 "resolve", 1487 "--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true", 1488 ]); 1489 insta::assert_snapshot!(output, @r###" 1490 ------- stderr ------- 1491 Resolving conflicts in: file 1492 Working copy (@) now at: vruxwmqv 2481a401 conflict | (conflict) conflict 1493 Parent commit (@-) : zsuskuln 10d994ef a | a 1494 Parent commit (@-) : royxmykx 7f215575 b | b 1495 Added 0 files, modified 1 files, removed 0 files 1496 Warning: There are unresolved conflicts at these paths: 1497 file 2-sided conflict 1498 New conflicts appeared in 1 commits: 1499 vruxwmqv 2481a401 conflict | (conflict) conflict 1500 Hint: To resolve the conflicts, start by creating a commit on top of 1501 the conflicted commit: 1502 jj new vruxwmqv 1503 Then use `jj resolve`, or edit the conflict markers in the file directly. 1504 Once the conflicts are resolved, you can inspect the result with `jj diff`. 1505 Then run `jj squash` to move the resolution into the conflicted commit. 1506 [EOF] 1507 "###); 1508 insta::assert_snapshot!( 1509 std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r" 1510 <<<<<<<<<<< Conflict 1 of 1 1511 +++++++++++ Contents of side #1 1512 <<<<<<< a 1513 ----------- Contents of base 1514 ======= base 1515 +++++++++++ Contents of side #2 1516 >>>>>>> b 1517 >>>>>>>>>>> Conflict 1 of 1 ends 1518 "); 1519 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1520 diff --git a/file b/file 1521 --- a/file 1522 +++ b/file 1523 @@ -1,8 +1,8 @@ 1524 <<<<<<<<<<< Conflict 1 of 1 1525 +++++++++++ Contents of side #1 1526 -<<<<<<< a 1527 +<<<<<<< A 1528 ----------- Contents of base 1529 -======= base 1530 +======= BASE 1531 +++++++++++ Contents of side #2 1532 ->>>>>>> b 1533 +>>>>>>> B 1534 >>>>>>>>>>> Conflict 1 of 1 ends 1535 [EOF] 1536 "); 1537 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1538 file 2-sided conflict 1539 [EOF] 1540 "); 1541 1542 // If the merge tool accepts the marker length as an argument, then the conflict 1543 // markers should be at least as long as "$marker_length" 1544 work_dir.run_jj(["undo"]).success(); 1545 std::fs::write( 1546 &editor_script, 1547 indoc! {b" 1548 expect-arg 0 1549 11\0write 1550 <<<<<<<<<<< 1551 <<<<<<< A 1552 ||||||||||| 1553 ======= BASE 1554 =========== 1555 >>>>>>> B 1556 >>>>>>>>>>> 1557 \0fail 1558 "}, 1559 ) 1560 .unwrap(); 1561 let output = work_dir.run_jj([ 1562 "resolve", 1563 r#"--config=merge-tools.fake-editor.merge-args=["$output", "$marker_length"]"#, 1564 ]); 1565 insta::assert_snapshot!(output, @r###" 1566 ------- stderr ------- 1567 Resolving conflicts in: file 1568 Working copy (@) now at: vruxwmqv 2cf0bfd3 conflict | (conflict) conflict 1569 Parent commit (@-) : zsuskuln 10d994ef a | a 1570 Parent commit (@-) : royxmykx 7f215575 b | b 1571 Added 0 files, modified 1 files, removed 0 files 1572 Warning: There are unresolved conflicts at these paths: 1573 file 2-sided conflict 1574 New conflicts appeared in 1 commits: 1575 vruxwmqv 2cf0bfd3 conflict | (conflict) conflict 1576 Hint: To resolve the conflicts, start by creating a commit on top of 1577 the conflicted commit: 1578 jj new vruxwmqv 1579 Then use `jj resolve`, or edit the conflict markers in the file directly. 1580 Once the conflicts are resolved, you can inspect the result with `jj diff`. 1581 Then run `jj squash` to move the resolution into the conflicted commit. 1582 [EOF] 1583 "###); 1584 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1585 diff --git a/file b/file 1586 --- a/file 1587 +++ b/file 1588 @@ -1,8 +1,8 @@ 1589 <<<<<<<<<<< Conflict 1 of 1 1590 +++++++++++ Contents of side #1 1591 -<<<<<<< a 1592 +<<<<<<< A 1593 ----------- Contents of base 1594 -======= base 1595 +======= BASE 1596 +++++++++++ Contents of side #2 1597 ->>>>>>> b 1598 +>>>>>>> B 1599 >>>>>>>>>>> Conflict 1 of 1 ends 1600 [EOF] 1601 "); 1602 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1603 file 2-sided conflict 1604 [EOF] 1605 "); 1606} 1607 1608#[test] 1609fn test_multiple_conflicts() { 1610 let mut test_env = TestEnvironment::default(); 1611 let editor_script = test_env.set_up_fake_editor(); 1612 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1613 let work_dir = test_env.work_dir("repo"); 1614 1615 create_commit_with_files( 1616 &work_dir, 1617 "base", 1618 &[], 1619 &[ 1620 ( 1621 "this_file_has_a_very_long_name_to_test_padding", 1622 "first base\n", 1623 ), 1624 ("another_file", "second base\n"), 1625 ], 1626 ); 1627 create_commit_with_files( 1628 &work_dir, 1629 "a", 1630 &["base"], 1631 &[ 1632 ( 1633 "this_file_has_a_very_long_name_to_test_padding", 1634 "first a\n", 1635 ), 1636 ("another_file", "second a\n"), 1637 ], 1638 ); 1639 create_commit_with_files( 1640 &work_dir, 1641 "b", 1642 &["base"], 1643 &[ 1644 ( 1645 "this_file_has_a_very_long_name_to_test_padding", 1646 "first b\n", 1647 ), 1648 ("another_file", "second b\n"), 1649 ], 1650 ); 1651 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 1652 // Test the setup 1653 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1654 @ conflict 1655 ├─╮ 1656 │ ○ b 1657 ○ │ a 1658 ├─╯ 1659 ○ base 16601661 [EOF] 1662 "); 1663 insta::assert_snapshot!( 1664 work_dir.read_file("this_file_has_a_very_long_name_to_test_padding"), @r" 1665 <<<<<<< Conflict 1 of 1 1666 %%%%%%% Changes from base to side #1 1667 -first base 1668 +first a 1669 +++++++ Contents of side #2 1670 first b 1671 >>>>>>> Conflict 1 of 1 ends 1672 "); 1673 insta::assert_snapshot!(work_dir.read_file("another_file"), @r" 1674 <<<<<<< Conflict 1 of 1 1675 %%%%%%% Changes from base to side #1 1676 -second base 1677 +second a 1678 +++++++ Contents of side #2 1679 second b 1680 >>>>>>> Conflict 1 of 1 ends 1681 "); 1682 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1683 another_file 2-sided conflict 1684 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1685 [EOF] 1686 "); 1687 // Test colors 1688 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r" 1689 another_file 2-sided conflict 1690 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1691 [EOF] 1692 "); 1693 1694 // Check that we can manually pick which of the conflicts to resolve first 1695 std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap(); 1696 let output = work_dir.run_jj(["resolve", "another_file"]); 1697 insta::assert_snapshot!(output, @r###" 1698 ------- stderr ------- 1699 Resolving conflicts in: another_file 1700 Working copy (@) now at: vruxwmqv d3584f6e conflict | (conflict) conflict 1701 Parent commit (@-) : zsuskuln 2c821f70 a | a 1702 Parent commit (@-) : royxmykx 4c2029de b | b 1703 Added 0 files, modified 1 files, removed 0 files 1704 Warning: There are unresolved conflicts at these paths: 1705 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1706 New conflicts appeared in 1 commits: 1707 vruxwmqv d3584f6e conflict | (conflict) conflict 1708 Hint: To resolve the conflicts, start by creating a commit on top of 1709 the conflicted commit: 1710 jj new vruxwmqv 1711 Then use `jj resolve`, or edit the conflict markers in the file directly. 1712 Once the conflicts are resolved, you can inspect the result with `jj diff`. 1713 Then run `jj squash` to move the resolution into the conflicted commit. 1714 [EOF] 1715 "###); 1716 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1717 diff --git a/another_file b/another_file 1718 index 0000000000..a9fcc7d486 100644 1719 --- a/another_file 1720 +++ b/another_file 1721 @@ -1,7 +1,1 @@ 1722 -<<<<<<< Conflict 1 of 1 1723 -%%%%%%% Changes from base to side #1 1724 --second base 1725 -+second a 1726 -+++++++ Contents of side #2 1727 -second b 1728 ->>>>>>> Conflict 1 of 1 ends 1729 +resolution another_file 1730 [EOF] 1731 "); 1732 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1733 this_file_has_a_very_long_name_to_test_padding 2-sided conflict 1734 [EOF] 1735 "); 1736 1737 // Repeat the above with the `--quiet` option. 1738 work_dir.run_jj(["undo"]).success(); 1739 std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap(); 1740 let output = work_dir.run_jj(["resolve", "--quiet", "another_file"]); 1741 insta::assert_snapshot!(output, @""); 1742 1743 // Without a path, `jj resolve` should call the merge tool multiple times 1744 work_dir.run_jj(["undo"]).success(); 1745 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 1746 std::fs::write( 1747 &editor_script, 1748 [ 1749 "expect\n", 1750 "write\nfirst resolution for auto-chosen file\n", 1751 "next invocation\n", 1752 "expect\n", 1753 "write\nsecond resolution for auto-chosen file\n", 1754 ] 1755 .join("\0"), 1756 ) 1757 .unwrap(); 1758 work_dir.run_jj(["resolve"]).success(); 1759 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1760 diff --git a/another_file b/another_file 1761 index 0000000000..7903e1c1c7 100644 1762 --- a/another_file 1763 +++ b/another_file 1764 @@ -1,7 +1,1 @@ 1765 -<<<<<<< Conflict 1 of 1 1766 -%%%%%%% Changes from base to side #1 1767 --second base 1768 -+second a 1769 -+++++++ Contents of side #2 1770 -second b 1771 ->>>>>>> Conflict 1 of 1 ends 1772 +first resolution for auto-chosen file 1773 diff --git a/this_file_has_a_very_long_name_to_test_padding b/this_file_has_a_very_long_name_to_test_padding 1774 index 0000000000..f8c72adf17 100644 1775 --- a/this_file_has_a_very_long_name_to_test_padding 1776 +++ b/this_file_has_a_very_long_name_to_test_padding 1777 @@ -1,7 +1,1 @@ 1778 -<<<<<<< Conflict 1 of 1 1779 -%%%%%%% Changes from base to side #1 1780 --first base 1781 -+first a 1782 -+++++++ Contents of side #2 1783 -first b 1784 ->>>>>>> Conflict 1 of 1 ends 1785 +second resolution for auto-chosen file 1786 [EOF] 1787 "); 1788 1789 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1790 ------- stderr ------- 1791 Error: No conflicts found at this revision 1792 [EOF] 1793 [exit status: 2] 1794 "); 1795 insta::assert_snapshot!(work_dir.run_jj(["resolve"]), @r" 1796 ------- stderr ------- 1797 Error: No conflicts found at this revision 1798 [EOF] 1799 [exit status: 2] 1800 "); 1801} 1802 1803#[test] 1804fn test_multiple_conflicts_with_error() { 1805 let mut test_env = TestEnvironment::default(); 1806 let editor_script = test_env.set_up_fake_editor(); 1807 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1808 let work_dir = test_env.work_dir("repo"); 1809 1810 // Create two conflicted files, and one non-conflicted file 1811 create_commit_with_files( 1812 &work_dir, 1813 "base", 1814 &[], 1815 &[ 1816 ("file1", "base1\n"), 1817 ("file2", "base2\n"), 1818 ("file3", "base3\n"), 1819 ], 1820 ); 1821 create_commit_with_files( 1822 &work_dir, 1823 "a", 1824 &["base"], 1825 &[("file1", "a1\n"), ("file2", "a2\n")], 1826 ); 1827 create_commit_with_files( 1828 &work_dir, 1829 "b", 1830 &["base"], 1831 &[("file1", "b1\n"), ("file2", "b2\n")], 1832 ); 1833 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]); 1834 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1835 file1 2-sided conflict 1836 file2 2-sided conflict 1837 [EOF] 1838 "); 1839 insta::assert_snapshot!(work_dir.read_file("file1"), @r" 1840 <<<<<<< Conflict 1 of 1 1841 %%%%%%% Changes from base to side #1 1842 -base1 1843 +a1 1844 +++++++ Contents of side #2 1845 b1 1846 >>>>>>> Conflict 1 of 1 ends 1847 " 1848 ); 1849 insta::assert_snapshot!(work_dir.read_file("file2"), @r" 1850 <<<<<<< Conflict 1 of 1 1851 %%%%%%% Changes from base to side #1 1852 -base2 1853 +a2 1854 +++++++ Contents of side #2 1855 b2 1856 >>>>>>> Conflict 1 of 1 ends 1857 " 1858 ); 1859 1860 // Test resolving one conflict, then exiting without resolving the second one 1861 std::fs::write( 1862 &editor_script, 1863 ["write\nresolution1\n", "next invocation\n"].join("\0"), 1864 ) 1865 .unwrap(); 1866 let output = work_dir.run_jj(["resolve"]); 1867 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r###" 1868 ------- stderr ------- 1869 Resolving conflicts in: file1 1870 Resolving conflicts in: file2 1871 Working copy (@) now at: vruxwmqv 98296abe conflict | (conflict) conflict 1872 Parent commit (@-) : zsuskuln 6c31698c a | a 1873 Parent commit (@-) : royxmykx ba0a5538 b | b 1874 Added 0 files, modified 1 files, removed 0 files 1875 Warning: There are unresolved conflicts at these paths: 1876 file2 2-sided conflict 1877 New conflicts appeared in 1 commits: 1878 vruxwmqv 98296abe conflict | (conflict) conflict 1879 Hint: To resolve the conflicts, start by creating a commit on top of 1880 the conflicted commit: 1881 jj new vruxwmqv 1882 Then use `jj resolve`, or edit the conflict markers in the file directly. 1883 Once the conflicts are resolved, you can inspect the result with `jj diff`. 1884 Then run `jj squash` to move the resolution into the conflicted commit. 1885 Error: Stopped due to error after resolving 1 conflicts 1886 Caused by: The output file is either unchanged or empty after the editor quit (run with --debug to see the exact invocation). 1887 [EOF] 1888 [exit status: 1] 1889 "###); 1890 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1891 diff --git a/file1 b/file1 1892 index 0000000000..95cc18629d 100644 1893 --- a/file1 1894 +++ b/file1 1895 @@ -1,7 +1,1 @@ 1896 -<<<<<<< Conflict 1 of 1 1897 -%%%%%%% Changes from base to side #1 1898 --base1 1899 -+a1 1900 -+++++++ Contents of side #2 1901 -b1 1902 ->>>>>>> Conflict 1 of 1 ends 1903 +resolution1 1904 [EOF] 1905 "); 1906 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1907 file2 2-sided conflict 1908 [EOF] 1909 "); 1910 1911 // Test resolving one conflict, then failing during the second resolution 1912 work_dir.run_jj(["undo"]).success(); 1913 std::fs::write( 1914 &editor_script, 1915 ["write\nresolution1\n", "next invocation\n", "fail"].join("\0"), 1916 ) 1917 .unwrap(); 1918 let output = work_dir.run_jj(["resolve"]); 1919 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r###" 1920 ------- stderr ------- 1921 Resolving conflicts in: file1 1922 Resolving conflicts in: file2 1923 Working copy (@) now at: vruxwmqv 7daa6406 conflict | (conflict) conflict 1924 Parent commit (@-) : zsuskuln 6c31698c a | a 1925 Parent commit (@-) : royxmykx ba0a5538 b | b 1926 Added 0 files, modified 1 files, removed 0 files 1927 Warning: There are unresolved conflicts at these paths: 1928 file2 2-sided conflict 1929 New conflicts appeared in 1 commits: 1930 vruxwmqv 7daa6406 conflict | (conflict) conflict 1931 Hint: To resolve the conflicts, start by creating a commit on top of 1932 the conflicted commit: 1933 jj new vruxwmqv 1934 Then use `jj resolve`, or edit the conflict markers in the file directly. 1935 Once the conflicts are resolved, you can inspect the result with `jj diff`. 1936 Then run `jj squash` to move the resolution into the conflicted commit. 1937 Error: Stopped due to error after resolving 1 conflicts 1938 Caused by: Tool exited with exit status: 1 (run with --debug to see the exact invocation) 1939 [EOF] 1940 [exit status: 1] 1941 "###); 1942 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r" 1943 diff --git a/file1 b/file1 1944 index 0000000000..95cc18629d 100644 1945 --- a/file1 1946 +++ b/file1 1947 @@ -1,7 +1,1 @@ 1948 -<<<<<<< Conflict 1 of 1 1949 -%%%%%%% Changes from base to side #1 1950 --base1 1951 -+a1 1952 -+++++++ Contents of side #2 1953 -b1 1954 ->>>>>>> Conflict 1 of 1 ends 1955 +resolution1 1956 [EOF] 1957 "); 1958 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1959 file2 2-sided conflict 1960 [EOF] 1961 "); 1962 1963 // Test immediately failing to resolve any conflict 1964 work_dir.run_jj(["undo"]).success(); 1965 std::fs::write(&editor_script, "fail").unwrap(); 1966 let output = work_dir.run_jj(["resolve"]); 1967 insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r" 1968 ------- stderr ------- 1969 Resolving conflicts in: file1 1970 Error: Failed to resolve conflicts 1971 Caused by: Tool exited with exit status: 1 (run with --debug to see the exact invocation) 1972 [EOF] 1973 [exit status: 1] 1974 "); 1975 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 1976 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 1977 file1 2-sided conflict 1978 file2 2-sided conflict 1979 [EOF] 1980 "); 1981} 1982 1983#[test] 1984fn test_resolve_with_contents_of_side() { 1985 let test_env = TestEnvironment::default(); 1986 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1987 let work_dir = test_env.work_dir("repo"); 1988 1989 create_commit_with_files( 1990 &work_dir, 1991 "base", 1992 &[], 1993 &[("file", "base\n"), ("other", "base\n")], 1994 ); 1995 create_commit_with_files( 1996 &work_dir, 1997 "a", 1998 &["base"], 1999 &[("file", "a\n"), ("other", "base\n")], 2000 ); 2001 create_commit_with_files( 2002 &work_dir, 2003 "b", 2004 &["base"], 2005 &[("file", "base\n"), ("other", "left\n")], 2006 ); 2007 create_commit_with_files( 2008 &work_dir, 2009 "c", 2010 &["base"], 2011 &[("file", "b\n"), ("other", "right\n")], 2012 ); 2013 create_commit_with_files(&work_dir, "conflict", &["a", "b", "c"], &[]); 2014 // Test the setup 2015 insta::assert_snapshot!(get_log_output(&work_dir), @r" 2016 @ conflict 2017 ├─┬─╮ 2018 │ │ ○ c 2019 │ ○ │ b 2020 │ ├─╯ 2021 ○ │ a 2022 ├─╯ 2023 ○ base 20242025 [EOF] 2026 "); 2027 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r" 2028 file 2-sided conflict 2029 other 2-sided conflict 2030 [EOF] 2031 "); 2032 insta::assert_snapshot!(work_dir.read_file("file"), @r" 2033 <<<<<<< Conflict 1 of 1 2034 %%%%%%% Changes from base to side #1 2035 -base 2036 +a 2037 +++++++ Contents of side #2 2038 b 2039 >>>>>>> Conflict 1 of 1 ends 2040 "); 2041 insta::assert_snapshot!(work_dir.read_file("other"), @r" 2042 <<<<<<< Conflict 1 of 1 2043 %%%%%%% Changes from base to side #1 2044 -base 2045 +left 2046 +++++++ Contents of side #2 2047 right 2048 >>>>>>> Conflict 1 of 1 ends 2049 "); 2050 2051 // Check that ":ours" merge tool works correctly 2052 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 2053 let output = work_dir.run_jj(["resolve", "--tool", ":ours"]); 2054 insta::assert_snapshot!(output, @r" 2055 ------- stderr ------- 2056 Working copy (@) now at: znkkpsqq 5410a03a conflict | conflict 2057 Parent commit (@-) : zsuskuln 72dced6e a | a 2058 Parent commit (@-) : royxmykx e5747f42 b | b 2059 Parent commit (@-) : vruxwmqv dd35236a c | c 2060 Added 0 files, modified 2 files, removed 0 files 2061 [EOF] 2062 "); 2063 insta::assert_snapshot!(work_dir.read_file("file"), @"a"); 2064 insta::assert_snapshot!(work_dir.read_file("other"), @"left"); 2065 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r#" 2066 ------- stderr ------- 2067 Error: No conflicts found at this revision 2068 [EOF] 2069 [exit status: 2] 2070 "#); 2071 2072 // Check that ":theirs" merge tool works correctly 2073 work_dir.run_jj(["undo"]).success(); 2074 insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @""); 2075 let output = work_dir.run_jj(["resolve", "--tool", ":theirs"]); 2076 insta::assert_snapshot!(output, @r" 2077 ------- stderr ------- 2078 Working copy (@) now at: znkkpsqq c07b2e9e conflict | conflict 2079 Parent commit (@-) : zsuskuln 72dced6e a | a 2080 Parent commit (@-) : royxmykx e5747f42 b | b 2081 Parent commit (@-) : vruxwmqv dd35236a c | c 2082 Added 0 files, modified 2 files, removed 0 files 2083 [EOF] 2084 "); 2085 insta::assert_snapshot!(work_dir.read_file("file"), @"b"); 2086 insta::assert_snapshot!(work_dir.read_file("other"), @"right"); 2087 insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r#" 2088 ------- stderr ------- 2089 Error: No conflicts found at this revision 2090 [EOF] 2091 [exit status: 2] 2092 "#); 2093}