just playing with tangled
at gvimdiff 2655 lines 93 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 test_case::test_case; 16use testutils::git; 17 18use crate::common::CommandOutput; 19use crate::common::TestEnvironment; 20use crate::common::TestWorkDir; 21 22fn git_repo_dir_for_jj_repo(work_dir: &TestWorkDir<'_>) -> std::path::PathBuf { 23 work_dir 24 .root() 25 .join(".jj") 26 .join("repo") 27 .join("store") 28 .join("git") 29} 30 31fn set_up(test_env: &TestEnvironment) { 32 test_env.run_jj_in(".", ["git", "init", "origin"]).success(); 33 let origin_dir = test_env.work_dir("origin"); 34 let origin_git_repo_path = git_repo_dir_for_jj_repo(&origin_dir); 35 36 origin_dir 37 .run_jj(["describe", "-m=description 1"]) 38 .success(); 39 origin_dir 40 .run_jj(["bookmark", "create", "-r@", "bookmark1"]) 41 .success(); 42 origin_dir 43 .run_jj(["new", "root()", "-m=description 2"]) 44 .success(); 45 origin_dir 46 .run_jj(["bookmark", "create", "-r@", "bookmark2"]) 47 .success(); 48 origin_dir.run_jj(["git", "export"]).success(); 49 50 test_env 51 .run_jj_in( 52 ".", 53 [ 54 "git", 55 "clone", 56 "--config=git.auto-local-bookmark=true", 57 origin_git_repo_path.to_str().unwrap(), 58 "local", 59 ], 60 ) 61 .success(); 62} 63 64#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 65#[test_case(true; "spawn a git subprocess for remote calls")] 66fn test_git_push_nothing(subprocess: bool) { 67 let test_env = TestEnvironment::default(); 68 set_up(&test_env); 69 let work_dir = test_env.work_dir("local"); 70 if !subprocess { 71 test_env.add_config("git.subprocess = false"); 72 } 73 // Show the setup. `insta` has trouble if this is done inside `set_up()` 74 insta::allow_duplicates! { 75 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 76 bookmark1: xtvrqkyv d13ecdbd (empty) description 1 77 @origin: xtvrqkyv d13ecdbd (empty) description 1 78 bookmark2: rlzusymt 8476341e (empty) description 2 79 @origin: rlzusymt 8476341e (empty) description 2 80 [EOF] 81 "); 82 } 83 // No bookmarks to push yet 84 let output = work_dir.run_jj(["git", "push", "--all"]); 85 insta::allow_duplicates! { 86 insta::assert_snapshot!(output, @r" 87 ------- stderr ------- 88 Nothing changed. 89 [EOF] 90 "); 91 } 92} 93 94#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 95#[test_case(true; "spawn a git subprocess for remote calls")] 96fn test_git_push_current_bookmark(subprocess: bool) { 97 let test_env = TestEnvironment::default(); 98 set_up(&test_env); 99 let work_dir = test_env.work_dir("local"); 100 if !subprocess { 101 test_env.add_config("git.subprocess = false"); 102 } 103 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 104 // Update some bookmarks. `bookmark1` is not a current bookmark, but 105 // `bookmark2` and `my-bookmark` are. 106 work_dir 107 .run_jj(["describe", "bookmark1", "-m", "modified bookmark1 commit"]) 108 .success(); 109 work_dir.run_jj(["new", "bookmark2"]).success(); 110 work_dir 111 .run_jj(["bookmark", "set", "bookmark2", "-r@"]) 112 .success(); 113 work_dir 114 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 115 .success(); 116 work_dir.run_jj(["describe", "-m", "foo"]).success(); 117 // Check the setup 118 insta::allow_duplicates! { 119 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 120 bookmark1: xtvrqkyv 0f8dc656 (empty) modified bookmark1 commit 121 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv hidden d13ecdbd (empty) description 1 122 bookmark2: yostqsxw bc7610b6 (empty) foo 123 @origin (behind by 1 commits): rlzusymt 8476341e (empty) description 2 124 my-bookmark: yostqsxw bc7610b6 (empty) foo 125 [EOF] 126 "); 127 } 128 // First dry-run. `bookmark1` should not get pushed. 129 let output = work_dir.run_jj(["git", "push", "--allow-new", "--dry-run"]); 130 insta::allow_duplicates! { 131 insta::assert_snapshot!(output, @r" 132 ------- stderr ------- 133 Changes to push to origin: 134 Move forward bookmark bookmark2 from 8476341eb395 to bc7610b65a91 135 Add bookmark my-bookmark to bc7610b65a91 136 Dry-run requested, not pushing. 137 [EOF] 138 "); 139 } 140 let output = work_dir.run_jj(["git", "push", "--allow-new"]); 141 insta::allow_duplicates! { 142 insta::assert_snapshot!(output, @r" 143 ------- stderr ------- 144 Changes to push to origin: 145 Move forward bookmark bookmark2 from 8476341eb395 to bc7610b65a91 146 Add bookmark my-bookmark to bc7610b65a91 147 [EOF] 148 "); 149 } 150 insta::allow_duplicates! { 151 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 152 bookmark1: xtvrqkyv 0f8dc656 (empty) modified bookmark1 commit 153 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv hidden d13ecdbd (empty) description 1 154 bookmark2: yostqsxw bc7610b6 (empty) foo 155 @origin: yostqsxw bc7610b6 (empty) foo 156 my-bookmark: yostqsxw bc7610b6 (empty) foo 157 @origin: yostqsxw bc7610b6 (empty) foo 158 [EOF] 159 "); 160 } 161 162 // Try pushing backwards 163 work_dir 164 .run_jj([ 165 "bookmark", 166 "set", 167 "bookmark2", 168 "-rbookmark2-", 169 "--allow-backwards", 170 ]) 171 .success(); 172 // This behavior is a strangeness of our definition of the default push revset. 173 // We could consider changing it. 174 let output = work_dir.run_jj(["git", "push"]); 175 insta::allow_duplicates! { 176 insta::assert_snapshot!(output, @r" 177 ------- stderr ------- 178 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@ 179 Nothing changed. 180 [EOF] 181 "); 182 } 183 // We can move a bookmark backwards 184 let output = work_dir.run_jj(["git", "push", "-bbookmark2"]); 185 insta::allow_duplicates! { 186 insta::assert_snapshot!(output, @r" 187 ------- stderr ------- 188 Changes to push to origin: 189 Move backward bookmark bookmark2 from bc7610b65a91 to 8476341eb395 190 [EOF] 191 "); 192 } 193} 194 195#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 196#[test_case(true; "spawn a git subprocess for remote calls")] 197fn test_git_push_parent_bookmark(subprocess: bool) { 198 let test_env = TestEnvironment::default(); 199 set_up(&test_env); 200 let work_dir = test_env.work_dir("local"); 201 if !subprocess { 202 test_env.add_config("git.subprocess = false"); 203 } 204 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 205 work_dir.run_jj(["edit", "bookmark1"]).success(); 206 work_dir 207 .run_jj(["describe", "-m", "modified bookmark1 commit"]) 208 .success(); 209 work_dir 210 .run_jj(["new", "-m", "non-empty description"]) 211 .success(); 212 work_dir.write_file("file", "file"); 213 let output = work_dir.run_jj(["git", "push"]); 214 insta::allow_duplicates! { 215 insta::assert_snapshot!(output, @r" 216 ------- stderr ------- 217 Changes to push to origin: 218 Move sideways bookmark bookmark1 from d13ecdbda2a2 to e612d524a5c6 219 [EOF] 220 "); 221 } 222} 223 224#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 225#[test_case(true; "spawn a git subprocess for remote calls")] 226fn test_git_push_no_matching_bookmark(subprocess: bool) { 227 let test_env = TestEnvironment::default(); 228 set_up(&test_env); 229 let work_dir = test_env.work_dir("local"); 230 if !subprocess { 231 test_env.add_config("git.subprocess = false"); 232 } 233 work_dir.run_jj(["new"]).success(); 234 let output = work_dir.run_jj(["git", "push"]); 235 insta::allow_duplicates! { 236 insta::assert_snapshot!(output, @r" 237 ------- stderr ------- 238 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@ 239 Nothing changed. 240 [EOF] 241 "); 242 } 243} 244 245#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 246#[test_case(true; "spawn a git subprocess for remote calls")] 247fn test_git_push_matching_bookmark_unchanged(subprocess: bool) { 248 let test_env = TestEnvironment::default(); 249 set_up(&test_env); 250 let work_dir = test_env.work_dir("local"); 251 if !subprocess { 252 test_env.add_config("git.subprocess = false"); 253 } 254 work_dir.run_jj(["new", "bookmark1"]).success(); 255 let output = work_dir.run_jj(["git", "push"]); 256 insta::allow_duplicates! { 257 insta::assert_snapshot!(output, @r" 258 ------- stderr ------- 259 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@ 260 Nothing changed. 261 [EOF] 262 "); 263 } 264} 265 266/// Test that `jj git push` without arguments pushes a bookmark to the specified 267/// remote even if it's already up to date on another remote 268/// (`remote_bookmarks(remote=<remote>)..@` vs. `remote_bookmarks()..@`). 269#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 270#[test_case(true; "spawn a git subprocess for remote calls")] 271fn test_git_push_other_remote_has_bookmark(subprocess: bool) { 272 let test_env = TestEnvironment::default(); 273 set_up(&test_env); 274 let work_dir = test_env.work_dir("local"); 275 if !subprocess { 276 test_env.add_config("git.subprocess = false"); 277 } 278 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 279 // Create another remote (but actually the same) 280 let other_remote_path = test_env 281 .env_root() 282 .join("origin") 283 .join(".jj") 284 .join("repo") 285 .join("store") 286 .join("git"); 287 work_dir 288 .run_jj([ 289 "git", 290 "remote", 291 "add", 292 "other", 293 other_remote_path.to_str().unwrap(), 294 ]) 295 .success(); 296 // Modify bookmark1 and push it to `origin` 297 work_dir.run_jj(["edit", "bookmark1"]).success(); 298 work_dir.run_jj(["describe", "-m=modified"]).success(); 299 let output = work_dir.run_jj(["git", "push"]); 300 insta::allow_duplicates! { 301 insta::assert_snapshot!(output, @r" 302 ------- stderr ------- 303 Changes to push to origin: 304 Move sideways bookmark bookmark1 from d13ecdbda2a2 to a657f1b61b94 305 [EOF] 306 "); 307 } 308 // Since it's already pushed to origin, nothing will happen if push again 309 let output = work_dir.run_jj(["git", "push"]); 310 insta::allow_duplicates! { 311 insta::assert_snapshot!(output, @r" 312 ------- stderr ------- 313 Warning: No bookmarks found in the default push revset: remote_bookmarks(remote=origin)..@ 314 Nothing changed. 315 [EOF] 316 "); 317 } 318 // The bookmark was moved on the "other" remote as well (since it's actually the 319 // same remote), but `jj` is not aware of that since it thinks this is a 320 // different remote. So, the push should fail. 321 // 322 // But it succeeds! That's because the bookmark is created at the same location 323 // as it is on the remote. This would also work for a descendant. 324 // 325 // TODO: Saner test? 326 let output = work_dir.run_jj(["git", "push", "--allow-new", "--remote=other"]); 327 insta::allow_duplicates! { 328 insta::assert_snapshot!(output, @r" 329 ------- stderr ------- 330 Changes to push to other: 331 Add bookmark bookmark1 to a657f1b61b94 332 [EOF] 333 "); 334 } 335} 336 337#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 338#[test_case(true; "spawn a git subprocess for remote calls")] 339fn test_git_push_forward_unexpectedly_moved(subprocess: bool) { 340 let test_env = TestEnvironment::default(); 341 set_up(&test_env); 342 let work_dir = test_env.work_dir("local"); 343 if !subprocess { 344 test_env.add_config("git.subprocess = false"); 345 } 346 347 // Move bookmark1 forward on the remote 348 let origin_dir = test_env.work_dir("origin"); 349 origin_dir 350 .run_jj(["new", "bookmark1", "-m=remote"]) 351 .success(); 352 origin_dir.write_file("remote", "remote"); 353 origin_dir 354 .run_jj(["bookmark", "set", "bookmark1", "-r@"]) 355 .success(); 356 origin_dir.run_jj(["git", "export"]).success(); 357 358 // Move bookmark1 forward to another commit locally 359 work_dir.run_jj(["new", "bookmark1", "-m=local"]).success(); 360 work_dir.write_file("local", "local"); 361 work_dir 362 .run_jj(["bookmark", "set", "bookmark1", "-r@"]) 363 .success(); 364 365 // Pushing should fail 366 let output = work_dir.run_jj(["git", "push"]); 367 if subprocess { 368 insta::assert_snapshot!(output, @r" 369 ------- stderr ------- 370 Changes to push to origin: 371 Move forward bookmark bookmark1 from d13ecdbda2a2 to 6750425ff51c 372 Error: Failed to push some bookmarks 373 Hint: The following references unexpectedly moved on the remote: 374 refs/heads/bookmark1 (reason: stale info) 375 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 376 [EOF] 377 [exit status: 1] 378 "); 379 } else { 380 insta::assert_snapshot!(output, @r" 381 ------- stderr ------- 382 Changes to push to origin: 383 Move forward bookmark bookmark1 from d13ecdbda2a2 to 6750425ff51c 384 Error: Failed to push some bookmarks 385 Hint: The following references unexpectedly moved on the remote: 386 refs/heads/bookmark1 387 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 388 [EOF] 389 [exit status: 1] 390 "); 391 } 392} 393 394#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 395#[test_case(true; "spawn a git subprocess for remote calls")] 396fn test_git_push_sideways_unexpectedly_moved(subprocess: bool) { 397 let test_env = TestEnvironment::default(); 398 set_up(&test_env); 399 let work_dir = test_env.work_dir("local"); 400 if !subprocess { 401 test_env.add_config("git.subprocess = false"); 402 } 403 404 // Move bookmark1 forward on the remote 405 let origin_dir = test_env.work_dir("origin"); 406 origin_dir 407 .run_jj(["new", "bookmark1", "-m=remote"]) 408 .success(); 409 origin_dir.write_file("remote", "remote"); 410 origin_dir 411 .run_jj(["bookmark", "set", "bookmark1", "-r@"]) 412 .success(); 413 insta::allow_duplicates! { 414 insta::assert_snapshot!(get_bookmark_output(&origin_dir), @r" 415 bookmark1: vruxwmqv 80284bec remote 416 @git (behind by 1 commits): qpvuntsm d13ecdbd (empty) description 1 417 bookmark2: zsuskuln 8476341e (empty) description 2 418 @git: zsuskuln 8476341e (empty) description 2 419 [EOF] 420 "); 421 } 422 origin_dir.run_jj(["git", "export"]).success(); 423 424 // Move bookmark1 sideways to another commit locally 425 work_dir.run_jj(["new", "root()", "-m=local"]).success(); 426 work_dir.write_file("local", "local"); 427 work_dir 428 .run_jj(["bookmark", "set", "bookmark1", "--allow-backwards", "-r@"]) 429 .success(); 430 insta::allow_duplicates! { 431 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 432 bookmark1: kmkuslsw 0f8bf988 local 433 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv d13ecdbd (empty) description 1 434 bookmark2: rlzusymt 8476341e (empty) description 2 435 @origin: rlzusymt 8476341e (empty) description 2 436 [EOF] 437 "); 438 } 439 440 let output = work_dir.run_jj(["git", "push"]); 441 if subprocess { 442 insta::assert_snapshot!(output, @r" 443 ------- stderr ------- 444 Changes to push to origin: 445 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 0f8bf988588e 446 Error: Failed to push some bookmarks 447 Hint: The following references unexpectedly moved on the remote: 448 refs/heads/bookmark1 (reason: stale info) 449 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 450 [EOF] 451 [exit status: 1] 452 "); 453 } else { 454 insta::assert_snapshot!(output, @r" 455 ------- stderr ------- 456 Changes to push to origin: 457 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 0f8bf988588e 458 Error: Failed to push some bookmarks 459 Hint: The following references unexpectedly moved on the remote: 460 refs/heads/bookmark1 461 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 462 [EOF] 463 [exit status: 1] 464 "); 465 } 466} 467 468// This tests whether the push checks that the remote bookmarks are in expected 469// positions. 470#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 471#[test_case(true; "spawn a git subprocess for remote calls")] 472fn test_git_push_deletion_unexpectedly_moved(subprocess: bool) { 473 let test_env = TestEnvironment::default(); 474 set_up(&test_env); 475 let work_dir = test_env.work_dir("local"); 476 if !subprocess { 477 test_env.add_config("git.subprocess = false"); 478 } 479 480 // Move bookmark1 forward on the remote 481 let origin_dir = test_env.work_dir("origin"); 482 origin_dir 483 .run_jj(["new", "bookmark1", "-m=remote"]) 484 .success(); 485 origin_dir.write_file("remote", "remote"); 486 origin_dir 487 .run_jj(["bookmark", "set", "bookmark1", "-r@"]) 488 .success(); 489 insta::allow_duplicates! { 490 insta::assert_snapshot!(get_bookmark_output(&origin_dir), @r" 491 bookmark1: vruxwmqv 80284bec remote 492 @git (behind by 1 commits): qpvuntsm d13ecdbd (empty) description 1 493 bookmark2: zsuskuln 8476341e (empty) description 2 494 @git: zsuskuln 8476341e (empty) description 2 495 [EOF] 496 "); 497 } 498 origin_dir.run_jj(["git", "export"]).success(); 499 500 // Delete bookmark1 locally 501 work_dir 502 .run_jj(["bookmark", "delete", "bookmark1"]) 503 .success(); 504 insta::allow_duplicates! { 505 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 506 bookmark1 (deleted) 507 @origin: xtvrqkyv d13ecdbd (empty) description 1 508 bookmark2: rlzusymt 8476341e (empty) description 2 509 @origin: rlzusymt 8476341e (empty) description 2 510 [EOF] 511 "); 512 } 513 514 let output = work_dir.run_jj(["git", "push", "--bookmark", "bookmark1"]); 515 if subprocess { 516 insta::assert_snapshot!(output, @r" 517 ------- stderr ------- 518 Changes to push to origin: 519 Delete bookmark bookmark1 from d13ecdbda2a2 520 Error: Failed to push some bookmarks 521 Hint: The following references unexpectedly moved on the remote: 522 refs/heads/bookmark1 (reason: stale info) 523 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 524 [EOF] 525 [exit status: 1] 526 "); 527 } else { 528 insta::assert_snapshot!(output, @r" 529 ------- stderr ------- 530 Changes to push to origin: 531 Delete bookmark bookmark1 from d13ecdbda2a2 532 Error: Failed to push some bookmarks 533 Hint: The following references unexpectedly moved on the remote: 534 refs/heads/bookmark1 535 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 536 [EOF] 537 [exit status: 1] 538 "); 539 } 540} 541 542#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 543#[test_case(true; "spawn a git subprocess for remote calls")] 544fn test_git_push_unexpectedly_deleted(subprocess: bool) { 545 let test_env = TestEnvironment::default(); 546 set_up(&test_env); 547 let work_dir = test_env.work_dir("local"); 548 if !subprocess { 549 test_env.add_config("git.subprocess = false"); 550 } 551 552 // Delete bookmark1 forward on the remote 553 let origin_dir = test_env.work_dir("origin"); 554 origin_dir 555 .run_jj(["bookmark", "delete", "bookmark1"]) 556 .success(); 557 insta::allow_duplicates! { 558 insta::assert_snapshot!(get_bookmark_output(&origin_dir), @r" 559 bookmark1 (deleted) 560 @git: qpvuntsm d13ecdbd (empty) description 1 561 bookmark2: zsuskuln 8476341e (empty) description 2 562 @git: zsuskuln 8476341e (empty) description 2 563 [EOF] 564 "); 565 } 566 origin_dir.run_jj(["git", "export"]).success(); 567 568 // Move bookmark1 sideways to another commit locally 569 work_dir.run_jj(["new", "root()", "-m=local"]).success(); 570 work_dir.write_file("local", "local"); 571 work_dir 572 .run_jj(["bookmark", "set", "bookmark1", "--allow-backwards", "-r@"]) 573 .success(); 574 insta::allow_duplicates! { 575 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 576 bookmark1: kpqxywon 1ebe27ba local 577 @origin (ahead by 1 commits, behind by 1 commits): xtvrqkyv d13ecdbd (empty) description 1 578 bookmark2: rlzusymt 8476341e (empty) description 2 579 @origin: rlzusymt 8476341e (empty) description 2 580 [EOF] 581 "); 582 } 583 584 // Pushing a moved bookmark fails if deleted on remote 585 let output = work_dir.run_jj(["git", "push"]); 586 if subprocess { 587 insta::assert_snapshot!(output, @r" 588 ------- stderr ------- 589 Changes to push to origin: 590 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 1ebe27ba04bf 591 Error: Failed to push some bookmarks 592 Hint: The following references unexpectedly moved on the remote: 593 refs/heads/bookmark1 (reason: stale info) 594 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 595 [EOF] 596 [exit status: 1] 597 "); 598 } else { 599 insta::assert_snapshot!(output, @r" 600 ------- stderr ------- 601 Changes to push to origin: 602 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 1ebe27ba04bf 603 Error: Failed to push some bookmarks 604 Hint: The following references unexpectedly moved on the remote: 605 refs/heads/bookmark1 606 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 607 [EOF] 608 [exit status: 1] 609 "); 610 } 611 612 work_dir 613 .run_jj(["bookmark", "delete", "bookmark1"]) 614 .success(); 615 insta::allow_duplicates! { 616 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 617 bookmark1 (deleted) 618 @origin: xtvrqkyv d13ecdbd (empty) description 1 619 bookmark2: rlzusymt 8476341e (empty) description 2 620 @origin: rlzusymt 8476341e (empty) description 2 621 [EOF] 622 "); 623 } 624 625 if subprocess { 626 // git does not allow to push a deleted bookmark if we expect it to exist even 627 // though it was already deleted 628 let output = work_dir.run_jj(["git", "push", "-bbookmark1"]); 629 insta::assert_snapshot!(output, @r" 630 ------- stderr ------- 631 Changes to push to origin: 632 Delete bookmark bookmark1 from d13ecdbda2a2 633 Error: Failed to push some bookmarks 634 Hint: The following references unexpectedly moved on the remote: 635 refs/heads/bookmark1 (reason: stale info) 636 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 637 [EOF] 638 [exit status: 1] 639 "); 640 } else { 641 // Pushing a *deleted* bookmark succeeds if deleted on remote, even if we expect 642 // bookmark1@origin to exist and point somewhere. 643 let output = work_dir.run_jj(["git", "push", "-bbookmark1"]); 644 insta::assert_snapshot!(output, @r" 645 ------- stderr ------- 646 Changes to push to origin: 647 Delete bookmark bookmark1 from d13ecdbda2a2 648 [EOF] 649 "); 650 } 651} 652 653#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 654#[test_case(true; "spawn a git subprocess for remote calls")] 655fn test_git_push_creation_unexpectedly_already_exists(subprocess: bool) { 656 let test_env = TestEnvironment::default(); 657 set_up(&test_env); 658 let work_dir = test_env.work_dir("local"); 659 if !subprocess { 660 test_env.add_config("git.subprocess = false"); 661 } 662 663 // Forget bookmark1 locally 664 work_dir 665 .run_jj(["bookmark", "forget", "--include-remotes", "bookmark1"]) 666 .success(); 667 668 // Create a new branh1 669 work_dir 670 .run_jj(["new", "root()", "-m=new bookmark1"]) 671 .success(); 672 work_dir.write_file("local", "local"); 673 work_dir 674 .run_jj(["bookmark", "create", "-r@", "bookmark1"]) 675 .success(); 676 insta::allow_duplicates! { 677 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 678 bookmark1: yostqsxw cb17dcdc new bookmark1 679 bookmark2: rlzusymt 8476341e (empty) description 2 680 @origin: rlzusymt 8476341e (empty) description 2 681 [EOF] 682 "); 683 } 684 685 let output = work_dir.run_jj(["git", "push", "--allow-new"]); 686 if subprocess { 687 insta::assert_snapshot!(output, @r" 688 ------- stderr ------- 689 Changes to push to origin: 690 Add bookmark bookmark1 to cb17dcdc74d5 691 Error: Failed to push some bookmarks 692 Hint: The following references unexpectedly moved on the remote: 693 refs/heads/bookmark1 (reason: stale info) 694 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 695 [EOF] 696 [exit status: 1] 697 "); 698 } else { 699 insta::assert_snapshot!(output, @r" 700 ------- stderr ------- 701 Changes to push to origin: 702 Add bookmark bookmark1 to cb17dcdc74d5 703 Error: Failed to push some bookmarks 704 Hint: The following references unexpectedly moved on the remote: 705 refs/heads/bookmark1 706 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 707 [EOF] 708 [exit status: 1] 709 "); 710 } 711} 712 713#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 714#[test_case(true; "spawn a git subprocess for remote calls")] 715fn test_git_push_locally_created_and_rewritten(subprocess: bool) { 716 let test_env = TestEnvironment::default(); 717 set_up(&test_env); 718 let work_dir = test_env.work_dir("local"); 719 if !subprocess { 720 test_env.add_config("git.subprocess = false"); 721 } 722 // Ensure that remote bookmarks aren't tracked automatically 723 test_env.add_config("git.auto-local-bookmark = false"); 724 725 // Push locally-created bookmark 726 work_dir.run_jj(["new", "root()", "-mlocal 1"]).success(); 727 work_dir 728 .run_jj(["bookmark", "create", "-r@", "my"]) 729 .success(); 730 let output = work_dir.run_jj(["git", "push"]); 731 insta::allow_duplicates! { 732 insta::assert_snapshot!(output, @r" 733 ------- stderr ------- 734 Warning: Refusing to create new remote bookmark my@origin 735 Hint: Use --allow-new to push new bookmark. Use --remote to specify the remote to push to. 736 Nothing changed. 737 [EOF] 738 "); 739 } 740 // Either --allow-new or git.push-new-bookmarks=true should work 741 let output = work_dir.run_jj(["git", "push", "--allow-new", "--dry-run"]); 742 insta::allow_duplicates! { 743 insta::assert_snapshot!(output, @r" 744 ------- stderr ------- 745 Changes to push to origin: 746 Add bookmark my to fcc999921ce9 747 Dry-run requested, not pushing. 748 [EOF] 749 "); 750 } 751 let output = work_dir.run_jj(["git", "push", "--config=git.push-new-bookmarks=true"]); 752 insta::allow_duplicates! { 753 insta::assert_snapshot!(output, @r" 754 ------- stderr ------- 755 Changes to push to origin: 756 Add bookmark my to fcc999921ce9 757 [EOF] 758 "); 759 } 760 761 // Rewrite it and push again, which would fail if the pushed bookmark weren't 762 // set to "tracking" 763 work_dir.run_jj(["describe", "-mlocal 2"]).success(); 764 insta::allow_duplicates! { 765 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 766 bookmark1: xtvrqkyv d13ecdbd (empty) description 1 767 @origin: xtvrqkyv d13ecdbd (empty) description 1 768 bookmark2: rlzusymt 8476341e (empty) description 2 769 @origin: rlzusymt 8476341e (empty) description 2 770 my: vruxwmqv 423bb660 (empty) local 2 771 @origin (ahead by 1 commits, behind by 1 commits): vruxwmqv hidden fcc99992 (empty) local 1 772 [EOF] 773 "); 774 } 775 let output = work_dir.run_jj(["git", "push"]); 776 insta::allow_duplicates! { 777 insta::assert_snapshot!(output, @r" 778 ------- stderr ------- 779 Changes to push to origin: 780 Move sideways bookmark my from fcc999921ce9 to 423bb66069e7 781 [EOF] 782 "); 783 } 784} 785 786#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 787#[test_case(true; "spawn a git subprocess for remote calls")] 788fn test_git_push_multiple(subprocess: bool) { 789 let test_env = TestEnvironment::default(); 790 set_up(&test_env); 791 let work_dir = test_env.work_dir("local"); 792 if !subprocess { 793 test_env.add_config("git.subprocess = false"); 794 } 795 work_dir 796 .run_jj(["bookmark", "delete", "bookmark1"]) 797 .success(); 798 work_dir 799 .run_jj(["bookmark", "set", "--allow-backwards", "bookmark2", "-r@"]) 800 .success(); 801 work_dir 802 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 803 .success(); 804 work_dir.run_jj(["describe", "-m", "foo"]).success(); 805 // Check the setup 806 insta::allow_duplicates! { 807 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 808 bookmark1 (deleted) 809 @origin: xtvrqkyv d13ecdbd (empty) description 1 810 bookmark2: yqosqzyt c4a3c310 (empty) foo 811 @origin (ahead by 1 commits, behind by 1 commits): rlzusymt 8476341e (empty) description 2 812 my-bookmark: yqosqzyt c4a3c310 (empty) foo 813 [EOF] 814 "); 815 } 816 // First dry-run 817 let output = work_dir.run_jj(["git", "push", "--all", "--deleted", "--dry-run"]); 818 insta::allow_duplicates! { 819 insta::assert_snapshot!(output, @r" 820 ------- stderr ------- 821 Changes to push to origin: 822 Delete bookmark bookmark1 from d13ecdbda2a2 823 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92 824 Add bookmark my-bookmark to c4a3c3105d92 825 Dry-run requested, not pushing. 826 [EOF] 827 "); 828 } 829 // Dry run requesting two specific bookmarks 830 let output = work_dir.run_jj([ 831 "git", 832 "push", 833 "--allow-new", 834 "-b=bookmark1", 835 "-b=my-bookmark", 836 "--dry-run", 837 ]); 838 insta::allow_duplicates! { 839 insta::assert_snapshot!(output, @r" 840 ------- stderr ------- 841 Changes to push to origin: 842 Delete bookmark bookmark1 from d13ecdbda2a2 843 Add bookmark my-bookmark to c4a3c3105d92 844 Dry-run requested, not pushing. 845 [EOF] 846 "); 847 } 848 // Dry run requesting two specific bookmarks twice 849 let output = work_dir.run_jj([ 850 "git", 851 "push", 852 "--allow-new", 853 "-b=bookmark1", 854 "-b=my-bookmark", 855 "-b=bookmark1", 856 "-b=glob:my-*", 857 "--dry-run", 858 ]); 859 insta::allow_duplicates! { 860 insta::assert_snapshot!(output, @r" 861 ------- stderr ------- 862 Changes to push to origin: 863 Delete bookmark bookmark1 from d13ecdbda2a2 864 Add bookmark my-bookmark to c4a3c3105d92 865 Dry-run requested, not pushing. 866 [EOF] 867 "); 868 } 869 // Dry run with glob pattern 870 let output = work_dir.run_jj(["git", "push", "-b=glob:bookmark?", "--dry-run"]); 871 insta::allow_duplicates! { 872 insta::assert_snapshot!(output, @r" 873 ------- stderr ------- 874 Changes to push to origin: 875 Delete bookmark bookmark1 from d13ecdbda2a2 876 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92 877 Dry-run requested, not pushing. 878 [EOF] 879 "); 880 } 881 882 // Unmatched bookmark name is error 883 let output = work_dir.run_jj(["git", "push", "-b=foo"]); 884 insta::allow_duplicates! { 885 insta::assert_snapshot!(output, @r" 886 ------- stderr ------- 887 Error: No such bookmark: foo 888 [EOF] 889 [exit status: 1] 890 "); 891 } 892 let output = work_dir.run_jj(["git", "push", "-b=foo", "-b=glob:?bookmark"]); 893 insta::allow_duplicates! { 894 insta::assert_snapshot!(output, @r" 895 ------- stderr ------- 896 Error: No matching bookmarks for patterns: foo, ?bookmark 897 [EOF] 898 [exit status: 1] 899 "); 900 } 901 902 // --deleted is required to push deleted bookmarks even with --all 903 let output = work_dir.run_jj(["git", "push", "--all", "--dry-run"]); 904 insta::assert_snapshot!(output, @r" 905 ------- stderr ------- 906 Warning: Refusing to push deleted bookmark bookmark1 907 Hint: Push deleted bookmarks with --deleted or forget the bookmark to suppress this warning. 908 Changes to push to origin: 909 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92 910 Add bookmark my-bookmark to c4a3c3105d92 911 Dry-run requested, not pushing. 912 [EOF] 913 "); 914 let output = work_dir.run_jj(["git", "push", "--all", "--deleted", "--dry-run"]); 915 insta::assert_snapshot!(output, @r" 916 ------- stderr ------- 917 Changes to push to origin: 918 Delete bookmark bookmark1 from d13ecdbda2a2 919 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92 920 Add bookmark my-bookmark to c4a3c3105d92 921 Dry-run requested, not pushing. 922 [EOF] 923 "); 924 925 let output = work_dir.run_jj(["git", "push", "--all", "--deleted"]); 926 insta::allow_duplicates! { 927 insta::assert_snapshot!(output, @r" 928 ------- stderr ------- 929 Changes to push to origin: 930 Delete bookmark bookmark1 from d13ecdbda2a2 931 Move sideways bookmark bookmark2 from 8476341eb395 to c4a3c3105d92 932 Add bookmark my-bookmark to c4a3c3105d92 933 [EOF] 934 "); 935 } 936 insta::allow_duplicates! { 937 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 938 bookmark2: yqosqzyt c4a3c310 (empty) foo 939 @origin: yqosqzyt c4a3c310 (empty) foo 940 my-bookmark: yqosqzyt c4a3c310 (empty) foo 941 @origin: yqosqzyt c4a3c310 (empty) foo 942 [EOF] 943 "); 944 } 945 let output = work_dir.run_jj(["log", "-rall()"]); 946 insta::allow_duplicates! { 947 insta::assert_snapshot!(output, @r" 948 @ yqosqzyt test.user@example.com 2001-02-03 08:05:17 bookmark2 my-bookmark c4a3c310 949 │ (empty) foo 950 │ ○ rlzusymt test.user@example.com 2001-02-03 08:05:10 8476341e 951 ├─╯ (empty) description 2 952 │ ○ xtvrqkyv test.user@example.com 2001-02-03 08:05:08 d13ecdbd 953 ├─╯ (empty) description 1 954 ◆ zzzzzzzz root() 00000000 955 [EOF] 956 "); 957 } 958} 959 960#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 961#[test_case(true; "spawn a git subprocess for remote calls")] 962fn test_git_push_changes(subprocess: bool) { 963 let test_env = TestEnvironment::default(); 964 set_up(&test_env); 965 let work_dir = test_env.work_dir("local"); 966 if !subprocess { 967 test_env.add_config("git.subprocess = false"); 968 } 969 work_dir.run_jj(["describe", "-m", "foo"]).success(); 970 work_dir.write_file("file", "contents"); 971 work_dir.run_jj(["new", "-m", "bar"]).success(); 972 work_dir.write_file("file", "modified"); 973 974 let output = work_dir.run_jj(["git", "push", "--change", "@"]); 975 insta::allow_duplicates! { 976 insta::assert_snapshot!(output, @r" 977 ------- stderr ------- 978 Creating bookmark push-yostqsxwqrlt for revision yostqsxwqrlt 979 Changes to push to origin: 980 Add bookmark push-yostqsxwqrlt to cf1a53a8800a 981 [EOF] 982 "); 983 } 984 // test pushing two changes at once 985 work_dir.write_file("file", "modified2"); 986 let output = work_dir.run_jj(["git", "push", "-c=(@|@-)"]); 987 insta::allow_duplicates! { 988 insta::assert_snapshot!(output, @r" 989 ------- stderr ------- 990 Error: Revset `(@|@-)` resolved to more than one revision 991 Hint: The revset `(@|@-)` resolved to these revisions: 992 yostqsxw 16c16966 push-yostqsxwqrlt* | bar 993 yqosqzyt a050abf4 foo 994 Hint: Prefix the expression with `all:` to allow any number of revisions (i.e. `all:(@|@-)`). 995 [EOF] 996 [exit status: 1] 997 "); 998 } 999 // test pushing two changes at once, part 2 1000 let output = work_dir.run_jj(["git", "push", "-c=all:(@|@-)"]); 1001 insta::allow_duplicates! { 1002 insta::assert_snapshot!(output, @r" 1003 ------- stderr ------- 1004 Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw 1005 Changes to push to origin: 1006 Move sideways bookmark push-yostqsxwqrlt from cf1a53a8800a to 16c169664e9f 1007 Add bookmark push-yqosqzytrlsw to a050abf4ff07 1008 [EOF] 1009 "); 1010 } 1011 // specifying the same change twice doesn't break things 1012 work_dir.write_file("file", "modified3"); 1013 let output = work_dir.run_jj(["git", "push", "-c=all:(@|@)"]); 1014 insta::allow_duplicates! { 1015 insta::assert_snapshot!(output, @r" 1016 ------- stderr ------- 1017 Changes to push to origin: 1018 Move sideways bookmark push-yostqsxwqrlt from 16c169664e9f to ef6313d50ac1 1019 [EOF] 1020 "); 1021 } 1022 1023 // specifying the same bookmark with --change/--bookmark doesn't break things 1024 work_dir.write_file("file", "modified4"); 1025 let output = work_dir.run_jj(["git", "push", "-c=@", "-b=push-yostqsxwqrlt"]); 1026 insta::allow_duplicates! { 1027 insta::assert_snapshot!(output, @r" 1028 ------- stderr ------- 1029 Changes to push to origin: 1030 Move sideways bookmark push-yostqsxwqrlt from ef6313d50ac1 to c1e65d3a64ce 1031 [EOF] 1032 "); 1033 } 1034 1035 // try again with --change that moves the bookmark forward 1036 work_dir.write_file("file", "modified5"); 1037 work_dir 1038 .run_jj([ 1039 "bookmark", 1040 "set", 1041 "-r=@-", 1042 "--allow-backwards", 1043 "push-yostqsxwqrlt", 1044 ]) 1045 .success(); 1046 let output = work_dir.run_jj(["status"]); 1047 insta::allow_duplicates! { 1048 insta::assert_snapshot!(output, @r" 1049 Working copy changes: 1050 M file 1051 Working copy (@) : yostqsxw 38cb417c bar 1052 Parent commit (@-): yqosqzyt a050abf4 push-yostqsxwqrlt* push-yqosqzytrlsw | foo 1053 [EOF] 1054 "); 1055 } 1056 let output = work_dir.run_jj(["git", "push", "-c=@", "-b=push-yostqsxwqrlt"]); 1057 insta::allow_duplicates! { 1058 insta::assert_snapshot!(output, @r" 1059 ------- stderr ------- 1060 Changes to push to origin: 1061 Move sideways bookmark push-yostqsxwqrlt from c1e65d3a64ce to 38cb417ce3a6 1062 [EOF] 1063 "); 1064 } 1065 let output = work_dir.run_jj(["status"]); 1066 insta::allow_duplicates! { 1067 insta::assert_snapshot!(output, @r" 1068 Working copy changes: 1069 M file 1070 Working copy (@) : yostqsxw 38cb417c push-yostqsxwqrlt | bar 1071 Parent commit (@-): yqosqzyt a050abf4 push-yqosqzytrlsw | foo 1072 [EOF] 1073 "); 1074 } 1075 1076 // Test changing `git.push-bookmark-prefix`. It causes us to push again. 1077 let output = work_dir.run_jj([ 1078 "git", 1079 "push", 1080 "--config=git.push-bookmark-prefix=test-", 1081 "--change=@", 1082 ]); 1083 insta::allow_duplicates! { 1084 insta::assert_snapshot!(output, @r" 1085 ------- stderr ------- 1086 Creating bookmark test-yostqsxwqrlt for revision yostqsxwqrlt 1087 Changes to push to origin: 1088 Add bookmark test-yostqsxwqrlt to 38cb417ce3a6 1089 [EOF] 1090 "); 1091 } 1092} 1093 1094#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1095#[test_case(true; "spawn a git subprocess for remote calls")] 1096fn test_git_push_changes_with_name(subprocess: bool) { 1097 let test_env = TestEnvironment::default(); 1098 set_up(&test_env); 1099 let work_dir = test_env.work_dir("local"); 1100 if subprocess { 1101 test_env.add_config("git.subprocess = true"); 1102 } 1103 work_dir.run_jj(["describe", "-m", "foo"]).success(); 1104 work_dir.write_file("file", "contents"); 1105 work_dir.run_jj(["new", "-m", "pushed"]).success(); 1106 work_dir.write_file("file", "modified"); 1107 1108 // Normal behavior. 1109 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]); 1110 insta::allow_duplicates! { 1111 insta::assert_snapshot!(output, @r" 1112 ------- stderr ------- 1113 Changes to push to origin: 1114 Add bookmark b1 to 3e677c129c1d 1115 [EOF] 1116 "); 1117 } 1118 // Spaces before the = sign are treated like part of the bookmark name and such 1119 // bookmarks cannot be pushed. 1120 let output = work_dir.run_jj(["git", "push", "--named", "b1 = @"]); 1121 insta::allow_duplicates! { 1122 insta::assert_snapshot!(output, @r" 1123 ------- stderr ------- 1124 Error: Could not parse 'b1 ' as a bookmark name 1125 Caused by: 1126 1: Failed to parse bookmark name: Syntax error 1127 2: --> 1:3 1128 | 1129 1 | b1 1130 | ^--- 1131 | 1132 = expected <EOI> 1133 Hint: For example, `--named myfeature=@` is valid syntax 1134 [EOF] 1135 [exit status: 2] 1136 "); 1137 } 1138 // test pushing a change with an empty name 1139 let output = work_dir.run_jj(["git", "push", "--named", "=@"]); 1140 insta::allow_duplicates! { 1141 insta::assert_snapshot!(output, @r" 1142 ------- stderr ------- 1143 Error: Argument '=@' must have the form NAME=REVISION, with both NAME and REVISION non-empty 1144 Hint: For example, `--named myfeature=@` is valid syntax 1145 [EOF] 1146 [exit status: 2] 1147 "); 1148 } 1149 // Unparsable name 1150 let output = work_dir.run_jj(["git", "push", "--named", ":!:=@"]); 1151 insta::allow_duplicates! { 1152 insta::assert_snapshot!(output, @r" 1153 ------- stderr ------- 1154 Error: Could not parse ':!:' as a bookmark name 1155 Caused by: 1156 1: Failed to parse bookmark name: Syntax error 1157 2: --> 1:1 1158 | 1159 1 | :!: 1160 | ^--- 1161 | 1162 = expected <identifier>, <string_literal>, or <raw_string_literal> 1163 Hint: For example, `--named myfeature=@` is valid syntax 1164 [EOF] 1165 [exit status: 2] 1166 "); 1167 } 1168 // test pushing a change with an empty revision 1169 let output = work_dir.run_jj(["git", "push", "--named", "b2="]); 1170 insta::allow_duplicates! { 1171 insta::assert_snapshot!(output, @r" 1172 ------- stderr ------- 1173 Error: Argument 'b2=' must have the form NAME=REVISION, with both NAME and REVISION non-empty 1174 Hint: For example, `--named myfeature=@` is valid syntax 1175 [EOF] 1176 [exit status: 2] 1177 "); 1178 } 1179 // test pushing a change with no equals sign 1180 let output = work_dir.run_jj(["git", "push", "--named", "b2"]); 1181 insta::allow_duplicates! { 1182 insta::assert_snapshot!(output, @r" 1183 ------- stderr ------- 1184 Error: Argument 'b2' must include '=' and have the form NAME=REVISION 1185 Hint: For example, `--named myfeature=@` is valid syntax 1186 [EOF] 1187 [exit status: 2] 1188 "); 1189 } 1190 1191 // test pushing the same change with the same name again 1192 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]); 1193 insta::allow_duplicates! { 1194 insta::assert_snapshot!(output, @r" 1195 ------- stderr ------- 1196 Error: Bookmark already exists: b1 1197 Hint: Use 'jj bookmark move' to move it, and 'jj git push -b b1 [--allow-new]' to push it 1198 [EOF] 1199 [exit status: 1] 1200 "); 1201 } 1202 // test pushing two changes at once 1203 work_dir.write_file("file", "modified2"); 1204 let output = work_dir.run_jj(["git", "push", "--named=b2=all:(@|@-)"]); 1205 insta::allow_duplicates! { 1206 insta::assert_snapshot!(output, @r" 1207 ------- stderr ------- 1208 Error: Revset `all:(@|@-)` resolved to more than one revision 1209 Hint: The revset `all:(@|@-)` resolved to these revisions: 1210 yostqsxw 101e6730 b1* | pushed 1211 yqosqzyt a050abf4 foo 1212 [EOF] 1213 [exit status: 1] 1214 "); 1215 } 1216 1217 // specifying the same bookmark with --named/--bookmark 1218 work_dir.write_file("file", "modified4"); 1219 let output = work_dir.run_jj(["git", "push", "--named=b2=@", "-b=b2"]); 1220 insta::allow_duplicates! { 1221 insta::assert_snapshot!(output, @r" 1222 ------- stderr ------- 1223 Changes to push to origin: 1224 Add bookmark b2 to 477da21559d5 1225 [EOF] 1226 "); 1227 } 1228} 1229 1230#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1231#[test_case(true; "spawn a git subprocess for remote calls")] 1232fn test_git_push_changes_with_name_deleted_tracked(subprocess: bool) { 1233 let test_env = TestEnvironment::default(); 1234 set_up(&test_env); 1235 // Unset immutable_heads so that untracking branches does not move the working 1236 // copy 1237 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 1238 let work_dir = test_env.work_dir("local"); 1239 if subprocess { 1240 test_env.add_config("git.subprocess = true"); 1241 } 1242 // Create a second empty remote `another_remote` 1243 test_env 1244 .run_jj_in(".", ["git", "init", "another_remote"]) 1245 .success(); 1246 let another_remote_git_repo_path = 1247 git_repo_dir_for_jj_repo(&test_env.work_dir("another_remote")); 1248 work_dir 1249 .run_jj([ 1250 "git", 1251 "remote", 1252 "add", 1253 "another_remote", 1254 another_remote_git_repo_path.to_str().unwrap(), 1255 ]) 1256 .success(); 1257 work_dir.run_jj(["describe", "-m", "foo"]).success(); 1258 work_dir.write_file("file", "contents"); 1259 work_dir.run_jj(["new", "-m", "pushed"]).success(); 1260 work_dir.write_file("file", "modified"); 1261 // Normal push as part of the test setup 1262 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]); 1263 insta::allow_duplicates! { 1264 insta::assert_snapshot!(output, @r" 1265 ------- stderr ------- 1266 Changes to push to origin: 1267 Add bookmark b1 to fd39fc9ddae4 1268 [EOF] 1269 "); 1270 } 1271 work_dir.run_jj(["bookmark", "delete", "b1"]).success(); 1272 1273 // Test the setup 1274 let output = work_dir 1275 .run_jj(["bookmark", "list", "--all", "b1"]) 1276 .success(); 1277 insta::allow_duplicates! { 1278 insta::assert_snapshot!(output, @r" 1279 b1 (deleted) 1280 @origin: kpqxywon fd39fc9d pushed 1281 [EOF] 1282 ------- stderr ------- 1283 Hint: Bookmarks marked as deleted will be *deleted permanently* on the remote on the next `jj git push`. Use `jj bookmark forget` to prevent this. 1284 [EOF] 1285 "); 1286 } 1287 1288 // Can't push `b1` with --named to the same or another remote if it's deleted 1289 // locally and still tracked on `origin` 1290 let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=another_remote"]); 1291 insta::allow_duplicates! { 1292 insta::assert_snapshot!(output, @r" 1293 ------- stderr ------- 1294 Error: Tracked remote bookmarks exist for deleted bookmark: b1 1295 Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack 'glob:b1@*'` to disassociate them. 1296 [EOF] 1297 [exit status: 1] 1298 "); 1299 } 1300 let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=origin"]); 1301 insta::allow_duplicates! { 1302 insta::assert_snapshot!(output, @r" 1303 ------- stderr ------- 1304 Error: Tracked remote bookmarks exist for deleted bookmark: b1 1305 Hint: Use `jj bookmark set` to recreate the local bookmark. Run `jj bookmark untrack 'glob:b1@*'` to disassociate them. 1306 [EOF] 1307 [exit status: 1] 1308 "); 1309 } 1310 1311 // OK to push to a different remote once the bookmark is no longer tracked on 1312 // `origin` 1313 work_dir 1314 .run_jj(["bookmark", "untrack", "b1@origin"]) 1315 .success(); 1316 let output = work_dir 1317 .run_jj(["bookmark", "list", "--all", "b1"]) 1318 .success(); 1319 insta::allow_duplicates! { 1320 insta::assert_snapshot!(output, @r" 1321 b1@origin: kpqxywon fd39fc9d pushed 1322 [EOF] 1323 "); 1324 } 1325 let output = work_dir.run_jj(["git", "push", "--named", "b1=@", "--remote=another_remote"]); 1326 insta::allow_duplicates! { 1327 insta::assert_snapshot!(output, @r" 1328 ------- stderr ------- 1329 Changes to push to another_remote: 1330 Add bookmark b1 to fd39fc9ddae4 1331 [EOF] 1332 "); 1333 } 1334 let output = work_dir 1335 .run_jj(["bookmark", "list", "--all", "b1"]) 1336 .success(); 1337 insta::allow_duplicates! { 1338 insta::assert_snapshot!(output, @r" 1339 b1: kpqxywon fd39fc9d pushed 1340 @another_remote: kpqxywon fd39fc9d pushed 1341 b1@origin: kpqxywon fd39fc9d pushed 1342 [EOF] 1343 "); 1344 } 1345} 1346 1347#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1348#[test_case(true; "spawn a git subprocess for remote calls")] 1349fn test_git_push_changes_with_name_untracked_or_forgotten(subprocess: bool) { 1350 let test_env = TestEnvironment::default(); 1351 set_up(&test_env); 1352 let work_dir = test_env.work_dir("local"); 1353 if subprocess { 1354 test_env.add_config("git.subprocess = true"); 1355 } 1356 // Unset immutable_heads so that untracking branches does not move the working 1357 // copy 1358 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 1359 work_dir 1360 .run_jj(["describe", "-m", "pushed_to_remote"]) 1361 .success(); 1362 work_dir.write_file("file", "contents"); 1363 work_dir 1364 .run_jj(["new", "-m", "child", "--no-edit"]) 1365 .success(); 1366 work_dir.write_file("file", "modified"); 1367 1368 // Push a branch to a remote, but forget the local branch 1369 work_dir 1370 .run_jj(["git", "push", "--named", "b1=@"]) 1371 .success(); 1372 work_dir 1373 .run_jj(["bookmark", "untrack", "b1@origin"]) 1374 .success(); 1375 work_dir.run_jj(["bookmark", "delete", "b1"]).success(); 1376 1377 let output = work_dir 1378 .run_jj(&[ 1379 "log", 1380 "-r=::@+", 1381 r#"-T=separate(" ", commit_id.shortest(3), bookmarks, description)"#, 1382 ]) 1383 .success(); 1384 insta::allow_duplicates! { 1385 insta::assert_snapshot!(output, @r" 1386 ○ c9c child 1387 @ 10b b1@origin pushed_to_remote 1388 ◆ 000 1389 [EOF] 1390 "); 1391 } 1392 let output = work_dir 1393 .run_jj(["bookmark", "list", "--all", "b1"]) 1394 .success(); 1395 insta::allow_duplicates! { 1396 insta::assert_snapshot!(output, @r" 1397 b1@origin: yqosqzyt 10b6b209 pushed_to_remote 1398 [EOF] 1399 "); 1400 } 1401 1402 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]); 1403 insta::allow_duplicates! { 1404 insta::assert_snapshot!(output, @r" 1405 ------- stderr ------- 1406 Error: Non-tracking remote bookmark b1@origin exists 1407 Hint: Run `jj bookmark track b1@origin` to import the remote bookmark. 1408 [EOF] 1409 [exit status: 1] 1410 "); 1411 } 1412 1413 let output = work_dir.run_jj(["git", "push", "--named", "b1=@+"]); 1414 insta::allow_duplicates! { 1415 insta::assert_snapshot!(output, @r" 1416 ------- stderr ------- 1417 Error: Non-tracking remote bookmark b1@origin exists 1418 Hint: Run `jj bookmark track b1@origin` to import the remote bookmark. 1419 [EOF] 1420 [exit status: 1] 1421 "); 1422 } 1423 1424 // The bookmarked is still pushed to the remote, but let's entirely forget 1425 // it. In other words, let's forget the remote-tracking bookmarks. 1426 work_dir 1427 .run_jj(&["bookmark", "forget", "b1", "--include-remotes"]) 1428 .success(); 1429 let output = work_dir 1430 .run_jj(["bookmark", "list", "--all", "b1"]) 1431 .success(); 1432 insta::allow_duplicates! { 1433 insta::assert_snapshot!(output, @""); 1434 } 1435 1436 // Make sure push still errors if we try to push a bookmark with the same name 1437 // to a different location. 1438 let output = work_dir.run_jj(["git", "push", "--named", "b1=@+"]); 1439 insta::allow_duplicates! { 1440 insta::assert_snapshot!(output, @r" 1441 ------- stderr ------- 1442 Changes to push to origin: 1443 Add bookmark b1 to c9c824c88955 1444 Error: Failed to push some bookmarks 1445 Hint: The following references unexpectedly moved on the remote: 1446 refs/heads/b1 (reason: stale info) 1447 Hint: Try fetching from the remote, then make the bookmark point to where you want it to be, and push again. 1448 [EOF] 1449 [exit status: 1] 1450 "); 1451 } 1452 1453 // The bookmark is still forgotten 1454 let output = work_dir.run_jj(["bookmark", "list", "--all", "b1"]); 1455 insta::allow_duplicates! { 1456 insta::assert_snapshot!(output, @""); 1457 } 1458 // In this case, pushing the bookmark to the same location where it already is 1459 // succeeds. TODO: This seems pretty safe, but perhaps it should still show 1460 // an error or some sort of warning? 1461 let output = work_dir.run_jj(["git", "push", "--named", "b1=@"]); 1462 insta::allow_duplicates! { 1463 insta::assert_snapshot!(output, @r" 1464 ------- stderr ------- 1465 Changes to push to origin: 1466 Add bookmark b1 to 10b6b209c4a3 1467 [EOF] 1468 "); 1469 } 1470} 1471 1472#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1473#[test_case(true; "spawn a git subprocess for remote calls")] 1474fn test_git_push_revisions(subprocess: bool) { 1475 let test_env = TestEnvironment::default(); 1476 set_up(&test_env); 1477 let work_dir = test_env.work_dir("local"); 1478 if !subprocess { 1479 test_env.add_config("git.subprocess = false"); 1480 } 1481 work_dir.run_jj(["describe", "-m", "foo"]).success(); 1482 work_dir.write_file("file", "contents"); 1483 work_dir.run_jj(["new", "-m", "bar"]).success(); 1484 work_dir 1485 .run_jj(["bookmark", "create", "-r@", "bookmark-1"]) 1486 .success(); 1487 work_dir.write_file("file", "modified"); 1488 work_dir.run_jj(["new", "-m", "baz"]).success(); 1489 work_dir 1490 .run_jj(["bookmark", "create", "-r@", "bookmark-2a"]) 1491 .success(); 1492 work_dir 1493 .run_jj(["bookmark", "create", "-r@", "bookmark-2b"]) 1494 .success(); 1495 work_dir.write_file("file", "modified again"); 1496 1497 // Push an empty set 1498 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=none()"]); 1499 insta::allow_duplicates! { 1500 insta::assert_snapshot!(output, @r" 1501 ------- stderr ------- 1502 Warning: No bookmarks point to the specified revisions: none() 1503 Nothing changed. 1504 [EOF] 1505 "); 1506 } 1507 // Push a revision with no bookmarks 1508 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@--"]); 1509 insta::allow_duplicates! { 1510 insta::assert_snapshot!(output, @r" 1511 ------- stderr ------- 1512 Warning: No bookmarks point to the specified revisions: @-- 1513 Nothing changed. 1514 [EOF] 1515 "); 1516 } 1517 // Push a revision with a single bookmark 1518 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@-", "--dry-run"]); 1519 insta::allow_duplicates! { 1520 insta::assert_snapshot!(output, @r" 1521 ------- stderr ------- 1522 Changes to push to origin: 1523 Add bookmark bookmark-1 to 5f432a855e59 1524 Dry-run requested, not pushing. 1525 [EOF] 1526 "); 1527 } 1528 // Push multiple revisions of which some have bookmarks 1529 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@--", "-r=@-", "--dry-run"]); 1530 insta::allow_duplicates! { 1531 insta::assert_snapshot!(output, @r" 1532 ------- stderr ------- 1533 Warning: No bookmarks point to the specified revisions: @-- 1534 Changes to push to origin: 1535 Add bookmark bookmark-1 to 5f432a855e59 1536 Dry-run requested, not pushing. 1537 [EOF] 1538 "); 1539 } 1540 // Push a revision with a multiple bookmarks 1541 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@", "--dry-run"]); 1542 insta::allow_duplicates! { 1543 insta::assert_snapshot!(output, @r" 1544 ------- stderr ------- 1545 Changes to push to origin: 1546 Add bookmark bookmark-2a to 84f499037f5c 1547 Add bookmark bookmark-2b to 84f499037f5c 1548 Dry-run requested, not pushing. 1549 [EOF] 1550 "); 1551 } 1552 // Repeating a commit doesn't result in repeated messages about the bookmark 1553 let output = work_dir.run_jj(["git", "push", "--allow-new", "-r=@-", "-r=@-", "--dry-run"]); 1554 insta::allow_duplicates! { 1555 insta::assert_snapshot!(output, @r" 1556 ------- stderr ------- 1557 Changes to push to origin: 1558 Add bookmark bookmark-1 to 5f432a855e59 1559 Dry-run requested, not pushing. 1560 [EOF] 1561 "); 1562 } 1563} 1564 1565#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1566#[test_case(true; "spawn a git subprocess for remote calls")] 1567fn test_git_push_mixed(subprocess: bool) { 1568 let test_env = TestEnvironment::default(); 1569 set_up(&test_env); 1570 let work_dir = test_env.work_dir("local"); 1571 if !subprocess { 1572 test_env.add_config("git.subprocess = false"); 1573 } 1574 work_dir.run_jj(["describe", "-m", "foo"]).success(); 1575 work_dir.write_file("file", "contents"); 1576 work_dir.run_jj(["new", "-m", "bar"]).success(); 1577 work_dir 1578 .run_jj(["bookmark", "create", "-r@", "bookmark-1"]) 1579 .success(); 1580 work_dir.write_file("file", "modified"); 1581 work_dir.run_jj(["new", "-m", "baz"]).success(); 1582 work_dir 1583 .run_jj(["bookmark", "create", "-r@", "bookmark-2a"]) 1584 .success(); 1585 work_dir 1586 .run_jj(["bookmark", "create", "-r@", "bookmark-2b"]) 1587 .success(); 1588 work_dir.write_file("file", "modified again"); 1589 1590 // --allow-new is not implied for --bookmark=.. and -r=.. 1591 let output = work_dir.run_jj([ 1592 "git", 1593 "push", 1594 "--change=@--", 1595 "--bookmark=bookmark-1", 1596 "-r=@", 1597 ]); 1598 insta::allow_duplicates! { 1599 insta::assert_snapshot!(output, @r" 1600 ------- stderr ------- 1601 Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw 1602 Error: Refusing to create new remote bookmark bookmark-1@origin 1603 Hint: Use --allow-new to push new bookmark. Use --remote to specify the remote to push to. 1604 [EOF] 1605 [exit status: 1] 1606 "); 1607 } 1608 1609 let output = work_dir.run_jj([ 1610 "git", 1611 "push", 1612 "--allow-new", 1613 "--change=@--", 1614 "--bookmark=bookmark-1", 1615 "-r=@", 1616 ]); 1617 insta::allow_duplicates! { 1618 insta::assert_snapshot!(output, @r" 1619 ------- stderr ------- 1620 Creating bookmark push-yqosqzytrlsw for revision yqosqzytrlsw 1621 Changes to push to origin: 1622 Add bookmark push-yqosqzytrlsw to a050abf4ff07 1623 Add bookmark bookmark-1 to 5f432a855e59 1624 Add bookmark bookmark-2a to 84f499037f5c 1625 Add bookmark bookmark-2b to 84f499037f5c 1626 [EOF] 1627 "); 1628 } 1629} 1630 1631#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1632#[test_case(true; "spawn a git subprocess for remote calls")] 1633fn test_git_push_unsnapshotted_change(subprocess: bool) { 1634 let test_env = TestEnvironment::default(); 1635 set_up(&test_env); 1636 let work_dir = test_env.work_dir("local"); 1637 if !subprocess { 1638 test_env.add_config("git.subprocess = false"); 1639 } 1640 work_dir.run_jj(["describe", "-m", "foo"]).success(); 1641 work_dir.write_file("file", "contents"); 1642 work_dir.run_jj(["git", "push", "--change", "@"]).success(); 1643 work_dir.write_file("file", "modified"); 1644 work_dir.run_jj(["git", "push", "--change", "@"]).success(); 1645} 1646 1647#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1648#[test_case(true; "spawn a git subprocess for remote calls")] 1649fn test_git_push_conflict(subprocess: bool) { 1650 let test_env = TestEnvironment::default(); 1651 set_up(&test_env); 1652 let work_dir = test_env.work_dir("local"); 1653 if !subprocess { 1654 test_env.add_config("git.subprocess = false"); 1655 } 1656 work_dir.write_file("file", "first"); 1657 work_dir.run_jj(["commit", "-m", "first"]).success(); 1658 work_dir.write_file("file", "second"); 1659 work_dir.run_jj(["commit", "-m", "second"]).success(); 1660 work_dir.write_file("file", "third"); 1661 work_dir 1662 .run_jj(["rebase", "-r", "@", "-d", "@--"]) 1663 .success(); 1664 work_dir 1665 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 1666 .success(); 1667 work_dir.run_jj(["describe", "-m", "third"]).success(); 1668 let output = work_dir.run_jj(["git", "push", "--all"]); 1669 insta::allow_duplicates! { 1670 insta::assert_snapshot!(output, @r" 1671 ------- stderr ------- 1672 Error: Won't push commit e2221a796300 since it has conflicts 1673 Hint: Rejected commit: yostqsxw e2221a79 my-bookmark | (conflict) third 1674 [EOF] 1675 [exit status: 1] 1676 "); 1677 } 1678} 1679 1680#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1681#[test_case(true; "spawn a git subprocess for remote calls")] 1682fn test_git_push_no_description(subprocess: bool) { 1683 let test_env = TestEnvironment::default(); 1684 set_up(&test_env); 1685 let work_dir = test_env.work_dir("local"); 1686 if !subprocess { 1687 test_env.add_config("git.subprocess = false"); 1688 } 1689 work_dir 1690 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 1691 .success(); 1692 work_dir.run_jj(["describe", "-m="]).success(); 1693 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark", "my-bookmark"]); 1694 insta::allow_duplicates! { 1695 insta::assert_snapshot!(output, @r" 1696 ------- stderr ------- 1697 Error: Won't push commit 5b36783cd11c since it has no description 1698 Hint: Rejected commit: yqosqzyt 5b36783c my-bookmark | (empty) (no description set) 1699 [EOF] 1700 [exit status: 1] 1701 "); 1702 } 1703 work_dir 1704 .run_jj([ 1705 "git", 1706 "push", 1707 "--allow-new", 1708 "--bookmark", 1709 "my-bookmark", 1710 "--allow-empty-description", 1711 ]) 1712 .success(); 1713} 1714 1715#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1716#[test_case(true; "spawn a git subprocess for remote calls")] 1717fn test_git_push_no_description_in_immutable(subprocess: bool) { 1718 let test_env = TestEnvironment::default(); 1719 set_up(&test_env); 1720 let work_dir = test_env.work_dir("local"); 1721 if !subprocess { 1722 test_env.add_config("git.subprocess = false"); 1723 } 1724 work_dir 1725 .run_jj(["bookmark", "create", "-r@", "imm"]) 1726 .success(); 1727 work_dir.run_jj(["describe", "-m="]).success(); 1728 work_dir.run_jj(["new", "-m", "foo"]).success(); 1729 work_dir.write_file("file", "contents"); 1730 work_dir 1731 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 1732 .success(); 1733 1734 let output = work_dir.run_jj([ 1735 "git", 1736 "push", 1737 "--allow-new", 1738 "--bookmark=my-bookmark", 1739 "--dry-run", 1740 ]); 1741 insta::allow_duplicates! { 1742 insta::assert_snapshot!(output, @r" 1743 ------- stderr ------- 1744 Error: Won't push commit 5b36783cd11c since it has no description 1745 Hint: Rejected commit: yqosqzyt 5b36783c imm | (empty) (no description set) 1746 [EOF] 1747 [exit status: 1] 1748 "); 1749 } 1750 1751 test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#); 1752 let output = work_dir.run_jj([ 1753 "git", 1754 "push", 1755 "--allow-new", 1756 "--bookmark=my-bookmark", 1757 "--dry-run", 1758 ]); 1759 insta::allow_duplicates! { 1760 insta::assert_snapshot!(output, @r" 1761 ------- stderr ------- 1762 Changes to push to origin: 1763 Add bookmark my-bookmark to ea7373507ad9 1764 Dry-run requested, not pushing. 1765 [EOF] 1766 "); 1767 } 1768} 1769 1770#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1771#[test_case(true; "spawn a git subprocess for remote calls")] 1772fn test_git_push_missing_author(subprocess: bool) { 1773 let test_env = TestEnvironment::default(); 1774 set_up(&test_env); 1775 let work_dir = test_env.work_dir("local"); 1776 if !subprocess { 1777 test_env.add_config("git.subprocess = false"); 1778 } 1779 let run_without_var = |var: &str, args: &[&str]| { 1780 work_dir 1781 .run_jj_with(|cmd| cmd.args(args).env_remove(var)) 1782 .success(); 1783 }; 1784 run_without_var("JJ_USER", &["new", "root()", "-m=initial"]); 1785 run_without_var("JJ_USER", &["bookmark", "create", "-r@", "missing-name"]); 1786 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark", "missing-name"]); 1787 insta::allow_duplicates! { 1788 insta::assert_snapshot!(output, @r" 1789 ------- stderr ------- 1790 Error: Won't push commit 944313939bbd since it has no author and/or committer set 1791 Hint: Rejected commit: vruxwmqv 94431393 missing-name | (empty) initial 1792 [EOF] 1793 [exit status: 1] 1794 "); 1795 } 1796 run_without_var("JJ_EMAIL", &["new", "root()", "-m=initial"]); 1797 run_without_var("JJ_EMAIL", &["bookmark", "create", "-r@", "missing-email"]); 1798 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-email"]); 1799 insta::allow_duplicates! { 1800 insta::assert_snapshot!(output, @r" 1801 ------- stderr ------- 1802 Error: Won't push commit 59354714f789 since it has no author and/or committer set 1803 Hint: Rejected commit: kpqxywon 59354714 missing-email | (empty) initial 1804 [EOF] 1805 [exit status: 1] 1806 "); 1807 } 1808} 1809 1810#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1811#[test_case(true; "spawn a git subprocess for remote calls")] 1812fn test_git_push_missing_author_in_immutable(subprocess: bool) { 1813 let test_env = TestEnvironment::default(); 1814 set_up(&test_env); 1815 let work_dir = test_env.work_dir("local"); 1816 if !subprocess { 1817 test_env.add_config("git.subprocess = false"); 1818 } 1819 let run_without_var = |var: &str, args: &[&str]| { 1820 work_dir 1821 .run_jj_with(|cmd| cmd.args(args).env_remove(var)) 1822 .success(); 1823 }; 1824 run_without_var("JJ_USER", &["new", "root()", "-m=no author name"]); 1825 run_without_var("JJ_EMAIL", &["new", "-m=no author email"]); 1826 work_dir 1827 .run_jj(["bookmark", "create", "-r@", "imm"]) 1828 .success(); 1829 work_dir.run_jj(["new", "-m", "foo"]).success(); 1830 work_dir.write_file("file", "contents"); 1831 work_dir 1832 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 1833 .success(); 1834 1835 let output = work_dir.run_jj([ 1836 "git", 1837 "push", 1838 "--allow-new", 1839 "--bookmark=my-bookmark", 1840 "--dry-run", 1841 ]); 1842 insta::allow_duplicates! { 1843 insta::assert_snapshot!(output, @r" 1844 ------- stderr ------- 1845 Error: Won't push commit 011f740bf8b5 since it has no author and/or committer set 1846 Hint: Rejected commit: yostqsxw 011f740b imm | (empty) no author email 1847 [EOF] 1848 [exit status: 1] 1849 "); 1850 } 1851 1852 test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#); 1853 let output = work_dir.run_jj([ 1854 "git", 1855 "push", 1856 "--allow-new", 1857 "--bookmark=my-bookmark", 1858 "--dry-run", 1859 ]); 1860 insta::allow_duplicates! { 1861 insta::assert_snapshot!(output, @r" 1862 ------- stderr ------- 1863 Changes to push to origin: 1864 Add bookmark my-bookmark to 68fdae89de4f 1865 Dry-run requested, not pushing. 1866 [EOF] 1867 "); 1868 } 1869} 1870 1871#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1872#[test_case(true; "spawn a git subprocess for remote calls")] 1873fn test_git_push_missing_committer(subprocess: bool) { 1874 let test_env = TestEnvironment::default(); 1875 set_up(&test_env); 1876 let work_dir = test_env.work_dir("local"); 1877 if !subprocess { 1878 test_env.add_config("git.subprocess = false"); 1879 } 1880 let run_without_var = |var: &str, args: &[&str]| { 1881 work_dir 1882 .run_jj_with(|cmd| cmd.args(args).env_remove(var)) 1883 .success(); 1884 }; 1885 work_dir 1886 .run_jj(["bookmark", "create", "-r@", "missing-name"]) 1887 .success(); 1888 run_without_var("JJ_USER", &["describe", "-m=no committer name"]); 1889 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-name"]); 1890 insta::allow_duplicates! { 1891 insta::assert_snapshot!(output, @r" 1892 ------- stderr ------- 1893 Error: Won't push commit 4fd190283d1a since it has no author and/or committer set 1894 Hint: Rejected commit: yqosqzyt 4fd19028 missing-name | (empty) no committer name 1895 [EOF] 1896 [exit status: 1] 1897 "); 1898 } 1899 work_dir.run_jj(["new", "root()"]).success(); 1900 work_dir 1901 .run_jj(["bookmark", "create", "-r@", "missing-email"]) 1902 .success(); 1903 run_without_var("JJ_EMAIL", &["describe", "-m=no committer email"]); 1904 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-email"]); 1905 insta::allow_duplicates! { 1906 insta::assert_snapshot!(output, @r" 1907 ------- stderr ------- 1908 Error: Won't push commit eab97428a6ec since it has no author and/or committer set 1909 Hint: Rejected commit: kpqxywon eab97428 missing-email | (empty) no committer email 1910 [EOF] 1911 [exit status: 1] 1912 "); 1913 } 1914 1915 // Test message when there are multiple reasons (missing committer and 1916 // description) 1917 run_without_var("JJ_EMAIL", &["describe", "-m=", "missing-email"]); 1918 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark=missing-email"]); 1919 insta::allow_duplicates! { 1920 insta::assert_snapshot!(output, @r" 1921 ------- stderr ------- 1922 Error: Won't push commit 1143ed607f54 since it has no description and it has no author and/or committer set 1923 Hint: Rejected commit: kpqxywon 1143ed60 missing-email | (empty) (no description set) 1924 [EOF] 1925 [exit status: 1] 1926 "); 1927 } 1928} 1929 1930#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1931#[test_case(true; "spawn a git subprocess for remote calls")] 1932fn test_git_push_missing_committer_in_immutable(subprocess: bool) { 1933 let test_env = TestEnvironment::default(); 1934 set_up(&test_env); 1935 let work_dir = test_env.work_dir("local"); 1936 if !subprocess { 1937 test_env.add_config("git.subprocess = false"); 1938 } 1939 let run_without_var = |var: &str, args: &[&str]| { 1940 work_dir 1941 .run_jj_with(|cmd| cmd.args(args).env_remove(var)) 1942 .success(); 1943 }; 1944 run_without_var("JJ_USER", &["describe", "-m=no committer name"]); 1945 work_dir.run_jj(["new"]).success(); 1946 run_without_var("JJ_EMAIL", &["describe", "-m=no committer email"]); 1947 work_dir 1948 .run_jj(["bookmark", "create", "-r@", "imm"]) 1949 .success(); 1950 work_dir.run_jj(["new", "-m", "foo"]).success(); 1951 work_dir.write_file("file", "contents"); 1952 work_dir 1953 .run_jj(["bookmark", "create", "-r@", "my-bookmark"]) 1954 .success(); 1955 1956 let output = work_dir.run_jj([ 1957 "git", 1958 "push", 1959 "--allow-new", 1960 "--bookmark=my-bookmark", 1961 "--dry-run", 1962 ]); 1963 insta::allow_duplicates! { 1964 insta::assert_snapshot!(output, @r" 1965 ------- stderr ------- 1966 Error: Won't push commit 7e61dc727a8f since it has no author and/or committer set 1967 Hint: Rejected commit: yostqsxw 7e61dc72 imm | (empty) no committer email 1968 [EOF] 1969 [exit status: 1] 1970 "); 1971 } 1972 1973 test_env.add_config(r#"revset-aliases."immutable_heads()" = "imm""#); 1974 let output = work_dir.run_jj([ 1975 "git", 1976 "push", 1977 "--allow-new", 1978 "--bookmark=my-bookmark", 1979 "--dry-run", 1980 ]); 1981 insta::allow_duplicates! { 1982 insta::assert_snapshot!(output, @r" 1983 ------- stderr ------- 1984 Changes to push to origin: 1985 Add bookmark my-bookmark to c79f85e90b4a 1986 Dry-run requested, not pushing. 1987 [EOF] 1988 "); 1989 } 1990} 1991 1992#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1993#[test_case(true; "spawn a git subprocess for remote calls")] 1994fn test_git_push_deleted(subprocess: bool) { 1995 let test_env = TestEnvironment::default(); 1996 set_up(&test_env); 1997 let work_dir = test_env.work_dir("local"); 1998 if !subprocess { 1999 test_env.add_config("git.subprocess = false"); 2000 } 2001 2002 work_dir 2003 .run_jj(["bookmark", "delete", "bookmark1"]) 2004 .success(); 2005 let output = work_dir.run_jj(["git", "push", "--deleted"]); 2006 insta::allow_duplicates! { 2007 insta::assert_snapshot!(output, @r" 2008 ------- stderr ------- 2009 Changes to push to origin: 2010 Delete bookmark bookmark1 from d13ecdbda2a2 2011 [EOF] 2012 "); 2013 } 2014 let output = work_dir.run_jj(["log", "-rall()"]); 2015 insta::allow_duplicates! { 2016 insta::assert_snapshot!(output, @r" 2017 @ yqosqzyt test.user@example.com 2001-02-03 08:05:13 5b36783c 2018 │ (empty) (no description set) 2019 │ ○ rlzusymt test.user@example.com 2001-02-03 08:05:10 bookmark2 8476341e 2020 ├─╯ (empty) description 2 2021 │ ○ xtvrqkyv test.user@example.com 2001-02-03 08:05:08 d13ecdbd 2022 ├─╯ (empty) description 1 2023 ◆ zzzzzzzz root() 00000000 2024 [EOF] 2025 "); 2026 } 2027 let output = work_dir.run_jj(["git", "push", "--deleted"]); 2028 insta::allow_duplicates! { 2029 insta::assert_snapshot!(output, @r" 2030 ------- stderr ------- 2031 Nothing changed. 2032 [EOF] 2033 "); 2034 } 2035} 2036 2037#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2038#[test_case(true; "spawn a git subprocess for remote calls")] 2039fn test_git_push_conflicting_bookmarks(subprocess: bool) { 2040 let test_env = TestEnvironment::default(); 2041 set_up(&test_env); 2042 let work_dir = test_env.work_dir("local"); 2043 if !subprocess { 2044 test_env.add_config("git.subprocess = false"); 2045 } 2046 test_env.add_config("git.auto-local-bookmark = true"); 2047 let git_repo = { 2048 let mut git_repo_path = work_dir.root().to_owned(); 2049 git_repo_path.extend([".jj", "repo", "store", "git"]); 2050 git::open(&git_repo_path) 2051 }; 2052 2053 // Forget remote ref, move local ref, then fetch to create conflict. 2054 git_repo 2055 .find_reference("refs/remotes/origin/bookmark2") 2056 .unwrap() 2057 .delete() 2058 .unwrap(); 2059 work_dir.run_jj(["git", "import"]).success(); 2060 work_dir 2061 .run_jj(["new", "root()", "-m=description 3"]) 2062 .success(); 2063 work_dir 2064 .run_jj(["bookmark", "create", "-r@", "bookmark2"]) 2065 .success(); 2066 work_dir.run_jj(["git", "fetch"]).success(); 2067 insta::allow_duplicates! { 2068 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 2069 bookmark1: xtvrqkyv d13ecdbd (empty) description 1 2070 @origin: xtvrqkyv d13ecdbd (empty) description 1 2071 bookmark2 (conflicted): 2072 + yostqsxw 8e670e2d (empty) description 3 2073 + rlzusymt 8476341e (empty) description 2 2074 @origin (behind by 1 commits): rlzusymt 8476341e (empty) description 2 2075 [EOF] 2076 "); 2077 } 2078 2079 let bump_bookmark1 = || { 2080 work_dir.run_jj(["new", "bookmark1", "-m=bump"]).success(); 2081 work_dir 2082 .run_jj(["bookmark", "set", "bookmark1", "-r@"]) 2083 .success(); 2084 }; 2085 2086 // Conflicting bookmark at @ 2087 let output = work_dir.run_jj(["git", "push", "--allow-new"]); 2088 insta::allow_duplicates! { 2089 insta::assert_snapshot!(output, @r" 2090 ------- stderr ------- 2091 Warning: Bookmark bookmark2 is conflicted 2092 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up. 2093 Nothing changed. 2094 [EOF] 2095 "); 2096 } 2097 2098 // --bookmark should be blocked by conflicting bookmark 2099 let output = work_dir.run_jj(["git", "push", "--allow-new", "--bookmark", "bookmark2"]); 2100 insta::allow_duplicates! { 2101 insta::assert_snapshot!(output, @r" 2102 ------- stderr ------- 2103 Error: Bookmark bookmark2 is conflicted 2104 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up. 2105 [EOF] 2106 [exit status: 1] 2107 "); 2108 } 2109 2110 // --all shouldn't be blocked by conflicting bookmark 2111 bump_bookmark1(); 2112 let output = work_dir.run_jj(["git", "push", "--all"]); 2113 insta::allow_duplicates! { 2114 insta::assert_snapshot!(output, @r" 2115 ------- stderr ------- 2116 Warning: Bookmark bookmark2 is conflicted 2117 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up. 2118 Changes to push to origin: 2119 Move forward bookmark bookmark1 from d13ecdbda2a2 to 8df52121b022 2120 [EOF] 2121 "); 2122 } 2123 2124 // --revisions shouldn't be blocked by conflicting bookmark 2125 bump_bookmark1(); 2126 let output = work_dir.run_jj(["git", "push", "--allow-new", "-rall()"]); 2127 insta::allow_duplicates! { 2128 insta::assert_snapshot!(output, @r" 2129 ------- stderr ------- 2130 Warning: Bookmark bookmark2 is conflicted 2131 Hint: Run `jj bookmark list` to inspect, and use `jj bookmark set` to fix it up. 2132 Changes to push to origin: 2133 Move forward bookmark bookmark1 from 8df52121b022 to 345e1f64a64d 2134 [EOF] 2135 "); 2136 } 2137} 2138 2139#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2140#[test_case(true; "spawn a git subprocess for remote calls")] 2141fn test_git_push_deleted_untracked(subprocess: bool) { 2142 let test_env = TestEnvironment::default(); 2143 set_up(&test_env); 2144 let work_dir = test_env.work_dir("local"); 2145 if !subprocess { 2146 test_env.add_config("git.subprocess = false"); 2147 } 2148 2149 // Absent local bookmark shouldn't be considered "deleted" compared to 2150 // non-tracking remote bookmark. 2151 work_dir 2152 .run_jj(["bookmark", "delete", "bookmark1"]) 2153 .success(); 2154 work_dir 2155 .run_jj(["bookmark", "untrack", "bookmark1@origin"]) 2156 .success(); 2157 let output = work_dir.run_jj(["git", "push", "--deleted"]); 2158 insta::allow_duplicates! { 2159 insta::assert_snapshot!(output, @r" 2160 ------- stderr ------- 2161 Nothing changed. 2162 [EOF] 2163 "); 2164 } 2165 let output = work_dir.run_jj(["git", "push", "--bookmark=bookmark1"]); 2166 insta::allow_duplicates! { 2167 insta::assert_snapshot!(output, @r" 2168 ------- stderr ------- 2169 Error: No such bookmark: bookmark1 2170 [EOF] 2171 [exit status: 1] 2172 "); 2173 } 2174} 2175 2176#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2177#[test_case(true; "spawn a git subprocess for remote calls")] 2178fn test_git_push_tracked_vs_all(subprocess: bool) { 2179 let test_env = TestEnvironment::default(); 2180 set_up(&test_env); 2181 let work_dir = test_env.work_dir("local"); 2182 if !subprocess { 2183 test_env.add_config("git.subprocess = false"); 2184 } 2185 work_dir 2186 .run_jj(["new", "bookmark1", "-mmoved bookmark1"]) 2187 .success(); 2188 work_dir 2189 .run_jj(["bookmark", "set", "bookmark1", "-r@"]) 2190 .success(); 2191 work_dir 2192 .run_jj(["new", "bookmark2", "-mmoved bookmark2"]) 2193 .success(); 2194 work_dir 2195 .run_jj(["bookmark", "delete", "bookmark2"]) 2196 .success(); 2197 work_dir 2198 .run_jj(["bookmark", "untrack", "bookmark1@origin"]) 2199 .success(); 2200 work_dir 2201 .run_jj(["bookmark", "create", "-r@", "bookmark3"]) 2202 .success(); 2203 insta::allow_duplicates! { 2204 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 2205 bookmark1: vruxwmqv db059e3f (empty) moved bookmark1 2206 bookmark1@origin: xtvrqkyv d13ecdbd (empty) description 1 2207 bookmark2 (deleted) 2208 @origin: rlzusymt 8476341e (empty) description 2 2209 bookmark3: znkkpsqq 1aa4f1f2 (empty) moved bookmark2 2210 [EOF] 2211 "); 2212 } 2213 2214 // At this point, only bookmark2 is still tracked. 2215 // `jj git push --tracked --deleted` would try to push it and no other 2216 // bookmarks. 2217 let output = work_dir.run_jj(["git", "push", "--tracked", "--dry-run"]); 2218 insta::allow_duplicates! { 2219 insta::assert_snapshot!(output, @r" 2220 ------- stderr ------- 2221 Warning: Refusing to push deleted bookmark bookmark2 2222 Hint: Push deleted bookmarks with --deleted or forget the bookmark to suppress this warning. 2223 Nothing changed. 2224 [EOF] 2225 "); 2226 } 2227 let output = work_dir.run_jj(["git", "push", "--tracked", "--deleted", "--dry-run"]); 2228 insta::allow_duplicates! { 2229 insta::assert_snapshot!(output, @r" 2230 ------- stderr ------- 2231 Changes to push to origin: 2232 Delete bookmark bookmark2 from 8476341eb395 2233 Dry-run requested, not pushing. 2234 [EOF] 2235 "); 2236 } 2237 2238 // Untrack the last remaining tracked bookmark. 2239 work_dir 2240 .run_jj(["bookmark", "untrack", "bookmark2@origin"]) 2241 .success(); 2242 insta::allow_duplicates! { 2243 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 2244 bookmark1: vruxwmqv db059e3f (empty) moved bookmark1 2245 bookmark1@origin: xtvrqkyv d13ecdbd (empty) description 1 2246 bookmark2@origin: rlzusymt 8476341e (empty) description 2 2247 bookmark3: znkkpsqq 1aa4f1f2 (empty) moved bookmark2 2248 [EOF] 2249 "); 2250 } 2251 2252 // Now, no bookmarks are tracked. --tracked does not push anything 2253 let output = work_dir.run_jj(["git", "push", "--tracked"]); 2254 insta::allow_duplicates! { 2255 insta::assert_snapshot!(output, @r" 2256 ------- stderr ------- 2257 Nothing changed. 2258 [EOF] 2259 "); 2260 } 2261 2262 // All bookmarks are still untracked. 2263 // - --all tries to push bookmark1, but fails because a bookmark with the same 2264 // name exist on the remote. 2265 // - --all succeeds in pushing bookmark3, since there is no bookmark of the same 2266 // name on the remote. 2267 // - It does not try to push bookmark2. 2268 // 2269 // TODO: Not trying to push bookmark2 could be considered correct, or perhaps 2270 // we want to consider this as a deletion of the bookmark that failed because 2271 // the bookmark was untracked. In the latter case, an error message should be 2272 // printed. Some considerations: 2273 // - Whatever we do should be consistent with what `jj bookmark list` does; it 2274 // currently does *not* list bookmarks like bookmark2 as "about to be 2275 // deleted", as can be seen above. 2276 // - We could consider showing some hint on `jj bookmark untrack 2277 // bookmark2@origin` instead of showing an error here. 2278 let output = work_dir.run_jj(["git", "push", "--all"]); 2279 insta::allow_duplicates! { 2280 insta::assert_snapshot!(output, @r" 2281 ------- stderr ------- 2282 Warning: Non-tracking remote bookmark bookmark1@origin exists 2283 Hint: Run `jj bookmark track bookmark1@origin` to import the remote bookmark. 2284 Changes to push to origin: 2285 Add bookmark bookmark3 to 1aa4f1f2ef7f 2286 [EOF] 2287 "); 2288 } 2289} 2290 2291#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2292#[test_case(true; "spawn a git subprocess for remote calls")] 2293fn test_git_push_moved_forward_untracked(subprocess: bool) { 2294 let test_env = TestEnvironment::default(); 2295 set_up(&test_env); 2296 let work_dir = test_env.work_dir("local"); 2297 if !subprocess { 2298 test_env.add_config("git.subprocess = false"); 2299 } 2300 2301 work_dir 2302 .run_jj(["new", "bookmark1", "-mmoved bookmark1"]) 2303 .success(); 2304 work_dir 2305 .run_jj(["bookmark", "set", "bookmark1", "-r@"]) 2306 .success(); 2307 work_dir 2308 .run_jj(["bookmark", "untrack", "bookmark1@origin"]) 2309 .success(); 2310 let output = work_dir.run_jj(["git", "push", "--allow-new"]); 2311 insta::allow_duplicates! { 2312 insta::assert_snapshot!(output, @r" 2313 ------- stderr ------- 2314 Warning: Non-tracking remote bookmark bookmark1@origin exists 2315 Hint: Run `jj bookmark track bookmark1@origin` to import the remote bookmark. 2316 Nothing changed. 2317 [EOF] 2318 "); 2319 } 2320} 2321 2322#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2323#[test_case(true; "spawn a git subprocess for remote calls")] 2324fn test_git_push_moved_sideways_untracked(subprocess: bool) { 2325 let test_env = TestEnvironment::default(); 2326 set_up(&test_env); 2327 let work_dir = test_env.work_dir("local"); 2328 if !subprocess { 2329 test_env.add_config("git.subprocess = false"); 2330 } 2331 2332 work_dir 2333 .run_jj(["new", "root()", "-mmoved bookmark1"]) 2334 .success(); 2335 work_dir 2336 .run_jj(["bookmark", "set", "--allow-backwards", "bookmark1", "-r@"]) 2337 .success(); 2338 work_dir 2339 .run_jj(["bookmark", "untrack", "bookmark1@origin"]) 2340 .success(); 2341 let output = work_dir.run_jj(["git", "push", "--allow-new"]); 2342 insta::allow_duplicates! { 2343 insta::assert_snapshot!(output, @r" 2344 ------- stderr ------- 2345 Warning: Non-tracking remote bookmark bookmark1@origin exists 2346 Hint: Run `jj bookmark track bookmark1@origin` to import the remote bookmark. 2347 Nothing changed. 2348 [EOF] 2349 "); 2350 } 2351} 2352 2353#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2354#[test_case(true; "spawn a git subprocess for remote calls")] 2355fn test_git_push_to_remote_named_git(subprocess: bool) { 2356 let test_env = TestEnvironment::default(); 2357 set_up(&test_env); 2358 let work_dir = test_env.work_dir("local"); 2359 if !subprocess { 2360 test_env.add_config("git.subprocess = false"); 2361 } 2362 let git_repo_path = { 2363 let mut git_repo_path = work_dir.root().to_owned(); 2364 git_repo_path.extend([".jj", "repo", "store", "git"]); 2365 git_repo_path 2366 }; 2367 git::rename_remote(&git_repo_path, "origin", "git"); 2368 2369 let output = work_dir.run_jj(["git", "push", "--all", "--remote=git"]); 2370 insta::allow_duplicates! { 2371 insta::assert_snapshot!(output, @r" 2372 ------- stderr ------- 2373 Changes to push to git: 2374 Add bookmark bookmark1 to d13ecdbda2a2 2375 Add bookmark bookmark2 to 8476341eb395 2376 Error: Git remote named 'git' is reserved for local Git repository 2377 Hint: Run `jj git remote rename` to give a different name. 2378 [EOF] 2379 [exit status: 1] 2380 "); 2381 } 2382} 2383 2384#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2385#[test_case(true; "spawn a git subprocess for remote calls")] 2386fn test_git_push_to_remote_with_slashes(subprocess: bool) { 2387 let test_env = TestEnvironment::default(); 2388 set_up(&test_env); 2389 let work_dir = test_env.work_dir("local"); 2390 if !subprocess { 2391 test_env.add_config("git.subprocess = false"); 2392 } 2393 let git_repo_path = { 2394 let mut git_repo_path = work_dir.root().to_owned(); 2395 git_repo_path.extend([".jj", "repo", "store", "git"]); 2396 git_repo_path 2397 }; 2398 git::rename_remote(&git_repo_path, "origin", "slash/origin"); 2399 2400 let output = work_dir.run_jj(["git", "push", "--all", "--remote=slash/origin"]); 2401 insta::allow_duplicates! { 2402 insta::assert_snapshot!(output, @r" 2403 ------- stderr ------- 2404 Changes to push to slash/origin: 2405 Add bookmark bookmark1 to d13ecdbda2a2 2406 Add bookmark bookmark2 to 8476341eb395 2407 Error: Git remotes with slashes are incompatible with jj: slash/origin 2408 Hint: Run `jj git remote rename` to give a different name. 2409 [EOF] 2410 [exit status: 1] 2411 "); 2412 } 2413} 2414 2415#[test] 2416fn test_git_push_sign_on_push() { 2417 let test_env = TestEnvironment::default(); 2418 set_up(&test_env); 2419 let work_dir = test_env.work_dir("local"); 2420 let template = r#" 2421 separate("\n", 2422 description.first_line(), 2423 if(signature, 2424 separate(", ", 2425 "Signature: " ++ signature.display(), 2426 "Status: " ++ signature.status(), 2427 "Key: " ++ signature.key(), 2428 ) 2429 ) 2430 ) 2431 "#; 2432 work_dir 2433 .run_jj(["new", "bookmark2", "-m", "commit to be signed 1"]) 2434 .success(); 2435 work_dir 2436 .run_jj(["new", "-m", "commit to be signed 2"]) 2437 .success(); 2438 work_dir 2439 .run_jj(["bookmark", "set", "bookmark2", "-r@"]) 2440 .success(); 2441 work_dir 2442 .run_jj(["new", "-m", "commit which should not be signed 1"]) 2443 .success(); 2444 work_dir 2445 .run_jj(["new", "-m", "commit which should not be signed 2"]) 2446 .success(); 2447 // There should be no signed commits initially 2448 let output = work_dir.run_jj(["log", "-T", template]); 2449 insta::assert_snapshot!(output, @r" 2450 @ commit which should not be signed 2 2451 ○ commit which should not be signed 1 2452 ○ commit to be signed 2 2453 ○ commit to be signed 1 2454 ○ description 2 2455 │ ○ description 1 2456 ├─╯ 24572458 [EOF] 2459 "); 2460 test_env.add_config( 2461 r#" 2462 signing.backend = "test" 2463 signing.key = "impeccable" 2464 git.sign-on-push = true 2465 "#, 2466 ); 2467 let output = work_dir.run_jj(["git", "push", "--dry-run"]); 2468 insta::assert_snapshot!(output, @r" 2469 ------- stderr ------- 2470 Changes to push to origin: 2471 Move forward bookmark bookmark2 from 8476341eb395 to 8710e91a14a1 2472 Dry-run requested, not pushing. 2473 [EOF] 2474 "); 2475 // There should be no signed commits after performing a dry run 2476 let output = work_dir.run_jj(["log", "-T", template]); 2477 insta::assert_snapshot!(output, @r" 2478 @ commit which should not be signed 2 2479 ○ commit which should not be signed 1 2480 ○ commit to be signed 2 2481 ○ commit to be signed 1 2482 ○ description 2 2483 │ ○ description 1 2484 ├─╯ 24852486 [EOF] 2487 "); 2488 let output = work_dir.run_jj(["git", "push"]); 2489 insta::assert_snapshot!(output, @r" 2490 ------- stderr ------- 2491 Updated signatures of 2 commits 2492 Rebased 2 descendant commits 2493 Changes to push to origin: 2494 Move forward bookmark bookmark2 from 8476341eb395 to a6259c482040 2495 Working copy (@) now at: kmkuslsw b5f47345 (empty) commit which should not be signed 2 2496 Parent commit (@-) : kpqxywon 90df08d3 (empty) commit which should not be signed 1 2497 [EOF] 2498 "); 2499 // Only commits which are being pushed should be signed 2500 let output = work_dir.run_jj(["log", "-T", template]); 2501 insta::assert_snapshot!(output, @r" 2502 @ commit which should not be signed 2 2503 ○ commit which should not be signed 1 2504 ○ commit to be signed 2 2505 │ Signature: test-display, Status: good, Key: impeccable 2506 ○ commit to be signed 1 2507 │ Signature: test-display, Status: good, Key: impeccable 2508 ○ description 2 2509 │ ○ description 1 2510 ├─╯ 25112512 [EOF] 2513 "); 2514 2515 // Immutable commits should not be signed 2516 let output = work_dir.run_jj([ 2517 "bookmark", 2518 "create", 2519 "bookmark3", 2520 "-r", 2521 "description('commit which should not be signed 1')", 2522 ]); 2523 insta::assert_snapshot!(output, @r" 2524 ------- stderr ------- 2525 Created 1 bookmarks pointing to kpqxywon 90df08d3 bookmark3 | (empty) commit which should not be signed 1 2526 [EOF] 2527 "); 2528 let output = work_dir.run_jj(["bookmark", "move", "bookmark2", "--to", "bookmark3"]); 2529 insta::assert_snapshot!(output, @r" 2530 ------- stderr ------- 2531 Moved 1 bookmarks to kpqxywon 90df08d3 bookmark2* bookmark3 | (empty) commit which should not be signed 1 2532 [EOF] 2533 "); 2534 test_env.add_config(r#"revset-aliases."immutable_heads()" = "bookmark3""#); 2535 let output = work_dir.run_jj(["git", "push"]); 2536 insta::assert_snapshot!(output, @r" 2537 ------- stderr ------- 2538 Warning: Refusing to create new remote bookmark bookmark3@origin 2539 Hint: Use --allow-new to push new bookmark. Use --remote to specify the remote to push to. 2540 Changes to push to origin: 2541 Move forward bookmark bookmark2 from a6259c482040 to 90df08d3d612 2542 [EOF] 2543 "); 2544 let output = work_dir.run_jj(["log", "-T", template, "-r", "::"]); 2545 insta::assert_snapshot!(output, @r" 2546 @ commit which should not be signed 2 2547 ◆ commit which should not be signed 1 2548 ◆ commit to be signed 2 2549 │ Signature: test-display, Status: good, Key: impeccable 2550 ◆ commit to be signed 1 2551 │ Signature: test-display, Status: good, Key: impeccable 2552 ◆ description 2 2553 │ ○ description 1 2554 ├─╯ 25552556 [EOF] 2557 "); 2558} 2559 2560#[test] 2561fn test_git_push_rejected_by_remote() { 2562 let test_env = TestEnvironment::default(); 2563 set_up(&test_env); 2564 let work_dir = test_env.work_dir("local"); 2565 // show repo state 2566 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 2567 bookmark1: xtvrqkyv d13ecdbd (empty) description 1 2568 @origin: xtvrqkyv d13ecdbd (empty) description 1 2569 bookmark2: rlzusymt 8476341e (empty) description 2 2570 @origin: rlzusymt 8476341e (empty) description 2 2571 [EOF] 2572 "); 2573 2574 // create a hook on the remote that prevents pushing 2575 let hook_path = test_env 2576 .env_root() 2577 .join("origin") 2578 .join(".jj") 2579 .join("repo") 2580 .join("store") 2581 .join("git") 2582 .join("hooks") 2583 .join("update"); 2584 2585 std::fs::write(&hook_path, "#!/bin/sh\nexit 1").unwrap(); 2586 #[cfg(unix)] 2587 { 2588 use std::os::unix::fs::PermissionsExt as _; 2589 2590 std::fs::set_permissions(&hook_path, std::fs::Permissions::from_mode(0o700)).unwrap(); 2591 } 2592 2593 // create new commit on top of bookmark1 2594 work_dir.run_jj(["new", "bookmark1"]).success(); 2595 work_dir.write_file("file", "file"); 2596 work_dir.run_jj(["describe", "-m=update"]).success(); 2597 2598 // update bookmark 2599 work_dir.run_jj(["bookmark", "move", "bookmark1"]).success(); 2600 2601 // push bookmark 2602 let output = work_dir.run_jj(["git", "push"]); 2603 2604 // The git remote sideband adds a dummy suffix of 8 spaces to attempt to clear 2605 // any leftover data. This is done to help with cases where the line is 2606 // rewritten. 2607 // 2608 // However, a common option in a lot of editors removes trailing whitespace. 2609 // This means that anyone with that option that opens this file would make the 2610 // following snapshot fail. Using the insta filter here normalizes the 2611 // output. 2612 let mut settings = insta::Settings::clone_current(); 2613 settings.add_filter(r"\s*\n", "\n"); 2614 settings.bind(|| { 2615 insta::assert_snapshot!(output, @r" 2616 ------- stderr ------- 2617 Changes to push to origin: 2618 Move forward bookmark bookmark1 from d13ecdbda2a2 to dd5c09b30f9f 2619 remote: error: hook declined to update refs/heads/bookmark1 2620 Error: Failed to push some bookmarks 2621 Hint: The remote rejected the following updates: 2622 refs/heads/bookmark1 (reason: hook declined) 2623 Hint: Try checking if you have permission to push to all the bookmarks. 2624 [EOF] 2625 [exit status: 1] 2626 "); 2627 }); 2628} 2629 2630#[must_use] 2631fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput { 2632 // --quiet to suppress deleted bookmarks hint 2633 work_dir.run_jj(["bookmark", "list", "--all-remotes", "--quiet"]) 2634} 2635 2636// TODO: Remove with the `git.subprocess` setting. 2637#[cfg(not(feature = "git2"))] 2638#[test] 2639fn test_git_push_git2_warning() { 2640 let test_env = TestEnvironment::default(); 2641 set_up(&test_env); 2642 let work_dir = test_env.work_dir("local"); 2643 test_env.add_config("git.subprocess = false"); 2644 work_dir 2645 .run_jj(["describe", "bookmark1", "-m", "modified bookmark1 commit"]) 2646 .success(); 2647 let output = work_dir.run_jj(["git", "push", "--all"]); 2648 insta::assert_snapshot!(output, @r#" 2649 ------- stderr ------- 2650 Warning: Deprecated config: jj was compiled without `git.subprocess = false` support 2651 Changes to push to origin: 2652 Move sideways bookmark bookmark1 from d13ecdbda2a2 to 0f8dc6560f32 2653 [EOF] 2654 "#); 2655}