just playing with tangled
at main 967 lines 31 kB view raw
1// Copyright 2024 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15use crate::common::CommandOutput; 16use crate::common::TestEnvironment; 17use crate::common::TestWorkDir; 18 19#[test] 20fn test_absorb_simple() { 21 let test_env = TestEnvironment::default(); 22 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 23 let work_dir = test_env.work_dir("repo"); 24 25 work_dir.run_jj(["describe", "-m0"]).success(); 26 work_dir.write_file("file1", ""); 27 28 work_dir.run_jj(["new", "-m1"]).success(); 29 work_dir.write_file("file1", "1a\n1b\n"); 30 31 work_dir.run_jj(["new", "-m2"]).success(); 32 work_dir.write_file("file1", "1a\n1b\n2a\n2b\n"); 33 34 // Empty commit 35 work_dir.run_jj(["new"]).success(); 36 let output = work_dir.run_jj(["absorb"]); 37 insta::assert_snapshot!(output, @r" 38 ------- stderr ------- 39 Nothing changed. 40 [EOF] 41 "); 42 43 // Insert first and last lines 44 work_dir.write_file("file1", "1X\n1a\n1b\n2a\n2b\n2Z\n"); 45 let output = work_dir.run_jj(["absorb"]); 46 insta::assert_snapshot!(output, @r" 47 ------- stderr ------- 48 Absorbed changes into 2 revisions: 49 zsuskuln 95568809 2 50 kkmpptxz bd7d4016 1 51 Working copy (@) now at: yqosqzyt 977269ac (empty) (no description set) 52 Parent commit (@-) : zsuskuln 95568809 2 53 [EOF] 54 "); 55 56 // Modify middle line in hunk 57 work_dir.write_file("file1", "1X\n1A\n1b\n2a\n2b\n2Z\n"); 58 let output = work_dir.run_jj(["absorb"]); 59 insta::assert_snapshot!(output, @r" 60 ------- stderr ------- 61 Absorbed changes into 1 revisions: 62 kkmpptxz 5810eb0f 1 63 Rebased 1 descendant commits. 64 Working copy (@) now at: vruxwmqv 48c7d8fa (empty) (no description set) 65 Parent commit (@-) : zsuskuln 8edd60a2 2 66 [EOF] 67 "); 68 69 // Remove middle line from hunk 70 work_dir.write_file("file1", "1X\n1A\n1b\n2a\n2Z\n"); 71 let output = work_dir.run_jj(["absorb"]); 72 insta::assert_snapshot!(output, @r" 73 ------- stderr ------- 74 Absorbed changes into 1 revisions: 75 zsuskuln dd109863 2 76 Working copy (@) now at: yostqsxw 7482f74b (empty) (no description set) 77 Parent commit (@-) : zsuskuln dd109863 2 78 [EOF] 79 "); 80 81 // Insert ambiguous line in between 82 work_dir.write_file("file1", "1X\n1A\n1b\nY\n2a\n2Z\n"); 83 let output = work_dir.run_jj(["absorb"]); 84 insta::assert_snapshot!(output, @r" 85 ------- stderr ------- 86 Nothing changed. 87 [EOF] 88 "); 89 90 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 91 @ yostqsxw bde51bc9 (no description set) 92 │ diff --git a/file1 b/file1 93 │ index 8653ca354d..88eb438902 100644 94 │ --- a/file1 95 │ +++ b/file1 96 │ @@ -1,5 +1,6 @@ 97 │ 1X 98 │ 1A 99 │ 1b 100 │ +Y 101 │ 2a 102 │ 2Z 103 ○ zsuskuln dd109863 2 104 │ diff --git a/file1 b/file1 105 │ index ed237b5112..8653ca354d 100644 106 │ --- a/file1 107 │ +++ b/file1 108 │ @@ -1,3 +1,5 @@ 109 │ 1X 110 │ 1A 111 │ 1b 112 │ +2a 113 │ +2Z 114 ○ kkmpptxz 5810eb0f 1 115 │ diff --git a/file1 b/file1 116 │ index e69de29bb2..ed237b5112 100644 117 │ --- a/file1 118 │ +++ b/file1 119 │ @@ -0,0 +1,3 @@ 120 │ +1X 121 │ +1A 122 │ +1b 123 ○ qpvuntsm 6a446874 0 124 │ diff --git a/file1 b/file1 125 ~ new file mode 100644 126 index 0000000000..e69de29bb2 127 [EOF] 128 "); 129 insta::assert_snapshot!(get_evolog(&work_dir, "description(1)"), @r" 130 ○ kkmpptxz 5810eb0f 1 131 ├─╮ -- operation 5876e0f3d35d (2001-02-03 08:05:14) absorb changes into 1 commits 132 │ ○ yqosqzyt hidden 39b42898 (no description set) 133 │ │ -- operation a2c449e239df (2001-02-03 08:05:14) snapshot working copy 134 │ ○ yqosqzyt hidden 977269ac (empty) (no description set) 135 │ -- operation 4a9cb11bbdd5 (2001-02-03 08:05:13) absorb changes into 2 commits 136 ○ kkmpptxz hidden bd7d4016 1 137 ├─╮ -- operation 4a9cb11bbdd5 (2001-02-03 08:05:13) absorb changes into 2 commits 138 │ ○ mzvwutvl hidden 0b307741 (no description set) 139 │ │ -- operation 51ebffcb116e (2001-02-03 08:05:13) snapshot working copy 140 │ ○ mzvwutvl hidden f2709b4e (empty) (no description set) 141 │ -- operation b92e661fdac1 (2001-02-03 08:05:11) new empty commit 142 ○ kkmpptxz hidden 1553c5e8 1 143 │ -- operation 35926ea345b0 (2001-02-03 08:05:10) snapshot working copy 144 ○ kkmpptxz hidden eb943711 (empty) 1 145 -- operation da1318a72167 (2001-02-03 08:05:09) new empty commit 146 [EOF] 147 "); 148 insta::assert_snapshot!(get_evolog(&work_dir, "description(2)"), @r" 149 ○ zsuskuln dd109863 2 150 ├─╮ -- operation fc078244f126 (2001-02-03 08:05:15) absorb changes into 1 commits 151 │ ○ vruxwmqv hidden 761492a8 (no description set) 152 │ │ -- operation b15694dee324 (2001-02-03 08:05:15) snapshot working copy 153 │ ○ vruxwmqv hidden 48c7d8fa (empty) (no description set) 154 │ -- operation 5876e0f3d35d (2001-02-03 08:05:14) absorb changes into 1 commits 155 ○ zsuskuln hidden 8edd60a2 2 156 │ -- operation 5876e0f3d35d (2001-02-03 08:05:14) absorb changes into 1 commits 157 ○ zsuskuln hidden 95568809 2 158 ├─╮ -- operation 4a9cb11bbdd5 (2001-02-03 08:05:13) absorb changes into 2 commits 159 │ ○ mzvwutvl hidden 0b307741 (no description set) 160 │ │ -- operation 51ebffcb116e (2001-02-03 08:05:13) snapshot working copy 161 │ ○ mzvwutvl hidden f2709b4e (empty) (no description set) 162 │ -- operation b92e661fdac1 (2001-02-03 08:05:11) new empty commit 163 ○ zsuskuln hidden 36fad385 2 164 │ -- operation ab92cd8883ff (2001-02-03 08:05:11) snapshot working copy 165 ○ zsuskuln hidden 561fbce9 (empty) 2 166 -- operation 9339ac427cfc (2001-02-03 08:05:10) new empty commit 167 [EOF] 168 "); 169} 170 171#[test] 172fn test_absorb_replace_single_line_hunk() { 173 let test_env = TestEnvironment::default(); 174 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 175 let work_dir = test_env.work_dir("repo"); 176 177 work_dir.run_jj(["describe", "-m1"]).success(); 178 work_dir.write_file("file1", "1a\n"); 179 180 work_dir.run_jj(["new", "-m2"]).success(); 181 work_dir.write_file("file1", "2a\n1a\n2b\n"); 182 183 // Replace single-line hunk, which produces a conflict right now. If our 184 // merge logic were based on interleaved delta, the hunk would be applied 185 // cleanly. 186 work_dir.run_jj(["new"]).success(); 187 work_dir.write_file("file1", "2a\n1A\n2b\n"); 188 let output = work_dir.run_jj(["absorb"]); 189 insta::assert_snapshot!(output, @r###" 190 ------- stderr ------- 191 Absorbed changes into 1 revisions: 192 qpvuntsm 5bdb5ca1 (conflict) 1 193 Rebased 1 descendant commits. 194 Working copy (@) now at: mzvwutvl 804fe9d9 (empty) (no description set) 195 Parent commit (@-) : kkmpptxz 6068e8fc 2 196 New conflicts appeared in 1 commits: 197 qpvuntsm 5bdb5ca1 (conflict) 1 198 Hint: To resolve the conflicts, start by creating a commit on top of 199 the conflicted commit: 200 jj new qpvuntsm 201 Then use `jj resolve`, or edit the conflict markers in the file directly. 202 Once the conflicts are resolved, you can inspect the result with `jj diff`. 203 Then run `jj squash` to move the resolution into the conflicted commit. 204 [EOF] 205 "###); 206 207 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 208 @ mzvwutvl 804fe9d9 (empty) (no description set) 209 ○ kkmpptxz 6068e8fc 2 210 │ diff --git a/file1 b/file1 211 │ index 0000000000..2f87e8e465 100644 212 │ --- a/file1 213 │ +++ b/file1 214 │ @@ -1,10 +1,3 @@ 215 │ -<<<<<<< Conflict 1 of 1 216 │ -%%%%%%% Changes from base to side #1 217 │ --2a 218 │ - 1a 219 │ --2b 220 │ -+++++++ Contents of side #2 221 │ 2a 222 │ 1A 223 │ 2b 224 │ ->>>>>>> Conflict 1 of 1 ends 225 × qpvuntsm 5bdb5ca1 (conflict) 1 226 │ diff --git a/file1 b/file1 227 ~ new file mode 100644 228 index 0000000000..0000000000 229 --- /dev/null 230 +++ b/file1 231 @@ -0,0 +1,10 @@ 232 +<<<<<<< Conflict 1 of 1 233 +%%%%%%% Changes from base to side #1 234 +-2a 235 + 1a 236 +-2b 237 ++++++++ Contents of side #2 238 +2a 239 +1A 240 +2b 241 +>>>>>>> Conflict 1 of 1 ends 242 [EOF] 243 "); 244} 245 246#[test] 247fn test_absorb_merge() { 248 let test_env = TestEnvironment::default(); 249 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 250 let work_dir = test_env.work_dir("repo"); 251 252 work_dir.run_jj(["describe", "-m0"]).success(); 253 work_dir.write_file("file1", "0a\n"); 254 255 work_dir.run_jj(["new", "-m1"]).success(); 256 work_dir.write_file("file1", "1a\n1b\n0a\n"); 257 258 work_dir.run_jj(["new", "-m2", "description(0)"]).success(); 259 work_dir.write_file("file1", "0a\n2a\n2b\n"); 260 261 let output = work_dir.run_jj(["new", "-m3", "description(1)", "description(2)"]); 262 insta::assert_snapshot!(output, @r" 263 ------- stderr ------- 264 Working copy (@) now at: mzvwutvl 42875bf7 (empty) 3 265 Parent commit (@-) : kkmpptxz 9c66f62f 1 266 Parent commit (@-) : zsuskuln 6a3dcbcf 2 267 Added 0 files, modified 1 files, removed 0 files 268 [EOF] 269 "); 270 271 // Modify first and last lines, absorb from merge 272 work_dir.write_file("file1", "1A\n1b\n0a\n2a\n2B\n"); 273 let output = work_dir.run_jj(["absorb"]); 274 insta::assert_snapshot!(output, @r" 275 ------- stderr ------- 276 Absorbed changes into 2 revisions: 277 zsuskuln a6fde7ea 2 278 kkmpptxz 00ecc958 1 279 Rebased 1 descendant commits. 280 Working copy (@) now at: mzvwutvl 30499858 (empty) 3 281 Parent commit (@-) : kkmpptxz 00ecc958 1 282 Parent commit (@-) : zsuskuln a6fde7ea 2 283 [EOF] 284 "); 285 286 // Add hunk to merge revision 287 work_dir.write_file("file2", "3a\n"); 288 289 // Absorb into merge 290 work_dir.run_jj(["new"]).success(); 291 work_dir.write_file("file2", "3A\n"); 292 let output = work_dir.run_jj(["absorb"]); 293 insta::assert_snapshot!(output, @r" 294 ------- stderr ------- 295 Absorbed changes into 1 revisions: 296 mzvwutvl faf778a4 3 297 Working copy (@) now at: vruxwmqv cec519a1 (empty) (no description set) 298 Parent commit (@-) : mzvwutvl faf778a4 3 299 [EOF] 300 "); 301 302 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 303 @ vruxwmqv cec519a1 (empty) (no description set) 304 ○ mzvwutvl faf778a4 3 305 ├─╮ diff --git a/file2 b/file2 306 │ │ new file mode 100644 307 │ │ index 0000000000..44442d2d7b 308 │ │ --- /dev/null 309 │ │ +++ b/file2 310 │ │ @@ -0,0 +1,1 @@ 311 │ │ +3A 312 │ ○ zsuskuln a6fde7ea 2 313 │ │ diff --git a/file1 b/file1 314 │ │ index eb6e8821f1..4907935b9f 100644 315 │ │ --- a/file1 316 │ │ +++ b/file1 317 │ │ @@ -1,1 +1,3 @@ 318 │ │ 0a 319 │ │ +2a 320 │ │ +2B 321 ○ │ kkmpptxz 00ecc958 1 322 ├─╯ diff --git a/file1 b/file1 323 │ index eb6e8821f1..902dd8ef13 100644 324 │ --- a/file1 325 │ +++ b/file1 326 │ @@ -1,1 +1,3 @@ 327 │ +1A 328 │ +1b 329 │ 0a 330 ○ qpvuntsm d4f07be5 0 331 │ diff --git a/file1 b/file1 332 ~ new file mode 100644 333 index 0000000000..eb6e8821f1 334 --- /dev/null 335 +++ b/file1 336 @@ -0,0 +1,1 @@ 337 +0a 338 [EOF] 339 "); 340} 341 342#[test] 343fn test_absorb_discardable_merge_with_descendant() { 344 let test_env = TestEnvironment::default(); 345 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 346 let work_dir = test_env.work_dir("repo"); 347 348 work_dir.run_jj(["describe", "-m0"]).success(); 349 work_dir.write_file("file1", "0a\n"); 350 351 work_dir.run_jj(["new", "-m1"]).success(); 352 work_dir.write_file("file1", "1a\n1b\n0a\n"); 353 354 work_dir.run_jj(["new", "-m2", "description(0)"]).success(); 355 work_dir.write_file("file1", "0a\n2a\n2b\n"); 356 357 let output = work_dir.run_jj(["new", "description(1)", "description(2)"]); 358 insta::assert_snapshot!(output, @r" 359 ------- stderr ------- 360 Working copy (@) now at: mzvwutvl ad00b91a (empty) (no description set) 361 Parent commit (@-) : kkmpptxz 9c66f62f 1 362 Parent commit (@-) : zsuskuln 6a3dcbcf 2 363 Added 0 files, modified 1 files, removed 0 files 364 [EOF] 365 "); 366 367 // Modify first and last lines in the merge commit 368 work_dir.write_file("file1", "1A\n1b\n0a\n2a\n2B\n"); 369 // Add new commit on top 370 work_dir.run_jj(["new", "-m3"]).success(); 371 work_dir.write_file("file2", "3a\n"); 372 // Then absorb the merge commit 373 let output = work_dir.run_jj(["absorb", "--from=@-"]); 374 insta::assert_snapshot!(output, @r" 375 ------- stderr ------- 376 Absorbed changes into 2 revisions: 377 zsuskuln a6cd8e87 2 378 kkmpptxz 98b7d214 1 379 Rebased 1 descendant commits. 380 Working copy (@) now at: royxmykx df946e9b 3 381 Parent commit (@-) : kkmpptxz 98b7d214 1 382 Parent commit (@-) : zsuskuln a6cd8e87 2 383 [EOF] 384 "); 385 386 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 387 @ royxmykx df946e9b 3 388 ├─╮ diff --git a/file2 b/file2 389 │ │ new file mode 100644 390 │ │ index 0000000000..31cd755d20 391 │ │ --- /dev/null 392 │ │ +++ b/file2 393 │ │ @@ -0,0 +1,1 @@ 394 │ │ +3a 395 │ ○ zsuskuln a6cd8e87 2 396 │ │ diff --git a/file1 b/file1 397 │ │ index eb6e8821f1..4907935b9f 100644 398 │ │ --- a/file1 399 │ │ +++ b/file1 400 │ │ @@ -1,1 +1,3 @@ 401 │ │ 0a 402 │ │ +2a 403 │ │ +2B 404 ○ │ kkmpptxz 98b7d214 1 405 ├─╯ diff --git a/file1 b/file1 406 │ index eb6e8821f1..902dd8ef13 100644 407 │ --- a/file1 408 │ +++ b/file1 409 │ @@ -1,1 +1,3 @@ 410 │ +1A 411 │ +1b 412 │ 0a 413 ○ qpvuntsm d4f07be5 0 414 │ diff --git a/file1 b/file1 415 ~ new file mode 100644 416 index 0000000000..eb6e8821f1 417 --- /dev/null 418 +++ b/file1 419 @@ -0,0 +1,1 @@ 420 +0a 421 [EOF] 422 "); 423} 424 425#[test] 426fn test_absorb_conflict() { 427 let test_env = TestEnvironment::default(); 428 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 429 let work_dir = test_env.work_dir("repo"); 430 431 work_dir.run_jj(["describe", "-m1"]).success(); 432 work_dir.write_file("file1", "1a\n1b\n"); 433 434 work_dir.run_jj(["new", "root()"]).success(); 435 work_dir.write_file("file1", "2a\n2b\n"); 436 let output = work_dir.run_jj(["rebase", "-r@", "-ddescription(1)"]); 437 insta::assert_snapshot!(output, @r###" 438 ------- stderr ------- 439 Rebased 1 commits to destination 440 Working copy (@) now at: kkmpptxz 66d44b8c (conflict) (no description set) 441 Parent commit (@-) : qpvuntsm e35bcaff 1 442 Added 0 files, modified 1 files, removed 0 files 443 Warning: There are unresolved conflicts at these paths: 444 file1 2-sided conflict 445 New conflicts appeared in 1 commits: 446 kkmpptxz 66d44b8c (conflict) (no description set) 447 Hint: To resolve the conflicts, start by creating a commit on top of 448 the conflicted commit: 449 jj new kkmpptxz 450 Then use `jj resolve`, or edit the conflict markers in the file directly. 451 Once the conflicts are resolved, you can inspect the result with `jj diff`. 452 Then run `jj squash` to move the resolution into the conflicted commit. 453 [EOF] 454 "###); 455 456 let conflict_content = work_dir.read_file("file1"); 457 insta::assert_snapshot!(conflict_content, @r" 458 <<<<<<< Conflict 1 of 1 459 %%%%%%% Changes from base to side #1 460 +1a 461 +1b 462 +++++++ Contents of side #2 463 2a 464 2b 465 >>>>>>> Conflict 1 of 1 ends 466 "); 467 468 // Cannot absorb from conflict 469 let output = work_dir.run_jj(["absorb"]); 470 insta::assert_snapshot!(output, @r" 471 ------- stderr ------- 472 Warning: Skipping file1: Is a conflict 473 Nothing changed. 474 [EOF] 475 "); 476 477 // Cannot absorb from resolved conflict 478 work_dir.run_jj(["new"]).success(); 479 work_dir.write_file("file1", "1A\n1b\n2a\n2B\n"); 480 let output = work_dir.run_jj(["absorb"]); 481 insta::assert_snapshot!(output, @r" 482 ------- stderr ------- 483 Warning: Skipping file1: Is a conflict 484 Nothing changed. 485 [EOF] 486 "); 487} 488 489#[test] 490fn test_absorb_deleted_file() { 491 let test_env = TestEnvironment::default(); 492 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 493 let work_dir = test_env.work_dir("repo"); 494 495 work_dir.run_jj(["describe", "-m1"]).success(); 496 work_dir.write_file("file1", "1a\n"); 497 work_dir.write_file("file2", "1a\n"); 498 work_dir.write_file("file3", ""); 499 500 work_dir.run_jj(["new"]).success(); 501 work_dir.remove_file("file1"); 502 work_dir.write_file("file2", ""); // emptied 503 work_dir.remove_file("file3"); // no content change 504 505 // Since the destinations are chosen based on content diffs, file3 cannot be 506 // absorbed. 507 let output = work_dir.run_jj(["absorb"]); 508 insta::assert_snapshot!(output, @r" 509 ------- stderr ------- 510 Absorbed changes into 1 revisions: 511 qpvuntsm 38af7fd3 1 512 Rebased 1 descendant commits. 513 Working copy (@) now at: kkmpptxz efd883f6 (no description set) 514 Parent commit (@-) : qpvuntsm 38af7fd3 1 515 Remaining changes: 516 D file3 517 [EOF] 518 "); 519 520 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 521 @ kkmpptxz efd883f6 (no description set) 522 │ diff --git a/file3 b/file3 523 │ deleted file mode 100644 524 │ index e69de29bb2..0000000000 525 ○ qpvuntsm 38af7fd3 1 526 │ diff --git a/file2 b/file2 527 ~ new file mode 100644 528 index 0000000000..e69de29bb2 529 diff --git a/file3 b/file3 530 new file mode 100644 531 index 0000000000..e69de29bb2 532 [EOF] 533 "); 534} 535 536#[test] 537fn test_absorb_deleted_file_with_multiple_hunks() { 538 let test_env = TestEnvironment::default(); 539 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 540 let work_dir = test_env.work_dir("repo"); 541 542 work_dir.run_jj(["describe", "-m1"]).success(); 543 work_dir.write_file("file1", "1a\n1b\n"); 544 work_dir.write_file("file2", "1a\n"); 545 546 work_dir.run_jj(["new", "-m2"]).success(); 547 work_dir.write_file("file1", "1a\n"); 548 work_dir.write_file("file2", "1a\n1b\n"); 549 550 // These changes produce conflicts because 551 // - for file1, "1a\n" is deleted from the commit 1, 552 // - for file2, two consecutive hunks are deleted. 553 // 554 // Since file2 change is split to two separate hunks, the file deletion 555 // cannot be propagated. If we implement merging based on interleaved delta, 556 // the file2 change will apply cleanly. The file1 change might be split into 557 // "1a\n" deletion at the commit 1 and file deletion at the commit 2, but 558 // I'm not sure if that's intuitive. 559 work_dir.run_jj(["new"]).success(); 560 work_dir.remove_file("file1"); 561 work_dir.remove_file("file2"); 562 let output = work_dir.run_jj(["absorb"]); 563 insta::assert_snapshot!(output, @r###" 564 ------- stderr ------- 565 Absorbed changes into 2 revisions: 566 kkmpptxz 8407ab95 (conflict) 2 567 qpvuntsm f1473264 (conflict) 1 568 Rebased 1 descendant commits. 569 Working copy (@) now at: zsuskuln b56f0c39 (no description set) 570 Parent commit (@-) : kkmpptxz 8407ab95 (conflict) 2 571 New conflicts appeared in 2 commits: 572 kkmpptxz 8407ab95 (conflict) 2 573 qpvuntsm f1473264 (conflict) 1 574 Hint: To resolve the conflicts, start by creating a commit on top of 575 the first conflicted commit: 576 jj new qpvuntsm 577 Then use `jj resolve`, or edit the conflict markers in the file directly. 578 Once the conflicts are resolved, you can inspect the result with `jj diff`. 579 Then run `jj squash` to move the resolution into the conflicted commit. 580 Remaining changes: 581 D file2 582 [EOF] 583 "###); 584 585 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 586 @ zsuskuln b56f0c39 (no description set) 587 │ diff --git a/file2 b/file2 588 │ deleted file mode 100644 589 │ index 0000000000..0000000000 590 │ --- a/file2 591 │ +++ /dev/null 592 │ @@ -1,7 +0,0 @@ 593 │ -<<<<<<< Conflict 1 of 1 594 │ -%%%%%%% Changes from base to side #1 595 │ --1a 596 │ - 1b 597 │ -+++++++ Contents of side #2 598 │ -1a 599 │ ->>>>>>> Conflict 1 of 1 ends 600 × kkmpptxz 8407ab95 (conflict) 2 601 │ diff --git a/file1 b/file1 602 │ deleted file mode 100644 603 │ index 0000000000..0000000000 604 │ --- a/file1 605 │ +++ /dev/null 606 │ @@ -1,6 +0,0 @@ 607 │ -<<<<<<< Conflict 1 of 1 608 │ -%%%%%%% Changes from base to side #1 609 │ - 1a 610 │ -+1b 611 │ -+++++++ Contents of side #2 612 │ ->>>>>>> Conflict 1 of 1 ends 613 │ diff --git a/file2 b/file2 614 │ --- a/file2 615 │ +++ b/file2 616 │ @@ -1,7 +1,7 @@ 617 │ <<<<<<< Conflict 1 of 1 618 │ %%%%%%% Changes from base to side #1 619 │ - 1a 620 │ --1b 621 │ +-1a 622 │ + 1b 623 │ +++++++ Contents of side #2 624 │ -1b 625 │ +1a 626 │ >>>>>>> Conflict 1 of 1 ends 627 × qpvuntsm f1473264 (conflict) 1 628 │ diff --git a/file1 b/file1 629 ~ new file mode 100644 630 index 0000000000..0000000000 631 --- /dev/null 632 +++ b/file1 633 @@ -0,0 +1,6 @@ 634 +<<<<<<< Conflict 1 of 1 635 +%%%%%%% Changes from base to side #1 636 + 1a 637 ++1b 638 ++++++++ Contents of side #2 639 +>>>>>>> Conflict 1 of 1 ends 640 diff --git a/file2 b/file2 641 new file mode 100644 642 index 0000000000..0000000000 643 --- /dev/null 644 +++ b/file2 645 @@ -0,0 +1,7 @@ 646 +<<<<<<< Conflict 1 of 1 647 +%%%%%%% Changes from base to side #1 648 + 1a 649 +-1b 650 ++++++++ Contents of side #2 651 +1b 652 +>>>>>>> Conflict 1 of 1 ends 653 [EOF] 654 "); 655} 656 657#[test] 658fn test_absorb_file_mode() { 659 let 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 work_dir.run_jj(["describe", "-m1"]).success(); 664 work_dir.write_file("file1", "1a\n"); 665 work_dir.run_jj(["file", "chmod", "x", "file1"]).success(); 666 667 // Modify content and mode 668 work_dir.run_jj(["new"]).success(); 669 work_dir.write_file("file1", "1A\n"); 670 work_dir.run_jj(["file", "chmod", "n", "file1"]).success(); 671 672 // Mode change shouldn't be absorbed 673 let output = work_dir.run_jj(["absorb"]); 674 insta::assert_snapshot!(output, @r" 675 ------- stderr ------- 676 Absorbed changes into 1 revisions: 677 qpvuntsm 2a0c7f1d 1 678 Rebased 1 descendant commits. 679 Working copy (@) now at: zsuskuln 8ca9761d (no description set) 680 Parent commit (@-) : qpvuntsm 2a0c7f1d 1 681 Remaining changes: 682 M file1 683 [EOF] 684 "); 685 686 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 687 @ zsuskuln 8ca9761d (no description set) 688 │ diff --git a/file1 b/file1 689 │ old mode 100755 690 │ new mode 100644 691 ○ qpvuntsm 2a0c7f1d 1 692 │ diff --git a/file1 b/file1 693 ~ new file mode 100755 694 index 0000000000..268de3f3ec 695 --- /dev/null 696 +++ b/file1 697 @@ -0,0 +1,1 @@ 698 +1A 699 [EOF] 700 "); 701} 702 703#[test] 704fn test_absorb_from_into() { 705 let test_env = TestEnvironment::default(); 706 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 707 let work_dir = test_env.work_dir("repo"); 708 709 work_dir.run_jj(["new", "-m1"]).success(); 710 work_dir.write_file("file1", "1a\n1b\n1c\n"); 711 712 work_dir.run_jj(["new", "-m2"]).success(); 713 work_dir.write_file("file1", "1a\n2a\n1b\n1c\n2b\n"); 714 715 // Line "X" and "Z" have unambiguous adjacent line within the destinations 716 // range. Line "Y" doesn't have such line. 717 work_dir.run_jj(["new"]).success(); 718 work_dir.write_file("file1", "1a\nX\n2a\n1b\nY\n1c\n2b\nZ\n"); 719 let output = work_dir.run_jj(["absorb", "--into=@-"]); 720 insta::assert_snapshot!(output, @r" 721 ------- stderr ------- 722 Absorbed changes into 1 revisions: 723 kkmpptxz cae507ef 2 724 Rebased 1 descendant commits. 725 Working copy (@) now at: zsuskuln f02fd9ea (no description set) 726 Parent commit (@-) : kkmpptxz cae507ef 2 727 Remaining changes: 728 M file1 729 [EOF] 730 "); 731 732 insta::assert_snapshot!(get_diffs(&work_dir, "@-::"), @r" 733 @ zsuskuln f02fd9ea (no description set) 734 │ diff --git a/file1 b/file1 735 │ index faf62af049..c2d0b12547 100644 736 │ --- a/file1 737 │ +++ b/file1 738 │ @@ -2,6 +2,7 @@ 739 │ X 740 │ 2a 741 │ 1b 742 │ +Y 743 │ 1c 744 │ 2b 745 │ Z 746 ○ kkmpptxz cae507ef 2 747 │ diff --git a/file1 b/file1 748 ~ index 352e9b3794..faf62af049 100644 749 --- a/file1 750 +++ b/file1 751 @@ -1,3 +1,7 @@ 752 1a 753 +X 754 +2a 755 1b 756 1c 757 +2b 758 +Z 759 [EOF] 760 "); 761 762 // Absorb all lines from the working-copy parent. An empty commit won't be 763 // discarded because "absorb" isn't a command to squash commit descriptions. 764 let output = work_dir.run_jj(["absorb", "--from=@-"]); 765 insta::assert_snapshot!(output, @r" 766 ------- stderr ------- 767 Absorbed changes into 1 revisions: 768 rlvkpnrz ddaed33d 1 769 Rebased 2 descendant commits. 770 Working copy (@) now at: zsuskuln 3652e5e5 (no description set) 771 Parent commit (@-) : kkmpptxz 7f4339e7 (empty) 2 772 [EOF] 773 "); 774 775 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 776 @ zsuskuln 3652e5e5 (no description set) 777 │ diff --git a/file1 b/file1 778 │ index faf62af049..c2d0b12547 100644 779 │ --- a/file1 780 │ +++ b/file1 781 │ @@ -2,6 +2,7 @@ 782 │ X 783 │ 2a 784 │ 1b 785 │ +Y 786 │ 1c 787 │ 2b 788 │ Z 789 ○ kkmpptxz 7f4339e7 (empty) 2 790 ○ rlvkpnrz ddaed33d 1 791 │ diff --git a/file1 b/file1 792 │ new file mode 100644 793 │ index 0000000000..faf62af049 794 │ --- /dev/null 795 │ +++ b/file1 796 │ @@ -0,0 +1,7 @@ 797 │ +1a 798 │ +X 799 │ +2a 800 │ +1b 801 │ +1c 802 │ +2b 803 │ +Z 804 ○ qpvuntsm e8849ae1 (empty) (no description set) 805806 ~ 807 [EOF] 808 "); 809} 810 811#[test] 812fn test_absorb_paths() { 813 let test_env = TestEnvironment::default(); 814 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 815 let work_dir = test_env.work_dir("repo"); 816 817 work_dir.run_jj(["describe", "-m1"]).success(); 818 work_dir.write_file("file1", "1a\n"); 819 work_dir.write_file("file2", "1a\n"); 820 821 // Modify both files 822 work_dir.run_jj(["new"]).success(); 823 work_dir.write_file("file1", "1A\n"); 824 work_dir.write_file("file2", "1A\n"); 825 826 let output = work_dir.run_jj(["absorb", "unknown"]); 827 insta::assert_snapshot!(output, @r" 828 ------- stderr ------- 829 Nothing changed. 830 [EOF] 831 "); 832 833 let output = work_dir.run_jj(["absorb", "file1"]); 834 insta::assert_snapshot!(output, @r" 835 ------- stderr ------- 836 Absorbed changes into 1 revisions: 837 qpvuntsm ca07fabe 1 838 Rebased 1 descendant commits. 839 Working copy (@) now at: kkmpptxz 4d80ada8 (no description set) 840 Parent commit (@-) : qpvuntsm ca07fabe 1 841 Remaining changes: 842 M file2 843 [EOF] 844 "); 845 846 insta::assert_snapshot!(get_diffs(&work_dir, "mutable()"), @r" 847 @ kkmpptxz 4d80ada8 (no description set) 848 │ diff --git a/file2 b/file2 849 │ index a8994dc188..268de3f3ec 100644 850 │ --- a/file2 851 │ +++ b/file2 852 │ @@ -1,1 +1,1 @@ 853 │ -1a 854 │ +1A 855 ○ qpvuntsm ca07fabe 1 856 │ diff --git a/file1 b/file1 857 ~ new file mode 100644 858 index 0000000000..268de3f3ec 859 --- /dev/null 860 +++ b/file1 861 @@ -0,0 +1,1 @@ 862 +1A 863 diff --git a/file2 b/file2 864 new file mode 100644 865 index 0000000000..a8994dc188 866 --- /dev/null 867 +++ b/file2 868 @@ -0,0 +1,1 @@ 869 +1a 870 [EOF] 871 "); 872} 873 874#[test] 875fn test_absorb_immutable() { 876 let test_env = TestEnvironment::default(); 877 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 878 let work_dir = test_env.work_dir("repo"); 879 test_env.add_config("revset-aliases.'immutable_heads()' = 'present(main)'"); 880 881 work_dir.run_jj(["describe", "-m1"]).success(); 882 work_dir.write_file("file1", "1a\n1b\n"); 883 884 work_dir.run_jj(["new", "-m2"]).success(); 885 work_dir 886 .run_jj(["bookmark", "set", "-r@-", "main"]) 887 .success(); 888 work_dir.write_file("file1", "1a\n1b\n2a\n2b\n"); 889 890 work_dir.run_jj(["new"]).success(); 891 work_dir.write_file("file1", "1A\n1b\n2a\n2B\n"); 892 893 // Immutable revisions are excluded by default 894 let output = work_dir.run_jj(["absorb"]); 895 insta::assert_snapshot!(output, @r" 896 ------- stderr ------- 897 Absorbed changes into 1 revisions: 898 kkmpptxz e68cc3e2 2 899 Rebased 1 descendant commits. 900 Working copy (@) now at: mzvwutvl 88443af7 (no description set) 901 Parent commit (@-) : kkmpptxz e68cc3e2 2 902 Remaining changes: 903 M file1 904 [EOF] 905 "); 906 907 // Immutable revisions shouldn't be rewritten 908 let output = work_dir.run_jj(["absorb", "--into=all()"]); 909 insta::assert_snapshot!(output, @r#" 910 ------- stderr ------- 911 Error: Commit e35bcaffcb55 is immutable 912 Hint: Could not modify commit: qpvuntsm e35bcaff main | 1 913 Hint: Immutable commits are used to protect shared history. 914 Hint: For more information, see: 915 - https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits 916 - `jj help -k config`, "Set of immutable commits" 917 Hint: This operation would rewrite 1 immutable commits. 918 [EOF] 919 [exit status: 1] 920 "#); 921 922 insta::assert_snapshot!(get_diffs(&work_dir, ".."), @r" 923 @ mzvwutvl 88443af7 (no description set) 924 │ diff --git a/file1 b/file1 925 │ index 75e4047831..428796ca20 100644 926 │ --- a/file1 927 │ +++ b/file1 928 │ @@ -1,4 +1,4 @@ 929 │ -1a 930 │ +1A 931 │ 1b 932 │ 2a 933 │ 2B 934 ○ kkmpptxz e68cc3e2 2 935 │ diff --git a/file1 b/file1 936 │ index 8c5268f893..75e4047831 100644 937 │ --- a/file1 938 │ +++ b/file1 939 │ @@ -1,2 +1,4 @@ 940 │ 1a 941 │ 1b 942 │ +2a 943 │ +2B 944 ◆ qpvuntsm e35bcaff 1 945 │ diff --git a/file1 b/file1 946 ~ new file mode 100644 947 index 0000000000..8c5268f893 948 --- /dev/null 949 +++ b/file1 950 @@ -0,0 +1,2 @@ 951 +1a 952 +1b 953 [EOF] 954 "); 955} 956 957#[must_use] 958fn get_diffs(work_dir: &TestWorkDir, revision: &str) -> CommandOutput { 959 let template = r#"format_commit_summary_with_refs(self, "") ++ "\n""#; 960 work_dir.run_jj(["log", "-r", revision, "-T", template, "--git"]) 961} 962 963#[must_use] 964fn get_evolog(work_dir: &TestWorkDir, revision: &str) -> CommandOutput { 965 let template = r#"format_commit_summary_with_refs(self, "") ++ "\n""#; 966 work_dir.run_jj(["evolog", "-r", revision, "-T", template]) 967}