just playing with tangled
at gvimdiff 43 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 test_case::test_case; 19 20use crate::common::CommandOutput; 21use crate::common::TestEnvironment; 22 23#[must_use] 24fn get_log_output(test_env: &TestEnvironment, cwd: &Path) -> CommandOutput { 25 let template = r#"separate(" ", change_id.short(), empty, local_bookmarks, description)"#; 26 test_env.run_jj_in(cwd, ["log", "-T", template]) 27} 28 29#[must_use] 30fn get_workspace_log_output(test_env: &TestEnvironment, cwd: &Path) -> CommandOutput { 31 let template = r#"separate(" ", change_id.short(), working_copies, description)"#; 32 test_env.run_jj_in(cwd, ["log", "-T", template, "-r", "all()"]) 33} 34 35#[must_use] 36fn get_recorded_dates(test_env: &TestEnvironment, cwd: &Path, revset: &str) -> CommandOutput { 37 let template = r#"separate("\n", "Author date: " ++ author.timestamp(), "Committer date: " ++ committer.timestamp())"#; 38 test_env.run_jj_in(cwd, ["log", "--no-graph", "-T", template, "-r", revset]) 39} 40 41#[test] 42fn test_split_by_paths() { 43 let mut test_env = TestEnvironment::default(); 44 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 45 let repo_path = test_env.env_root().join("repo"); 46 47 std::fs::write(repo_path.join("file1"), "foo").unwrap(); 48 std::fs::write(repo_path.join("file2"), "foo").unwrap(); 49 std::fs::write(repo_path.join("file3"), "foo").unwrap(); 50 51 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 52 @ qpvuntsmwlqt false 53 ◆ zzzzzzzzzzzz true 54 [EOF] 55 "); 56 insta::assert_snapshot!(get_recorded_dates(&test_env, &repo_path,"@"), @r" 57 Author date: 2001-02-03 04:05:08.000 +07:00 58 Committer date: 2001-02-03 04:05:08.000 +07:00[EOF] 59 "); 60 61 let edit_script = test_env.set_up_fake_editor(); 62 std::fs::write( 63 edit_script, 64 ["dump editor0", "next invocation\n", "dump editor1"].join("\0"), 65 ) 66 .unwrap(); 67 let output = test_env.run_jj_in(&repo_path, ["split", "file2"]); 68 insta::assert_snapshot!(output, @r" 69 ------- stderr ------- 70 First part: qpvuntsm 65569ca7 (no description set) 71 Second part: zsuskuln 709756f0 (no description set) 72 Working copy (@) now at: zsuskuln 709756f0 (no description set) 73 Parent commit (@-) : qpvuntsm 65569ca7 (no description set) 74 [EOF] 75 "); 76 insta::assert_snapshot!( 77 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @r#" 78 JJ: Enter a description for the first commit. 79 80 81 JJ: This commit contains the following changes: 82 JJ: A file2 83 JJ: 84 JJ: Lines starting with "JJ:" (like this one) will be removed. 85 "#); 86 assert!(!test_env.env_root().join("editor1").exists()); 87 88 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 89 @ zsuskulnrvyr false 90 ○ qpvuntsmwlqt false 91 ◆ zzzzzzzzzzzz true 92 [EOF] 93 "); 94 95 // The author dates of the new commits should be inherited from the commit being 96 // split. The committer dates should be newer. 97 insta::assert_snapshot!(get_recorded_dates(&test_env, &repo_path,"@"), @r" 98 Author date: 2001-02-03 04:05:08.000 +07:00 99 Committer date: 2001-02-03 04:05:10.000 +07:00[EOF] 100 "); 101 insta::assert_snapshot!(get_recorded_dates(&test_env, &repo_path,"@-"), @r" 102 Author date: 2001-02-03 04:05:08.000 +07:00 103 Committer date: 2001-02-03 04:05:10.000 +07:00[EOF] 104 "); 105 106 let output = test_env.run_jj_in(&repo_path, ["diff", "-s", "-r", "@-"]); 107 insta::assert_snapshot!(output, @r" 108 A file2 109 [EOF] 110 "); 111 let output = test_env.run_jj_in(&repo_path, ["diff", "-s"]); 112 insta::assert_snapshot!(output, @r" 113 A file1 114 A file3 115 [EOF] 116 "); 117 118 // Insert an empty commit after @- with "split ." 119 test_env.set_up_fake_editor(); 120 let output = test_env.run_jj_in(&repo_path, ["split", "-r", "@-", "."]); 121 insta::assert_snapshot!(output, @r" 122 ------- stderr ------- 123 Warning: All changes have been selected, so the second commit will be empty 124 Rebased 1 descendant commits 125 First part: qpvuntsm 9da0eea0 (no description set) 126 Second part: znkkpsqq 5b5714a3 (empty) (no description set) 127 Working copy (@) now at: zsuskuln 0c798ee7 (no description set) 128 Parent commit (@-) : znkkpsqq 5b5714a3 (empty) (no description set) 129 [EOF] 130 "); 131 132 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 133 @ zsuskulnrvyr false 134 ○ znkkpsqqskkl true 135 ○ qpvuntsmwlqt false 136 ◆ zzzzzzzzzzzz true 137 [EOF] 138 "); 139 140 let output = test_env.run_jj_in(&repo_path, ["diff", "-s", "-r", "@--"]); 141 insta::assert_snapshot!(output, @r" 142 A file2 143 [EOF] 144 "); 145 146 // Remove newly created empty commit 147 test_env.run_jj_in(&repo_path, ["abandon", "@-"]).success(); 148 149 // Insert an empty commit before @- with "split nonexistent" 150 test_env.set_up_fake_editor(); 151 let output = test_env.run_jj_in(&repo_path, ["split", "-r", "@-", "nonexistent"]); 152 insta::assert_snapshot!(output, @r" 153 ------- stderr ------- 154 Warning: No changes have been selected, so the first commit will be empty 155 Rebased 1 descendant commits 156 First part: qpvuntsm bd42f95a (empty) (no description set) 157 Second part: lylxulpl ed55c86b (no description set) 158 Working copy (@) now at: zsuskuln 1e1ed741 (no description set) 159 Parent commit (@-) : lylxulpl ed55c86b (no description set) 160 [EOF] 161 "); 162 163 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r" 164 @ zsuskulnrvyr false 165 ○ lylxulplsnyw false 166 ○ qpvuntsmwlqt true 167 ◆ zzzzzzzzzzzz true 168 [EOF] 169 "); 170 171 let output = test_env.run_jj_in(&repo_path, ["diff", "-s", "-r", "@-"]); 172 insta::assert_snapshot!(output, @r" 173 A file2 174 [EOF] 175 "); 176} 177 178#[test] 179fn test_split_with_non_empty_description() { 180 let mut test_env = TestEnvironment::default(); 181 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 182 test_env.add_config(r#"ui.default-description = "\n\nTESTED=TODO""#); 183 let workspace_path = test_env.env_root().join("repo"); 184 185 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 186 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 187 test_env 188 .run_jj_in(&workspace_path, ["describe", "-m", "test"]) 189 .success(); 190 let edit_script = test_env.set_up_fake_editor(); 191 std::fs::write( 192 edit_script, 193 [ 194 "dump editor1", 195 "write\npart 1", 196 "next invocation\n", 197 "dump editor2", 198 "write\npart 2", 199 ] 200 .join("\0"), 201 ) 202 .unwrap(); 203 let output = test_env.run_jj_in(&workspace_path, ["split", "file1"]); 204 insta::assert_snapshot!(output, @r" 205 ------- stderr ------- 206 First part: qpvuntsm 231a3c00 part 1 207 Second part: kkmpptxz e96291aa part 2 208 Working copy (@) now at: kkmpptxz e96291aa part 2 209 Parent commit (@-) : qpvuntsm 231a3c00 part 1 210 [EOF] 211 "); 212 213 insta::assert_snapshot!( 214 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r#" 215 JJ: Enter a description for the first commit. 216 test 217 218 JJ: This commit contains the following changes: 219 JJ: A file1 220 JJ: 221 JJ: Lines starting with "JJ:" (like this one) will be removed. 222 "#); 223 insta::assert_snapshot!( 224 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r#" 225 JJ: Enter a description for the second commit. 226 test 227 228 JJ: This commit contains the following changes: 229 JJ: A file2 230 JJ: 231 JJ: Lines starting with "JJ:" (like this one) will be removed. 232 "#); 233 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 234 @ kkmpptxzrspx false part 2 235 ○ qpvuntsmwlqt false part 1 236 ◆ zzzzzzzzzzzz true 237 [EOF] 238 "); 239} 240 241#[test] 242fn test_split_with_default_description() { 243 let mut test_env = TestEnvironment::default(); 244 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 245 test_env.add_config(r#"ui.default-description = "\n\nTESTED=TODO""#); 246 let workspace_path = test_env.env_root().join("repo"); 247 248 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 249 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 250 251 let edit_script = test_env.set_up_fake_editor(); 252 std::fs::write( 253 edit_script, 254 ["dump editor1", "next invocation\n", "dump editor2"].join("\0"), 255 ) 256 .unwrap(); 257 let output = test_env.run_jj_in(&workspace_path, ["split", "file1"]); 258 insta::assert_snapshot!(output, @r" 259 ------- stderr ------- 260 First part: qpvuntsm 02ee5d60 TESTED=TODO 261 Second part: rlvkpnrz 33cd046b (no description set) 262 Working copy (@) now at: rlvkpnrz 33cd046b (no description set) 263 Parent commit (@-) : qpvuntsm 02ee5d60 TESTED=TODO 264 [EOF] 265 "); 266 267 // Since the commit being split has no description, the user will only be 268 // prompted to add a description to the first commit, which will use the 269 // default value we set. The second commit will inherit the empty 270 // description from the commit being split. 271 insta::assert_snapshot!( 272 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r#" 273 JJ: Enter a description for the first commit. 274 275 276 TESTED=TODO 277 278 JJ: This commit contains the following changes: 279 JJ: A file1 280 JJ: 281 JJ: Lines starting with "JJ:" (like this one) will be removed. 282 "#); 283 assert!(!test_env.env_root().join("editor2").exists()); 284 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 285 @ rlvkpnrzqnoo false 286 ○ qpvuntsmwlqt false TESTED=TODO 287 ◆ zzzzzzzzzzzz true 288 [EOF] 289 "); 290} 291 292#[test] 293fn test_split_with_descendants() { 294 // Configure the environment and make the initial commits. 295 let mut test_env = TestEnvironment::default(); 296 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 297 let workspace_path = test_env.env_root().join("repo"); 298 299 // First commit. This is the one we will split later. 300 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 301 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 302 test_env 303 .run_jj_in(&workspace_path, ["commit", "-m", "Add file1 & file2"]) 304 .success(); 305 // Second commit. 306 std::fs::write(workspace_path.join("file3"), "baz\n").unwrap(); 307 test_env 308 .run_jj_in(&workspace_path, ["commit", "-m", "Add file3"]) 309 .success(); 310 // Third commit. 311 std::fs::write(workspace_path.join("file4"), "foobarbaz\n").unwrap(); 312 test_env 313 .run_jj_in(&workspace_path, ["describe", "-m", "Add file4"]) 314 .success(); 315 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###" 316 @ kkmpptxzrspx false Add file4 317 ○ rlvkpnrzqnoo false Add file3 318 ○ qpvuntsmwlqt false Add file1 & file2 319 ◆ zzzzzzzzzzzz true 320 [EOF] 321 "###); 322 323 // Set up the editor and do the split. 324 let edit_script = test_env.set_up_fake_editor(); 325 std::fs::write( 326 edit_script, 327 [ 328 "dump editor1", 329 "write\nAdd file1", 330 "next invocation\n", 331 "dump editor2", 332 "write\nAdd file2", 333 ] 334 .join("\0"), 335 ) 336 .unwrap(); 337 let output = test_env.run_jj_in(&workspace_path, ["split", "file1", "-r", "qpvu"]); 338 insta::assert_snapshot!(output, @r" 339 ------- stderr ------- 340 Rebased 2 descendant commits 341 First part: qpvuntsm 34dd141b Add file1 342 Second part: royxmykx 465e03d0 Add file2 343 Working copy (@) now at: kkmpptxz 2d5d641f Add file4 344 Parent commit (@-) : rlvkpnrz b3bd9eb7 Add file3 345 [EOF] 346 "); 347 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r###" 348 @ kkmpptxzrspx false Add file4 349 ○ rlvkpnrzqnoo false Add file3 350 ○ royxmykxtrkr false Add file2 351 ○ qpvuntsmwlqt false Add file1 352 ◆ zzzzzzzzzzzz true 353 [EOF] 354 "###); 355 356 // The commit we're splitting has a description, so the user will be 357 // prompted to enter a description for each of the commits. 358 insta::assert_snapshot!( 359 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r#" 360 JJ: Enter a description for the first commit. 361 Add file1 & file2 362 363 JJ: This commit contains the following changes: 364 JJ: A file1 365 JJ: 366 JJ: Lines starting with "JJ:" (like this one) will be removed. 367 "#); 368 insta::assert_snapshot!( 369 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r#" 370 JJ: Enter a description for the second commit. 371 Add file1 & file2 372 373 JJ: This commit contains the following changes: 374 JJ: A file2 375 JJ: 376 JJ: Lines starting with "JJ:" (like this one) will be removed. 377 "#); 378 379 // Check the evolog for the first commit. It shows four entries: 380 // - The initial empty commit. 381 // - The rewritten commit from the snapshot after the files were added. 382 // - The rewritten commit once the description is added during `jj commit`. 383 // - The rewritten commit after the split. 384 let evolog_1 = test_env.run_jj_in(&workspace_path, ["evolog", "-r", "qpvun"]); 385 insta::assert_snapshot!(evolog_1, @r###" 386 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:12 34dd141b 387 │ Add file1 388 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 764d46f1 389 │ Add file1 & file2 390 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 44af2155 391 │ (no description set) 392 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:07 230dd059 393 (empty) (no description set) 394 [EOF] 395 "###); 396 397 // The evolog for the second commit is the same, except that the change id 398 // changes after the split. 399 let evolog_2 = test_env.run_jj_in(&workspace_path, ["evolog", "-r", "royxm"]); 400 insta::assert_snapshot!(evolog_2, @r###" 401 ○ royxmykx test.user@example.com 2001-02-03 08:05:12 465e03d0 402 │ Add file2 403 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 764d46f1 404 │ Add file1 & file2 405 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 44af2155 406 │ (no description set) 407 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:07 230dd059 408 (empty) (no description set) 409 [EOF] 410 "###); 411} 412 413// This test makes sure that the children of the commit being split retain any 414// other parents which weren't involved in the split. 415#[test] 416fn test_split_with_merge_child() { 417 let mut test_env = TestEnvironment::default(); 418 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 419 let workspace_path = test_env.env_root().join("repo"); 420 test_env 421 .run_jj_in(&workspace_path, ["describe", "-m=1"]) 422 .success(); 423 test_env 424 .run_jj_in(&workspace_path, ["new", "root()", "-m=a"]) 425 .success(); 426 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 427 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 428 test_env 429 .run_jj_in( 430 &workspace_path, 431 ["new", "description(1)", "description(a)", "-m=2"], 432 ) 433 .success(); 434 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 435 @ zsuskulnrvyr true 2 436 ├─╮ 437 │ ○ kkmpptxzrspx false a 438 ○ │ qpvuntsmwlqt true 1 439 ├─╯ 440 ◆ zzzzzzzzzzzz true 441 [EOF] 442 "); 443 444 // Set up the editor and do the split. 445 let edit_script = test_env.set_up_fake_editor(); 446 std::fs::write( 447 edit_script, 448 ["write\nAdd file1", "next invocation\n", "write\nAdd file2"].join("\0"), 449 ) 450 .unwrap(); 451 let output = test_env.run_jj_in(&workspace_path, ["split", "-r", "description(a)", "file1"]); 452 insta::assert_snapshot!(output, @r" 453 ------- stderr ------- 454 Rebased 1 descendant commits 455 First part: kkmpptxz e8006b47 Add file1 456 Second part: royxmykx 5e1b793d Add file2 457 Working copy (@) now at: zsuskuln 696935af (empty) 2 458 Parent commit (@-) : qpvuntsm 8b64ddff (empty) 1 459 Parent commit (@-) : royxmykx 5e1b793d Add file2 460 [EOF] 461 "); 462 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 463 @ zsuskulnrvyr true 2 464 ├─╮ 465 │ ○ royxmykxtrkr false Add file2 466 │ ○ kkmpptxzrspx false Add file1 467 ○ │ qpvuntsmwlqt true 1 468 ├─╯ 469 ◆ zzzzzzzzzzzz true 470 [EOF] 471 "); 472} 473 474#[test] 475// Split a commit with no descendants into siblings. Also tests that the default 476// description is set correctly on the first commit. 477fn test_split_parallel_no_descendants() { 478 let mut test_env = TestEnvironment::default(); 479 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 480 test_env.add_config(r#"ui.default-description = "\n\nTESTED=TODO""#); 481 let workspace_path = test_env.env_root().join("repo"); 482 483 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 484 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 485 486 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 487 @ qpvuntsmwlqt false 488 ◆ zzzzzzzzzzzz true 489 [EOF] 490 "); 491 492 let edit_script = test_env.set_up_fake_editor(); 493 std::fs::write( 494 edit_script, 495 ["dump editor1", "next invocation\n", "dump editor2"].join("\0"), 496 ) 497 .unwrap(); 498 let output = test_env.run_jj_in(&workspace_path, ["split", "--parallel", "file1"]); 499 insta::assert_snapshot!(output, @r" 500 ------- stderr ------- 501 First part: qpvuntsm 48018df6 TESTED=TODO 502 Second part: kkmpptxz 7eddbf93 (no description set) 503 Working copy (@) now at: kkmpptxz 7eddbf93 (no description set) 504 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 505 Added 0 files, modified 0 files, removed 1 files 506 [EOF] 507 "); 508 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 509 @ kkmpptxzrspx false 510 │ ○ qpvuntsmwlqt false TESTED=TODO 511 ├─╯ 512 ◆ zzzzzzzzzzzz true 513 [EOF] 514 "); 515 516 // Since the commit being split has no description, the user will only be 517 // prompted to add a description to the first commit, which will use the 518 // default value we set. The second commit will inherit the empty 519 // description from the commit being split. 520 insta::assert_snapshot!( 521 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r#" 522 JJ: Enter a description for the first commit. 523 524 525 TESTED=TODO 526 527 JJ: This commit contains the following changes: 528 JJ: A file1 529 JJ: 530 JJ: Lines starting with "JJ:" (like this one) will be removed. 531 "#); 532 assert!(!test_env.env_root().join("editor2").exists()); 533 534 // Check the evolog for the first commit. It shows three entries: 535 // - The initial empty commit. 536 // - The rewritten commit from the snapshot after the files were added. 537 // - The rewritten commit after the split. 538 let evolog_1 = test_env.run_jj_in(&workspace_path, ["evolog", "-r", "qpvun"]); 539 insta::assert_snapshot!(evolog_1, @r###" 540 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:09 48018df6 541 │ TESTED=TODO 542 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 44af2155 543 │ (no description set) 544 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:07 230dd059 545 (empty) (no description set) 546 [EOF] 547 "###); 548 549 // The evolog for the second commit is the same, except that the change id 550 // changes after the split. 551 let evolog_2 = test_env.run_jj_in(&workspace_path, ["evolog", "-r", "kkmpp"]); 552 insta::assert_snapshot!(evolog_2, @r###" 553 @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 7eddbf93 554 │ (no description set) 555 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 44af2155 556 │ (no description set) 557 ○ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:07 230dd059 558 (empty) (no description set) 559 [EOF] 560 "###); 561} 562 563#[test] 564fn test_split_parallel_with_descendants() { 565 // Configure the environment and make the initial commits. 566 let mut test_env = TestEnvironment::default(); 567 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 568 let workspace_path = test_env.env_root().join("repo"); 569 570 // First commit. This is the one we will split later. 571 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 572 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 573 test_env 574 .run_jj_in(&workspace_path, ["commit", "-m", "Add file1 & file2"]) 575 .success(); 576 // Second commit. This will be the child of the sibling commits after the split. 577 std::fs::write(workspace_path.join("file3"), "baz\n").unwrap(); 578 test_env 579 .run_jj_in(&workspace_path, ["commit", "-m", "Add file3"]) 580 .success(); 581 // Third commit. 582 std::fs::write(workspace_path.join("file4"), "foobarbaz\n").unwrap(); 583 test_env 584 .run_jj_in(&workspace_path, ["describe", "-m", "Add file4"]) 585 .success(); 586 // Move back to the previous commit so that we don't have to pass a revision 587 // to the split command. 588 test_env 589 .run_jj_in(&workspace_path, ["prev", "--edit"]) 590 .success(); 591 test_env 592 .run_jj_in(&workspace_path, ["prev", "--edit"]) 593 .success(); 594 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 595 ○ kkmpptxzrspx false Add file4 596 ○ rlvkpnrzqnoo false Add file3 597 @ qpvuntsmwlqt false Add file1 & file2 598 ◆ zzzzzzzzzzzz true 599 [EOF] 600 "); 601 602 // Set up the editor and do the split. 603 let edit_script = test_env.set_up_fake_editor(); 604 std::fs::write( 605 edit_script, 606 [ 607 "dump editor1", 608 "write\nAdd file1", 609 "next invocation\n", 610 "dump editor2", 611 "write\nAdd file2", 612 ] 613 .join("\0"), 614 ) 615 .unwrap(); 616 let output = test_env.run_jj_in(&workspace_path, ["split", "--parallel", "file1"]); 617 insta::assert_snapshot!(output, @r" 618 ------- stderr ------- 619 Rebased 2 descendant commits 620 First part: qpvuntsm 84df941d Add file1 621 Second part: vruxwmqv 94753be3 Add file2 622 Working copy (@) now at: vruxwmqv 94753be3 Add file2 623 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 624 Added 0 files, modified 0 files, removed 1 files 625 [EOF] 626 "); 627 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 628 ○ kkmpptxzrspx false Add file4 629 ○ rlvkpnrzqnoo false Add file3 630 ├─╮ 631 │ @ vruxwmqvtpmx false Add file2 632 ○ │ qpvuntsmwlqt false Add file1 633 ├─╯ 634 ◆ zzzzzzzzzzzz true 635 [EOF] 636 "); 637 638 // The commit we're splitting has a description, so the user will be 639 // prompted to enter a description for each of the sibling commits. 640 insta::assert_snapshot!( 641 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r#" 642 JJ: Enter a description for the first commit. 643 Add file1 & file2 644 645 JJ: This commit contains the following changes: 646 JJ: A file1 647 JJ: 648 JJ: Lines starting with "JJ:" (like this one) will be removed. 649 "#); 650 insta::assert_snapshot!( 651 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r#" 652 JJ: Enter a description for the second commit. 653 Add file1 & file2 654 655 JJ: This commit contains the following changes: 656 JJ: A file2 657 JJ: 658 JJ: Lines starting with "JJ:" (like this one) will be removed. 659 "#); 660} 661 662// This test makes sure that the children of the commit being split retain any 663// other parents which weren't involved in the split. 664#[test] 665fn test_split_parallel_with_merge_child() { 666 let mut test_env = TestEnvironment::default(); 667 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 668 let workspace_path = test_env.env_root().join("repo"); 669 test_env 670 .run_jj_in(&workspace_path, ["describe", "-m=1"]) 671 .success(); 672 test_env 673 .run_jj_in(&workspace_path, ["new", "root()", "-m=a"]) 674 .success(); 675 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 676 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 677 test_env 678 .run_jj_in( 679 &workspace_path, 680 ["new", "description(1)", "description(a)", "-m=2"], 681 ) 682 .success(); 683 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 684 @ zsuskulnrvyr true 2 685 ├─╮ 686 │ ○ kkmpptxzrspx false a 687 ○ │ qpvuntsmwlqt true 1 688 ├─╯ 689 ◆ zzzzzzzzzzzz true 690 [EOF] 691 "); 692 693 // Set up the editor and do the split. 694 let edit_script = test_env.set_up_fake_editor(); 695 std::fs::write( 696 edit_script, 697 ["write\nAdd file1", "next invocation\n", "write\nAdd file2"].join("\0"), 698 ) 699 .unwrap(); 700 let output = test_env.run_jj_in( 701 &workspace_path, 702 ["split", "-r", "description(a)", "--parallel", "file1"], 703 ); 704 insta::assert_snapshot!(output, @r" 705 ------- stderr ------- 706 Rebased 1 descendant commits 707 First part: kkmpptxz e8006b47 Add file1 708 Second part: royxmykx 2cc60f3d Add file2 709 Working copy (@) now at: zsuskuln 35b5d7eb (empty) 2 710 Parent commit (@-) : qpvuntsm 8b64ddff (empty) 1 711 Parent commit (@-) : kkmpptxz e8006b47 Add file1 712 Parent commit (@-) : royxmykx 2cc60f3d Add file2 713 [EOF] 714 "); 715 insta::assert_snapshot!(get_log_output(&test_env, &workspace_path), @r" 716 @ zsuskulnrvyr true 2 717 ├─┬─╮ 718 │ │ ○ royxmykxtrkr false Add file2 719 │ ○ │ kkmpptxzrspx false Add file1 720 │ ├─╯ 721 ○ │ qpvuntsmwlqt true 1 722 ├─╯ 723 ◆ zzzzzzzzzzzz true 724 [EOF] 725 "); 726} 727 728// Make sure `jj split` would refuse to split an empty commit. 729#[test] 730fn test_split_empty() { 731 let test_env = TestEnvironment::default(); 732 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 733 let workspace_path = test_env.env_root().join("repo"); 734 test_env 735 .run_jj_in(&workspace_path, ["describe", "--message", "abc"]) 736 .success(); 737 738 let output = test_env.run_jj_in(&workspace_path, ["split"]); 739 insta::assert_snapshot!(output, @r" 740 ------- stderr ------- 741 Error: Refusing to split empty commit 2ab033062e9fdf7fad2ded8e89c1f145e3698190. 742 Hint: Use `jj new` if you want to create another empty commit. 743 [EOF] 744 [exit status: 1] 745 "); 746} 747 748#[test] 749fn test_split_message_editor_avoids_unc() { 750 let mut test_env = TestEnvironment::default(); 751 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 752 let repo_path = test_env.env_root().join("repo"); 753 754 std::fs::write(repo_path.join("file1"), "foo").unwrap(); 755 std::fs::write(repo_path.join("file2"), "foo").unwrap(); 756 757 let edit_script = test_env.set_up_fake_editor(); 758 std::fs::write(edit_script, "dump-path path").unwrap(); 759 test_env.run_jj_in(&repo_path, ["split", "file2"]).success(); 760 761 let edited_path = 762 PathBuf::from(std::fs::read_to_string(test_env.env_root().join("path")).unwrap()); 763 // While `assert!(!edited_path.starts_with("//?/"))` could work here in most 764 // cases, it fails when it is not safe to strip the prefix, such as paths 765 // over 260 chars. 766 assert_eq!(edited_path, dunce::simplified(&edited_path)); 767} 768 769#[test] 770fn test_split_interactive() { 771 let mut test_env = TestEnvironment::default(); 772 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 773 let workspace_path = test_env.env_root().join("repo"); 774 775 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 776 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 777 let edit_script = test_env.set_up_fake_editor(); 778 std::fs::write(edit_script, ["dump editor"].join("\0")).unwrap(); 779 780 let diff_editor = test_env.set_up_fake_diff_editor(); 781 let diff_script = ["rm file2", "dump JJ-INSTRUCTIONS instrs"].join("\0"); 782 std::fs::write(diff_editor, diff_script).unwrap(); 783 784 // Split the working commit interactively and select only file1 785 let output = test_env.run_jj_in(&workspace_path, ["split"]); 786 insta::assert_snapshot!(output, @r" 787 ------- stderr ------- 788 First part: qpvuntsm 0e15949e (no description set) 789 Second part: rlvkpnrz 9ed12e4c (no description set) 790 Working copy (@) now at: rlvkpnrz 9ed12e4c (no description set) 791 Parent commit (@-) : qpvuntsm 0e15949e (no description set) 792 [EOF] 793 "); 794 795 insta::assert_snapshot!( 796 std::fs::read_to_string(test_env.env_root().join("instrs")).unwrap(), @r" 797 You are splitting a commit into two: qpvuntsm 44af2155 (no description set) 798 799 The diff initially shows the changes in the commit you're splitting. 800 801 Adjust the right side until it shows the contents you want for the first commit. 802 The remainder will be in the second commit. 803 "); 804 805 insta::assert_snapshot!( 806 std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r#" 807 JJ: Enter a description for the first commit. 808 809 810 JJ: This commit contains the following changes: 811 JJ: A file1 812 JJ: 813 JJ: Lines starting with "JJ:" (like this one) will be removed. 814 "#); 815 816 let output = test_env.run_jj_in(&workspace_path, ["log", "--summary"]); 817 insta::assert_snapshot!(output, @r" 818 @ rlvkpnrz test.user@example.com 2001-02-03 08:05:08 9ed12e4c 819 │ (no description set) 820 │ A file2 821 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 0e15949e 822 │ (no description set) 823 │ A file1 824 ◆ zzzzzzzz root() 00000000 825 [EOF] 826 "); 827} 828 829#[test] 830fn test_split_interactive_with_paths() { 831 let mut test_env = TestEnvironment::default(); 832 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 833 let workspace_path = test_env.env_root().join("repo"); 834 835 std::fs::write(workspace_path.join("file2"), "").unwrap(); 836 std::fs::write(workspace_path.join("file3"), "").unwrap(); 837 test_env.run_jj_in(&workspace_path, ["new"]).success(); 838 std::fs::write(workspace_path.join("file1"), "foo\n").unwrap(); 839 std::fs::write(workspace_path.join("file2"), "bar\n").unwrap(); 840 std::fs::write(workspace_path.join("file3"), "baz\n").unwrap(); 841 842 let edit_script = test_env.set_up_fake_editor(); 843 std::fs::write(edit_script, ["dump editor"].join("\0")).unwrap(); 844 let diff_editor = test_env.set_up_fake_diff_editor(); 845 // On the before side, file2 is empty. On the after side, it contains "bar". 846 // The "reset file2" copies the empty version from the before side to the 847 // after side, effectively "unselecting" the changes and leaving only the 848 // changes made to file1. file3 doesn't appear on either side since it isn't 849 // in the filesets passed to `jj split`. 850 let diff_script = [ 851 "files-before file2", 852 "files-after JJ-INSTRUCTIONS file1 file2", 853 "reset file2", 854 ] 855 .join("\0"); 856 std::fs::write(diff_editor, diff_script).unwrap(); 857 858 // Select file1 and file2 by args, then select file1 interactively via the diff 859 // script. 860 let output = test_env.run_jj_in(&workspace_path, ["split", "-i", "file1", "file2"]); 861 insta::assert_snapshot!(output, @r" 862 ------- stderr ------- 863 First part: rlvkpnrz e3d766b8 (no description set) 864 Second part: kkmpptxz 4cf22d3b (no description set) 865 Working copy (@) now at: kkmpptxz 4cf22d3b (no description set) 866 Parent commit (@-) : rlvkpnrz e3d766b8 (no description set) 867 [EOF] 868 "); 869 870 insta::assert_snapshot!( 871 std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r#" 872 JJ: Enter a description for the first commit. 873 874 875 JJ: This commit contains the following changes: 876 JJ: A file1 877 JJ: 878 JJ: Lines starting with "JJ:" (like this one) will be removed. 879 "#); 880 881 let output = test_env.run_jj_in(&workspace_path, ["log", "--summary"]); 882 insta::assert_snapshot!(output, @r" 883 @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 4cf22d3b 884 │ (no description set) 885 │ M file2 886 │ M file3 887 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 e3d766b8 888 │ (no description set) 889 │ A file1 890 ○ qpvuntsm test.user@example.com 2001-02-03 08:05:08 497ed465 891 │ (no description set) 892 │ A file2 893 │ A file3 894 ◆ zzzzzzzz root() 00000000 895 [EOF] 896 "); 897} 898 899// When a commit is split, the second commit produced by the split becomes the 900// working copy commit for all workspaces whose working copy commit was the 901// target of the split. This test does a split where the target commit is the 902// working copy commit for two different workspaces. 903#[test] 904fn test_split_with_multiple_workspaces_same_working_copy() { 905 let mut test_env = TestEnvironment::default(); 906 test_env.run_jj_in(".", ["git", "init", "main"]).success(); 907 let main_path = test_env.env_root().join("main"); 908 let secondary_path = test_env.env_root().join("secondary"); 909 910 test_env 911 .run_jj_in(&main_path, ["desc", "-m", "first-commit"]) 912 .success(); 913 std::fs::write(main_path.join("file1"), "foo").unwrap(); 914 std::fs::write(main_path.join("file2"), "foo").unwrap(); 915 916 // Create the second workspace and change its working copy commit to match 917 // the default workspace. 918 test_env 919 .run_jj_in( 920 &main_path, 921 ["workspace", "add", "--name", "second", "../secondary"], 922 ) 923 .success(); 924 // Change the working copy in the second workspace. 925 test_env 926 .run_jj_in(&secondary_path, ["edit", "-r", "description(first-commit)"]) 927 .success(); 928 // Check the working-copy commit in each workspace in the log output. The "@" 929 // node in the graph indicates the current workspace's working-copy commit. 930 insta::assert_snapshot!(get_workspace_log_output(&test_env, &main_path), @r" 931 @ qpvuntsmwlqt default@ second@ first-commit 932 ◆ zzzzzzzzzzzz 933 [EOF] 934 "); 935 936 // Do the split in the default workspace. 937 std::fs::write( 938 test_env.set_up_fake_editor(), 939 ["", "next invocation\n", "write\nsecond-commit"].join("\0"), 940 ) 941 .unwrap(); 942 test_env.run_jj_in(&main_path, ["split", "file2"]).success(); 943 // The working copy for both workspaces will be the second split commit. 944 insta::assert_snapshot!(get_workspace_log_output(&test_env, &main_path), @r" 945 @ royxmykxtrkr default@ second@ second-commit 946 ○ qpvuntsmwlqt first-commit 947 ◆ zzzzzzzzzzzz 948 [EOF] 949 "); 950 951 // Test again with a --parallel split. 952 test_env.run_jj_in(&main_path, ["undo"]).success(); 953 std::fs::write( 954 test_env.set_up_fake_editor(), 955 ["", "next invocation\n", "write\nsecond-commit"].join("\0"), 956 ) 957 .unwrap(); 958 test_env 959 .run_jj_in(&main_path, ["split", "file2", "--parallel"]) 960 .success(); 961 insta::assert_snapshot!(get_workspace_log_output(&test_env, &main_path), @r" 962 @ yostqsxwqrlt default@ second@ second-commit 963 │ ○ qpvuntsmwlqt first-commit 964 ├─╯ 965 ◆ zzzzzzzzzzzz 966 [EOF] 967 "); 968} 969 970// A workspace should only have its working copy commit updated if the target 971// commit is the working copy commit. 972#[test] 973fn test_split_with_multiple_workspaces_different_working_copy() { 974 let mut test_env = TestEnvironment::default(); 975 test_env.run_jj_in(".", ["git", "init", "main"]).success(); 976 let main_path = test_env.env_root().join("main"); 977 978 test_env 979 .run_jj_in(&main_path, ["desc", "-m", "first-commit"]) 980 .success(); 981 std::fs::write(main_path.join("file1"), "foo").unwrap(); 982 std::fs::write(main_path.join("file2"), "foo").unwrap(); 983 984 // Create the second workspace with a different working copy commit. 985 test_env 986 .run_jj_in( 987 &main_path, 988 ["workspace", "add", "--name", "second", "../secondary"], 989 ) 990 .success(); 991 // Check the working-copy commit in each workspace in the log output. The "@" 992 // node in the graph indicates the current workspace's working-copy commit. 993 insta::assert_snapshot!(get_workspace_log_output(&test_env, &main_path), @r" 994 @ qpvuntsmwlqt default@ first-commit 995 │ ○ pmmvwywvzvvn second@ 996 ├─╯ 997 ◆ zzzzzzzzzzzz 998 [EOF] 999 "); 1000 1001 // Do the split in the default workspace. 1002 std::fs::write( 1003 test_env.set_up_fake_editor(), 1004 ["", "next invocation\n", "write\nsecond-commit"].join("\0"), 1005 ) 1006 .unwrap(); 1007 test_env.run_jj_in(&main_path, ["split", "file2"]).success(); 1008 // Only the working copy commit for the default workspace changes. 1009 insta::assert_snapshot!(get_workspace_log_output(&test_env, &main_path), @r" 1010 @ mzvwutvlkqwt default@ second-commit 1011 ○ qpvuntsmwlqt first-commit 1012 │ ○ pmmvwywvzvvn second@ 1013 ├─╯ 1014 ◆ zzzzzzzzzzzz 1015 [EOF] 1016 "); 1017 1018 // Test again with a --parallel split. 1019 test_env.run_jj_in(&main_path, ["undo"]).success(); 1020 std::fs::write( 1021 test_env.set_up_fake_editor(), 1022 ["", "next invocation\n", "write\nsecond-commit"].join("\0"), 1023 ) 1024 .unwrap(); 1025 test_env 1026 .run_jj_in(&main_path, ["split", "file2", "--parallel"]) 1027 .success(); 1028 insta::assert_snapshot!(get_workspace_log_output(&test_env, &main_path), @r" 1029 @ vruxwmqvtpmx default@ second-commit 1030 │ ○ qpvuntsmwlqt first-commit 1031 ├─╯ 1032 │ ○ pmmvwywvzvvn second@ 1033 ├─╯ 1034 ◆ zzzzzzzzzzzz 1035 [EOF] 1036 "); 1037} 1038 1039enum BookmarkBehavior { 1040 Default, 1041 MoveBookmarkToChild, 1042 LeaveBookmarkWithTarget, 1043} 1044 1045// TODO: https://github.com/jj-vcs/jj/issues/3419 - Delete params when the config is removed. 1046#[test_case(BookmarkBehavior::Default; "default_behavior")] 1047#[test_case(BookmarkBehavior::MoveBookmarkToChild; "move_bookmark_to_child")] 1048#[test_case(BookmarkBehavior::LeaveBookmarkWithTarget; "leave_bookmark_with_target")] 1049fn test_split_with_bookmarks(bookmark_behavior: BookmarkBehavior) { 1050 let mut test_env = TestEnvironment::default(); 1051 test_env.run_jj_in(".", ["git", "init", "main"]).success(); 1052 let main_path = test_env.env_root().join("main"); 1053 1054 match bookmark_behavior { 1055 BookmarkBehavior::LeaveBookmarkWithTarget => { 1056 test_env.add_config("split.legacy-bookmark-behavior=false"); 1057 } 1058 BookmarkBehavior::MoveBookmarkToChild => { 1059 test_env.add_config("split.legacy-bookmark-behavior=true"); 1060 } 1061 BookmarkBehavior::Default => (), 1062 } 1063 1064 // Setup. 1065 test_env 1066 .run_jj_in(&main_path, ["desc", "-m", "first-commit"]) 1067 .success(); 1068 std::fs::write(main_path.join("file1"), "foo").unwrap(); 1069 std::fs::write(main_path.join("file2"), "foo").unwrap(); 1070 test_env 1071 .run_jj_in(&main_path, ["bookmark", "set", "'*le-signet*'", "-r", "@"]) 1072 .success(); 1073 insta::allow_duplicates! { 1074 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r" 1075 @ qpvuntsmwlqt false *le-signet* first-commit 1076 ◆ zzzzzzzzzzzz true 1077 [EOF] 1078 "); 1079 } 1080 1081 // Do the split. 1082 std::fs::write( 1083 test_env.set_up_fake_editor(), 1084 ["", "next invocation\n", "write\nsecond-commit"].join("\0"), 1085 ) 1086 .unwrap(); 1087 let output = test_env.run_jj_in(&main_path, ["split", "file2"]); 1088 match bookmark_behavior { 1089 BookmarkBehavior::LeaveBookmarkWithTarget => { 1090 insta::allow_duplicates! { 1091 insta::assert_snapshot!(output, @r" 1092 ------- stderr ------- 1093 First part: qpvuntsm 63d0c5ed *le-signet* | first-commit 1094 Second part: mzvwutvl a9f5665f second-commit 1095 Working copy (@) now at: mzvwutvl a9f5665f second-commit 1096 Parent commit (@-) : qpvuntsm 63d0c5ed *le-signet* | first-commit 1097 [EOF] 1098 "); 1099 } 1100 insta::allow_duplicates! { 1101 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r" 1102 @ mzvwutvlkqwt false second-commit 1103 ○ qpvuntsmwlqt false *le-signet* first-commit 1104 ◆ zzzzzzzzzzzz true 1105 [EOF] 1106 "); 1107 } 1108 } 1109 BookmarkBehavior::Default | BookmarkBehavior::MoveBookmarkToChild => { 1110 insta::allow_duplicates! { 1111 insta::assert_snapshot!(output, @r" 1112 ------- stderr ------- 1113 First part: qpvuntsm 63d0c5ed first-commit 1114 Second part: mzvwutvl a9f5665f *le-signet* | second-commit 1115 Working copy (@) now at: mzvwutvl a9f5665f *le-signet* | second-commit 1116 Parent commit (@-) : qpvuntsm 63d0c5ed first-commit 1117 [EOF] 1118 "); 1119 } 1120 insta::allow_duplicates! { 1121 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r" 1122 @ mzvwutvlkqwt false *le-signet* second-commit 1123 ○ qpvuntsmwlqt false first-commit 1124 ◆ zzzzzzzzzzzz true 1125 [EOF] 1126 "); 1127 } 1128 } 1129 } 1130 1131 // Test again with a --parallel split. 1132 test_env.run_jj_in(&main_path, ["undo"]).success(); 1133 std::fs::write( 1134 test_env.set_up_fake_editor(), 1135 ["", "next invocation\n", "write\nsecond-commit"].join("\0"), 1136 ) 1137 .unwrap(); 1138 test_env 1139 .run_jj_in(&main_path, ["split", "file2", "--parallel"]) 1140 .success(); 1141 match bookmark_behavior { 1142 BookmarkBehavior::LeaveBookmarkWithTarget => { 1143 insta::allow_duplicates! { 1144 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r" 1145 @ vruxwmqvtpmx false second-commit 1146 │ ○ qpvuntsmwlqt false *le-signet* first-commit 1147 ├─╯ 1148 ◆ zzzzzzzzzzzz true 1149 [EOF] 1150 "); 1151 } 1152 } 1153 BookmarkBehavior::Default | BookmarkBehavior::MoveBookmarkToChild => { 1154 insta::allow_duplicates! { 1155 insta::assert_snapshot!(get_log_output(&test_env, &main_path), @r" 1156 @ vruxwmqvtpmx false *le-signet* second-commit 1157 │ ○ qpvuntsmwlqt false first-commit 1158 ├─╯ 1159 ◆ zzzzzzzzzzzz true 1160 [EOF] 1161 "); 1162 } 1163 } 1164 } 1165}