diff --git a/lib/tests/test_git.rs b/lib/tests/test_git.rs index f10e43310..5c88d1078 100644 --- a/lib/tests/test_git.rs +++ b/lib/tests/test_git.rs @@ -489,6 +489,124 @@ fn test_import_refs_reimport_with_deleted_remote_ref() { assert_eq!(*view.heads(), expected_heads); } +/// This test is nearly identical to the previous one, except the branches are +/// moved sideways instead of being deleted. +#[test] +fn test_import_refs_reimport_with_moved_remote_ref() { + let settings = testutils::user_settings(); + let git_settings = GitSettings::default(); + let test_workspace = TestRepo::init(true); + let repo = &test_workspace.repo; + let git_repo = get_git_repo(repo); + + let commit_base = empty_git_commit(&git_repo, "refs/heads/main", &[]); + let commit_main = empty_git_commit(&git_repo, "refs/heads/main", &[&commit_base]); + let commit_remote_only = empty_git_commit( + &git_repo, + "refs/remotes/origin/feature-remote-only", + &[&commit_base], + ); + let commit_remote_and_local = empty_git_commit( + &git_repo, + "refs/remotes/origin/feature-remote-and-local", + &[&commit_base], + ); + git_ref( + &git_repo, + "refs/heads/feature-remote-and-local", + commit_remote_and_local.id(), + ); + + let mut tx = repo.start_transaction(&settings, "test"); + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + let repo = tx.commit(); + + let expected_heads = hashset! { + jj_id(&commit_main), + jj_id(dbg!(&commit_remote_only)), + jj_id(dbg!(&commit_remote_and_local)), + }; + let view = repo.view(); + assert_eq!(*view.heads(), expected_heads); + assert_eq!(view.branches().len(), 3); + assert_eq!( + view.branches().get("feature-remote-only"), + Some(&BranchTarget { + // Even though the git repo does not have a local branch for `feature-remote-only`, jj + // creates one. This follows the model explained in docs/branches.md. + local_target: Some(RefTarget::Normal(jj_id(&commit_remote_only))), + remote_targets: btreemap! { + "origin".to_string() => RefTarget::Normal(jj_id(&commit_remote_only)) + }, + }), + ); + assert_eq!( + view.branches().get("feature-remote-and-local"), + Some(&BranchTarget { + local_target: Some(RefTarget::Normal(jj_id(&commit_remote_and_local))), + remote_targets: btreemap! { + "origin".to_string() => RefTarget::Normal(jj_id(&commit_remote_and_local)) + }, + }), + ); + view.branches().get("main").unwrap(); // branch #3 of 3 + + // Simulate fetching from a remote where feature-remote-only and + // feature-remote-and-local branches were moved. This leads to the + // following import moving the corresponding local branches. + delete_git_ref(&git_repo, "refs/remotes/origin/feature-remote-only"); + delete_git_ref(&git_repo, "refs/remotes/origin/feature-remote-and-local"); + let new_commit_remote_only = empty_git_commit( + &git_repo, + "refs/remotes/origin/feature-remote-only", + &[&commit_base], + ); + let new_commit_remote_and_local = empty_git_commit( + &git_repo, + "refs/remotes/origin/feature-remote-and-local", + &[&commit_base], + ); + + let mut tx = repo.start_transaction(&settings, "test"); + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + let repo = tx.commit(); + + let view = repo.view(); + assert_eq!(view.branches().len(), 3); + // The local branches are moved + assert_eq!( + view.branches().get("feature-remote-only"), + Some(&BranchTarget { + local_target: Some(RefTarget::Normal(jj_id(&new_commit_remote_only))), + remote_targets: btreemap! { + "origin".to_string() => RefTarget::Normal(jj_id(&new_commit_remote_only)) + }, + }), + ); + assert_eq!( + view.branches().get("feature-remote-and-local"), + Some(&BranchTarget { + local_target: Some(RefTarget::Normal(jj_id(&new_commit_remote_and_local))), + remote_targets: btreemap! { + "origin".to_string() => RefTarget::Normal(jj_id(&new_commit_remote_and_local)) + }, + }), + ); + view.branches().get("main").unwrap(); // branch #3 of 3 + let expected_heads = hashset! { + jj_id(&commit_main), + jj_id(&new_commit_remote_and_local), + jj_id(&new_commit_remote_only), + // Neither commit_remote_only nor commit_remote_and_local should be + // listed as a head. commit_remote_only was never affected by #864, + // but commit_remote_and_local is. + jj_id(&commit_remote_and_local), + }; + assert_eq!(*view.heads(), expected_heads); +} + #[test] fn test_import_refs_reimport_git_head_with_fixed_ref() { // Simulate external `git checkout` in colocated repo, from named branch. diff --git a/tests/test_git_colocated.rs b/tests/test_git_colocated.rs index 4f46969a3..fa2847dc7 100644 --- a/tests/test_git_colocated.rs +++ b/tests/test_git_colocated.rs @@ -234,36 +234,45 @@ fn test_git_colocated_conflicting_git_refs() { } #[test] -fn test_git_colocated_fetch_deleted_branch() { +fn test_git_colocated_fetch_deleted_or_moved_branch() { let test_env = TestEnvironment::default(); let origin_path = test_env.env_root().join("origin"); git2::Repository::init(&origin_path).unwrap(); test_env.jj_cmd_success(&origin_path, &["init", "--git-repo=."]); test_env.jj_cmd_success(&origin_path, &["describe", "-m=A"]); test_env.jj_cmd_success(&origin_path, &["branch", "create", "A"]); - test_env.jj_cmd_success(&origin_path, &["new", "-m=B"]); - test_env.jj_cmd_success(&origin_path, &["branch", "create", "B"]); - test_env.jj_cmd_success(&origin_path, &["new", "-m=C"]); + test_env.jj_cmd_success(&origin_path, &["new", "-m=B_to_delete"]); + test_env.jj_cmd_success(&origin_path, &["branch", "create", "B_to_delete"]); + test_env.jj_cmd_success(&origin_path, &["new", "-m=original C", "@-"]); + test_env.jj_cmd_success(&origin_path, &["branch", "create", "C_to_move"]); let clone_path = test_env.env_root().join("clone"); git2::Repository::clone(origin_path.to_str().unwrap(), &clone_path).unwrap(); test_env.jj_cmd_success(&clone_path, &["init", "--git-repo=."]); test_env.jj_cmd_success(&clone_path, &["new", "A"]); insta::assert_snapshot!(get_log_output(&test_env, &clone_path), @r###" - @ 1fa8b2e27c8c3da9764bda953dd81a06fb292d1a - │ ◉ e1f4268fabd2c84e880c5eb5bd87e076180fc8e3 B + @ 0335878796213c3a701f1c9c34dcae242bee4131 + │ ◉ 8d4e006fd63547965fbc3a26556a9aa531076d32 C_to_move + ├─╯ + │ ◉ 929e298ae9edf969b405a304c75c10457c47d52c B_to_delete ├─╯ ◉ a86754f975f953fa25da4265764adc0c62e9ce6b A master HEAD@git ◉ 0000000000000000000000000000000000000000 "###); - test_env.jj_cmd_success(&origin_path, &["branch", "delete", "B"]); + test_env.jj_cmd_success(&origin_path, &["branch", "delete", "B_to_delete"]); + // Move branch C sideways + test_env.jj_cmd_success(&origin_path, &["describe", "C_to_move", "-m", "moved C"]); let stdout = test_env.jj_cmd_success(&clone_path, &["git", "fetch"]); insta::assert_snapshot!(stdout, @""); - // TODO: e1f4 should have been abandoned (#864) + // TODO: 929e and 8d4e should have been abandoned (#864) insta::assert_snapshot!(get_log_output(&test_env, &clone_path), @r###" - @ 1fa8b2e27c8c3da9764bda953dd81a06fb292d1a - │ ◉ e1f4268fabd2c84e880c5eb5bd87e076180fc8e3 + ◉ 04fd29df05638156b20044b3b6136b42abcb09ab C_to_move + │ @ 0335878796213c3a701f1c9c34dcae242bee4131 + ├─╯ + │ ◉ 8d4e006fd63547965fbc3a26556a9aa531076d32 + ├─╯ + │ ◉ 929e298ae9edf969b405a304c75c10457c47d52c ├─╯ ◉ a86754f975f953fa25da4265764adc0c62e9ce6b A master HEAD@git ◉ 0000000000000000000000000000000000000000