just playing with tangled
at ig/vimdiffwarn 1457 lines 49 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; 16use std::path::PathBuf; 17 18use crate::common::CommandOutput; 19use crate::common::TestEnvironment; 20 21#[test] 22fn test_squash() { 23 let test_env = TestEnvironment::default(); 24 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 25 let repo_path = test_env.env_root().join("repo"); 26 27 test_env 28 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "a"]) 29 .success(); 30 std::fs::write(repo_path.join("file1"), "a\n").unwrap(); 31 test_env.run_jj_in(&repo_path, ["new"]).success(); 32 test_env 33 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "b"]) 34 .success(); 35 std::fs::write(repo_path.join("file1"), "b\n").unwrap(); 36 test_env.run_jj_in(&repo_path, ["new"]).success(); 37 test_env 38 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "c"]) 39 .success(); 40 std::fs::write(repo_path.join("file1"), "c\n").unwrap(); 41 // Test the setup 42 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 43 @ 382c9bad7d42 c 44 ○ d5d59175b481 b 45 ○ 184ddbcce5a9 a 46 ◆ 000000000000 (empty) 47 [EOF] 48 "); 49 50 // Squashes the working copy into the parent by default 51 let output = test_env.run_jj_in(&repo_path, ["squash"]); 52 insta::assert_snapshot!(output, @r" 53 ------- stderr ------- 54 Working copy (@) now at: vruxwmqv f7bb78d8 (empty) (no description set) 55 Parent commit (@-) : kkmpptxz 59f44460 b c | (no description set) 56 [EOF] 57 "); 58 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 59 @ f7bb78d8da62 (empty) 60 ○ 59f4446070a0 b c 61 ○ 184ddbcce5a9 a 62 ◆ 000000000000 (empty) 63 [EOF] 64 "); 65 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1"]); 66 insta::assert_snapshot!(output, @r" 67 c 68 [EOF] 69 "); 70 71 // Can squash a given commit into its parent 72 test_env.run_jj_in(&repo_path, ["undo"]).success(); 73 let output = test_env.run_jj_in(&repo_path, ["squash", "-r", "b"]); 74 insta::assert_snapshot!(output, @r" 75 ------- stderr ------- 76 Rebased 1 descendant commits 77 Working copy (@) now at: mzvwutvl 1d70f50a c | (no description set) 78 Parent commit (@-) : qpvuntsm 9146bcc8 a b | (no description set) 79 [EOF] 80 "); 81 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 82 @ 1d70f50afa6d c 83 ○ 9146bcc8d996 a b 84 ◆ 000000000000 (empty) 85 [EOF] 86 "); 87 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "b"]); 88 insta::assert_snapshot!(output, @r" 89 b 90 [EOF] 91 "); 92 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1"]); 93 insta::assert_snapshot!(output, @r" 94 c 95 [EOF] 96 "); 97 98 // Cannot squash a merge commit (because it's unclear which parent it should go 99 // into) 100 test_env.run_jj_in(&repo_path, ["undo"]).success(); 101 test_env.run_jj_in(&repo_path, ["edit", "b"]).success(); 102 test_env.run_jj_in(&repo_path, ["new"]).success(); 103 test_env 104 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "d"]) 105 .success(); 106 std::fs::write(repo_path.join("file2"), "d\n").unwrap(); 107 test_env.run_jj_in(&repo_path, ["new", "c", "d"]).success(); 108 test_env 109 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "e"]) 110 .success(); 111 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 112 @ 41219719ab5f e (empty) 113 ├─╮ 114 │ ○ f86e2b3af3e3 d 115 ○ │ 382c9bad7d42 c 116 ├─╯ 117 ○ d5d59175b481 b 118 ○ 184ddbcce5a9 a 119 ◆ 000000000000 (empty) 120 [EOF] 121 "); 122 let output = test_env.run_jj_in(&repo_path, ["squash"]); 123 insta::assert_snapshot!(output, @r" 124 ------- stderr ------- 125 Error: Cannot squash merge commits without a specified destination 126 Hint: Use `--into` to specify which parent to squash into 127 [EOF] 128 [exit status: 1] 129 "); 130 131 // Can squash into a merge commit 132 test_env.run_jj_in(&repo_path, ["new", "e"]).success(); 133 std::fs::write(repo_path.join("file1"), "e\n").unwrap(); 134 let output = test_env.run_jj_in(&repo_path, ["squash"]); 135 insta::assert_snapshot!(output, @r" 136 ------- stderr ------- 137 Working copy (@) now at: xlzxqlsl b50b843d (empty) (no description set) 138 Parent commit (@-) : nmzmmopx 338cbc05 e | (no description set) 139 [EOF] 140 "); 141 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 142 @ b50b843d8555 (empty) 143 ○ 338cbc05e4e6 e 144 ├─╮ 145 │ ○ f86e2b3af3e3 d 146 ○ │ 382c9bad7d42 c 147 ├─╯ 148 ○ d5d59175b481 b 149 ○ 184ddbcce5a9 a 150 ◆ 000000000000 (empty) 151 [EOF] 152 "); 153 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "e"]); 154 insta::assert_snapshot!(output, @r" 155 e 156 [EOF] 157 "); 158} 159 160#[test] 161fn test_squash_partial() { 162 let mut test_env = TestEnvironment::default(); 163 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 164 let repo_path = test_env.env_root().join("repo"); 165 166 test_env 167 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "a"]) 168 .success(); 169 std::fs::write(repo_path.join("file1"), "a\n").unwrap(); 170 std::fs::write(repo_path.join("file2"), "a\n").unwrap(); 171 test_env.run_jj_in(&repo_path, ["new"]).success(); 172 test_env 173 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "b"]) 174 .success(); 175 std::fs::write(repo_path.join("file1"), "b\n").unwrap(); 176 std::fs::write(repo_path.join("file2"), "b\n").unwrap(); 177 test_env.run_jj_in(&repo_path, ["new"]).success(); 178 test_env 179 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "c"]) 180 .success(); 181 std::fs::write(repo_path.join("file1"), "c\n").unwrap(); 182 std::fs::write(repo_path.join("file2"), "c\n").unwrap(); 183 // Test the setup 184 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 185 @ a0b1a272ebc4 c 186 ○ d117da276a0f b 187 ○ 54d3c1c0e9fd a 188 ◆ 000000000000 (empty) 189 [EOF] 190 "); 191 192 // If we don't make any changes in the diff-editor, the whole change is moved 193 // into the parent 194 let edit_script = test_env.set_up_fake_diff_editor(); 195 std::fs::write(&edit_script, "dump JJ-INSTRUCTIONS instrs").unwrap(); 196 let output = test_env.run_jj_in(&repo_path, ["squash", "-r", "b", "-i"]); 197 insta::assert_snapshot!(output, @r" 198 ------- stderr ------- 199 Rebased 1 descendant commits 200 Working copy (@) now at: mzvwutvl 3c633226 c | (no description set) 201 Parent commit (@-) : qpvuntsm 38ffd8b9 a b | (no description set) 202 [EOF] 203 "); 204 205 insta::assert_snapshot!( 206 std::fs::read_to_string(test_env.env_root().join("instrs")).unwrap(), @r" 207 You are moving changes from: kkmpptxz d117da27 b | (no description set) 208 into commit: qpvuntsm 54d3c1c0 a | (no description set) 209 210 The left side of the diff shows the contents of the parent commit. The 211 right side initially shows the contents of the commit you're moving 212 changes from. 213 214 Adjust the right side until the diff shows the changes you want to move 215 to the destination. If you don't make any changes, then all the changes 216 from the source will be moved into the destination. 217 "); 218 219 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 220 @ 3c6332267ea8 c 221 ○ 38ffd8b98578 a b 222 ◆ 000000000000 (empty) 223 [EOF] 224 "); 225 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "a"]); 226 insta::assert_snapshot!(output, @r" 227 b 228 [EOF] 229 "); 230 231 // Can squash only some changes in interactive mode 232 test_env.run_jj_in(&repo_path, ["undo"]).success(); 233 std::fs::write(&edit_script, "reset file1").unwrap(); 234 let output = test_env.run_jj_in(&repo_path, ["squash", "-r", "b", "-i"]); 235 insta::assert_snapshot!(output, @r" 236 ------- stderr ------- 237 Rebased 2 descendant commits 238 Working copy (@) now at: mzvwutvl 57c3cf20 c | (no description set) 239 Parent commit (@-) : kkmpptxz c4925e01 b | (no description set) 240 [EOF] 241 "); 242 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 243 @ 57c3cf20d0b1 c 244 ○ c4925e01d298 b 245 ○ 1fc159063ed3 a 246 ◆ 000000000000 (empty) 247 [EOF] 248 "); 249 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "a"]); 250 insta::assert_snapshot!(output, @r" 251 a 252 [EOF] 253 "); 254 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2", "-r", "a"]); 255 insta::assert_snapshot!(output, @r" 256 b 257 [EOF] 258 "); 259 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "b"]); 260 insta::assert_snapshot!(output, @r" 261 b 262 [EOF] 263 "); 264 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2", "-r", "b"]); 265 insta::assert_snapshot!(output, @r" 266 b 267 [EOF] 268 "); 269 270 // Can squash only some changes in non-interactive mode 271 test_env.run_jj_in(&repo_path, ["undo"]).success(); 272 // Clear the script so we know it won't be used even without -i 273 std::fs::write(&edit_script, "").unwrap(); 274 let output = test_env.run_jj_in(&repo_path, ["squash", "-r", "b", "file2"]); 275 insta::assert_snapshot!(output, @r" 276 ------- stderr ------- 277 Rebased 2 descendant commits 278 Working copy (@) now at: mzvwutvl 64d7ad7c c | (no description set) 279 Parent commit (@-) : kkmpptxz 60a26452 b | (no description set) 280 [EOF] 281 "); 282 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 283 @ 64d7ad7c43c1 c 284 ○ 60a264527aee b 285 ○ 7314692d32e3 a 286 ◆ 000000000000 (empty) 287 [EOF] 288 "); 289 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "a"]); 290 insta::assert_snapshot!(output, @r" 291 a 292 [EOF] 293 "); 294 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2", "-r", "a"]); 295 insta::assert_snapshot!(output, @r" 296 b 297 [EOF] 298 "); 299 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "b"]); 300 insta::assert_snapshot!(output, @r" 301 b 302 [EOF] 303 "); 304 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2", "-r", "b"]); 305 insta::assert_snapshot!(output, @r" 306 b 307 [EOF] 308 "); 309 310 // If we specify only a non-existent file, then nothing changes. 311 test_env.run_jj_in(&repo_path, ["undo"]).success(); 312 let output = test_env.run_jj_in(&repo_path, ["squash", "-r", "b", "nonexistent"]); 313 insta::assert_snapshot!(output, @r" 314 ------- stderr ------- 315 Nothing changed. 316 [EOF] 317 "); 318 319 // We get a warning if we pass a positional argument that looks like a revset 320 test_env.run_jj_in(&repo_path, ["undo"]).success(); 321 let output = test_env.run_jj_in(&repo_path, ["squash", "b"]); 322 insta::assert_snapshot!(output, @r#" 323 ------- stderr ------- 324 Warning: The argument "b" is being interpreted as a fileset expression. To specify a revset, pass -r "b" instead. 325 Nothing changed. 326 [EOF] 327 "#); 328} 329 330#[test] 331fn test_squash_keep_emptied() { 332 let test_env = TestEnvironment::default(); 333 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 334 let repo_path = test_env.env_root().join("repo"); 335 336 test_env 337 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "a"]) 338 .success(); 339 std::fs::write(repo_path.join("file1"), "a\n").unwrap(); 340 test_env.run_jj_in(&repo_path, ["new"]).success(); 341 test_env 342 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "b"]) 343 .success(); 344 std::fs::write(repo_path.join("file1"), "b\n").unwrap(); 345 test_env.run_jj_in(&repo_path, ["new"]).success(); 346 test_env 347 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "c"]) 348 .success(); 349 std::fs::write(repo_path.join("file1"), "c\n").unwrap(); 350 // Test the setup 351 352 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 353 @ 382c9bad7d42 c 354 ○ d5d59175b481 b 355 ○ 184ddbcce5a9 a 356 ◆ 000000000000 (empty) 357 [EOF] 358 "); 359 360 let output = test_env.run_jj_in(&repo_path, ["squash", "-r", "b", "--keep-emptied"]); 361 insta::assert_snapshot!(output, @r" 362 ------- stderr ------- 363 Rebased 2 descendant commits 364 Working copy (@) now at: mzvwutvl 7ee7f18a c | (no description set) 365 Parent commit (@-) : kkmpptxz 9490bd7f b | (empty) (no description set) 366 [EOF] 367 "); 368 // With --keep-emptied, b remains even though it is now empty. 369 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 370 @ 7ee7f18a5223 c 371 ○ 9490bd7f1e6a b (empty) 372 ○ 53bf93080518 a 373 ◆ 000000000000 (empty) 374 [EOF] 375 "); 376 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "a"]); 377 insta::assert_snapshot!(output, @r" 378 b 379 [EOF] 380 "); 381} 382 383#[test] 384fn test_squash_from_to() { 385 let test_env = TestEnvironment::default(); 386 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 387 let repo_path = test_env.env_root().join("repo"); 388 389 // Create history like this: 390 // F 391 // | 392 // E C 393 // | | 394 // D B 395 // |/ 396 // A 397 // 398 // When moving changes between e.g. C and F, we should not get unrelated changes 399 // from B and D. 400 test_env 401 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "a"]) 402 .success(); 403 std::fs::write(repo_path.join("file1"), "a\n").unwrap(); 404 std::fs::write(repo_path.join("file2"), "a\n").unwrap(); 405 std::fs::write(repo_path.join("file3"), "a\n").unwrap(); 406 test_env.run_jj_in(&repo_path, ["new"]).success(); 407 test_env 408 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "b"]) 409 .success(); 410 std::fs::write(repo_path.join("file3"), "b\n").unwrap(); 411 test_env.run_jj_in(&repo_path, ["new"]).success(); 412 test_env 413 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "c"]) 414 .success(); 415 std::fs::write(repo_path.join("file1"), "c\n").unwrap(); 416 test_env.run_jj_in(&repo_path, ["edit", "a"]).success(); 417 test_env.run_jj_in(&repo_path, ["new"]).success(); 418 test_env 419 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "d"]) 420 .success(); 421 std::fs::write(repo_path.join("file3"), "d\n").unwrap(); 422 test_env.run_jj_in(&repo_path, ["new"]).success(); 423 test_env 424 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "e"]) 425 .success(); 426 std::fs::write(repo_path.join("file2"), "e\n").unwrap(); 427 test_env.run_jj_in(&repo_path, ["new"]).success(); 428 test_env 429 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "f"]) 430 .success(); 431 std::fs::write(repo_path.join("file2"), "f\n").unwrap(); 432 // Test the setup 433 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 434 @ a847ab4967fe f 435 ○ c2f9de87325d e 436 ○ e0dac715116f d 437 │ ○ 59597b34a0d8 c 438 │ ○ 12d6103dc0c8 b 439 ├─╯ 440 ○ b7b767179c44 a 441 ◆ 000000000000 (empty) 442 [EOF] 443 "); 444 445 // Errors out if source and destination are the same 446 let output = test_env.run_jj_in(&repo_path, ["squash", "--into", "@"]); 447 insta::assert_snapshot!(output, @r" 448 ------- stderr ------- 449 Error: Source and destination cannot be the same 450 [EOF] 451 [exit status: 1] 452 "); 453 454 // Can squash from sibling, which results in the source being abandoned 455 let output = test_env.run_jj_in(&repo_path, ["squash", "--from", "c"]); 456 insta::assert_snapshot!(output, @r" 457 ------- stderr ------- 458 Working copy (@) now at: kmkuslsw b902d1dd f | (no description set) 459 Parent commit (@-) : znkkpsqq c2f9de87 e | (no description set) 460 Added 0 files, modified 1 files, removed 0 files 461 [EOF] 462 "); 463 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 464 @ b902d1dd59d9 f 465 ○ c2f9de87325d e 466 ○ e0dac715116f d 467 │ ○ 12d6103dc0c8 b c 468 ├─╯ 469 ○ b7b767179c44 a 470 ◆ 000000000000 (empty) 471 [EOF] 472 "); 473 // The change from the source has been applied 474 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1"]); 475 insta::assert_snapshot!(output, @r" 476 c 477 [EOF] 478 "); 479 // File `file2`, which was not changed in source, is unchanged 480 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2"]); 481 insta::assert_snapshot!(output, @r" 482 f 483 [EOF] 484 "); 485 486 // Can squash from ancestor 487 test_env.run_jj_in(&repo_path, ["undo"]).success(); 488 let output = test_env.run_jj_in(&repo_path, ["squash", "--from", "@--"]); 489 insta::assert_snapshot!(output, @r" 490 ------- stderr ------- 491 Working copy (@) now at: kmkuslsw cfc5eb87 f | (no description set) 492 Parent commit (@-) : znkkpsqq 4dc7c279 e | (no description set) 493 [EOF] 494 "); 495 // The change has been removed from the source (the change pointed to by 'd' 496 // became empty and was abandoned) 497 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 498 @ cfc5eb876eb1 f 499 ○ 4dc7c27994bd e 500 │ ○ 59597b34a0d8 c 501 │ ○ 12d6103dc0c8 b 502 ├─╯ 503 ○ b7b767179c44 a d 504 ◆ 000000000000 (empty) 505 [EOF] 506 "); 507 // The change from the source has been applied (the file contents were already 508 // "f", as is typically the case when moving changes from an ancestor) 509 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2"]); 510 insta::assert_snapshot!(output, @r" 511 f 512 [EOF] 513 "); 514 515 // Can squash from descendant 516 test_env.run_jj_in(&repo_path, ["undo"]).success(); 517 let output = test_env.run_jj_in(&repo_path, ["squash", "--from", "e", "--into", "d"]); 518 insta::assert_snapshot!(output, @r" 519 ------- stderr ------- 520 Rebased 1 descendant commits 521 Working copy (@) now at: kmkuslsw 6de62c22 f | (no description set) 522 Parent commit (@-) : vruxwmqv 32196a11 d e | (no description set) 523 [EOF] 524 "); 525 // The change has been removed from the source (the change pointed to by 'e' 526 // became empty and was abandoned) 527 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 528 @ 6de62c22fa07 f 529 ○ 32196a117ee3 d e 530 │ ○ 59597b34a0d8 c 531 │ ○ 12d6103dc0c8 b 532 ├─╯ 533 ○ b7b767179c44 a 534 ◆ 000000000000 (empty) 535 [EOF] 536 "); 537 // The change from the source has been applied 538 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2", "-r", "d"]); 539 insta::assert_snapshot!(output, @r" 540 e 541 [EOF] 542 "); 543} 544 545#[test] 546fn test_squash_from_to_partial() { 547 let mut test_env = TestEnvironment::default(); 548 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 549 let repo_path = test_env.env_root().join("repo"); 550 551 // Create history like this: 552 // C 553 // | 554 // D B 555 // |/ 556 // A 557 test_env 558 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "a"]) 559 .success(); 560 std::fs::write(repo_path.join("file1"), "a\n").unwrap(); 561 std::fs::write(repo_path.join("file2"), "a\n").unwrap(); 562 std::fs::write(repo_path.join("file3"), "a\n").unwrap(); 563 test_env.run_jj_in(&repo_path, ["new"]).success(); 564 test_env 565 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "b"]) 566 .success(); 567 std::fs::write(repo_path.join("file3"), "b\n").unwrap(); 568 test_env.run_jj_in(&repo_path, ["new"]).success(); 569 test_env 570 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "c"]) 571 .success(); 572 std::fs::write(repo_path.join("file1"), "c\n").unwrap(); 573 std::fs::write(repo_path.join("file2"), "c\n").unwrap(); 574 test_env.run_jj_in(&repo_path, ["edit", "a"]).success(); 575 test_env.run_jj_in(&repo_path, ["new"]).success(); 576 test_env 577 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "d"]) 578 .success(); 579 std::fs::write(repo_path.join("file3"), "d\n").unwrap(); 580 // Test the setup 581 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 582 @ e0dac715116f d 583 │ ○ 087591be5a01 c 584 │ ○ 12d6103dc0c8 b 585 ├─╯ 586 ○ b7b767179c44 a 587 ◆ 000000000000 (empty) 588 [EOF] 589 "); 590 591 let edit_script = test_env.set_up_fake_diff_editor(); 592 593 // If we don't make any changes in the diff-editor, the whole change is moved 594 let output = test_env.run_jj_in(&repo_path, ["squash", "-i", "--from", "c"]); 595 insta::assert_snapshot!(output, @r" 596 ------- stderr ------- 597 Working copy (@) now at: vruxwmqv 987bcfb2 d | (no description set) 598 Parent commit (@-) : qpvuntsm b7b76717 a | (no description set) 599 Added 0 files, modified 2 files, removed 0 files 600 [EOF] 601 "); 602 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 603 @ 987bcfb2eb62 d 604 │ ○ 12d6103dc0c8 b c 605 ├─╯ 606 ○ b7b767179c44 a 607 ◆ 000000000000 (empty) 608 [EOF] 609 "); 610 // The changes from the source has been applied 611 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1"]); 612 insta::assert_snapshot!(output, @r" 613 c 614 [EOF] 615 "); 616 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2"]); 617 insta::assert_snapshot!(output, @r" 618 c 619 [EOF] 620 "); 621 // File `file3`, which was not changed in source, is unchanged 622 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file3"]); 623 insta::assert_snapshot!(output, @r" 624 d 625 [EOF] 626 "); 627 628 // Can squash only part of the change in interactive mode 629 test_env.run_jj_in(&repo_path, ["undo"]).success(); 630 std::fs::write(&edit_script, "reset file2").unwrap(); 631 let output = test_env.run_jj_in(&repo_path, ["squash", "-i", "--from", "c"]); 632 insta::assert_snapshot!(output, @r" 633 ------- stderr ------- 634 Working copy (@) now at: vruxwmqv 576244e8 d | (no description set) 635 Parent commit (@-) : qpvuntsm b7b76717 a | (no description set) 636 Added 0 files, modified 1 files, removed 0 files 637 [EOF] 638 "); 639 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 640 @ 576244e87883 d 641 │ ○ 6f486f2f4539 c 642 │ ○ 12d6103dc0c8 b 643 ├─╯ 644 ○ b7b767179c44 a 645 ◆ 000000000000 (empty) 646 [EOF] 647 "); 648 // The selected change from the source has been applied 649 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1"]); 650 insta::assert_snapshot!(output, @r" 651 c 652 [EOF] 653 "); 654 // The unselected change from the source has not been applied 655 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2"]); 656 insta::assert_snapshot!(output, @r" 657 a 658 [EOF] 659 "); 660 // File `file3`, which was changed in source's parent, is unchanged 661 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file3"]); 662 insta::assert_snapshot!(output, @r" 663 d 664 [EOF] 665 "); 666 667 // Can squash only part of the change from a sibling in non-interactive mode 668 test_env.run_jj_in(&repo_path, ["undo"]).success(); 669 // Clear the script so we know it won't be used 670 std::fs::write(&edit_script, "").unwrap(); 671 let output = test_env.run_jj_in(&repo_path, ["squash", "--from", "c", "file1"]); 672 insta::assert_snapshot!(output, @r" 673 ------- stderr ------- 674 Working copy (@) now at: vruxwmqv 5b407c24 d | (no description set) 675 Parent commit (@-) : qpvuntsm b7b76717 a | (no description set) 676 Added 0 files, modified 1 files, removed 0 files 677 [EOF] 678 "); 679 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 680 @ 5b407c249fa7 d 681 │ ○ 724d64da1487 c 682 │ ○ 12d6103dc0c8 b 683 ├─╯ 684 ○ b7b767179c44 a 685 ◆ 000000000000 (empty) 686 [EOF] 687 "); 688 // The selected change from the source has been applied 689 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1"]); 690 insta::assert_snapshot!(output, @r" 691 c 692 [EOF] 693 "); 694 // The unselected change from the source has not been applied 695 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2"]); 696 insta::assert_snapshot!(output, @r" 697 a 698 [EOF] 699 "); 700 // File `file3`, which was changed in source's parent, is unchanged 701 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file3"]); 702 insta::assert_snapshot!(output, @r" 703 d 704 [EOF] 705 "); 706 707 // Can squash only part of the change from a descendant in non-interactive mode 708 test_env.run_jj_in(&repo_path, ["undo"]).success(); 709 // Clear the script so we know it won't be used 710 std::fs::write(&edit_script, "").unwrap(); 711 let output = test_env.run_jj_in( 712 &repo_path, 713 ["squash", "--from", "c", "--into", "b", "file1"], 714 ); 715 insta::assert_snapshot!(output, @r" 716 ------- stderr ------- 717 Rebased 1 descendant commits 718 [EOF] 719 "); 720 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 721 @ e0dac715116f d 722 │ ○ d2a587ae205d c 723 │ ○ a53394306362 b 724 ├─╯ 725 ○ b7b767179c44 a 726 ◆ 000000000000 (empty) 727 [EOF] 728 "); 729 // The selected change from the source has been applied 730 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file1", "-r", "b"]); 731 insta::assert_snapshot!(output, @r" 732 c 733 [EOF] 734 "); 735 // The unselected change from the source has not been applied 736 let output = test_env.run_jj_in(&repo_path, ["file", "show", "file2", "-r", "b"]); 737 insta::assert_snapshot!(output, @r" 738 a 739 [EOF] 740 "); 741 742 // If we specify only a non-existent file, then nothing changes. 743 test_env.run_jj_in(&repo_path, ["undo"]).success(); 744 let output = test_env.run_jj_in(&repo_path, ["squash", "--from", "c", "nonexistent"]); 745 insta::assert_snapshot!(output, @r" 746 ------- stderr ------- 747 Nothing changed. 748 [EOF] 749 "); 750} 751 752#[test] 753fn test_squash_from_multiple() { 754 let test_env = TestEnvironment::default(); 755 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 756 let repo_path = test_env.env_root().join("repo"); 757 758 // Create history like this: 759 // F 760 // | 761 // E 762 // /|\ 763 // B C D 764 // \|/ 765 // A 766 let file = repo_path.join("file"); 767 test_env 768 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "a"]) 769 .success(); 770 std::fs::write(&file, "a\n").unwrap(); 771 test_env.run_jj_in(&repo_path, ["new"]).success(); 772 test_env 773 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "b"]) 774 .success(); 775 std::fs::write(&file, "b\n").unwrap(); 776 test_env.run_jj_in(&repo_path, ["new", "@-"]).success(); 777 test_env 778 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "c"]) 779 .success(); 780 std::fs::write(&file, "c\n").unwrap(); 781 test_env.run_jj_in(&repo_path, ["new", "@-"]).success(); 782 test_env 783 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "d"]) 784 .success(); 785 std::fs::write(&file, "d\n").unwrap(); 786 test_env 787 .run_jj_in(&repo_path, ["new", "all:visible_heads()"]) 788 .success(); 789 test_env 790 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "e"]) 791 .success(); 792 std::fs::write(&file, "e\n").unwrap(); 793 test_env.run_jj_in(&repo_path, ["new"]).success(); 794 test_env 795 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "f"]) 796 .success(); 797 std::fs::write(&file, "f\n").unwrap(); 798 // Test the setup 799 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 800 @ 94e57ecb8d4f f 801 ○ 78ed28eb87b8 e 802 ├─┬─╮ 803 │ │ ○ 35e764e4357c b 804 │ ○ │ 02a128cd4344 c 805 │ ├─╯ 806 ○ │ aaf7b53a1b64 d 807 ├─╯ 808 ○ 3b1673b6370c a 809 ◆ 000000000000 (empty) 810 [EOF] 811 "); 812 813 // Squash a few commits sideways 814 let output = test_env.run_jj_in(&repo_path, ["squash", "--from=b", "--from=c", "--into=d"]); 815 insta::assert_snapshot!(output, @r" 816 ------- stderr ------- 817 Rebased 2 descendant commits 818 Working copy (@) now at: kpqxywon 7ea39167 f | (no description set) 819 Parent commit (@-) : yostqsxw acfbf2a0 e | (no description set) 820 New conflicts appeared in 1 commits: 821 yqosqzyt 4df3b215 d | (conflict) (no description set) 822 Hint: To resolve the conflicts, start by updating to it: 823 jj new yqosqzyt 824 Then use `jj resolve`, or edit the conflict markers in the file directly. 825 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 826 Then run `jj squash` to move the resolution into the conflicted commit. 827 [EOF] 828 "); 829 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 830 @ 7ea391676d52 f 831 ○ acfbf2a0600d e 832 ├─╮ 833 × │ 4df3b2156c3d d 834 ├─╯ 835 ○ 3b1673b6370c a b c 836 ◆ 000000000000 (empty) 837 [EOF] 838 "); 839 // The changes from the sources have been applied 840 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=d", "file"]); 841 insta::assert_snapshot!(output, @r" 842 <<<<<<< Conflict 1 of 1 843 %%%%%%% Changes from base #1 to side #1 844 -a 845 +d 846 %%%%%%% Changes from base #2 to side #2 847 -a 848 +b 849 +++++++ Contents of side #3 850 c 851 >>>>>>> Conflict 1 of 1 ends 852 [EOF] 853 "); 854 855 // Squash a few commits up an down 856 test_env.run_jj_in(&repo_path, ["undo"]).success(); 857 let output = test_env.run_jj_in(&repo_path, ["squash", "--from=b|c|f", "--into=e"]); 858 insta::assert_snapshot!(output, @r" 859 ------- stderr ------- 860 Rebased 1 descendant commits 861 Working copy (@) now at: xznxytkn 6a670d1a (empty) (no description set) 862 Parent commit (@-) : yostqsxw c1293ff7 e f | (no description set) 863 [EOF] 864 "); 865 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 866 @ 6a670d1ac76e (empty) 867 ○ c1293ff7be51 e f 868 ├─╮ 869 ○ │ aaf7b53a1b64 d 870 ├─╯ 871 ○ 3b1673b6370c a b c 872 ◆ 000000000000 (empty) 873 [EOF] 874 "); 875 // The changes from the sources have been applied to the destination 876 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=e", "file"]); 877 insta::assert_snapshot!(output, @r" 878 f 879 [EOF] 880 "); 881 882 // Empty squash shouldn't crash 883 let output = test_env.run_jj_in(&repo_path, ["squash", "--from=none()"]); 884 insta::assert_snapshot!(output, @r" 885 ------- stderr ------- 886 Nothing changed. 887 [EOF] 888 "); 889} 890 891#[test] 892fn test_squash_from_multiple_partial() { 893 let test_env = TestEnvironment::default(); 894 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 895 let repo_path = test_env.env_root().join("repo"); 896 897 // Create history like this: 898 // F 899 // | 900 // E 901 // /|\ 902 // B C D 903 // \|/ 904 // A 905 let file1 = repo_path.join("file1"); 906 let file2 = repo_path.join("file2"); 907 test_env 908 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "a"]) 909 .success(); 910 std::fs::write(&file1, "a\n").unwrap(); 911 std::fs::write(&file2, "a\n").unwrap(); 912 test_env.run_jj_in(&repo_path, ["new"]).success(); 913 test_env 914 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "b"]) 915 .success(); 916 std::fs::write(&file1, "b\n").unwrap(); 917 std::fs::write(&file2, "b\n").unwrap(); 918 test_env.run_jj_in(&repo_path, ["new", "@-"]).success(); 919 test_env 920 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "c"]) 921 .success(); 922 std::fs::write(&file1, "c\n").unwrap(); 923 std::fs::write(&file2, "c\n").unwrap(); 924 test_env.run_jj_in(&repo_path, ["new", "@-"]).success(); 925 test_env 926 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "d"]) 927 .success(); 928 std::fs::write(&file1, "d\n").unwrap(); 929 std::fs::write(&file2, "d\n").unwrap(); 930 test_env 931 .run_jj_in(&repo_path, ["new", "all:visible_heads()"]) 932 .success(); 933 test_env 934 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "e"]) 935 .success(); 936 std::fs::write(&file1, "e\n").unwrap(); 937 std::fs::write(&file2, "e\n").unwrap(); 938 test_env.run_jj_in(&repo_path, ["new"]).success(); 939 test_env 940 .run_jj_in(&repo_path, ["bookmark", "create", "-r@", "f"]) 941 .success(); 942 std::fs::write(&file1, "f\n").unwrap(); 943 std::fs::write(&file2, "f\n").unwrap(); 944 // Test the setup 945 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 946 @ 30980b9045f7 f 947 ○ 5326a04aac1f e 948 ├─┬─╮ 949 │ │ ○ d117da276a0f b 950 │ ○ │ 93a7bfff61e7 c 951 │ ├─╯ 952 ○ │ 763809ca0131 d 953 ├─╯ 954 ○ 54d3c1c0e9fd a 955 ◆ 000000000000 (empty) 956 [EOF] 957 "); 958 959 // Partially squash a few commits sideways 960 let output = test_env.run_jj_in(&repo_path, ["squash", "--from=b|c", "--into=d", "file1"]); 961 insta::assert_snapshot!(output, @r" 962 ------- stderr ------- 963 Rebased 2 descendant commits 964 Working copy (@) now at: kpqxywon a8530305 f | (no description set) 965 Parent commit (@-) : yostqsxw 0a3637fc e | (no description set) 966 New conflicts appeared in 1 commits: 967 yqosqzyt 05a3ab3d d | (conflict) (no description set) 968 Hint: To resolve the conflicts, start by updating to it: 969 jj new yqosqzyt 970 Then use `jj resolve`, or edit the conflict markers in the file directly. 971 Once the conflicts are resolved, you may want to inspect the result with `jj diff`. 972 Then run `jj squash` to move the resolution into the conflicted commit. 973 [EOF] 974 "); 975 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 976 @ a8530305127c f 977 ○ 0a3637fca632 e 978 ├─┬─╮ 979 │ │ ○ 450d1499c1ae b 980 │ ○ │ 14b44bf0473c c 981 │ ├─╯ 982 × │ 05a3ab3dffc8 d 983 ├─╯ 984 ○ 54d3c1c0e9fd a 985 ◆ 000000000000 (empty) 986 [EOF] 987 "); 988 // The selected changes have been removed from the sources 989 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=b", "file1"]); 990 insta::assert_snapshot!(output, @r" 991 a 992 [EOF] 993 "); 994 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=c", "file1"]); 995 insta::assert_snapshot!(output, @r" 996 a 997 [EOF] 998 "); 999 // The selected changes from the sources have been applied 1000 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=d", "file1"]); 1001 insta::assert_snapshot!(output, @r" 1002 <<<<<<< Conflict 1 of 1 1003 %%%%%%% Changes from base #1 to side #1 1004 -a 1005 +d 1006 %%%%%%% Changes from base #2 to side #2 1007 -a 1008 +b 1009 +++++++ Contents of side #3 1010 c 1011 >>>>>>> Conflict 1 of 1 ends 1012 [EOF] 1013 "); 1014 // The unselected change from the sources have not been applied to the 1015 // destination 1016 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=d", "file2"]); 1017 insta::assert_snapshot!(output, @r" 1018 d 1019 [EOF] 1020 "); 1021 1022 // Partially squash a few commits up an down 1023 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1024 let output = test_env.run_jj_in(&repo_path, ["squash", "--from=b|c|f", "--into=e", "file1"]); 1025 insta::assert_snapshot!(output, @r" 1026 ------- stderr ------- 1027 Rebased 1 descendant commits 1028 Working copy (@) now at: kpqxywon 3b7559b8 f | (no description set) 1029 Parent commit (@-) : yostqsxw a3b1714c e | (no description set) 1030 [EOF] 1031 "); 1032 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 1033 @ 3b7559b89a57 f 1034 ○ a3b1714cdfb2 e 1035 ├─┬─╮ 1036 │ │ ○ 867efb38e801 b 1037 │ ○ │ 84dcb3d4b3eb c 1038 │ ├─╯ 1039 ○ │ 763809ca0131 d 1040 ├─╯ 1041 ○ 54d3c1c0e9fd a 1042 ◆ 000000000000 (empty) 1043 [EOF] 1044 "); 1045 // The selected changes have been removed from the sources 1046 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=b", "file1"]); 1047 insta::assert_snapshot!(output, @r" 1048 a 1049 [EOF] 1050 "); 1051 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=c", "file1"]); 1052 insta::assert_snapshot!(output, @r" 1053 a 1054 [EOF] 1055 "); 1056 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=f", "file1"]); 1057 insta::assert_snapshot!(output, @r" 1058 f 1059 [EOF] 1060 "); 1061 // The selected changes from the sources have been applied to the destination 1062 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=e", "file1"]); 1063 insta::assert_snapshot!(output, @r" 1064 f 1065 [EOF] 1066 "); 1067 // The unselected changes from the sources have not been applied 1068 let output = test_env.run_jj_in(&repo_path, ["file", "show", "-r=d", "file2"]); 1069 insta::assert_snapshot!(output, @r" 1070 d 1071 [EOF] 1072 "); 1073} 1074 1075#[test] 1076fn test_squash_from_multiple_partial_no_op() { 1077 let test_env = TestEnvironment::default(); 1078 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1079 let repo_path = test_env.env_root().join("repo"); 1080 1081 // Create history like this: 1082 // B C D 1083 // \|/ 1084 // A 1085 let file_a = repo_path.join("a"); 1086 let file_b = repo_path.join("b"); 1087 let file_c = repo_path.join("c"); 1088 let file_d = repo_path.join("d"); 1089 test_env 1090 .run_jj_in(&repo_path, ["describe", "-m=a"]) 1091 .success(); 1092 std::fs::write(file_a, "a\n").unwrap(); 1093 test_env.run_jj_in(&repo_path, ["new", "-m=b"]).success(); 1094 std::fs::write(file_b, "b\n").unwrap(); 1095 test_env 1096 .run_jj_in(&repo_path, ["new", "@-", "-m=c"]) 1097 .success(); 1098 std::fs::write(file_c, "c\n").unwrap(); 1099 test_env 1100 .run_jj_in(&repo_path, ["new", "@-", "-m=d"]) 1101 .success(); 1102 std::fs::write(file_d, "d\n").unwrap(); 1103 // Test the setup 1104 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 1105 @ b37ca1ee3306 d 1106 │ ○ f40b442af3e8 c 1107 ├─╯ 1108 │ ○ b73077b08c59 b 1109 ├─╯ 1110 ○ 2443ea76b0b1 a 1111 ◆ 000000000000 (empty) 1112 [EOF] 1113 "); 1114 1115 // Source commits that didn't match the paths are not rewritten 1116 let output = test_env.run_jj_in( 1117 &repo_path, 1118 ["squash", "--from=@-+ ~ @", "--into=@", "-m=d", "b"], 1119 ); 1120 insta::assert_snapshot!(output, @r" 1121 ------- stderr ------- 1122 Working copy (@) now at: mzvwutvl e178068a d 1123 Parent commit (@-) : qpvuntsm 2443ea76 a 1124 Added 1 files, modified 0 files, removed 0 files 1125 [EOF] 1126 "); 1127 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 1128 @ e178068add8c d 1129 │ ○ f40b442af3e8 c 1130 ├─╯ 1131 ○ 2443ea76b0b1 a 1132 ◆ 000000000000 (empty) 1133 [EOF] 1134 "); 1135 let output = test_env.run_jj_in( 1136 &repo_path, 1137 [ 1138 "evolog", 1139 "-T", 1140 r#"separate(" ", commit_id.short(), description)"#, 1141 ], 1142 ); 1143 insta::assert_snapshot!(output, @r" 1144 @ e178068add8c d 1145 ├─╮ 1146 │ ○ b73077b08c59 b 1147 │ ○ a786561e909f b 1148 ○ b37ca1ee3306 d 1149 ○ 1d9eb34614c9 d 1150 [EOF] 1151 "); 1152 1153 // If no source commits match the paths, then the whole operation is a no-op 1154 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1155 let output = test_env.run_jj_in( 1156 &repo_path, 1157 ["squash", "--from=@-+ ~ @", "--into=@", "-m=d", "a"], 1158 ); 1159 insta::assert_snapshot!(output, @r" 1160 ------- stderr ------- 1161 Nothing changed. 1162 [EOF] 1163 "); 1164 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 1165 @ b37ca1ee3306 d 1166 │ ○ f40b442af3e8 c 1167 ├─╯ 1168 │ ○ b73077b08c59 b 1169 ├─╯ 1170 ○ 2443ea76b0b1 a 1171 ◆ 000000000000 (empty) 1172 [EOF] 1173 "); 1174} 1175 1176#[must_use] 1177fn get_log_output(test_env: &TestEnvironment, repo_path: &Path) -> CommandOutput { 1178 let template = r#"separate( 1179 " ", 1180 commit_id.short(), 1181 bookmarks, 1182 description, 1183 if(empty, "(empty)") 1184 )"#; 1185 test_env.run_jj_in(repo_path, ["log", "-T", template]) 1186} 1187 1188#[test] 1189fn test_squash_description() { 1190 let mut test_env = TestEnvironment::default(); 1191 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1192 let repo_path = test_env.env_root().join("repo"); 1193 1194 let edit_script = test_env.set_up_fake_editor(); 1195 std::fs::write(&edit_script, r#"fail"#).unwrap(); 1196 1197 // If both descriptions are empty, the resulting description is empty 1198 std::fs::write(repo_path.join("file1"), "a\n").unwrap(); 1199 std::fs::write(repo_path.join("file2"), "a\n").unwrap(); 1200 test_env.run_jj_in(&repo_path, ["new"]).success(); 1201 std::fs::write(repo_path.join("file1"), "b\n").unwrap(); 1202 std::fs::write(repo_path.join("file2"), "b\n").unwrap(); 1203 test_env.run_jj_in(&repo_path, ["squash"]).success(); 1204 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @""); 1205 1206 // If the destination's description is empty and the source's description is 1207 // non-empty, the resulting description is from the source 1208 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1209 test_env 1210 .run_jj_in(&repo_path, ["describe", "-m", "source"]) 1211 .success(); 1212 test_env.run_jj_in(&repo_path, ["squash"]).success(); 1213 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1214 source 1215 [EOF] 1216 "); 1217 1218 // If the destination description is non-empty and the source's description is 1219 // empty, the resulting description is from the destination 1220 test_env 1221 .run_jj_in(&repo_path, ["op", "restore", "@--"]) 1222 .success(); 1223 test_env 1224 .run_jj_in(&repo_path, ["describe", "@-", "-m", "destination"]) 1225 .success(); 1226 test_env.run_jj_in(&repo_path, ["squash"]).success(); 1227 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1228 destination 1229 [EOF] 1230 "); 1231 1232 // An explicit description on the command-line overrides this 1233 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1234 test_env 1235 .run_jj_in(&repo_path, ["squash", "-m", "custom"]) 1236 .success(); 1237 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1238 custom 1239 [EOF] 1240 "); 1241 1242 // If both descriptions were non-empty, we get asked for a combined description 1243 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1244 test_env 1245 .run_jj_in(&repo_path, ["describe", "-m", "source"]) 1246 .success(); 1247 std::fs::write(&edit_script, "dump editor0").unwrap(); 1248 test_env.run_jj_in(&repo_path, ["squash"]).success(); 1249 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1250 destination 1251 1252 source 1253 [EOF] 1254 "); 1255 insta::assert_snapshot!( 1256 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @r#" 1257 JJ: Enter a description for the combined commit. 1258 JJ: Description from the destination commit: 1259 destination 1260 1261 JJ: Description from source commit: 1262 source 1263 1264 JJ: This commit contains the following changes: 1265 JJ: A file1 1266 JJ: A file2 1267 JJ: 1268 JJ: Lines starting with "JJ:" (like this one) will be removed. 1269 "#); 1270 1271 // An explicit description on the command-line overrides prevents launching an 1272 // editor 1273 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1274 test_env 1275 .run_jj_in(&repo_path, ["squash", "-m", "custom"]) 1276 .success(); 1277 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1278 custom 1279 [EOF] 1280 "); 1281 1282 // If the source's *content* doesn't become empty, then the source remains and 1283 // both descriptions are unchanged 1284 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1285 test_env 1286 .run_jj_in(&repo_path, ["squash", "file1"]) 1287 .success(); 1288 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1289 destination 1290 [EOF] 1291 "); 1292 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @r" 1293 source 1294 [EOF] 1295 "); 1296} 1297 1298#[test] 1299fn test_squash_description_editor_avoids_unc() { 1300 let mut test_env = TestEnvironment::default(); 1301 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1302 let repo_path = test_env.env_root().join("repo"); 1303 1304 let edit_script = test_env.set_up_fake_editor(); 1305 std::fs::write(repo_path.join("file1"), "a\n").unwrap(); 1306 std::fs::write(repo_path.join("file2"), "a\n").unwrap(); 1307 test_env.run_jj_in(&repo_path, ["new"]).success(); 1308 std::fs::write(repo_path.join("file1"), "b\n").unwrap(); 1309 std::fs::write(repo_path.join("file2"), "b\n").unwrap(); 1310 test_env 1311 .run_jj_in(&repo_path, ["describe", "@-", "-m", "destination"]) 1312 .success(); 1313 test_env 1314 .run_jj_in(&repo_path, ["describe", "-m", "source"]) 1315 .success(); 1316 1317 std::fs::write(edit_script, "dump-path path").unwrap(); 1318 test_env.run_jj_in(&repo_path, ["squash"]).success(); 1319 1320 let edited_path = 1321 PathBuf::from(std::fs::read_to_string(test_env.env_root().join("path")).unwrap()); 1322 // While `assert!(!edited_path.starts_with("//?/"))` could work here in most 1323 // cases, it fails when it is not safe to strip the prefix, such as paths 1324 // over 260 chars. 1325 assert_eq!(edited_path, dunce::simplified(&edited_path)); 1326} 1327 1328#[test] 1329fn test_squash_empty() { 1330 let mut test_env = TestEnvironment::default(); 1331 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1332 let repo_path = test_env.env_root().join("repo"); 1333 1334 test_env 1335 .run_jj_in(&repo_path, ["commit", "-m", "parent"]) 1336 .success(); 1337 1338 let output = test_env.run_jj_in(&repo_path, ["squash"]); 1339 insta::assert_snapshot!(output, @r" 1340 ------- stderr ------- 1341 Working copy (@) now at: kkmpptxz adece6e8 (empty) (no description set) 1342 Parent commit (@-) : qpvuntsm 5076fc41 (empty) parent 1343 [EOF] 1344 "); 1345 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1346 parent 1347 [EOF] 1348 "); 1349 1350 test_env 1351 .run_jj_in(&repo_path, ["describe", "-m", "child"]) 1352 .success(); 1353 test_env.set_up_fake_editor(); 1354 test_env.run_jj_in(&repo_path, ["squash"]).success(); 1355 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r" 1356 parent 1357 1358 child 1359 [EOF] 1360 "); 1361} 1362 1363#[test] 1364fn test_squash_use_destination_message() { 1365 let test_env = TestEnvironment::default(); 1366 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1367 let repo_path = test_env.env_root().join("repo"); 1368 1369 test_env.run_jj_in(&repo_path, ["commit", "-m=a"]).success(); 1370 test_env.run_jj_in(&repo_path, ["commit", "-m=b"]).success(); 1371 test_env 1372 .run_jj_in(&repo_path, ["describe", "-m=c"]) 1373 .success(); 1374 // Test the setup 1375 insta::assert_snapshot!(get_log_output_with_description(&test_env, &repo_path), @r" 1376 @ 8aac283daeac c 1377 ○ 017c7f689ed7 b 1378 ○ d8d5f980a897 a 1379 ◆ 000000000000 1380 [EOF] 1381 "); 1382 1383 // Squash the current revision using the short name for the option. 1384 test_env.run_jj_in(&repo_path, ["squash", "-u"]).success(); 1385 insta::assert_snapshot!(get_log_output_with_description(&test_env, &repo_path), @r" 1386 @ fd33e4bc332b 1387 ○ 3a17aa5dcce9 b 1388 ○ d8d5f980a897 a 1389 ◆ 000000000000 1390 [EOF] 1391 "); 1392 1393 // Undo and squash again, but this time squash both "b" and "c" into "a". 1394 test_env.run_jj_in(&repo_path, ["undo"]).success(); 1395 test_env 1396 .run_jj_in( 1397 &repo_path, 1398 [ 1399 "squash", 1400 "--use-destination-message", 1401 "--from", 1402 "description(b)::", 1403 "--into", 1404 "description(a)", 1405 ], 1406 ) 1407 .success(); 1408 insta::assert_snapshot!(get_log_output_with_description(&test_env, &repo_path), @r" 1409 @ 7c832accbf60 1410 ○ 688660377651 a 1411 ◆ 000000000000 1412 [EOF] 1413 "); 1414} 1415 1416// The --use-destination-message and --message options are incompatible. 1417#[test] 1418fn test_squash_use_destination_message_and_message_mutual_exclusion() { 1419 let test_env = TestEnvironment::default(); 1420 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1421 let repo_path = test_env.env_root().join("repo"); 1422 test_env.run_jj_in(&repo_path, ["commit", "-m=a"]).success(); 1423 test_env 1424 .run_jj_in(&repo_path, ["describe", "-m=b"]) 1425 .success(); 1426 insta::assert_snapshot!(test_env.run_jj_in( 1427 &repo_path, 1428 [ 1429 "squash", 1430 "--message=123", 1431 "--use-destination-message", 1432 ], 1433 ), @r" 1434 ------- stderr ------- 1435 error: the argument '--message <MESSAGE>' cannot be used with '--use-destination-message' 1436 1437 Usage: jj squash --message <MESSAGE> [FILESETS]... 1438 1439 For more information, try '--help'. 1440 [EOF] 1441 [exit status: 2] 1442 "); 1443} 1444 1445#[must_use] 1446fn get_description(test_env: &TestEnvironment, repo_path: &Path, rev: &str) -> CommandOutput { 1447 test_env.run_jj_in( 1448 repo_path, 1449 ["log", "--no-graph", "-T", "description", "-r", rev], 1450 ) 1451} 1452 1453#[must_use] 1454fn get_log_output_with_description(test_env: &TestEnvironment, repo_path: &Path) -> CommandOutput { 1455 let template = r#"separate(" ", commit_id.short(), description)"#; 1456 test_env.run_jj_in(repo_path, ["log", "-T", template]) 1457}