diff --git a/lib/tests/test_id_prefix.rs b/lib/tests/test_id_prefix.rs index a9ec064ddd..f80c8a52c3 100644 --- a/lib/tests/test_id_prefix.rs +++ b/lib/tests/test_id_prefix.rs @@ -13,7 +13,7 @@ // limitations under the License. use itertools::Itertools; -use jj_lib::backend::{CommitId, MillisSinceEpoch, Signature, Timestamp}; +use jj_lib::backend::{ChangeId, CommitId, MillisSinceEpoch, Signature, Timestamp}; use jj_lib::id_prefix::IdPrefixContext; use jj_lib::object_id::PrefixResolution::{AmbiguousMatch, NoMatch, SingleMatch}; use jj_lib::object_id::{HexPrefix, ObjectId}; @@ -253,3 +253,134 @@ fn test_id_prefix() { AmbiguousMatch ); } + +#[test] +fn test_id_prefix_divergent() { + let settings = testutils::user_settings(); + let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git); + let repo = &test_repo.repo; + let root_commit_id = repo.store().root_commit_id(); + + let mut tx = repo.start_transaction(&settings); + let mut create_commit_with_change_id = |parent_id: &CommitId, change_id: Option| { + let signature = Signature { + name: "Some One".to_string(), + email: "some.one@example.com".to_string(), + timestamp: Timestamp { + timestamp: MillisSinceEpoch(0), + tz_offset: 0, + }, + }; + let mut builder = tx + .mut_repo() + .new_commit( + &settings, + vec![parent_id.clone()], + repo.store().empty_merged_tree_id(), + ) + .set_author(signature.clone()) + .set_committer(signature); + if let Some(change_id) = change_id { + builder = builder.set_change_id(change_id); + } + builder.write().unwrap() + }; + + let first_commit = create_commit_with_change_id(root_commit_id, None); + let first_change_id = first_commit.change_id(); + let mut different_change_id_bytes = first_change_id.to_bytes(); + // Change the second byte of the change id, which corresponds to the third and + // fourth letter. + different_change_id_bytes[1] = different_change_id_bytes[1].wrapping_add(128); + let different_change_id = ChangeId::from_bytes(&different_change_id_bytes); + + let second_commit = + create_commit_with_change_id(first_commit.id(), Some(different_change_id.clone())); + let third_commit_divergent_with_second = + create_commit_with_change_id(second_commit.id(), Some(different_change_id)); + let commits = [ + first_commit.clone(), + second_commit.clone(), + third_commit_divergent_with_second.clone(), + ]; + let repo = tx.commit("test"); + + // Print the commit IDs and change IDs for reference + let change_prefixes = commits + .iter() + .enumerate() + .map(|(i, commit)| format!("{} {}", &commit.change_id().hex()[..4], i)) + .sorted() + .join("\n"); + insta::assert_snapshot!(change_prefixes, @r###" + 7811 0 + 7891 1 + 7891 2 + "###); + let commit_prefixes = commits + .iter() + .enumerate() + .map(|(i, commit)| format!("{} {}", &commit.id().hex()[..4], i)) + .sorted() + .join("\n"); + insta::assert_snapshot!(commit_prefixes, @r###" + 2a66 2 + 5d30 1 + ce90 0 + "###); + + let prefix = |x| HexPrefix::new(x).unwrap(); + + // Without a disambiguation revset + // --------------------------------------------------------------------------------------------- + let c = IdPrefixContext::default(); + assert_eq!( + c.shortest_change_prefix_len(repo.as_ref(), commits[0].change_id()), + 3 + ); + assert_eq!( + c.shortest_change_prefix_len(repo.as_ref(), commits[1].change_id()), + 3 + ); + assert_eq!( + c.shortest_change_prefix_len(repo.as_ref(), commits[2].change_id()), + 3 + ); + assert_eq!( + c.resolve_change_prefix(repo.as_ref(), &prefix("7")), + AmbiguousMatch + ); + assert_eq!( + c.resolve_change_prefix(repo.as_ref(), &prefix("781")), + SingleMatch(vec![first_commit.id().clone()]) + ); + assert_eq!( + c.resolve_change_prefix(repo.as_ref(), &prefix("789")), + SingleMatch(vec![ + second_commit.id().clone(), + third_commit_divergent_with_second.id().clone() + ]) + ); + + // Now, disambiguate within the revset containing only the second commit + // --------------------------------------------------------------------------------------------- + let expression = RevsetExpression::commits(vec![second_commit.id().clone()]); + let c = c.disambiguate_within(expression); + // The prefix is now shorter + assert_eq!( + c.shortest_change_prefix_len(repo.as_ref(), second_commit.change_id()), + 1 + ); + // We can still resolve commits outside the set + assert_eq!( + c.resolve_change_prefix(repo.as_ref(), &prefix("781")), + SingleMatch(vec![first_commit.id().clone()]) + ); + // Short prefix does not find the first commit. (This is correct) + // TODO(#2476): Looking up the divergent commits by their change id prefix only + // finds the id within the lookup set. + assert_eq!( + c.resolve_change_prefix(repo.as_ref(), &prefix("7")), + SingleMatch(vec![second_commit.id().clone()]) + ); +}