just playing with tangled
at gvimdiff 70 kB view raw
1// Copyright 2023 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::create_commit; 19use crate::common::CommandOutput; 20use crate::common::TestEnvironment; 21use crate::common::TestWorkDir; 22 23fn add_commit_to_branch(git_repo: &gix::Repository, branch: &str) -> gix::ObjectId { 24 git::add_commit( 25 git_repo, 26 &format!("refs/heads/{branch}"), 27 branch, // filename 28 branch.as_bytes(), // content 29 "message", 30 &[], 31 ) 32 .commit_id 33} 34 35/// Creates a remote Git repo containing a bookmark with the same name 36fn init_git_remote(test_env: &TestEnvironment, remote: &str) -> gix::Repository { 37 let git_repo_path = test_env.env_root().join(remote); 38 let git_repo = git::init(git_repo_path); 39 add_commit_to_branch(&git_repo, remote); 40 41 git_repo 42} 43 44/// Add a remote containing a bookmark with the same name 45fn add_git_remote( 46 test_env: &TestEnvironment, 47 work_dir: &TestWorkDir, 48 remote: &str, 49) -> gix::Repository { 50 let repo = init_git_remote(test_env, remote); 51 work_dir 52 .run_jj(["git", "remote", "add", remote, &format!("../{remote}")]) 53 .success(); 54 55 repo 56} 57 58#[must_use] 59fn get_bookmark_output(work_dir: &TestWorkDir) -> CommandOutput { 60 // --quiet to suppress deleted bookmarks hint 61 work_dir.run_jj(["bookmark", "list", "--all-remotes", "--quiet"]) 62} 63 64#[must_use] 65fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput { 66 let template = 67 r#"commit_id.short() ++ " \"" ++ description.first_line() ++ "\" " ++ bookmarks"#; 68 work_dir.run_jj(["log", "-T", template, "-r", "all()"]) 69} 70 71fn clone_git_remote_into( 72 test_env: &TestEnvironment, 73 upstream: &str, 74 fork: &str, 75) -> gix::Repository { 76 let upstream_path = test_env.env_root().join(upstream); 77 let fork_path = test_env.env_root().join(fork); 78 let fork_repo = git::clone(&fork_path, upstream_path.to_str().unwrap(), Some(upstream)); 79 80 // create local branch mirroring the upstream 81 let upstream_head = fork_repo 82 .find_reference(&format!("refs/remotes/{upstream}/{upstream}")) 83 .unwrap() 84 .peel_to_id_in_place() 85 .unwrap() 86 .detach(); 87 88 fork_repo 89 .reference( 90 format!("refs/heads/{upstream}"), 91 upstream_head, 92 gix::refs::transaction::PreviousValue::MustNotExist, 93 "create tracking head", 94 ) 95 .unwrap(); 96 97 fork_repo 98} 99 100#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 101#[test_case(true; "spawn a git subprocess for remote calls")] 102fn test_git_fetch_with_default_config(subprocess: bool) { 103 let test_env = TestEnvironment::default(); 104 if !subprocess { 105 test_env.add_config("git.subprocess = false"); 106 } 107 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 108 let work_dir = test_env.work_dir("repo"); 109 add_git_remote(&test_env, &work_dir, "origin"); 110 111 work_dir.run_jj(["git", "fetch"]).success(); 112 insta::allow_duplicates! { 113 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 114 origin@origin: qmyrypzk ab8b299e message 115 [EOF] 116 "); 117 } 118} 119 120#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 121#[test_case(true; "spawn a git subprocess for remote calls")] 122fn test_git_fetch_default_remote(subprocess: bool) { 123 let test_env = TestEnvironment::default(); 124 if !subprocess { 125 test_env.add_config("git.subprocess = false"); 126 } 127 test_env.add_config("git.auto-local-bookmark = true"); 128 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 129 let work_dir = test_env.work_dir("repo"); 130 add_git_remote(&test_env, &work_dir, "origin"); 131 132 work_dir.run_jj(["git", "fetch"]).success(); 133 insta::allow_duplicates! { 134 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 135 origin: qmyrypzk ab8b299e message 136 @origin: qmyrypzk ab8b299e message 137 [EOF] 138 "); 139 } 140} 141 142#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 143#[test_case(true; "spawn a git subprocess for remote calls")] 144fn test_git_fetch_single_remote(subprocess: bool) { 145 let test_env = TestEnvironment::default(); 146 if !subprocess { 147 test_env.add_config("git.subprocess = false"); 148 } 149 test_env.add_config("git.auto-local-bookmark = true"); 150 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 151 let work_dir = test_env.work_dir("repo"); 152 add_git_remote(&test_env, &work_dir, "rem1"); 153 154 let output = work_dir.run_jj(["git", "fetch"]); 155 insta::allow_duplicates! { 156 insta::assert_snapshot!(output, @r" 157 ------- stderr ------- 158 Hint: Fetching from the only existing remote: rem1 159 bookmark: rem1@rem1 [new] tracked 160 [EOF] 161 "); 162 } 163 insta::allow_duplicates! { 164 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 165 rem1: ppspxspk 4acd0343 message 166 @rem1: ppspxspk 4acd0343 message 167 [EOF] 168 "); 169 } 170} 171 172#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 173#[test_case(true; "spawn a git subprocess for remote calls")] 174fn test_git_fetch_single_remote_all_remotes_flag(subprocess: bool) { 175 let test_env = TestEnvironment::default(); 176 if !subprocess { 177 test_env.add_config("git.subprocess = false"); 178 } 179 test_env.add_config("git.auto-local-bookmark = true"); 180 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 181 let work_dir = test_env.work_dir("repo"); 182 add_git_remote(&test_env, &work_dir, "rem1"); 183 184 work_dir.run_jj(["git", "fetch", "--all-remotes"]).success(); 185 insta::allow_duplicates! { 186 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 187 rem1: ppspxspk 4acd0343 message 188 @rem1: ppspxspk 4acd0343 message 189 [EOF] 190 "); 191 } 192} 193 194#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 195#[test_case(true; "spawn a git subprocess for remote calls")] 196fn test_git_fetch_single_remote_from_arg(subprocess: bool) { 197 let test_env = TestEnvironment::default(); 198 if !subprocess { 199 test_env.add_config("git.subprocess = false"); 200 } 201 test_env.add_config("git.auto-local-bookmark = true"); 202 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 203 let work_dir = test_env.work_dir("repo"); 204 add_git_remote(&test_env, &work_dir, "rem1"); 205 206 work_dir 207 .run_jj(["git", "fetch", "--remote", "rem1"]) 208 .success(); 209 insta::allow_duplicates! { 210 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 211 rem1: ppspxspk 4acd0343 message 212 @rem1: ppspxspk 4acd0343 message 213 [EOF] 214 "); 215 } 216} 217 218#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 219#[test_case(true; "spawn a git subprocess for remote calls")] 220fn test_git_fetch_single_remote_from_config(subprocess: bool) { 221 let test_env = TestEnvironment::default(); 222 if !subprocess { 223 test_env.add_config("git.subprocess = false"); 224 } 225 test_env.add_config("git.auto-local-bookmark = true"); 226 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 227 let work_dir = test_env.work_dir("repo"); 228 add_git_remote(&test_env, &work_dir, "rem1"); 229 test_env.add_config(r#"git.fetch = "rem1""#); 230 231 work_dir.run_jj(["git", "fetch"]).success(); 232 insta::allow_duplicates! { 233 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 234 rem1: ppspxspk 4acd0343 message 235 @rem1: ppspxspk 4acd0343 message 236 [EOF] 237 "); 238 } 239} 240 241#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 242#[test_case(true; "spawn a git subprocess for remote calls")] 243fn test_git_fetch_multiple_remotes(subprocess: bool) { 244 let test_env = TestEnvironment::default(); 245 if !subprocess { 246 test_env.add_config("git.subprocess = false"); 247 } 248 test_env.add_config("git.auto-local-bookmark = true"); 249 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 250 let work_dir = test_env.work_dir("repo"); 251 add_git_remote(&test_env, &work_dir, "rem1"); 252 add_git_remote(&test_env, &work_dir, "rem2"); 253 254 work_dir 255 .run_jj(["git", "fetch", "--remote", "rem1", "--remote", "rem2"]) 256 .success(); 257 insta::allow_duplicates! { 258 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 259 rem1: ppspxspk 4acd0343 message 260 @rem1: ppspxspk 4acd0343 message 261 rem2: pzqqpnpo 44c57802 message 262 @rem2: pzqqpnpo 44c57802 message 263 [EOF] 264 "); 265 } 266} 267 268#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 269#[test_case(true; "spawn a git subprocess for remote calls")] 270fn test_git_fetch_with_glob(subprocess: bool) { 271 let test_env = TestEnvironment::default(); 272 if !subprocess { 273 test_env.add_config("git.subprocess = false"); 274 } 275 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 276 let work_dir = test_env.work_dir("repo"); 277 add_git_remote(&test_env, &work_dir, "rem1"); 278 add_git_remote(&test_env, &work_dir, "rem2"); 279 280 let output = work_dir.run_jj(["git", "fetch", "--remote", "glob:*"]); 281 insta::allow_duplicates! { 282 insta::assert_snapshot!(output, @r" 283 ------- stderr ------- 284 bookmark: rem1@rem1 [new] untracked 285 bookmark: rem2@rem2 [new] untracked 286 [EOF] 287 "); 288 } 289} 290 291#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 292#[test_case(true; "spawn a git subprocess for remote calls")] 293fn test_git_fetch_with_glob_and_exact_match(subprocess: bool) { 294 let test_env = TestEnvironment::default(); 295 if !subprocess { 296 test_env.add_config("git.subprocess = false"); 297 } 298 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 299 let work_dir = test_env.work_dir("repo"); 300 add_git_remote(&test_env, &work_dir, "rem1"); 301 add_git_remote(&test_env, &work_dir, "rem2"); 302 add_git_remote(&test_env, &work_dir, "upstream1"); 303 add_git_remote(&test_env, &work_dir, "upstream2"); 304 add_git_remote(&test_env, &work_dir, "origin"); 305 306 let output = work_dir.run_jj(["git", "fetch", "--remote=glob:rem*", "--remote=origin"]); 307 insta::allow_duplicates! { 308 insta::assert_snapshot!(output, @r" 309 ------- stderr ------- 310 bookmark: origin@origin [new] untracked 311 bookmark: rem1@rem1 [new] untracked 312 bookmark: rem2@rem2 [new] untracked 313 [EOF] 314 "); 315 } 316} 317 318#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 319#[test_case(true; "spawn a git subprocess for remote calls")] 320fn test_git_fetch_with_glob_from_config(subprocess: bool) { 321 let test_env = TestEnvironment::default(); 322 if !subprocess { 323 test_env.add_config("git.subprocess = false"); 324 } 325 test_env.add_config(r#"git.fetch = "glob:rem*""#); 326 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 327 let work_dir = test_env.work_dir("repo"); 328 add_git_remote(&test_env, &work_dir, "rem1"); 329 add_git_remote(&test_env, &work_dir, "rem2"); 330 add_git_remote(&test_env, &work_dir, "upstream"); 331 332 let output = work_dir.run_jj(["git", "fetch"]); 333 insta::allow_duplicates! { 334 insta::assert_snapshot!(output, @r" 335 ------- stderr ------- 336 bookmark: rem1@rem1 [new] untracked 337 bookmark: rem2@rem2 [new] untracked 338 [EOF] 339 "); 340 } 341} 342 343#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 344#[test_case(true; "spawn a git subprocess for remote calls")] 345fn test_git_fetch_with_glob_with_no_matching_remotes(subprocess: bool) { 346 let test_env = TestEnvironment::default(); 347 if !subprocess { 348 test_env.add_config("git.subprocess = false"); 349 } 350 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 351 let work_dir = test_env.work_dir("repo"); 352 add_git_remote(&test_env, &work_dir, "upstream"); 353 354 let output = work_dir.run_jj(["git", "fetch", "--remote=glob:rem*"]); 355 insta::allow_duplicates! { 356 insta::assert_snapshot!(output, @r" 357 ------- stderr ------- 358 Error: No matching git remotes for patterns: rem* 359 [EOF] 360 [exit status: 1] 361 "); 362 } 363 // No remote should have been fetched as part of the failing transaction 364 insta::allow_duplicates! { 365 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 366 } 367} 368 369#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 370#[test_case(true; "spawn a git subprocess for remote calls")] 371fn test_git_fetch_all_remotes(subprocess: bool) { 372 let test_env = TestEnvironment::default(); 373 if !subprocess { 374 test_env.add_config("git.subprocess = false"); 375 } 376 test_env.add_config("git.auto-local-bookmark = true"); 377 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 378 let work_dir = test_env.work_dir("repo"); 379 add_git_remote(&test_env, &work_dir, "rem1"); 380 add_git_remote(&test_env, &work_dir, "rem2"); 381 382 // add empty [remote "rem3"] section to .git/config, which should be ignored 383 work_dir 384 .run_jj(["git", "remote", "add", "rem3", "../unknown"]) 385 .success(); 386 work_dir 387 .run_jj(["git", "remote", "remove", "rem3"]) 388 .success(); 389 390 work_dir.run_jj(["git", "fetch", "--all-remotes"]).success(); 391 insta::allow_duplicates! { 392 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 393 rem1: ppspxspk 4acd0343 message 394 @rem1: ppspxspk 4acd0343 message 395 rem2: pzqqpnpo 44c57802 message 396 @rem2: pzqqpnpo 44c57802 message 397 [EOF] 398 "); 399 } 400} 401 402#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 403#[test_case(true; "spawn a git subprocess for remote calls")] 404fn test_git_fetch_multiple_remotes_from_config(subprocess: bool) { 405 let test_env = TestEnvironment::default(); 406 if !subprocess { 407 test_env.add_config("git.subprocess = false"); 408 } 409 test_env.add_config("git.auto-local-bookmark = true"); 410 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 411 let work_dir = test_env.work_dir("repo"); 412 add_git_remote(&test_env, &work_dir, "rem1"); 413 add_git_remote(&test_env, &work_dir, "rem2"); 414 test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#); 415 416 work_dir.run_jj(["git", "fetch"]).success(); 417 insta::allow_duplicates! { 418 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 419 rem1: ppspxspk 4acd0343 message 420 @rem1: ppspxspk 4acd0343 message 421 rem2: pzqqpnpo 44c57802 message 422 @rem2: pzqqpnpo 44c57802 message 423 [EOF] 424 "); 425 } 426} 427 428#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 429#[test_case(true; "spawn a git subprocess for remote calls")] 430fn test_git_fetch_nonexistent_remote(subprocess: bool) { 431 let test_env = TestEnvironment::default(); 432 if !subprocess { 433 test_env.add_config("git.subprocess = false"); 434 } 435 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 436 let work_dir = test_env.work_dir("repo"); 437 add_git_remote(&test_env, &work_dir, "rem1"); 438 439 let output = work_dir.run_jj(["git", "fetch", "--remote", "rem1", "--remote", "rem2"]); 440 insta::allow_duplicates! { 441 insta::assert_snapshot!(output, @r" 442 ------- stderr ------- 443 Error: No git remote named 'rem2' 444 [EOF] 445 [exit status: 1] 446 "); 447 } 448 insta::allow_duplicates! { 449 // No remote should have been fetched as part of the failing transaction 450 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 451 } 452} 453 454#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 455#[test_case(true; "spawn a git subprocess for remote calls")] 456fn test_git_fetch_nonexistent_remote_from_config(subprocess: bool) { 457 let test_env = TestEnvironment::default(); 458 if !subprocess { 459 test_env.add_config("git.subprocess = false"); 460 } 461 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 462 let work_dir = test_env.work_dir("repo"); 463 add_git_remote(&test_env, &work_dir, "rem1"); 464 test_env.add_config(r#"git.fetch = ["rem1", "rem2"]"#); 465 466 let output = work_dir.run_jj(["git", "fetch"]); 467 insta::allow_duplicates! { 468 insta::assert_snapshot!(output, @r" 469 ------- stderr ------- 470 Error: No git remote named 'rem2' 471 [EOF] 472 [exit status: 1] 473 "); 474 } 475 // No remote should have been fetched as part of the failing transaction 476 insta::allow_duplicates! { 477 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 478 } 479} 480 481#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 482#[test_case(true; "spawn a git subprocess for remote calls")] 483fn test_git_fetch_from_remote_named_git(subprocess: bool) { 484 let test_env = TestEnvironment::default(); 485 if !subprocess { 486 test_env.add_config("git.subprocess = false"); 487 } 488 test_env.add_config("git.auto-local-bookmark = true"); 489 let work_dir = test_env.work_dir("repo"); 490 init_git_remote(&test_env, "git"); 491 492 git::init(work_dir.root()); 493 git::add_remote(work_dir.root(), "git", "../git"); 494 495 // Existing remote named 'git' shouldn't block the repo initialization. 496 work_dir.run_jj(["git", "init", "--git-repo=."]).success(); 497 498 // Try fetching from the remote named 'git'. 499 let output = work_dir.run_jj(["git", "fetch", "--remote=git"]); 500 insta::allow_duplicates! { 501 insta::assert_snapshot!(output, @r" 502 ------- stderr ------- 503 Error: Git remote named 'git' is reserved for local Git repository 504 Hint: Run `jj git remote rename` to give a different name. 505 [EOF] 506 [exit status: 1] 507 "); 508 } 509 510 // Fetch remote refs by using the git CLI. 511 git::fetch(work_dir.root(), "git"); 512 513 // Implicit import shouldn't fail because of the remote ref. 514 let output = work_dir.run_jj(["bookmark", "list", "--all-remotes"]); 515 insta::allow_duplicates! { 516 insta::assert_snapshot!(output, @r" 517 ------- stderr ------- 518 Warning: Failed to import some Git refs: 519 refs/remotes/git/git 520 Hint: Git remote named 'git' is reserved for local Git repository. 521 Use `jj git remote rename` to give a different name. 522 [EOF] 523 "); 524 } 525 526 // Explicit import also works. Warnings are printed twice because this is a 527 // colocated repo. That should be fine since "jj git import" wouldn't be 528 // used in colocated environment. 529 insta::allow_duplicates! { 530 insta::assert_snapshot!(work_dir.run_jj(["git", "import"]), @r" 531 ------- stderr ------- 532 Warning: Failed to import some Git refs: 533 refs/remotes/git/git 534 Hint: Git remote named 'git' is reserved for local Git repository. 535 Use `jj git remote rename` to give a different name. 536 Warning: Failed to import some Git refs: 537 refs/remotes/git/git 538 Hint: Git remote named 'git' is reserved for local Git repository. 539 Use `jj git remote rename` to give a different name. 540 Nothing changed. 541 [EOF] 542 "); 543 } 544 545 // The remote can be renamed, and the ref can be imported. 546 work_dir 547 .run_jj(["git", "remote", "rename", "git", "bar"]) 548 .success(); 549 let output = work_dir.run_jj(["bookmark", "list", "--all-remotes"]); 550 insta::allow_duplicates! { 551 insta::assert_snapshot!(output, @r" 552 git: vkponlun 400c483d message 553 @bar: vkponlun 400c483d message 554 @git: vkponlun 400c483d message 555 [EOF] 556 ------- stderr ------- 557 Done importing changes from the underlying Git repo. 558 [EOF] 559 "); 560 } 561} 562 563#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 564#[test_case(true; "spawn a git subprocess for remote calls")] 565fn test_git_fetch_from_remote_with_slashes(subprocess: bool) { 566 let test_env = TestEnvironment::default(); 567 if !subprocess { 568 test_env.add_config("git.subprocess = false"); 569 } 570 test_env.add_config("git.auto-local-bookmark = true"); 571 let work_dir = test_env.work_dir("repo"); 572 init_git_remote(&test_env, "source"); 573 574 git::init(work_dir.root()); 575 git::add_remote(work_dir.root(), "slash/origin", "../source"); 576 577 // Existing remote with slash shouldn't block the repo initialization. 578 work_dir.run_jj(["git", "init", "--git-repo=."]).success(); 579 580 // Try fetching from the remote named 'git'. 581 let output = work_dir.run_jj(["git", "fetch", "--remote=slash/origin"]); 582 insta::allow_duplicates! { 583 insta::assert_snapshot!(output, @r" 584 ------- stderr ------- 585 Error: Git remotes with slashes are incompatible with jj: slash/origin 586 Hint: Run `jj git remote rename` to give a different name. 587 [EOF] 588 [exit status: 1] 589 "); 590 } 591} 592 593#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 594#[test_case(true; "spawn a git subprocess for remote calls")] 595fn test_git_fetch_prune_before_updating_tips(subprocess: bool) { 596 let test_env = TestEnvironment::default(); 597 if !subprocess { 598 test_env.add_config("git.subprocess = false"); 599 } 600 test_env.add_config("git.auto-local-bookmark = true"); 601 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 602 let work_dir = test_env.work_dir("repo"); 603 let git_repo = add_git_remote(&test_env, &work_dir, "origin"); 604 work_dir.run_jj(["git", "fetch"]).success(); 605 insta::allow_duplicates! { 606 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 607 origin: qmyrypzk ab8b299e message 608 @origin: qmyrypzk ab8b299e message 609 [EOF] 610 "); 611 } 612 613 // Remove origin bookmark in git repo and create origin/subname 614 let mut origin_reference = git_repo.find_reference("refs/heads/origin").unwrap(); 615 let commit_id = origin_reference.peel_to_commit().unwrap().id().detach(); 616 origin_reference.delete().unwrap(); 617 git_repo 618 .reference( 619 "refs/heads/origin/subname", 620 commit_id, 621 gix::refs::transaction::PreviousValue::MustNotExist, 622 "create new reference", 623 ) 624 .unwrap(); 625 626 work_dir.run_jj(["git", "fetch"]).success(); 627 insta::allow_duplicates! { 628 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 629 origin/subname: qmyrypzk ab8b299e message 630 @origin: qmyrypzk ab8b299e message 631 [EOF] 632 "); 633 } 634} 635 636#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 637#[test_case(true; "spawn a git subprocess for remote calls")] 638fn test_git_fetch_conflicting_bookmarks(subprocess: bool) { 639 let test_env = TestEnvironment::default(); 640 if !subprocess { 641 test_env.add_config("git.subprocess = false"); 642 } 643 test_env.add_config("git.auto-local-bookmark = true"); 644 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 645 let work_dir = test_env.work_dir("repo"); 646 add_git_remote(&test_env, &work_dir, "rem1"); 647 648 // Create a rem1 bookmark locally 649 work_dir.run_jj(["new", "root()"]).success(); 650 work_dir 651 .run_jj(["bookmark", "create", "-r@", "rem1"]) 652 .success(); 653 insta::allow_duplicates! { 654 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 655 rem1: kkmpptxz fcdbbd73 (empty) (no description set) 656 [EOF] 657 "); 658 } 659 660 work_dir 661 .run_jj(["git", "fetch", "--remote", "rem1", "--branch", "glob:*"]) 662 .success(); 663 // This should result in a CONFLICTED bookmark 664 insta::allow_duplicates! { 665 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 666 rem1 (conflicted): 667 + kkmpptxz fcdbbd73 (empty) (no description set) 668 + ppspxspk 4acd0343 message 669 @rem1 (behind by 1 commits): ppspxspk 4acd0343 message 670 [EOF] 671 "); 672 } 673} 674 675#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 676#[test_case(true; "spawn a git subprocess for remote calls")] 677fn test_git_fetch_conflicting_bookmarks_colocated(subprocess: bool) { 678 let test_env = TestEnvironment::default(); 679 if !subprocess { 680 test_env.add_config("git.subprocess = false"); 681 } 682 test_env.add_config("git.auto-local-bookmark = true"); 683 let work_dir = test_env.work_dir("repo"); 684 git::init(work_dir.root()); 685 // create_colocated_repo_and_bookmarks_from_trunk1(&test_env, &repo_path); 686 work_dir 687 .run_jj(["git", "init", "--git-repo", "."]) 688 .success(); 689 add_git_remote(&test_env, &work_dir, "rem1"); 690 insta::allow_duplicates! { 691 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 692 } 693 694 // Create a rem1 bookmark locally 695 work_dir.run_jj(["new", "root()"]).success(); 696 work_dir 697 .run_jj(["bookmark", "create", "-r@", "rem1"]) 698 .success(); 699 insta::allow_duplicates! { 700 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 701 rem1: zsuskuln f652c321 (empty) (no description set) 702 @git: zsuskuln f652c321 (empty) (no description set) 703 [EOF] 704 "); 705 } 706 707 work_dir 708 .run_jj(["git", "fetch", "--remote", "rem1", "--branch", "rem1"]) 709 .success(); 710 // This should result in a CONFLICTED bookmark 711 // See https://github.com/jj-vcs/jj/pull/1146#discussion_r1112372340 for the bug this tests for. 712 insta::allow_duplicates! { 713 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 714 rem1 (conflicted): 715 + zsuskuln f652c321 (empty) (no description set) 716 + ppspxspk 4acd0343 message 717 @git (behind by 1 commits): zsuskuln f652c321 (empty) (no description set) 718 @rem1 (behind by 1 commits): ppspxspk 4acd0343 message 719 [EOF] 720 "); 721 } 722} 723 724// Helper functions to test obtaining multiple bookmarks at once and changed 725// bookmarks 726fn create_colocated_repo_and_bookmarks_from_trunk1(work_dir: &TestWorkDir) -> String { 727 // Create a colocated repo in `source` to populate it more easily 728 work_dir 729 .run_jj(["git", "init", "--git-repo", "."]) 730 .success(); 731 create_commit(work_dir, "trunk1", &[]); 732 create_commit(work_dir, "a1", &["trunk1"]); 733 create_commit(work_dir, "a2", &["trunk1"]); 734 create_commit(work_dir, "b", &["trunk1"]); 735 format!( 736 " ===== Source git repo contents =====\n{}", 737 get_log_output(work_dir) 738 ) 739} 740 741fn create_trunk2_and_rebase_bookmarks(work_dir: &TestWorkDir) -> String { 742 create_commit(work_dir, "trunk2", &["trunk1"]); 743 for br in ["a1", "a2", "b"] { 744 work_dir 745 .run_jj(["rebase", "-b", br, "-d", "trunk2"]) 746 .success(); 747 } 748 format!( 749 " ===== Source git repo contents =====\n{}", 750 get_log_output(work_dir) 751 ) 752} 753 754#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 755#[test_case(true; "spawn a git subprocess for remote calls")] 756fn test_git_fetch_all(subprocess: bool) { 757 let test_env = TestEnvironment::default(); 758 if !subprocess { 759 test_env.add_config("git.subprocess = false"); 760 } 761 test_env.add_config("git.auto-local-bookmark = true"); 762 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 763 let source_dir = test_env.work_dir("source"); 764 git::init(source_dir.root()); 765 766 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated 767 let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]); 768 insta::allow_duplicates! { 769 insta::assert_snapshot!(output, @r#" 770 ------- stderr ------- 771 Fetching into new repo in "$TEST_ENV/target" 772 Nothing changed. 773 [EOF] 774 "#); 775 } 776 let target_dir = test_env.work_dir("target"); 777 778 let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir); 779 insta::allow_duplicates! { 780 insta::assert_snapshot!(source_log, @r#" 781 ===== Source git repo contents ===== 782 @ 8cc4df9dd488 "b" b 783 │ ○ e2a95b19b745 "a2" a2 784 ├─╯ 785 │ ○ dd42071fe1ad "a1" a1 786 ├─╯ 787 ○ 9929b494c411 "trunk1" trunk1 788 ◆ 000000000000 "" 789 [EOF] 790 "#); 791 } 792 793 // Nothing in our repo before the fetch 794 insta::allow_duplicates! { 795 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 796 @ 230dd059e1b0 "" 797 ◆ 000000000000 "" 798 [EOF] 799 "#); 800 } 801 insta::allow_duplicates! { 802 insta::assert_snapshot!(get_bookmark_output(&target_dir), @""); 803 } 804 let output = target_dir.run_jj(["git", "fetch"]); 805 insta::allow_duplicates! { 806 insta::assert_snapshot!(output, @r" 807 ------- stderr ------- 808 bookmark: a1@origin [new] tracked 809 bookmark: a2@origin [new] tracked 810 bookmark: b@origin [new] tracked 811 bookmark: trunk1@origin [new] tracked 812 [EOF] 813 "); 814 } 815 insta::allow_duplicates! { 816 insta::assert_snapshot!(get_bookmark_output(&target_dir), @r" 817 a1: spvnozwy dd42071f a1 818 @origin: spvnozwy dd42071f a1 819 a2: qnxtrkvv e2a95b19 a2 820 @origin: qnxtrkvv e2a95b19 a2 821 b: lnxrmsmo 8cc4df9d b 822 @origin: lnxrmsmo 8cc4df9d b 823 trunk1: qzywppkx 9929b494 trunk1 824 @origin: qzywppkx 9929b494 trunk1 825 [EOF] 826 "); 827 } 828 insta::allow_duplicates! { 829 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 830 @ 230dd059e1b0 "" 831 │ ○ 8cc4df9dd488 "b" b 832 │ │ ○ e2a95b19b745 "a2" a2 833 │ ├─╯ 834 │ │ ○ dd42071fe1ad "a1" a1 835 │ ├─╯ 836 │ ○ 9929b494c411 "trunk1" trunk1 837 ├─╯ 838 ◆ 000000000000 "" 839 [EOF] 840 "#); 841 } 842 843 // ==== Change both repos ==== 844 // First, change the target repo: 845 let source_log = create_trunk2_and_rebase_bookmarks(&source_dir); 846 insta::allow_duplicates! { 847 insta::assert_snapshot!(source_log, @r#" 848 ===== Source git repo contents ===== 849 ○ 7c277a6aa3c3 "b" b 850 │ ○ 698fed8731d8 "a2" a2 851 ├─╯ 852 │ ○ a3f2410627ff "a1" a1 853 ├─╯ 854 @ e7525a4649e3 "trunk2" trunk2 855 ○ 9929b494c411 "trunk1" trunk1 856 ◆ 000000000000 "" 857 [EOF] 858 "#); 859 } 860 // Change a bookmark in the source repo as well, so that it becomes conflicted. 861 target_dir 862 .run_jj(["describe", "b", "-m=new_descr_for_b_to_create_conflict"]) 863 .success(); 864 865 // Our repo before and after fetch 866 insta::allow_duplicates! { 867 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 868 @ 230dd059e1b0 "" 869 │ ○ 5b3bc9c99bb3 "new_descr_for_b_to_create_conflict" b* 870 │ │ ○ e2a95b19b745 "a2" a2 871 │ ├─╯ 872 │ │ ○ dd42071fe1ad "a1" a1 873 │ ├─╯ 874 │ ○ 9929b494c411 "trunk1" trunk1 875 ├─╯ 876 ◆ 000000000000 "" 877 [EOF] 878 "#); 879 } 880 insta::allow_duplicates! { 881 insta::assert_snapshot!(get_bookmark_output(&target_dir), @r" 882 a1: spvnozwy dd42071f a1 883 @origin: spvnozwy dd42071f a1 884 a2: qnxtrkvv e2a95b19 a2 885 @origin: qnxtrkvv e2a95b19 a2 886 b: lnxrmsmo 5b3bc9c9 new_descr_for_b_to_create_conflict 887 @origin (ahead by 1 commits, behind by 1 commits): lnxrmsmo hidden 8cc4df9d b 888 trunk1: qzywppkx 9929b494 trunk1 889 @origin: qzywppkx 9929b494 trunk1 890 [EOF] 891 "); 892 } 893 let output = target_dir.run_jj(["git", "fetch"]); 894 insta::allow_duplicates! { 895 insta::assert_snapshot!(output, @r" 896 ------- stderr ------- 897 bookmark: a1@origin [updated] tracked 898 bookmark: a2@origin [updated] tracked 899 bookmark: b@origin [updated] tracked 900 bookmark: trunk2@origin [new] tracked 901 Abandoned 2 commits that are no longer reachable. 902 [EOF] 903 "); 904 } 905 insta::allow_duplicates! { 906 insta::assert_snapshot!(get_bookmark_output(&target_dir), @r" 907 a1: vrnsrlyk a3f24106 a1 908 @origin: vrnsrlyk a3f24106 a1 909 a2: vlowznwy 698fed87 a2 910 @origin: vlowznwy 698fed87 a2 911 b (conflicted): 912 - lnxrmsmo hidden 8cc4df9d b 913 + lnxrmsmo 5b3bc9c9 new_descr_for_b_to_create_conflict 914 + uulqyxll 7c277a6a b 915 @origin (behind by 1 commits): uulqyxll 7c277a6a b 916 trunk1: qzywppkx 9929b494 trunk1 917 @origin: qzywppkx 9929b494 trunk1 918 trunk2: lzqpwqnx e7525a46 trunk2 919 @origin: lzqpwqnx e7525a46 trunk2 920 [EOF] 921 "); 922 } 923 insta::allow_duplicates! { 924 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 925 @ 230dd059e1b0 "" 926 │ ○ 7c277a6aa3c3 "b" b?? b@origin 927 │ │ ○ 698fed8731d8 "a2" a2 928 │ ├─╯ 929 │ │ ○ a3f2410627ff "a1" a1 930 │ ├─╯ 931 │ ○ e7525a4649e3 "trunk2" trunk2 932 │ │ ○ 5b3bc9c99bb3 "new_descr_for_b_to_create_conflict" b?? 933 │ ├─╯ 934 │ ○ 9929b494c411 "trunk1" trunk1 935 ├─╯ 936 ◆ 000000000000 "" 937 [EOF] 938 "#); 939 } 940} 941 942#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 943#[test_case(true; "spawn a git subprocess for remote calls")] 944fn test_git_fetch_some_of_many_bookmarks(subprocess: bool) { 945 let test_env = TestEnvironment::default(); 946 if !subprocess { 947 test_env.add_config("git.subprocess = false"); 948 } 949 test_env.add_config("git.auto-local-bookmark = true"); 950 test_env.add_config(r#"revset-aliases."immutable_heads()" = "none()""#); 951 let source_dir = test_env.work_dir("source"); 952 git::init(source_dir.root()); 953 954 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated 955 let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]); 956 insta::allow_duplicates! { 957 insta::assert_snapshot!(output, @r#" 958 ------- stderr ------- 959 Fetching into new repo in "$TEST_ENV/target" 960 Nothing changed. 961 [EOF] 962 "#); 963 } 964 let target_dir = test_env.work_dir("target"); 965 966 let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir); 967 insta::allow_duplicates! { 968 insta::assert_snapshot!(source_log, @r#" 969 ===== Source git repo contents ===== 970 @ 8cc4df9dd488 "b" b 971 │ ○ e2a95b19b745 "a2" a2 972 ├─╯ 973 │ ○ dd42071fe1ad "a1" a1 974 ├─╯ 975 ○ 9929b494c411 "trunk1" trunk1 976 ◆ 000000000000 "" 977 [EOF] 978 "#); 979 } 980 981 // Test an error message 982 let output = target_dir.run_jj(["git", "fetch", "--branch", "glob:^:a*"]); 983 insta::allow_duplicates! { 984 insta::assert_snapshot!(output, @r" 985 ------- stderr ------- 986 Error: Invalid branch pattern provided. When fetching, branch names and globs may not contain the characters `:`, `^`, `?`, `[`, `]` 987 [EOF] 988 [exit status: 1] 989 "); 990 } 991 let output = target_dir.run_jj(["git", "fetch", "--branch", "a*"]); 992 insta::allow_duplicates! { 993 insta::assert_snapshot!(output, @r" 994 ------- stderr ------- 995 Error: Branch names may not include `*`. 996 Hint: Prefix the pattern with `glob:` to expand `*` as a glob 997 [EOF] 998 [exit status: 1] 999 "); 1000 } 1001 1002 // Nothing in our repo before the fetch 1003 insta::allow_duplicates! { 1004 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1005 @ 230dd059e1b0 "" 1006 ◆ 000000000000 "" 1007 [EOF] 1008 "#); 1009 } 1010 // Fetch one bookmark... 1011 let output = target_dir.run_jj(["git", "fetch", "--branch", "b"]); 1012 insta::allow_duplicates! { 1013 insta::assert_snapshot!(output, @r" 1014 ------- stderr ------- 1015 bookmark: b@origin [new] tracked 1016 [EOF] 1017 "); 1018 } 1019 insta::allow_duplicates! { 1020 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1021 @ 230dd059e1b0 "" 1022 │ ○ 8cc4df9dd488 "b" b 1023 │ ○ 9929b494c411 "trunk1" 1024 ├─╯ 1025 ◆ 000000000000 "" 1026 [EOF] 1027 "#); 1028 } 1029 // ...check what the intermediate state looks like... 1030 insta::allow_duplicates! { 1031 insta::assert_snapshot!(get_bookmark_output(&target_dir), @r" 1032 b: lnxrmsmo 8cc4df9d b 1033 @origin: lnxrmsmo 8cc4df9d b 1034 [EOF] 1035 "); 1036 } 1037 // ...then fetch two others with a glob. 1038 let output = target_dir.run_jj(["git", "fetch", "--branch", "glob:a*"]); 1039 insta::allow_duplicates! { 1040 insta::assert_snapshot!(output, @r" 1041 ------- stderr ------- 1042 bookmark: a1@origin [new] tracked 1043 bookmark: a2@origin [new] tracked 1044 [EOF] 1045 "); 1046 } 1047 insta::allow_duplicates! { 1048 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1049 @ 230dd059e1b0 "" 1050 │ ○ e2a95b19b745 "a2" a2 1051 │ │ ○ dd42071fe1ad "a1" a1 1052 │ ├─╯ 1053 │ │ ○ 8cc4df9dd488 "b" b 1054 │ ├─╯ 1055 │ ○ 9929b494c411 "trunk1" 1056 ├─╯ 1057 ◆ 000000000000 "" 1058 [EOF] 1059 "#); 1060 } 1061 // Fetching the same bookmark again 1062 let output = target_dir.run_jj(["git", "fetch", "--branch", "a1"]); 1063 insta::allow_duplicates! { 1064 insta::assert_snapshot!(output, @r" 1065 ------- stderr ------- 1066 Nothing changed. 1067 [EOF] 1068 "); 1069 } 1070 insta::allow_duplicates! { 1071 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1072 @ 230dd059e1b0 "" 1073 │ ○ e2a95b19b745 "a2" a2 1074 │ │ ○ dd42071fe1ad "a1" a1 1075 │ ├─╯ 1076 │ │ ○ 8cc4df9dd488 "b" b 1077 │ ├─╯ 1078 │ ○ 9929b494c411 "trunk1" 1079 ├─╯ 1080 ◆ 000000000000 "" 1081 [EOF] 1082 "#); 1083 } 1084 1085 // ==== Change both repos ==== 1086 // First, change the target repo: 1087 let source_log = create_trunk2_and_rebase_bookmarks(&source_dir); 1088 insta::allow_duplicates! { 1089 insta::assert_snapshot!(source_log, @r#" 1090 ===== Source git repo contents ===== 1091 ○ 96c8ad25ad36 "b" b 1092 │ ○ 9ccd9f75fc0c "a2" a2 1093 ├─╯ 1094 │ ○ 527d2e46a87f "a1" a1 1095 ├─╯ 1096 @ 2f34a0e70741 "trunk2" trunk2 1097 ○ 9929b494c411 "trunk1" trunk1 1098 ◆ 000000000000 "" 1099 [EOF] 1100 "#); 1101 } 1102 // Change a bookmark in the source repo as well, so that it becomes conflicted. 1103 target_dir 1104 .run_jj(["describe", "b", "-m=new_descr_for_b_to_create_conflict"]) 1105 .success(); 1106 1107 // Our repo before and after fetch of two bookmarks 1108 insta::allow_duplicates! { 1109 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1110 @ 230dd059e1b0 "" 1111 │ ○ e9445259b932 "new_descr_for_b_to_create_conflict" b* 1112 │ │ ○ e2a95b19b745 "a2" a2 1113 │ ├─╯ 1114 │ │ ○ dd42071fe1ad "a1" a1 1115 │ ├─╯ 1116 │ ○ 9929b494c411 "trunk1" 1117 ├─╯ 1118 ◆ 000000000000 "" 1119 [EOF] 1120 "#); 1121 } 1122 let output = target_dir.run_jj(["git", "fetch", "--branch", "b", "--branch", "a1"]); 1123 insta::allow_duplicates! { 1124 insta::assert_snapshot!(output, @r" 1125 ------- stderr ------- 1126 bookmark: a1@origin [updated] tracked 1127 bookmark: b@origin [updated] tracked 1128 Abandoned 1 commits that are no longer reachable. 1129 [EOF] 1130 "); 1131 } 1132 insta::allow_duplicates! { 1133 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1134 @ 230dd059e1b0 "" 1135 │ ○ 96c8ad25ad36 "b" b?? b@origin 1136 │ │ ○ 527d2e46a87f "a1" a1 1137 │ ├─╯ 1138 │ ○ 2f34a0e70741 "trunk2" 1139 │ │ ○ e9445259b932 "new_descr_for_b_to_create_conflict" b?? 1140 │ ├─╯ 1141 │ │ ○ e2a95b19b745 "a2" a2 1142 │ ├─╯ 1143 │ ○ 9929b494c411 "trunk1" 1144 ├─╯ 1145 ◆ 000000000000 "" 1146 [EOF] 1147 "#); 1148 } 1149 1150 // We left a2 where it was before, let's see how `jj bookmark list` sees this. 1151 insta::allow_duplicates! { 1152 insta::assert_snapshot!(get_bookmark_output(&target_dir), @r" 1153 a1: qptloxlm 527d2e46 a1 1154 @origin: qptloxlm 527d2e46 a1 1155 a2: qnxtrkvv e2a95b19 a2 1156 @origin: qnxtrkvv e2a95b19 a2 1157 b (conflicted): 1158 - lnxrmsmo hidden 8cc4df9d b 1159 + lnxrmsmo e9445259 new_descr_for_b_to_create_conflict 1160 + rruvkzpm 96c8ad25 b 1161 @origin (behind by 1 commits): rruvkzpm 96c8ad25 b 1162 [EOF] 1163 "); 1164 } 1165 // Now, let's fetch a2 and double-check that fetching a1 and b again doesn't do 1166 // anything. 1167 let output = target_dir.run_jj(["git", "fetch", "--branch", "b", "--branch", "glob:a*"]); 1168 insta::allow_duplicates! { 1169 insta::assert_snapshot!(output, @r" 1170 ------- stderr ------- 1171 bookmark: a2@origin [updated] tracked 1172 Abandoned 1 commits that are no longer reachable. 1173 [EOF] 1174 "); 1175 } 1176 insta::allow_duplicates! { 1177 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1178 @ 230dd059e1b0 "" 1179 │ ○ 9ccd9f75fc0c "a2" a2 1180 │ │ ○ 96c8ad25ad36 "b" b?? b@origin 1181 │ ├─╯ 1182 │ │ ○ 527d2e46a87f "a1" a1 1183 │ ├─╯ 1184 │ ○ 2f34a0e70741 "trunk2" 1185 │ │ ○ e9445259b932 "new_descr_for_b_to_create_conflict" b?? 1186 │ ├─╯ 1187 │ ○ 9929b494c411 "trunk1" 1188 ├─╯ 1189 ◆ 000000000000 "" 1190 [EOF] 1191 "#); 1192 } 1193 insta::allow_duplicates! { 1194 insta::assert_snapshot!(get_bookmark_output(&target_dir), @r" 1195 a1: qptloxlm 527d2e46 a1 1196 @origin: qptloxlm 527d2e46 a1 1197 a2: ltuqxttq 9ccd9f75 a2 1198 @origin: ltuqxttq 9ccd9f75 a2 1199 b (conflicted): 1200 - lnxrmsmo hidden 8cc4df9d b 1201 + lnxrmsmo e9445259 new_descr_for_b_to_create_conflict 1202 + rruvkzpm 96c8ad25 b 1203 @origin (behind by 1 commits): rruvkzpm 96c8ad25 b 1204 [EOF] 1205 "); 1206 } 1207} 1208 1209#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1210#[test_case(true; "spawn a git subprocess for remote calls")] 1211fn test_git_fetch_bookmarks_some_missing(subprocess: bool) { 1212 let test_env = TestEnvironment::default(); 1213 if !subprocess { 1214 test_env.add_config("git.subprocess = false"); 1215 } 1216 test_env.add_config("git.auto-local-bookmark = true"); 1217 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1218 let work_dir = test_env.work_dir("repo"); 1219 add_git_remote(&test_env, &work_dir, "origin"); 1220 add_git_remote(&test_env, &work_dir, "rem1"); 1221 add_git_remote(&test_env, &work_dir, "rem2"); 1222 add_git_remote(&test_env, &work_dir, "rem3"); 1223 1224 // single missing bookmark, implicit remotes (@origin) 1225 let output = work_dir.run_jj(["git", "fetch", "--branch", "noexist"]); 1226 insta::allow_duplicates! { 1227 insta::assert_snapshot!(output, @r" 1228 ------- stderr ------- 1229 Warning: No branch matching `noexist` found on any specified/configured remote 1230 Nothing changed. 1231 [EOF] 1232 "); 1233 } 1234 insta::allow_duplicates! { 1235 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 1236 } 1237 1238 // multiple missing bookmarks, implicit remotes (@origin) 1239 let output = work_dir.run_jj([ 1240 "git", "fetch", "--branch", "noexist1", "--branch", "noexist2", 1241 ]); 1242 insta::allow_duplicates! { 1243 insta::assert_snapshot!(output, @r" 1244 ------- stderr ------- 1245 Warning: No branch matching `noexist1` found on any specified/configured remote 1246 Warning: No branch matching `noexist2` found on any specified/configured remote 1247 Nothing changed. 1248 [EOF] 1249 "); 1250 } 1251 insta::allow_duplicates! { 1252 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 1253 } 1254 1255 // single existing bookmark, implicit remotes (@origin) 1256 let output = work_dir.run_jj(["git", "fetch", "--branch", "origin"]); 1257 insta::allow_duplicates! { 1258 insta::assert_snapshot!(output, @r" 1259 ------- stderr ------- 1260 bookmark: origin@origin [new] tracked 1261 [EOF] 1262 "); 1263 } 1264 insta::allow_duplicates! { 1265 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1266 origin: qmyrypzk ab8b299e message 1267 @origin: qmyrypzk ab8b299e message 1268 [EOF] 1269 "); 1270 } 1271 1272 // multiple existing bookmark, explicit remotes, each bookmark is only in one 1273 // remote. 1274 let output = work_dir.run_jj([ 1275 "git", "fetch", "--branch", "rem1", "--branch", "rem2", "--branch", "rem3", "--remote", 1276 "rem1", "--remote", "rem2", "--remote", "rem3", 1277 ]); 1278 insta::allow_duplicates! { 1279 insta::assert_snapshot!(output, @r" 1280 ------- stderr ------- 1281 bookmark: rem1@rem1 [new] tracked 1282 bookmark: rem2@rem2 [new] tracked 1283 bookmark: rem3@rem3 [new] tracked 1284 [EOF] 1285 "); 1286 } 1287 insta::allow_duplicates! { 1288 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1289 origin: qmyrypzk ab8b299e message 1290 @origin: qmyrypzk ab8b299e message 1291 rem1: ppspxspk 4acd0343 message 1292 @rem1: ppspxspk 4acd0343 message 1293 rem2: pzqqpnpo 44c57802 message 1294 @rem2: pzqqpnpo 44c57802 message 1295 rem3: wrzwlmys 45a3faef message 1296 @rem3: wrzwlmys 45a3faef message 1297 [EOF] 1298 ") 1299 } 1300 1301 // multiple bookmarks, one exists, one doesn't 1302 let output = work_dir.run_jj([ 1303 "git", "fetch", "--branch", "rem1", "--branch", "notexist", "--remote", "rem1", 1304 ]); 1305 insta::allow_duplicates! { 1306 insta::assert_snapshot!(output, @r" 1307 ------- stderr ------- 1308 Warning: No branch matching `notexist` found on any specified/configured remote 1309 Nothing changed. 1310 [EOF] 1311 "); 1312 } 1313 insta::allow_duplicates! { 1314 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1315 origin: qmyrypzk ab8b299e message 1316 @origin: qmyrypzk ab8b299e message 1317 rem1: ppspxspk 4acd0343 message 1318 @rem1: ppspxspk 4acd0343 message 1319 rem2: pzqqpnpo 44c57802 message 1320 @rem2: pzqqpnpo 44c57802 message 1321 rem3: wrzwlmys 45a3faef message 1322 @rem3: wrzwlmys 45a3faef message 1323 [EOF] 1324 "); 1325 } 1326} 1327 1328#[test] 1329fn test_git_fetch_bookmarks_missing_with_subprocess_localized_message() { 1330 let test_env = TestEnvironment::default(); 1331 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1332 let work_dir = test_env.work_dir("repo"); 1333 add_git_remote(&test_env, &work_dir, "origin"); 1334 1335 // "fatal: couldn't find remote ref %s" shouldn't be localized. 1336 let output = work_dir.run_jj_with(|cmd| { 1337 cmd.args(["git", "fetch", "--branch=unknown"]) 1338 // Initialize locale as "en_US" which is the most common. 1339 .env("LC_ALL", "en_US.UTF-8") 1340 // Set some other locale variables for testing. 1341 .env("LC_MESSAGES", "en_US.UTF-8") 1342 .env("LANG", "en_US.UTF-8") 1343 // GNU gettext prioritizes LANGUAGE if translation is enabled. It works 1344 // no matter if system locale exists or not. 1345 .env("LANGUAGE", "zh_TW") 1346 }); 1347 insta::assert_snapshot!(output, @r" 1348 ------- stderr ------- 1349 Warning: No branch matching `unknown` found on any specified/configured remote 1350 Nothing changed. 1351 [EOF] 1352 "); 1353} 1354 1355// See `test_undo_restore_commands.rs` for fetch-undo-push and fetch-undo-fetch 1356// of the same bookmarks for various kinds of undo. 1357#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1358#[test_case(true; "spawn a git subprocess for remote calls")] 1359fn test_git_fetch_undo(subprocess: bool) { 1360 let test_env = TestEnvironment::default(); 1361 if !subprocess { 1362 test_env.add_config("git.subprocess = false"); 1363 } 1364 test_env.add_config("git.auto-local-bookmark = true"); 1365 let source_dir = test_env.work_dir("source"); 1366 git::init(source_dir.root()); 1367 1368 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated 1369 let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]); 1370 insta::allow_duplicates! { 1371 insta::assert_snapshot!(output, @r#" 1372 ------- stderr ------- 1373 Fetching into new repo in "$TEST_ENV/target" 1374 Nothing changed. 1375 [EOF] 1376 "#); 1377 } 1378 let target_dir = test_env.work_dir("target"); 1379 1380 let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir); 1381 insta::allow_duplicates! { 1382 insta::assert_snapshot!(source_log, @r#" 1383 ===== Source git repo contents ===== 1384 @ 8cc4df9dd488 "b" b 1385 │ ○ e2a95b19b745 "a2" a2 1386 ├─╯ 1387 │ ○ dd42071fe1ad "a1" a1 1388 ├─╯ 1389 ○ 9929b494c411 "trunk1" trunk1 1390 ◆ 000000000000 "" 1391 [EOF] 1392 "#); 1393 } 1394 1395 // Fetch 2 bookmarks 1396 let output = target_dir.run_jj(["git", "fetch", "--branch", "b", "--branch", "a1"]); 1397 insta::allow_duplicates! { 1398 insta::assert_snapshot!(output, @r" 1399 ------- stderr ------- 1400 bookmark: a1@origin [new] tracked 1401 bookmark: b@origin [new] tracked 1402 [EOF] 1403 "); 1404 } 1405 insta::allow_duplicates! { 1406 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1407 @ 230dd059e1b0 "" 1408 │ ○ 8cc4df9dd488 "b" b 1409 │ │ ○ dd42071fe1ad "a1" a1 1410 │ ├─╯ 1411 │ ○ 9929b494c411 "trunk1" 1412 ├─╯ 1413 ◆ 000000000000 "" 1414 [EOF] 1415 "#); 1416 } 1417 let output = target_dir.run_jj(["undo"]); 1418 insta::allow_duplicates! { 1419 insta::assert_snapshot!(output, @r" 1420 ------- stderr ------- 1421 Undid operation: 4bd67fb242bc (2001-02-03 08:05:18) fetch from git remote(s) origin 1422 [EOF] 1423 "); 1424 } 1425 // The undo works as expected 1426 insta::allow_duplicates! { 1427 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1428 @ 230dd059e1b0 "" 1429 ◆ 000000000000 "" 1430 [EOF] 1431 "#); 1432 } 1433 // Now try to fetch just one bookmark 1434 let output = target_dir.run_jj(["git", "fetch", "--branch", "b"]); 1435 insta::allow_duplicates! { 1436 insta::assert_snapshot!(output, @r" 1437 ------- stderr ------- 1438 bookmark: b@origin [new] tracked 1439 [EOF] 1440 "); 1441 } 1442 insta::allow_duplicates! { 1443 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1444 @ 230dd059e1b0 "" 1445 │ ○ 8cc4df9dd488 "b" b 1446 │ ○ 9929b494c411 "trunk1" 1447 ├─╯ 1448 ◆ 000000000000 "" 1449 [EOF] 1450 "#); 1451 } 1452} 1453 1454// Compare to `test_git_import_undo` in test_git_import_export 1455// TODO: Explain why these behaviors are useful 1456#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1457#[test_case(true; "spawn a git subprocess for remote calls")] 1458fn test_fetch_undo_what(subprocess: bool) { 1459 let test_env = TestEnvironment::default(); 1460 if !subprocess { 1461 test_env.add_config("git.subprocess = false"); 1462 } 1463 test_env.add_config("git.auto-local-bookmark = true"); 1464 let source_dir = test_env.work_dir("source"); 1465 git::init(source_dir.root()); 1466 1467 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated 1468 let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]); 1469 insta::allow_duplicates! { 1470 insta::assert_snapshot!(output, @r#" 1471 ------- stderr ------- 1472 Fetching into new repo in "$TEST_ENV/target" 1473 Nothing changed. 1474 [EOF] 1475 "#); 1476 } 1477 let work_dir = test_env.work_dir("target"); 1478 1479 let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir); 1480 insta::allow_duplicates! { 1481 insta::assert_snapshot!(source_log, @r#" 1482 ===== Source git repo contents ===== 1483 @ 8cc4df9dd488 "b" b 1484 │ ○ e2a95b19b745 "a2" a2 1485 ├─╯ 1486 │ ○ dd42071fe1ad "a1" a1 1487 ├─╯ 1488 ○ 9929b494c411 "trunk1" trunk1 1489 ◆ 000000000000 "" 1490 [EOF] 1491 "#); 1492 } 1493 1494 // Initial state we will try to return to after `op restore`. There are no 1495 // bookmarks. 1496 insta::allow_duplicates! { 1497 insta::assert_snapshot!(get_bookmark_output(&work_dir), @""); 1498 } 1499 let base_operation_id = work_dir.current_operation_id(); 1500 1501 // Fetch a bookmark 1502 let output = work_dir.run_jj(["git", "fetch", "--branch", "b"]); 1503 insta::allow_duplicates! { 1504 insta::assert_snapshot!(output, @r" 1505 ------- stderr ------- 1506 bookmark: b@origin [new] tracked 1507 [EOF] 1508 "); 1509 } 1510 insta::allow_duplicates! { 1511 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 1512 @ 230dd059e1b0 "" 1513 │ ○ 8cc4df9dd488 "b" b 1514 │ ○ 9929b494c411 "trunk1" 1515 ├─╯ 1516 ◆ 000000000000 "" 1517 [EOF] 1518 "#); 1519 } 1520 insta::allow_duplicates! { 1521 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1522 b: lnxrmsmo 8cc4df9d b 1523 @origin: lnxrmsmo 8cc4df9d b 1524 [EOF] 1525 "); 1526 } 1527 1528 // We can undo the change in the repo without moving the remote-tracking 1529 // bookmark 1530 let output = work_dir.run_jj(["op", "restore", "--what", "repo", &base_operation_id]); 1531 insta::allow_duplicates! { 1532 insta::assert_snapshot!(output, @r" 1533 ------- stderr ------- 1534 Restored to operation: eac759b9ab75 (2001-02-03 08:05:07) add workspace 'default' 1535 [EOF] 1536 "); 1537 } 1538 insta::allow_duplicates! { 1539 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1540 b (deleted) 1541 @origin: lnxrmsmo hidden 8cc4df9d b 1542 [EOF] 1543 "); 1544 } 1545 1546 // Now, let's demo restoring just the remote-tracking bookmark. First, let's 1547 // change our local repo state... 1548 work_dir 1549 .run_jj(["bookmark", "c", "-r@", "newbookmark"]) 1550 .success(); 1551 insta::allow_duplicates! { 1552 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1553 b (deleted) 1554 @origin: lnxrmsmo hidden 8cc4df9d b 1555 newbookmark: qpvuntsm 230dd059 (empty) (no description set) 1556 [EOF] 1557 "); 1558 } 1559 // Restoring just the remote-tracking state will not affect `newbookmark`, but 1560 // will eliminate `b@origin`. 1561 let output = work_dir.run_jj([ 1562 "op", 1563 "restore", 1564 "--what", 1565 "remote-tracking", 1566 &base_operation_id, 1567 ]); 1568 insta::allow_duplicates! { 1569 insta::assert_snapshot!(output, @r" 1570 ------- stderr ------- 1571 Restored to operation: eac759b9ab75 (2001-02-03 08:05:07) add workspace 'default' 1572 [EOF] 1573 "); 1574 } 1575 insta::allow_duplicates! { 1576 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1577 newbookmark: qpvuntsm 230dd059 (empty) (no description set) 1578 [EOF] 1579 "); 1580 } 1581} 1582 1583#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1584#[test_case(true; "spawn a git subprocess for remote calls")] 1585fn test_git_fetch_remove_fetch(subprocess: bool) { 1586 let test_env = TestEnvironment::default(); 1587 if !subprocess { 1588 test_env.add_config("git.subprocess = false"); 1589 } 1590 test_env.add_config("git.auto-local-bookmark = true"); 1591 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1592 let work_dir = test_env.work_dir("repo"); 1593 add_git_remote(&test_env, &work_dir, "origin"); 1594 1595 work_dir 1596 .run_jj(["bookmark", "create", "-r@", "origin"]) 1597 .success(); 1598 insta::allow_duplicates! { 1599 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1600 origin: qpvuntsm 230dd059 (empty) (no description set) 1601 [EOF] 1602 "); 1603 } 1604 1605 work_dir.run_jj(["git", "fetch"]).success(); 1606 insta::allow_duplicates! { 1607 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1608 origin (conflicted): 1609 + qpvuntsm 230dd059 (empty) (no description set) 1610 + qmyrypzk ab8b299e message 1611 @origin (behind by 1 commits): qmyrypzk ab8b299e message 1612 [EOF] 1613 "); 1614 } 1615 1616 work_dir 1617 .run_jj(["git", "remote", "remove", "origin"]) 1618 .success(); 1619 insta::allow_duplicates! { 1620 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1621 origin (conflicted): 1622 + qpvuntsm 230dd059 (empty) (no description set) 1623 + qmyrypzk ab8b299e message 1624 [EOF] 1625 "); 1626 } 1627 1628 work_dir 1629 .run_jj(["git", "remote", "add", "origin", "../origin"]) 1630 .success(); 1631 1632 // Check that origin@origin is properly recreated 1633 let output = work_dir.run_jj(["git", "fetch"]); 1634 insta::allow_duplicates! { 1635 insta::assert_snapshot!(output, @r" 1636 ------- stderr ------- 1637 bookmark: origin@origin [new] tracked 1638 [EOF] 1639 "); 1640 } 1641 insta::allow_duplicates! { 1642 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1643 origin (conflicted): 1644 + qpvuntsm 230dd059 (empty) (no description set) 1645 + qmyrypzk ab8b299e message 1646 @origin (behind by 1 commits): qmyrypzk ab8b299e message 1647 [EOF] 1648 "); 1649 } 1650} 1651 1652#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1653#[test_case(true; "spawn a git subprocess for remote calls")] 1654fn test_git_fetch_rename_fetch(subprocess: bool) { 1655 let test_env = TestEnvironment::default(); 1656 if !subprocess { 1657 test_env.add_config("git.subprocess = false"); 1658 } 1659 test_env.add_config("git.auto-local-bookmark = true"); 1660 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1661 let work_dir = test_env.work_dir("repo"); 1662 add_git_remote(&test_env, &work_dir, "origin"); 1663 1664 work_dir 1665 .run_jj(["bookmark", "create", "-r@", "origin"]) 1666 .success(); 1667 insta::allow_duplicates! { 1668 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1669 origin: qpvuntsm 230dd059 (empty) (no description set) 1670 [EOF] 1671 "); 1672 } 1673 1674 work_dir.run_jj(["git", "fetch"]).success(); 1675 insta::allow_duplicates! { 1676 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1677 origin (conflicted): 1678 + qpvuntsm 230dd059 (empty) (no description set) 1679 + qmyrypzk ab8b299e message 1680 @origin (behind by 1 commits): qmyrypzk ab8b299e message 1681 [EOF] 1682 "); 1683 } 1684 1685 work_dir 1686 .run_jj(["git", "remote", "rename", "origin", "upstream"]) 1687 .success(); 1688 insta::allow_duplicates! { 1689 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1690 origin (conflicted): 1691 + qpvuntsm 230dd059 (empty) (no description set) 1692 + qmyrypzk ab8b299e message 1693 @upstream (behind by 1 commits): qmyrypzk ab8b299e message 1694 [EOF] 1695 "); 1696 } 1697 1698 // Check that jj indicates that nothing has changed 1699 let output = work_dir.run_jj(["git", "fetch", "--remote", "upstream"]); 1700 insta::allow_duplicates! { 1701 insta::assert_snapshot!(output, @r" 1702 ------- stderr ------- 1703 Nothing changed. 1704 [EOF] 1705 "); 1706 } 1707} 1708 1709#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1710#[test_case(true; "spawn a git subprocess for remote calls")] 1711fn test_git_fetch_removed_bookmark(subprocess: bool) { 1712 let test_env = TestEnvironment::default(); 1713 if !subprocess { 1714 test_env.add_config("git.subprocess = false"); 1715 } 1716 test_env.add_config("git.auto-local-bookmark = true"); 1717 let source_dir = test_env.work_dir("source"); 1718 git::init(source_dir.root()); 1719 1720 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated 1721 let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]); 1722 insta::allow_duplicates! { 1723 insta::assert_snapshot!(output, @r#" 1724 ------- stderr ------- 1725 Fetching into new repo in "$TEST_ENV/target" 1726 Nothing changed. 1727 [EOF] 1728 "#); 1729 } 1730 let target_dir = test_env.work_dir("target"); 1731 1732 let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir); 1733 insta::allow_duplicates! { 1734 insta::assert_snapshot!(source_log, @r#" 1735 ===== Source git repo contents ===== 1736 @ 8cc4df9dd488 "b" b 1737 │ ○ e2a95b19b745 "a2" a2 1738 ├─╯ 1739 │ ○ dd42071fe1ad "a1" a1 1740 ├─╯ 1741 ○ 9929b494c411 "trunk1" trunk1 1742 ◆ 000000000000 "" 1743 [EOF] 1744 "#); 1745 } 1746 1747 // Fetch all bookmarks 1748 let output = target_dir.run_jj(["git", "fetch"]); 1749 insta::allow_duplicates! { 1750 insta::assert_snapshot!(output, @r" 1751 ------- stderr ------- 1752 bookmark: a1@origin [new] tracked 1753 bookmark: a2@origin [new] tracked 1754 bookmark: b@origin [new] tracked 1755 bookmark: trunk1@origin [new] tracked 1756 [EOF] 1757 "); 1758 } 1759 insta::allow_duplicates! { 1760 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1761 @ 230dd059e1b0 "" 1762 │ ○ 8cc4df9dd488 "b" b 1763 │ │ ○ e2a95b19b745 "a2" a2 1764 │ ├─╯ 1765 │ │ ○ dd42071fe1ad "a1" a1 1766 │ ├─╯ 1767 │ ○ 9929b494c411 "trunk1" trunk1 1768 ├─╯ 1769 ◆ 000000000000 "" 1770 [EOF] 1771 "#); 1772 } 1773 1774 // Remove a2 bookmark in origin 1775 source_dir 1776 .run_jj(["bookmark", "forget", "--include-remotes", "a2"]) 1777 .success(); 1778 1779 // Fetch bookmark a1 from origin and check that a2 is still there 1780 let output = target_dir.run_jj(["git", "fetch", "--branch", "a1"]); 1781 insta::allow_duplicates! { 1782 insta::assert_snapshot!(output, @r" 1783 ------- stderr ------- 1784 Nothing changed. 1785 [EOF] 1786 "); 1787 } 1788 insta::allow_duplicates! { 1789 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1790 @ 230dd059e1b0 "" 1791 │ ○ 8cc4df9dd488 "b" b 1792 │ │ ○ e2a95b19b745 "a2" a2 1793 │ ├─╯ 1794 │ │ ○ dd42071fe1ad "a1" a1 1795 │ ├─╯ 1796 │ ○ 9929b494c411 "trunk1" trunk1 1797 ├─╯ 1798 ◆ 000000000000 "" 1799 [EOF] 1800 "#); 1801 } 1802 1803 // Fetch bookmarks a2 from origin, and check that it has been removed locally 1804 let output = target_dir.run_jj(["git", "fetch", "--branch", "a2"]); 1805 insta::allow_duplicates! { 1806 insta::assert_snapshot!(output, @r" 1807 ------- stderr ------- 1808 bookmark: a2@origin [deleted] untracked 1809 Abandoned 1 commits that are no longer reachable. 1810 [EOF] 1811 "); 1812 } 1813 insta::allow_duplicates! { 1814 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1815 @ 230dd059e1b0 "" 1816 │ ○ 8cc4df9dd488 "b" b 1817 │ │ ○ dd42071fe1ad "a1" a1 1818 │ ├─╯ 1819 │ ○ 9929b494c411 "trunk1" trunk1 1820 ├─╯ 1821 ◆ 000000000000 "" 1822 [EOF] 1823 "#); 1824 } 1825} 1826 1827#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1828#[test_case(true; "spawn a git subprocess for remote calls")] 1829fn test_git_fetch_removed_parent_bookmark(subprocess: bool) { 1830 let test_env = TestEnvironment::default(); 1831 if !subprocess { 1832 test_env.add_config("git.subprocess = false"); 1833 } 1834 test_env.add_config("git.auto-local-bookmark = true"); 1835 let source_dir = test_env.work_dir("source"); 1836 git::init(source_dir.root()); 1837 1838 // Clone an empty repo. The target repo is a normal `jj` repo, *not* colocated 1839 let output = test_env.run_jj_in(".", ["git", "clone", "source", "target"]); 1840 insta::allow_duplicates! { 1841 insta::assert_snapshot!(output, @r#" 1842 ------- stderr ------- 1843 Fetching into new repo in "$TEST_ENV/target" 1844 Nothing changed. 1845 [EOF] 1846 "#); 1847 } 1848 let target_dir = test_env.work_dir("target"); 1849 1850 let source_log = create_colocated_repo_and_bookmarks_from_trunk1(&source_dir); 1851 insta::allow_duplicates! { 1852 insta::assert_snapshot!(source_log, @r#" 1853 ===== Source git repo contents ===== 1854 @ 8cc4df9dd488 "b" b 1855 │ ○ e2a95b19b745 "a2" a2 1856 ├─╯ 1857 │ ○ dd42071fe1ad "a1" a1 1858 ├─╯ 1859 ○ 9929b494c411 "trunk1" trunk1 1860 ◆ 000000000000 "" 1861 [EOF] 1862 "#); 1863 } 1864 1865 // Fetch all bookmarks 1866 let output = target_dir.run_jj(["git", "fetch"]); 1867 insta::allow_duplicates! { 1868 insta::assert_snapshot!(output, @r" 1869 ------- stderr ------- 1870 bookmark: a1@origin [new] tracked 1871 bookmark: a2@origin [new] tracked 1872 bookmark: b@origin [new] tracked 1873 bookmark: trunk1@origin [new] tracked 1874 [EOF] 1875 "); 1876 } 1877 insta::allow_duplicates! { 1878 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1879 @ 230dd059e1b0 "" 1880 │ ○ 8cc4df9dd488 "b" b 1881 │ │ ○ e2a95b19b745 "a2" a2 1882 │ ├─╯ 1883 │ │ ○ dd42071fe1ad "a1" a1 1884 │ ├─╯ 1885 │ ○ 9929b494c411 "trunk1" trunk1 1886 ├─╯ 1887 ◆ 000000000000 "" 1888 [EOF] 1889 "#); 1890 } 1891 1892 // Remove all bookmarks in origin. 1893 source_dir 1894 .run_jj(["bookmark", "forget", "--include-remotes", "glob:*"]) 1895 .success(); 1896 1897 // Fetch bookmarks master, trunk1 and a1 from origin and check that only those 1898 // bookmarks have been removed and that others were not rebased because of 1899 // abandoned commits. 1900 let output = target_dir.run_jj([ 1901 "git", "fetch", "--branch", "master", "--branch", "trunk1", "--branch", "a1", 1902 ]); 1903 insta::allow_duplicates! { 1904 insta::assert_snapshot!(output, @r" 1905 ------- stderr ------- 1906 bookmark: a1@origin [deleted] untracked 1907 bookmark: trunk1@origin [deleted] untracked 1908 Abandoned 1 commits that are no longer reachable. 1909 Warning: No branch matching `master` found on any specified/configured remote 1910 [EOF] 1911 "); 1912 } 1913 insta::allow_duplicates! { 1914 insta::assert_snapshot!(get_log_output(&target_dir), @r#" 1915 @ 230dd059e1b0 "" 1916 │ ○ 8cc4df9dd488 "b" b 1917 │ │ ○ e2a95b19b745 "a2" a2 1918 │ ├─╯ 1919 │ ○ 9929b494c411 "trunk1" 1920 ├─╯ 1921 ◆ 000000000000 "" 1922 [EOF] 1923 "#); 1924 } 1925} 1926 1927#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 1928#[test_case(true; "spawn a git subprocess for remote calls")] 1929fn test_git_fetch_remote_only_bookmark(subprocess: bool) { 1930 let test_env = TestEnvironment::default(); 1931 if !subprocess { 1932 test_env.add_config("git.subprocess = false"); 1933 } 1934 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 1935 let work_dir = test_env.work_dir("repo"); 1936 1937 // Create non-empty git repo to add as a remote 1938 let git_repo_path = test_env.env_root().join("git-repo"); 1939 let git_repo = git::init(git_repo_path); 1940 work_dir 1941 .run_jj(["git", "remote", "add", "origin", "../git-repo"]) 1942 .success(); 1943 1944 // Create a commit and a bookmark in the git repo 1945 let commit_result = git::add_commit( 1946 &git_repo, 1947 "refs/heads/feature1", 1948 "file", 1949 b"content", 1950 "message", 1951 &[], 1952 ); 1953 1954 // Fetch using git.auto_local_bookmark = true 1955 test_env.add_config("git.auto-local-bookmark = true"); 1956 work_dir 1957 .run_jj(["git", "fetch", "--remote=origin"]) 1958 .success(); 1959 insta::allow_duplicates! { 1960 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1961 feature1: qomsplrm ebeb70d8 message 1962 @origin: qomsplrm ebeb70d8 message 1963 [EOF] 1964 "); 1965 } 1966 1967 git::write_commit( 1968 &git_repo, 1969 "refs/heads/feature2", 1970 commit_result.tree_id, 1971 "message", 1972 &[], 1973 ); 1974 1975 // Fetch using git.auto_local_bookmark = false 1976 test_env.add_config("git.auto-local-bookmark = false"); 1977 work_dir 1978 .run_jj(["git", "fetch", "--remote=origin"]) 1979 .success(); 1980 insta::allow_duplicates! { 1981 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 1982 @ 230dd059e1b0 "" 1983 │ ◆ ebeb70d8c5f9 "message" feature1 feature2@origin 1984 ├─╯ 1985 ◆ 000000000000 "" 1986 [EOF] 1987 "#); 1988 } 1989 insta::allow_duplicates! { 1990 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 1991 feature1: qomsplrm ebeb70d8 message 1992 @origin: qomsplrm ebeb70d8 message 1993 feature2@origin: qomsplrm ebeb70d8 message 1994 [EOF] 1995 "); 1996 } 1997} 1998 1999#[cfg_attr(feature = "git2", test_case(false; "use git2 for remote calls"))] 2000#[test_case(true; "spawn a git subprocess for remote calls")] 2001fn test_git_fetch_preserve_commits_across_repos(subprocess: bool) { 2002 let test_env = TestEnvironment::default(); 2003 if !subprocess { 2004 test_env.add_config("git.subprocess = false"); 2005 } 2006 test_env.add_config("git.auto-local-bookmark = true"); 2007 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 2008 let work_dir = test_env.work_dir("repo"); 2009 2010 let upstream_repo = add_git_remote(&test_env, &work_dir, "upstream"); 2011 2012 let fork_path = test_env.env_root().join("fork"); 2013 let fork_repo = clone_git_remote_into(&test_env, "upstream", "fork"); 2014 work_dir 2015 .run_jj(["git", "remote", "add", "fork", "../fork"]) 2016 .success(); 2017 2018 // add commit to fork remote in another branch 2019 add_commit_to_branch(&fork_repo, "feature"); 2020 2021 // fetch remote bookmarks 2022 work_dir 2023 .run_jj(["git", "fetch", "--remote=fork", "--remote=upstream"]) 2024 .success(); 2025 insta::allow_duplicates! { 2026 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 2027 @ 230dd059e1b0 "" 2028 │ ○ bcd7cd779791 "message" upstream 2029 ├─╯ 2030 │ ○ 16ec9ef2877a "message" feature 2031 ├─╯ 2032 ◆ 000000000000 "" 2033 [EOF] 2034 "#); 2035 } 2036 insta::allow_duplicates! { 2037 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 2038 feature: srwrtuky 16ec9ef2 message 2039 @fork: srwrtuky 16ec9ef2 message 2040 upstream: zkvzklqn bcd7cd77 message 2041 @fork: zkvzklqn bcd7cd77 message 2042 @upstream: zkvzklqn bcd7cd77 message 2043 [EOF] 2044 "); 2045 } 2046 2047 // merge fork/feature into the upstream/upstream 2048 git::add_remote(upstream_repo.git_dir(), "fork", fork_path.to_str().unwrap()); 2049 git::fetch(upstream_repo.git_dir(), "fork"); 2050 2051 let base_id = upstream_repo 2052 .find_reference("refs/heads/upstream") 2053 .unwrap() 2054 .peel_to_commit() 2055 .unwrap() 2056 .id() 2057 .detach(); 2058 2059 let fork_id = upstream_repo 2060 .find_reference("refs/remotes/fork/feature") 2061 .unwrap() 2062 .peel_to_commit() 2063 .unwrap() 2064 .id() 2065 .detach(); 2066 2067 git::write_commit( 2068 &upstream_repo, 2069 "refs/heads/upstream", 2070 upstream_repo.empty_tree().id().detach(), 2071 "merge", 2072 &[base_id, fork_id], 2073 ); 2074 2075 // remove branch on the fork 2076 fork_repo 2077 .find_reference("refs/heads/feature") 2078 .unwrap() 2079 .delete() 2080 .unwrap(); 2081 2082 // fetch again on the jj repo, first looking at fork and then at upstream 2083 work_dir 2084 .run_jj(["git", "fetch", "--remote=fork", "--remote=upstream"]) 2085 .success(); 2086 insta::allow_duplicates! { 2087 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 2088 @ 230dd059e1b0 "" 2089 │ ○ f3e9250bd003 "merge" upstream* 2090 │ ├─╮ 2091 │ │ ○ 16ec9ef2877a "message" 2092 ├───╯ 2093 │ ○ bcd7cd779791 "message" upstream@fork 2094 ├─╯ 2095 ◆ 000000000000 "" 2096 [EOF] 2097 "#); 2098 } 2099 insta::allow_duplicates! { 2100 insta::assert_snapshot!(get_bookmark_output(&work_dir), @r" 2101 upstream: trrkvuqr f3e9250b merge 2102 @fork (behind by 2 commits): zkvzklqn bcd7cd77 message 2103 @upstream: trrkvuqr f3e9250b merge 2104 [EOF] 2105 "); 2106 } 2107} 2108 2109// TODO: Remove with the `git.subprocess` setting. 2110#[cfg(not(feature = "git2"))] 2111#[test] 2112fn test_git_fetch_git2_warning() { 2113 let test_env = TestEnvironment::default(); 2114 test_env.add_config("git.subprocess = false"); 2115 test_env.add_config("git.auto-local-bookmark = true"); 2116 test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 2117 let work_dir = test_env.work_dir("repo"); 2118 add_git_remote(&test_env, &work_dir, "origin"); 2119 2120 let output = work_dir.run_jj(["git", "fetch"]); 2121 insta::assert_snapshot!(output, @r#" 2122 ------- stderr ------- 2123 Warning: Deprecated config: jj was compiled without `git.subprocess = false` support 2124 bookmark: origin@origin [new] tracked 2125 [EOF] 2126 "#); 2127}