just playing with tangled
at diffedit3 1132 lines 35 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 itertools::Itertools; 16 17use crate::common::{escaped_fake_diff_editor_path, strip_last_line, TestEnvironment}; 18 19#[test] 20fn test_diff_basic() { 21 let test_env = TestEnvironment::default(); 22 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 23 let repo_path = test_env.env_root().join("repo"); 24 25 std::fs::write(repo_path.join("file1"), "foo\n").unwrap(); 26 std::fs::write(repo_path.join("file2"), "foo\n").unwrap(); 27 test_env.jj_cmd_ok(&repo_path, &["new"]); 28 std::fs::remove_file(repo_path.join("file1")).unwrap(); 29 std::fs::write(repo_path.join("file2"), "foo\nbar\n").unwrap(); 30 std::fs::write(repo_path.join("file3"), "foo\n").unwrap(); 31 32 let stdout = test_env.jj_cmd_success(&repo_path, &["diff"]); 33 insta::assert_snapshot!(stdout, @r###" 34 Removed regular file file1: 35 1 : foo 36 Modified regular file file2: 37 1 1: foo 38 2: bar 39 Added regular file file3: 40 1: foo 41 "###); 42 43 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--context=0"]); 44 insta::assert_snapshot!(stdout, @r###" 45 Removed regular file file1: 46 1 : foo 47 Modified regular file file2: 48 1 1: foo 49 2: bar 50 Added regular file file3: 51 1: foo 52 "###); 53 54 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]); 55 insta::assert_snapshot!(stdout, @r###" 56 D file1 57 M file2 58 A file3 59 "###); 60 61 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--types"]); 62 insta::assert_snapshot!(stdout, @r###" 63 F- file1 64 FF file2 65 -F file3 66 "###); 67 68 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--git"]); 69 insta::assert_snapshot!(stdout, @r###" 70 diff --git a/file1 b/file1 71 deleted file mode 100644 72 index 257cc5642c..0000000000 73 --- a/file1 74 +++ /dev/null 75 @@ -1,1 +1,0 @@ 76 -foo 77 diff --git a/file2 b/file2 78 index 257cc5642c...3bd1f0e297 100644 79 --- a/file2 80 +++ b/file2 81 @@ -1,1 +1,2 @@ 82 foo 83 +bar 84 diff --git a/file3 b/file3 85 new file mode 100644 86 index 0000000000..257cc5642c 87 --- /dev/null 88 +++ b/file3 89 @@ -1,0 +1,1 @@ 90 +foo 91 "###); 92 93 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--git", "--context=0"]); 94 insta::assert_snapshot!(stdout, @r###" 95 diff --git a/file1 b/file1 96 deleted file mode 100644 97 index 257cc5642c..0000000000 98 --- a/file1 99 +++ /dev/null 100 @@ -1,1 +1,0 @@ 101 -foo 102 diff --git a/file2 b/file2 103 index 257cc5642c...3bd1f0e297 100644 104 --- a/file2 105 +++ b/file2 106 @@ -2,0 +2,1 @@ 107 +bar 108 diff --git a/file3 b/file3 109 new file mode 100644 110 index 0000000000..257cc5642c 111 --- /dev/null 112 +++ b/file3 113 @@ -1,0 +1,1 @@ 114 +foo 115 "###); 116 117 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s", "--git"]); 118 insta::assert_snapshot!(stdout, @r###" 119 D file1 120 M file2 121 A file3 122 diff --git a/file1 b/file1 123 deleted file mode 100644 124 index 257cc5642c..0000000000 125 --- a/file1 126 +++ /dev/null 127 @@ -1,1 +1,0 @@ 128 -foo 129 diff --git a/file2 b/file2 130 index 257cc5642c...3bd1f0e297 100644 131 --- a/file2 132 +++ b/file2 133 @@ -1,1 +1,2 @@ 134 foo 135 +bar 136 diff --git a/file3 b/file3 137 new file mode 100644 138 index 0000000000..257cc5642c 139 --- /dev/null 140 +++ b/file3 141 @@ -1,0 +1,1 @@ 142 +foo 143 "###); 144 145 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]); 146 insta::assert_snapshot!(stdout, @r###" 147 file1 | 1 - 148 file2 | 1 + 149 file3 | 1 + 150 3 files changed, 2 insertions(+), 1 deletion(-) 151 "###); 152 153 // Filter by glob pattern 154 let stdout = test_env.jj_cmd_success( 155 &repo_path, 156 &[ 157 "diff", 158 "--config-toml=ui.allow-filesets=true", 159 "-s", 160 r#"glob:"file[12]""#, 161 ], 162 ); 163 insta::assert_snapshot!(stdout, @r###" 164 D file1 165 M file2 166 "###); 167 168 // Unmatched paths should generate warnings 169 let (stdout, stderr) = test_env.jj_cmd_ok( 170 test_env.env_root(), 171 &[ 172 "diff", 173 "-Rrepo", 174 "-s", 175 "repo", // matches directory 176 "repo/file1", // deleted in to_tree, but exists in from_tree 177 "repo/x", 178 "repo/y/z", 179 ], 180 ); 181 insta::assert_snapshot!(stdout.replace('\\', "/"), @r###" 182 D repo/file1 183 M repo/file2 184 A repo/file3 185 "###); 186 insta::assert_snapshot!(stderr.replace('\\', "/"), @r###" 187 Warning: No matching entries for paths: repo/x, repo/y/z 188 "###); 189 190 // Unmodified paths shouldn't generate warnings 191 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diff", "-s", "--from=@", "file2"]); 192 insta::assert_snapshot!(stdout, @""); 193 insta::assert_snapshot!(stderr, @""); 194} 195 196#[test] 197fn test_diff_empty() { 198 let test_env = TestEnvironment::default(); 199 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 200 let repo_path = test_env.env_root().join("repo"); 201 202 std::fs::write(repo_path.join("file1"), "").unwrap(); 203 let stdout = test_env.jj_cmd_success(&repo_path, &["diff"]); 204 insta::assert_snapshot!(stdout, @r###" 205 Added regular file file1: 206 (empty) 207 "###); 208 209 test_env.jj_cmd_ok(&repo_path, &["new"]); 210 std::fs::remove_file(repo_path.join("file1")).unwrap(); 211 let stdout = test_env.jj_cmd_success(&repo_path, &["diff"]); 212 insta::assert_snapshot!(stdout, @r###" 213 Removed regular file file1: 214 (empty) 215 "###); 216 217 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]); 218 insta::assert_snapshot!(stdout, @r###" 219 file1 | 0 220 1 file changed, 0 insertions(+), 0 deletions(-) 221 "###); 222} 223 224#[test] 225fn test_diff_types() { 226 let test_env = TestEnvironment::default(); 227 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 228 let repo_path = test_env.env_root().join("repo"); 229 230 let file_path = repo_path.join("foo"); 231 232 // Missing 233 test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-m=missing"]); 234 235 // Normal file 236 test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-m=file"]); 237 std::fs::write(&file_path, "foo").unwrap(); 238 239 // Conflict (add/add) 240 test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-m=conflict"]); 241 std::fs::write(&file_path, "foo").unwrap(); 242 test_env.jj_cmd_ok(&repo_path, &["new", "root()"]); 243 std::fs::write(&file_path, "bar").unwrap(); 244 test_env.jj_cmd_ok(&repo_path, &["squash", r#"--into=description("conflict")"#]); 245 246 #[cfg(unix)] 247 { 248 use std::os::unix::fs::PermissionsExt; 249 use std::path::PathBuf; 250 251 // Executable 252 test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-m=executable"]); 253 std::fs::write(&file_path, "foo").unwrap(); 254 std::fs::set_permissions(&file_path, std::fs::Permissions::from_mode(0o755)).unwrap(); 255 256 // Symlink 257 test_env.jj_cmd_ok(&repo_path, &["new", "root()", "-m=symlink"]); 258 std::os::unix::fs::symlink(PathBuf::from("."), &file_path).unwrap(); 259 } 260 261 let diff = |from: &str, to: &str| { 262 test_env.jj_cmd_success( 263 &repo_path, 264 &[ 265 "diff", 266 "--types", 267 &format!(r#"--from=description("{}")"#, from), 268 &format!(r#"--to=description("{}")"#, to), 269 ], 270 ) 271 }; 272 insta::assert_snapshot!(diff("missing", "file"), @r###" 273 -F foo 274 "###); 275 insta::assert_snapshot!(diff("file", "conflict"), @r###" 276 FC foo 277 "###); 278 insta::assert_snapshot!(diff("conflict", "missing"), @r###" 279 C- foo 280 "###); 281 282 #[cfg(unix)] 283 { 284 insta::assert_snapshot!(diff("symlink", "file"), @r###" 285 LF foo 286 "###); 287 insta::assert_snapshot!(diff("missing", "executable"), @r###" 288 -F foo 289 "###); 290 } 291} 292 293#[test] 294fn test_diff_bad_args() { 295 let test_env = TestEnvironment::default(); 296 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 297 let repo_path = test_env.env_root().join("repo"); 298 299 let stderr = test_env.jj_cmd_cli_error(&repo_path, &["diff", "-s", "--types"]); 300 insta::assert_snapshot!(stderr, @r###" 301 error: the argument '--summary' cannot be used with '--types' 302 303 Usage: jj diff --summary [PATHS]... 304 305 For more information, try '--help'. 306 "###); 307 308 let stderr = test_env.jj_cmd_cli_error(&repo_path, &["diff", "--color-words", "--git"]); 309 insta::assert_snapshot!(stderr, @r###" 310 error: the argument '--color-words' cannot be used with '--git' 311 312 Usage: jj diff --color-words [PATHS]... 313 314 For more information, try '--help'. 315 "###); 316} 317 318#[test] 319fn test_diff_relative_paths() { 320 let test_env = TestEnvironment::default(); 321 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 322 let repo_path = test_env.env_root().join("repo"); 323 324 std::fs::create_dir_all(repo_path.join("dir1").join("subdir1")).unwrap(); 325 std::fs::create_dir(repo_path.join("dir2")).unwrap(); 326 std::fs::write(repo_path.join("file1"), "foo1\n").unwrap(); 327 std::fs::write(repo_path.join("dir1").join("file2"), "foo2\n").unwrap(); 328 std::fs::write( 329 repo_path.join("dir1").join("subdir1").join("file3"), 330 "foo3\n", 331 ) 332 .unwrap(); 333 std::fs::write(repo_path.join("dir2").join("file4"), "foo4\n").unwrap(); 334 test_env.jj_cmd_ok(&repo_path, &["new"]); 335 std::fs::write(repo_path.join("file1"), "bar1\n").unwrap(); 336 std::fs::write(repo_path.join("dir1").join("file2"), "bar2\n").unwrap(); 337 std::fs::write( 338 repo_path.join("dir1").join("subdir1").join("file3"), 339 "bar3\n", 340 ) 341 .unwrap(); 342 std::fs::write(repo_path.join("dir2").join("file4"), "bar4\n").unwrap(); 343 344 let stdout = test_env.jj_cmd_success(&repo_path.join("dir1"), &["diff"]); 345 #[cfg(unix)] 346 insta::assert_snapshot!(stdout, @r###" 347 Modified regular file file2: 348 1 1: foo2bar2 349 Modified regular file subdir1/file3: 350 1 1: foo3bar3 351 Modified regular file ../dir2/file4: 352 1 1: foo4bar4 353 Modified regular file ../file1: 354 1 1: foo1bar1 355 "###); 356 #[cfg(windows)] 357 insta::assert_snapshot!(stdout, @r###" 358 Modified regular file file2: 359 1 1: foo2bar2 360 Modified regular file subdir1\file3: 361 1 1: foo3bar3 362 Modified regular file ..\dir2\file4: 363 1 1: foo4bar4 364 Modified regular file ..\file1: 365 1 1: foo1bar1 366 "###); 367 368 let stdout = test_env.jj_cmd_success(&repo_path.join("dir1"), &["diff", "-s"]); 369 #[cfg(unix)] 370 insta::assert_snapshot!(stdout, @r###" 371 M file2 372 M subdir1/file3 373 M ../dir2/file4 374 M ../file1 375 "###); 376 #[cfg(windows)] 377 insta::assert_snapshot!(stdout, @r###" 378 M file2 379 M subdir1\file3 380 M ..\dir2\file4 381 M ..\file1 382 "###); 383 384 let stdout = test_env.jj_cmd_success(&repo_path.join("dir1"), &["diff", "--types"]); 385 #[cfg(unix)] 386 insta::assert_snapshot!(stdout, @r###" 387 FF file2 388 FF subdir1/file3 389 FF ../dir2/file4 390 FF ../file1 391 "###); 392 #[cfg(windows)] 393 insta::assert_snapshot!(stdout, @r###" 394 FF file2 395 FF subdir1\file3 396 FF ..\dir2\file4 397 FF ..\file1 398 "###); 399 400 let stdout = test_env.jj_cmd_success(&repo_path.join("dir1"), &["diff", "--git"]); 401 insta::assert_snapshot!(stdout, @r###" 402 diff --git a/dir1/file2 b/dir1/file2 403 index 54b060eee9...1fe912cdd8 100644 404 --- a/dir1/file2 405 +++ b/dir1/file2 406 @@ -1,1 +1,1 @@ 407 -foo2 408 +bar2 409 diff --git a/dir1/subdir1/file3 b/dir1/subdir1/file3 410 index c1ec6c6f12...f3c8b75ec6 100644 411 --- a/dir1/subdir1/file3 412 +++ b/dir1/subdir1/file3 413 @@ -1,1 +1,1 @@ 414 -foo3 415 +bar3 416 diff --git a/dir2/file4 b/dir2/file4 417 index a0016dbc4c...17375f7a12 100644 418 --- a/dir2/file4 419 +++ b/dir2/file4 420 @@ -1,1 +1,1 @@ 421 -foo4 422 +bar4 423 diff --git a/file1 b/file1 424 index 1715acd6a5...05c4fe6772 100644 425 --- a/file1 426 +++ b/file1 427 @@ -1,1 +1,1 @@ 428 -foo1 429 +bar1 430 "###); 431 432 let stdout = test_env.jj_cmd_success(&repo_path.join("dir1"), &["diff", "--stat"]); 433 #[cfg(unix)] 434 insta::assert_snapshot!(stdout, @r###" 435 file2 | 2 +- 436 subdir1/file3 | 2 +- 437 ../dir2/file4 | 2 +- 438 ../file1 | 2 +- 439 4 files changed, 4 insertions(+), 4 deletions(-) 440 "###); 441 #[cfg(windows)] 442 insta::assert_snapshot!(stdout, @r###" 443 file2 | 2 +- 444 subdir1\file3 | 2 +- 445 ..\dir2\file4 | 2 +- 446 ..\file1 | 2 +- 447 4 files changed, 4 insertions(+), 4 deletions(-) 448 "###); 449} 450 451#[test] 452fn test_diff_missing_newline() { 453 let test_env = TestEnvironment::default(); 454 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 455 let repo_path = test_env.env_root().join("repo"); 456 457 std::fs::write(repo_path.join("file1"), "foo").unwrap(); 458 std::fs::write(repo_path.join("file2"), "foo\nbar").unwrap(); 459 test_env.jj_cmd_ok(&repo_path, &["new"]); 460 std::fs::write(repo_path.join("file1"), "foo\nbar").unwrap(); 461 std::fs::write(repo_path.join("file2"), "foo").unwrap(); 462 463 let stdout = test_env.jj_cmd_success(&repo_path, &["diff"]); 464 insta::assert_snapshot!(stdout, @r###" 465 Modified regular file file1: 466 1 1: foo 467 2: bar 468 Modified regular file file2: 469 1 1: foo 470 2 : bar 471 "###); 472 473 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--git"]); 474 insta::assert_snapshot!(stdout, @r###" 475 diff --git a/file1 b/file1 476 index 1910281566...a907ec3f43 100644 477 --- a/file1 478 +++ b/file1 479 @@ -1,1 +1,2 @@ 480 -foo 481 \ No newline at end of file 482 +foo 483 +bar 484 \ No newline at end of file 485 diff --git a/file2 b/file2 486 index a907ec3f43...1910281566 100644 487 --- a/file2 488 +++ b/file2 489 @@ -1,2 +1,1 @@ 490 -foo 491 -bar 492 \ No newline at end of file 493 +foo 494 \ No newline at end of file 495 "###); 496 497 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]); 498 insta::assert_snapshot!(stdout, @r###" 499 file1 | 3 ++- 500 file2 | 3 +-- 501 2 files changed, 3 insertions(+), 3 deletions(-) 502 "###); 503} 504 505#[test] 506fn test_color_words_diff_missing_newline() { 507 let test_env = TestEnvironment::default(); 508 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 509 let repo_path = test_env.env_root().join("repo"); 510 511 std::fs::write(repo_path.join("file1"), "").unwrap(); 512 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Empty"]); 513 std::fs::write(repo_path.join("file1"), "a\nb\nc\nd\ne\nf\ng\nh\ni").unwrap(); 514 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Add no newline"]); 515 std::fs::write(repo_path.join("file1"), "A\nb\nc\nd\ne\nf\ng\nh\ni").unwrap(); 516 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Modify first line"]); 517 std::fs::write(repo_path.join("file1"), "A\nb\nc\nd\nE\nf\ng\nh\ni").unwrap(); 518 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Modify middle line"]); 519 std::fs::write(repo_path.join("file1"), "A\nb\nc\nd\nE\nf\ng\nh\nI").unwrap(); 520 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Modify last line"]); 521 std::fs::write(repo_path.join("file1"), "A\nb\nc\nd\nE\nf\ng\nh\nI\n").unwrap(); 522 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Append newline"]); 523 std::fs::write(repo_path.join("file1"), "A\nb\nc\nd\nE\nf\ng\nh\nI").unwrap(); 524 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Remove newline"]); 525 std::fs::write(repo_path.join("file1"), "").unwrap(); 526 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "=== Empty"]); 527 528 let stdout = test_env.jj_cmd_success( 529 &repo_path, 530 &[ 531 "log", 532 "-Tdescription", 533 "-pr::@-", 534 "--no-graph", 535 "--reversed", 536 ], 537 ); 538 insta::assert_snapshot!(stdout, @r###" 539 === Empty 540 Added regular file file1: 541 (empty) 542 === Add no newline 543 Modified regular file file1: 544 1: a 545 2: b 546 3: c 547 4: d 548 5: e 549 6: f 550 7: g 551 8: h 552 9: i 553 === Modify first line 554 Modified regular file file1: 555 1 1: aA 556 2 2: b 557 3 3: c 558 4 4: d 559 ... 560 === Modify middle line 561 Modified regular file file1: 562 1 1: A 563 2 2: b 564 3 3: c 565 4 4: d 566 5 5: eE 567 6 6: f 568 7 7: g 569 8 8: h 570 9 9: i 571 === Modify last line 572 Modified regular file file1: 573 ... 574 6 6: f 575 7 7: g 576 8 8: h 577 9 9: iI 578 === Append newline 579 Modified regular file file1: 580 ... 581 6 6: f 582 7 7: g 583 8 8: h 584 9 9: I 585 === Remove newline 586 Modified regular file file1: 587 ... 588 6 6: f 589 7 7: g 590 8 8: h 591 9 9: I 592 === Empty 593 Modified regular file file1: 594 1 : A 595 2 : b 596 3 : c 597 4 : d 598 5 : E 599 6 : f 600 7 : g 601 8 : h 602 9 : I 603 "###); 604} 605 606#[test] 607fn test_diff_skipped_context() { 608 let test_env = TestEnvironment::default(); 609 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 610 let repo_path = test_env.env_root().join("repo"); 611 612 std::fs::write(repo_path.join("file1"), "a\nb\nc\nd\ne\nf\ng\nh\ni\nj").unwrap(); 613 test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "=== Left side of diffs"]); 614 615 test_env.jj_cmd_ok(&repo_path, &["new", "@", "-m", "=== Must skip 2 lines"]); 616 std::fs::write(repo_path.join("file1"), "A\nb\nc\nd\ne\nf\ng\nh\ni\nJ").unwrap(); 617 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== Don't skip 1 line"]); 618 std::fs::write(repo_path.join("file1"), "A\nb\nc\nd\ne\nf\ng\nh\nI\nj").unwrap(); 619 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== No gap to skip"]); 620 std::fs::write(repo_path.join("file1"), "a\nB\nc\nd\ne\nf\ng\nh\nI\nj").unwrap(); 621 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== No gap to skip"]); 622 std::fs::write(repo_path.join("file1"), "a\nb\nC\nd\ne\nf\ng\nh\nI\nj").unwrap(); 623 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== 1 line at start"]); 624 std::fs::write(repo_path.join("file1"), "a\nb\nc\nd\nE\nf\ng\nh\ni\nj").unwrap(); 625 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== 1 line at end"]); 626 std::fs::write(repo_path.join("file1"), "a\nb\nc\nd\ne\nF\ng\nh\ni\nj").unwrap(); 627 628 let stdout = test_env.jj_cmd_success( 629 &repo_path, 630 &["log", "-Tdescription", "-p", "--no-graph", "--reversed"], 631 ); 632 insta::assert_snapshot!(stdout, @r###" 633 === Left side of diffs 634 Added regular file file1: 635 1: a 636 2: b 637 3: c 638 4: d 639 5: e 640 6: f 641 7: g 642 8: h 643 9: i 644 10: j 645 === Must skip 2 lines 646 Modified regular file file1: 647 1 1: aA 648 2 2: b 649 3 3: c 650 4 4: d 651 ... 652 7 7: g 653 8 8: h 654 9 9: i 655 10 10: jJ 656 === Don't skip 1 line 657 Modified regular file file1: 658 1 1: aA 659 2 2: b 660 3 3: c 661 4 4: d 662 5 5: e 663 6 6: f 664 7 7: g 665 8 8: h 666 9 9: iI 667 10 10: j 668 === No gap to skip 669 Modified regular file file1: 670 1 1: a 671 2 2: bB 672 3 3: c 673 4 4: d 674 5 5: e 675 6 6: f 676 7 7: g 677 8 8: h 678 9 9: iI 679 10 10: j 680 === No gap to skip 681 Modified regular file file1: 682 1 1: a 683 2 2: b 684 3 3: cC 685 4 4: d 686 5 5: e 687 6 6: f 688 7 7: g 689 8 8: h 690 9 9: iI 691 10 10: j 692 === 1 line at start 693 Modified regular file file1: 694 1 1: a 695 2 2: b 696 3 3: c 697 4 4: d 698 5 5: eE 699 6 6: f 700 7 7: g 701 8 8: h 702 ... 703 === 1 line at end 704 Modified regular file file1: 705 ... 706 3 3: c 707 4 4: d 708 5 5: e 709 6 6: fF 710 7 7: g 711 8 8: h 712 9 9: i 713 10 10: j 714 "###); 715} 716 717#[test] 718fn test_diff_skipped_context_nondefault() { 719 let test_env = TestEnvironment::default(); 720 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 721 let repo_path = test_env.env_root().join("repo"); 722 723 std::fs::write(repo_path.join("file1"), "a\nb\nc\nd").unwrap(); 724 test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "=== Left side of diffs"]); 725 726 test_env.jj_cmd_ok(&repo_path, &["new", "@", "-m", "=== Must skip 2 lines"]); 727 std::fs::write(repo_path.join("file1"), "A\nb\nc\nD").unwrap(); 728 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== Don't skip 1 line"]); 729 std::fs::write(repo_path.join("file1"), "A\nb\nC\nd").unwrap(); 730 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== No gap to skip"]); 731 std::fs::write(repo_path.join("file1"), "a\nB\nC\nd").unwrap(); 732 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== 1 line at start"]); 733 std::fs::write(repo_path.join("file1"), "a\nB\nc\nd").unwrap(); 734 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m", "=== 1 line at end"]); 735 std::fs::write(repo_path.join("file1"), "a\nb\nC\nd").unwrap(); 736 737 let stdout = test_env.jj_cmd_success( 738 &repo_path, 739 &[ 740 "log", 741 "-Tdescription", 742 "-p", 743 "--no-graph", 744 "--reversed", 745 "--context=0", 746 ], 747 ); 748 insta::assert_snapshot!(stdout, @r###" 749 === Left side of diffs 750 Added regular file file1: 751 1: a 752 2: b 753 3: c 754 4: d 755 === Must skip 2 lines 756 Modified regular file file1: 757 1 1: aA 758 ... 759 4 4: dD 760 === Don't skip 1 line 761 Modified regular file file1: 762 1 1: aA 763 2 2: b 764 3 3: cC 765 4 4: d 766 === No gap to skip 767 Modified regular file file1: 768 1 1: a 769 2 2: bB 770 3 3: cC 771 4 4: d 772 === 1 line at start 773 Modified regular file file1: 774 1 1: a 775 2 2: bB 776 ... 777 === 1 line at end 778 Modified regular file file1: 779 ... 780 3 3: cC 781 4 4: d 782 "###); 783} 784 785#[test] 786fn test_diff_external_tool() { 787 let mut test_env = TestEnvironment::default(); 788 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 789 let repo_path = test_env.env_root().join("repo"); 790 791 std::fs::write(repo_path.join("file1"), "foo\n").unwrap(); 792 std::fs::write(repo_path.join("file2"), "foo\n").unwrap(); 793 test_env.jj_cmd_ok(&repo_path, &["new"]); 794 std::fs::remove_file(repo_path.join("file1")).unwrap(); 795 std::fs::write(repo_path.join("file2"), "foo\nbar\n").unwrap(); 796 std::fs::write(repo_path.join("file3"), "foo\n").unwrap(); 797 798 let edit_script = test_env.set_up_fake_diff_editor(); 799 std::fs::write( 800 &edit_script, 801 "print-files-before\0print --\0print-files-after", 802 ) 803 .unwrap(); 804 805 // diff without file patterns 806 insta::assert_snapshot!( 807 test_env.jj_cmd_success(&repo_path, &["diff", "--tool=fake-diff-editor"]), @r###" 808 file1 809 file2 810 -- 811 file2 812 file3 813 "###); 814 815 // diff with file patterns 816 insta::assert_snapshot!( 817 test_env.jj_cmd_success(&repo_path, &["diff", "--tool=fake-diff-editor", "file1"]), @r###" 818 file1 819 -- 820 "###); 821 822 insta::assert_snapshot!( 823 test_env.jj_cmd_success(&repo_path, &["log", "-p", "--tool=fake-diff-editor"]), @r###" 824 @ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 0cba70c7 825 │ (no description set) 826 │ file1 827 │ file2 828 │ -- 829 │ file2 830 │ file3 831 ◉ qpvuntsm test.user@example.com 2001-02-03 08:05:08 39b5a56f 832 │ (no description set) 833 │ -- 834 │ file1 835 │ file2 836 ◉ zzzzzzzz root() 00000000 837 -- 838 "###); 839 840 insta::assert_snapshot!( 841 test_env.jj_cmd_success(&repo_path, &["show", "--tool=fake-diff-editor"]), @r###" 842 Commit ID: 0cba70c72186eabb5a2f91be63a8366b9f6da6c6 843 Change ID: rlvkpnrzqnoowoytxnquwvuryrwnrmlp 844 Author: Test User <test.user@example.com> (2001-02-03 08:05:08) 845 Committer: Test User <test.user@example.com> (2001-02-03 08:05:09) 846 847 (no description set) 848 849 file1 850 file2 851 -- 852 file2 853 file3 854 "###); 855 856 // Enabled by default, looks up the merge-tools table 857 let config = "--config-toml=ui.diff.tool='fake-diff-editor'"; 858 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", config]), @r###" 859 file1 860 file2 861 -- 862 file2 863 file3 864 "###); 865 866 // Inlined command arguments 867 let command = escaped_fake_diff_editor_path(); 868 let config = format!(r#"--config-toml=ui.diff.tool=["{command}", "$right", "$left"]"#); 869 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", &config]), @r###" 870 file2 871 file3 872 -- 873 file1 874 file2 875 "###); 876 877 // Output of external diff tool shouldn't be escaped 878 std::fs::write(&edit_script, "print \x1b[1;31mred").unwrap(); 879 insta::assert_snapshot!( 880 test_env.jj_cmd_success(&repo_path, &["diff", "--color=always", "--tool=fake-diff-editor"]), 881 @r###" 882 red 883 "###); 884 885 // Non-zero exit code isn't an error 886 std::fs::write(&edit_script, "print diff\0fail").unwrap(); 887 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["show", "--tool=fake-diff-editor"]); 888 insta::assert_snapshot!(stdout, @r###" 889 Commit ID: 0cba70c72186eabb5a2f91be63a8366b9f6da6c6 890 Change ID: rlvkpnrzqnoowoytxnquwvuryrwnrmlp 891 Author: Test User <test.user@example.com> (2001-02-03 08:05:08) 892 Committer: Test User <test.user@example.com> (2001-02-03 08:05:09) 893 894 (no description set) 895 896 diff 897 "###); 898 insta::assert_snapshot!(stderr, @r###" 899 Warning: Tool exited with a non-zero code (run with --debug to see the exact invocation). Exit code: 1. 900 "###); 901 902 // --tool=:builtin shouldn't be ignored 903 let stderr = test_env.jj_cmd_failure(&repo_path, &["diff", "--tool=:builtin"]); 904 insta::assert_snapshot!(strip_last_line(&stderr), @r###" 905 Error: Failed to generate diff 906 Caused by: 907 1: Error executing ':builtin' (run with --debug to see the exact invocation) 908 "###); 909} 910 911#[cfg(unix)] 912#[test] 913fn test_diff_external_tool_symlink() { 914 let mut test_env = TestEnvironment::default(); 915 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 916 let repo_path = test_env.env_root().join("repo"); 917 918 let external_file_path = test_env.env_root().join("external-file"); 919 std::fs::write(&external_file_path, "").unwrap(); 920 let external_file_permissions = external_file_path.symlink_metadata().unwrap().permissions(); 921 922 std::os::unix::fs::symlink("non-existent1", repo_path.join("dead")).unwrap(); 923 std::os::unix::fs::symlink(&external_file_path, repo_path.join("file")).unwrap(); 924 test_env.jj_cmd_ok(&repo_path, &["new"]); 925 std::fs::remove_file(repo_path.join("dead")).unwrap(); 926 std::os::unix::fs::symlink("non-existent2", repo_path.join("dead")).unwrap(); 927 std::fs::remove_file(repo_path.join("file")).unwrap(); 928 std::fs::write(repo_path.join("file"), "").unwrap(); 929 930 let edit_script = test_env.set_up_fake_diff_editor(); 931 std::fs::write( 932 edit_script, 933 "print-files-before\0print --\0print-files-after", 934 ) 935 .unwrap(); 936 937 // Shouldn't try to change permission of symlinks 938 insta::assert_snapshot!( 939 test_env.jj_cmd_success(&repo_path, &["diff", "--tool=fake-diff-editor"]), @r###" 940 dead 941 file 942 -- 943 dead 944 file 945 "###); 946 947 // External file should be intact 948 assert_eq!( 949 external_file_path.symlink_metadata().unwrap().permissions(), 950 external_file_permissions 951 ); 952} 953 954#[test] 955fn test_diff_stat() { 956 let test_env = TestEnvironment::default(); 957 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 958 let repo_path = test_env.env_root().join("repo"); 959 std::fs::write(repo_path.join("file1"), "foo\n").unwrap(); 960 961 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]); 962 insta::assert_snapshot!(stdout, @r###" 963 file1 | 1 + 964 1 file changed, 1 insertion(+), 0 deletions(-) 965 "###); 966 967 test_env.jj_cmd_ok(&repo_path, &["new"]); 968 969 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]); 970 insta::assert_snapshot!(stdout, @"0 files changed, 0 insertions(+), 0 deletions(-)"); 971 972 std::fs::write(repo_path.join("file1"), "foo\nbar\n").unwrap(); 973 test_env.jj_cmd_ok(&repo_path, &["new"]); 974 std::fs::write(repo_path.join("file1"), "bar\n").unwrap(); 975 976 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]); 977 insta::assert_snapshot!(stdout, @r###" 978 file1 | 1 - 979 1 file changed, 0 insertions(+), 1 deletion(-) 980 "###); 981} 982 983#[test] 984fn test_diff_stat_long_name_or_stat() { 985 let mut test_env = TestEnvironment::default(); 986 test_env.add_env_var("COLUMNS", "30"); 987 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 988 let repo_path = test_env.env_root().join("repo"); 989 990 let get_stat = |test_env: &TestEnvironment, path_length: usize, stat_size: usize| { 991 test_env.jj_cmd_ok(&repo_path, &["new", "root()"]); 992 let ascii_name = "1234567890".chars().cycle().take(path_length).join(""); 993 let han_name = "一二三四五六七八九十" 994 .chars() 995 .cycle() 996 .take(path_length) 997 .join(""); 998 let content = "content line\n".repeat(stat_size); 999 std::fs::write(repo_path.join(ascii_name), &content).unwrap(); 1000 std::fs::write(repo_path.join(han_name), &content).unwrap(); 1001 test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]) 1002 }; 1003 1004 insta::assert_snapshot!(get_stat(&test_env, 1, 1), @r###" 1005 1 | 1 + 1006 一 | 1 + 1007 2 files changed, 2 insertions(+), 0 deletions(-) 1008 "###); 1009 insta::assert_snapshot!(get_stat(&test_env, 1, 10), @r###" 1010 1 | 10 ++++++++++ 1011 一 | 10 ++++++++++ 1012 2 files changed, 20 insertions(+), 0 deletions(-) 1013 "###); 1014 insta::assert_snapshot!(get_stat(&test_env, 1, 100), @r###" 1015 1 | 100 +++++++++++++++++ 1016 一 | 100 +++++++++++++++++ 1017 2 files changed, 200 insertions(+), 0 deletions(-) 1018 "###); 1019 insta::assert_snapshot!(get_stat(&test_env, 10, 1), @r###" 1020 1234567890 | 1 + 1021 ...五六七八九十 | 1 + 1022 2 files changed, 2 insertions(+), 0 deletions(-) 1023 "###); 1024 insta::assert_snapshot!(get_stat(&test_env, 10, 10), @r###" 1025 1234567890 | 10 +++++++ 1026 ...六七八九十 | 10 +++++++ 1027 2 files changed, 20 insertions(+), 0 deletions(-) 1028 "###); 1029 insta::assert_snapshot!(get_stat(&test_env, 10, 100), @r###" 1030 1234567890 | 100 ++++++ 1031 ...六七八九十 | 100 ++++++ 1032 2 files changed, 200 insertions(+), 0 deletions(-) 1033 "###); 1034 insta::assert_snapshot!(get_stat(&test_env, 50, 1), @r###" 1035 ...901234567890 | 1 + 1036 ...五六七八九十 | 1 + 1037 2 files changed, 2 insertions(+), 0 deletions(-) 1038 "###); 1039 insta::assert_snapshot!(get_stat(&test_env, 50, 10), @r###" 1040 ...01234567890 | 10 +++++++ 1041 ...六七八九十 | 10 +++++++ 1042 2 files changed, 20 insertions(+), 0 deletions(-) 1043 "###); 1044 insta::assert_snapshot!(get_stat(&test_env, 50, 100), @r###" 1045 ...01234567890 | 100 ++++++ 1046 ...六七八九十 | 100 ++++++ 1047 2 files changed, 200 insertions(+), 0 deletions(-) 1048 "###); 1049 1050 // Lengths around where we introduce the ellipsis 1051 insta::assert_snapshot!(get_stat(&test_env, 13, 100), @r###" 1052 1234567890123 | 100 ++++++ 1053 ...九十一二三 | 100 ++++++ 1054 2 files changed, 200 insertions(+), 0 deletions(-) 1055 "###); 1056 insta::assert_snapshot!(get_stat(&test_env, 14, 100), @r###" 1057 12345678901234 | 100 ++++++ 1058 ...十一二三四 | 100 ++++++ 1059 2 files changed, 200 insertions(+), 0 deletions(-) 1060 "###); 1061 insta::assert_snapshot!(get_stat(&test_env, 15, 100), @r###" 1062 ...56789012345 | 100 ++++++ 1063 ...一二三四五 | 100 ++++++ 1064 2 files changed, 200 insertions(+), 0 deletions(-) 1065 "###); 1066 insta::assert_snapshot!(get_stat(&test_env, 16, 100), @r###" 1067 ...67890123456 | 100 ++++++ 1068 ...二三四五六 | 100 ++++++ 1069 2 files changed, 200 insertions(+), 0 deletions(-) 1070 "###); 1071 1072 // Very narrow terminal (doesn't have to fit, just don't crash) 1073 test_env.add_env_var("COLUMNS", "10"); 1074 insta::assert_snapshot!(get_stat(&test_env, 10, 10), @r###" 1075 ... | 10 ++ 1076 ... | 10 ++ 1077 2 files changed, 20 insertions(+), 0 deletions(-) 1078 "###); 1079 test_env.add_env_var("COLUMNS", "3"); 1080 insta::assert_snapshot!(get_stat(&test_env, 10, 10), @r###" 1081 ... | 10 ++ 1082 ... | 10 ++ 1083 2 files changed, 20 insertions(+), 0 deletions(-) 1084 "###); 1085 insta::assert_snapshot!(get_stat(&test_env, 3, 10), @r###" 1086 123 | 10 ++ 1087 ... | 10 ++ 1088 2 files changed, 20 insertions(+), 0 deletions(-) 1089 "###); 1090 insta::assert_snapshot!(get_stat(&test_env, 1, 10), @r###" 1091 1 | 10 ++ 1092 一 | 10 ++ 1093 2 files changed, 20 insertions(+), 0 deletions(-) 1094 "###); 1095} 1096 1097#[test] 1098fn test_diff_binary() { 1099 let test_env = TestEnvironment::default(); 1100 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 1101 let repo_path = test_env.env_root().join("repo"); 1102 1103 std::fs::write(repo_path.join("file1.png"), b"\x89PNG\r\n\x1a\nabcdefg\0").unwrap(); 1104 std::fs::write(repo_path.join("file2.png"), b"\x89PNG\r\n\x1a\n0123456\0").unwrap(); 1105 test_env.jj_cmd_ok(&repo_path, &["new"]); 1106 std::fs::remove_file(repo_path.join("file1.png")).unwrap(); 1107 std::fs::write(repo_path.join("file2.png"), "foo\nbar\n").unwrap(); 1108 std::fs::write(repo_path.join("file3.png"), b"\x89PNG\r\n\x1a\nxyz\0").unwrap(); 1109 // try a file that's valid UTF-8 but contains control characters 1110 std::fs::write(repo_path.join("file4.png"), b"\0\0\0").unwrap(); 1111 1112 let stdout = test_env.jj_cmd_success(&repo_path, &["diff"]); 1113 insta::assert_snapshot!(stdout, @r###" 1114 Removed regular file file1.png: 1115 (binary) 1116 Modified regular file file2.png: 1117 (binary) 1118 Added regular file file3.png: 1119 (binary) 1120 Added regular file file4.png: 1121 (binary) 1122 "###); 1123 1124 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]); 1125 insta::assert_snapshot!(stdout, @r###" 1126 file1.png | 3 --- 1127 file2.png | 5 ++--- 1128 file3.png | 3 +++ 1129 file4.png | 1 + 1130 4 files changed, 6 insertions(+), 6 deletions(-) 1131 "###); 1132}