diff --git a/lib/tests/test_git.rs b/lib/tests/test_git.rs index fa6e117fc..5133b7632 100644 --- a/lib/tests/test_git.rs +++ b/lib/tests/test_git.rs @@ -312,6 +312,109 @@ fn test_import_refs_reimport_git_head_counts() { assert!(tx.mut_repo().view().heads().contains(&jj_id(&commit))); } +#[test] +fn test_import_refs_reimport_git_head_without_ref() { + // Simulate external `git checkout` in colocated repo, from anonymous branch. + let settings = testutils::user_settings(); + let git_settings = GitSettings::default(); + let test_repo = TestRepo::init(true); + let repo = &test_repo.repo; + let git_repo = repo.store().git_repo().unwrap(); + + // First, HEAD points to commit1. + let mut tx = repo.start_transaction(&settings, "test"); + let commit1 = write_random_commit(tx.mut_repo(), &settings); + let commit2 = write_random_commit(tx.mut_repo(), &settings); + git_repo.set_head_detached(git_id(&commit1)).unwrap(); + + // Import HEAD. + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + assert!(tx.mut_repo().view().heads().contains(commit1.id())); + assert!(tx.mut_repo().view().heads().contains(commit2.id())); + + // Move HEAD to commit2 (by e.g. `git checkout` command) + git_repo.set_head_detached(git_id(&commit2)).unwrap(); + + // Reimport HEAD, which abandons the old HEAD branch because jj thinks it + // would be rewritten by e.g. `git commit --amend` command. + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + assert!(!tx.mut_repo().view().heads().contains(commit1.id())); + assert!(tx.mut_repo().view().heads().contains(commit2.id())); +} + +#[test] +fn test_import_refs_reimport_git_head_with_moved_ref() { + // Simulate external history rewriting in colocated repo. + let settings = testutils::user_settings(); + let git_settings = GitSettings::default(); + let test_repo = TestRepo::init(true); + let repo = &test_repo.repo; + let git_repo = repo.store().git_repo().unwrap(); + + // First, both HEAD and main point to commit1. + let mut tx = repo.start_transaction(&settings, "test"); + let commit1 = write_random_commit(tx.mut_repo(), &settings); + let commit2 = write_random_commit(tx.mut_repo(), &settings); + git_repo + .reference("refs/heads/main", git_id(&commit1), true, "test") + .unwrap(); + git_repo.set_head_detached(git_id(&commit1)).unwrap(); + + // Import HEAD and main. + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + assert!(tx.mut_repo().view().heads().contains(commit1.id())); + assert!(tx.mut_repo().view().heads().contains(commit2.id())); + + // Move both HEAD and main to commit2 (by e.g. `git commit --amend` command) + git_repo + .reference("refs/heads/main", git_id(&commit2), true, "test") + .unwrap(); + git_repo.set_head_detached(git_id(&commit2)).unwrap(); + + // Reimport HEAD and main, which abandons the old HEAD/main branch. + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + assert!(!tx.mut_repo().view().heads().contains(commit1.id())); + assert!(tx.mut_repo().view().heads().contains(commit2.id())); +} + +#[test] +fn test_import_refs_reimport_git_head_with_fixed_ref() { + // Simulate external `git checkout` in colocated repo, from named branch. + let settings = testutils::user_settings(); + let git_settings = GitSettings::default(); + let test_repo = TestRepo::init(true); + let repo = &test_repo.repo; + let git_repo = repo.store().git_repo().unwrap(); + + // First, both HEAD and main point to commit1. + let mut tx = repo.start_transaction(&settings, "test"); + let commit1 = write_random_commit(tx.mut_repo(), &settings); + let commit2 = write_random_commit(tx.mut_repo(), &settings); + git_repo + .reference("refs/heads/main", git_id(&commit1), true, "test") + .unwrap(); + git_repo.set_head_detached(git_id(&commit1)).unwrap(); + + // Import HEAD and main. + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + assert!(tx.mut_repo().view().heads().contains(commit1.id())); + assert!(tx.mut_repo().view().heads().contains(commit2.id())); + + // Move only HEAD to commit2 (by e.g. `git checkout` command) + git_repo.set_head_detached(git_id(&commit2)).unwrap(); + + // Reimport HEAD, which shouldn't abandon the old HEAD branch. + git::import_refs(tx.mut_repo(), &git_repo, &git_settings).unwrap(); + tx.mut_repo().rebase_descendants(&settings).unwrap(); + assert!(tx.mut_repo().view().heads().contains(commit1.id())); + assert!(tx.mut_repo().view().heads().contains(commit2.id())); +} + #[test] fn test_import_refs_reimport_all_from_root_removed() { // Test that if a chain of commits all the way from the root gets unreferenced, diff --git a/tests/test_git_colocated.rs b/tests/test_git_colocated.rs index b32edac55..5e8e4dfb5 100644 --- a/tests/test_git_colocated.rs +++ b/tests/test_git_colocated.rs @@ -267,6 +267,44 @@ fn test_git_colocated_fetch_deleted_branch() { "###); } +#[test] +fn test_git_colocated_external_checkout() { + let test_env = TestEnvironment::default(); + let repo_path = test_env.env_root().join("repo"); + let git_repo = git2::Repository::init(&repo_path).unwrap(); + test_env.jj_cmd_success(&repo_path, &["init", "--git-repo=."]); + test_env.jj_cmd_success(&repo_path, &["ci", "-m=A"]); + test_env.jj_cmd_success(&repo_path, &["new", "-m=B", "root"]); + test_env.jj_cmd_success(&repo_path, &["new"]); + + // Checked out anonymous branch + insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" + @ 53637cd508ff02427dd78eca98f5b2450a6370ce + ◉ 66f4d1806ae41bd604f69155dece64062a0056cf + │ ◉ a86754f975f953fa25da4265764adc0c62e9ce6b master + ├─╯ + ◉ 0000000000000000000000000000000000000000 + "###); + + // Check out another branch by external command + git_repo + .set_head_detached( + git_repo + .find_reference("refs/heads/master") + .unwrap() + .target() + .unwrap(), + ) + .unwrap(); + + // The old HEAD branch gets abandoned because jj thinks it has been rewritten. + insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" + @ 0521ce3b8c4e29aab79f3c750e2845dcbc4c3874 + ◉ a86754f975f953fa25da4265764adc0c62e9ce6b master + ◉ 0000000000000000000000000000000000000000 + "###); +} + #[test] fn test_git_colocated_squash_undo() { let test_env = TestEnvironment::default();