// Copyright 2024 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::fmt::Write as _;use std::rc::Rc; use itertools::Itertools as _;use jj_lib::annotate::FileAnnotation;use jj_lib::annotate::FileAnnotator;use jj_lib::backend::CommitId;use jj_lib::backend::MergedTreeId;use jj_lib::backend::MillisSinceEpoch;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::backend::TreeValue;use jj_lib::commit::Commit;use jj_lib::repo::MutableRepo;use jj_lib::repo::Repo;use jj_lib::repo_path::RepoPath;use jj_lib::revset::ResolvedRevsetExpression;use jj_lib::revset::RevsetExpression;use testutils::create_tree;use testutils::read_file;use testutils::repo_path;use testutils::TestRepo; fn create_commit_fn( mut_repo: &mut MutableRepo,) -> impl FnMut(&str, &[&CommitId], MergedTreeId) -> Commit + use<'_> { // stabilize commit IDs for ease of debugging let signature = Signature { name: "Some One".to_owned(), email: "some.one@example.com".to_owned(), timestamp: Timestamp { timestamp: MillisSinceEpoch(0), tz_offset: 0, }, }; move |description, parent_ids, tree_id| { let parent_ids = parent_ids.iter().map(|&id| id.clone()).collect(); mut_repo .new_commit(parent_ids, tree_id) .set_author(signature.clone()) .set_committer(signature.clone()) .set_description(description) .write() .unwrap() }} fn annotate(repo: &dyn Repo, commit: &Commit, file_path: &RepoPath) -> String { let domain = RevsetExpression::all(); annotate_within(repo, commit, &domain, file_path)} fn annotate_within( repo: &dyn Repo, commit: &Commit, domain: &Rc<ResolvedRevsetExpression>, file_path: &RepoPath,) -> String { let mut annotator = FileAnnotator::from_commit(commit, file_path).unwrap(); annotator.compute(repo, domain).unwrap(); format_annotation(repo, &annotator.to_annotation())} fn annotate_parent_tree(repo: &dyn Repo, commit: &Commit, file_path: &RepoPath) -> String { let tree = commit.parent_tree(repo).unwrap(); let text = match tree.path_value(file_path).unwrap().into_resolved().unwrap() { Some(TreeValue::File { id, .. }) => read_file(repo.store(), file_path, &id), value => panic!("unexpected path value: {value:?}"), }; let mut annotator = FileAnnotator::with_file_content(commit.id(), file_path, text); annotator.compute(repo, &RevsetExpression::all()).unwrap(); format_annotation(repo, &annotator.to_annotation())} fn format_annotation(repo: &dyn Repo, annotation: &FileAnnotation) -> String { let mut output = String::new(); for (origin, line) in annotation.line_origins() { let line_origin = origin.unwrap_or_else(|line_origin| line_origin); let line_number = line_origin.line_number + 1; let commit = repo.store().get_commit(&line_origin.commit_id).unwrap(); let desc = commit.description().trim_end(); let sigil = if origin.is_err() { '*' } else { ' ' }; write!(output, "{desc}:{line_number}{sigil}: {line}").unwrap(); } output} #[test]fn test_annotate_linear() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let root_commit_id = repo.store().root_commit_id(); let file_path = repo_path("file"); let mut tx = repo.start_transaction(); let mut create_commit = create_commit_fn(tx.repo_mut()); let content1 = ""; let content2 = "2a\n2b\n"; let content3 = "2b\n3\n"; let tree1 = create_tree(repo, &[(file_path, content1)]); let tree2 = create_tree(repo, &[(file_path, content2)]); let tree3 = create_tree(repo, &[(file_path, content3)]); let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); let commit3 = create_commit("commit3", &[commit2.id()], tree3.id()); let commit4 = create_commit("commit4", &[commit3.id()], tree3.id()); // empty commit drop(create_commit); insta::assert_snapshot!(annotate(tx.repo(), &commit1, file_path), @""); insta::assert_snapshot!(annotate(tx.repo(), &commit2, file_path), @r" commit2:1 : 2a commit2:2 : 2b "); insta::assert_snapshot!(annotate(tx.repo(), &commit3, file_path), @r" commit2:2 : 2b commit3:2 : 3 "); insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r" commit2:2 : 2b commit3:2 : 3 ");} #[test]fn test_annotate_merge_simple() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let root_commit_id = repo.store().root_commit_id(); let file_path = repo_path("file"); // 4 "2 1 3" // |\ // | 3 "1 3" // | | // 2 | "2 1" // |/ // 1 "1" let mut tx = repo.start_transaction(); let mut create_commit = create_commit_fn(tx.repo_mut()); let content1 = "1\n"; let content2 = "2\n1\n"; let content3 = "1\n3\n"; let content4 = "2\n1\n3\n"; let tree1 = create_tree(repo, &[(file_path, content1)]); let tree2 = create_tree(repo, &[(file_path, content2)]); let tree3 = create_tree(repo, &[(file_path, content3)]); let tree4 = create_tree(repo, &[(file_path, content4)]); let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); let commit3 = create_commit("commit3", &[commit1.id()], tree3.id()); let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id()); drop(create_commit); insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r" commit2:1 : 2 commit1:1 : 1 commit3:2 : 3 "); // Exclude the fork commit and its ancestors. let domain = RevsetExpression::commit(commit1.id().clone()) .ancestors() .negated(); insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r" commit2:1 : 2 commit1:1*: 1 commit3:2 : 3 "); // Exclude one side of the merge and its ancestors. let domain = RevsetExpression::commit(commit2.id().clone()) .ancestors() .negated(); insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r" commit2:1*: 2 commit2:2*: 1 commit3:2 : 3 "); // Exclude both sides of the merge and their ancestors. let domain = RevsetExpression::commit(commit4.id().clone()); insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r" commit2:1*: 2 commit2:2*: 1 commit3:2*: 3 "); // Exclude intermediate commit, which is useless but works. let domain = RevsetExpression::commit(commit3.id().clone()).negated(); insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r" commit2:1 : 2 commit1:1 : 1 commit4:3 : 3 "); // Calculate incrementally let mut annotator = FileAnnotator::from_commit(&commit4, file_path).unwrap(); assert_eq!(annotator.pending_commits().collect_vec(), [commit4.id()]); insta::assert_snapshot!(format_annotation(tx.repo(), &annotator.to_annotation()), @r" commit4:1*: 2 commit4:2*: 1 commit4:3*: 3 "); annotator .compute( tx.repo(), &RevsetExpression::commits(vec![ commit4.id().clone(), commit3.id().clone(), commit2.id().clone(), ]), ) .unwrap(); assert_eq!(annotator.pending_commits().collect_vec(), [commit1.id()]); insta::assert_snapshot!(format_annotation(tx.repo(), &annotator.to_annotation()), @r" commit2:1 : 2 commit1:1*: 1 commit3:2 : 3 "); annotator .compute( tx.repo(), &RevsetExpression::commits(vec![commit1.id().clone()]), ) .unwrap(); assert!(annotator.pending_commits().next().is_none()); insta::assert_snapshot!(format_annotation(tx.repo(), &annotator.to_annotation()), @r" commit2:1 : 2 commit1:1 : 1 commit3:2 : 3 ");} #[test]fn test_annotate_merge_split() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let root_commit_id = repo.store().root_commit_id(); let file_path = repo_path("file"); // 4 "2 1a 1b 3 4" // |\ // | 3 "1b 3" // | | // 2 | "2 1a" // |/ // 1 "1a 1b" let mut tx = repo.start_transaction(); let mut create_commit = create_commit_fn(tx.repo_mut()); let content1 = "1a\n1b\n"; let content2 = "2\n1a\n"; let content3 = "1b\n3\n"; let content4 = "2\n1a\n1b\n3\n4\n"; let tree1 = create_tree(repo, &[(file_path, content1)]); let tree2 = create_tree(repo, &[(file_path, content2)]); let tree3 = create_tree(repo, &[(file_path, content3)]); let tree4 = create_tree(repo, &[(file_path, content4)]); let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); let commit3 = create_commit("commit3", &[commit1.id()], tree3.id()); let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id()); drop(create_commit); insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r" commit2:1 : 2 commit1:1 : 1a commit1:2 : 1b commit3:2 : 3 commit4:5 : 4 ");} #[test]fn test_annotate_merge_split_interleaved() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let root_commit_id = repo.store().root_commit_id(); let file_path = repo_path("file"); // 6 "1a 4 1b 6 2a 5 2b" // |\ // | 5 "1b 5 2b" // | | // 4 | "1a 4 2a" // |/ // 3 "1a 1b 2a 2b" // |\ // | 2 "2a 2b" // | // 1 "1a 1b" let mut tx = repo.start_transaction(); let mut create_commit = create_commit_fn(tx.repo_mut()); let content1 = "1a\n1b\n"; let content2 = "2a\n2b\n"; let content3 = "1a\n1b\n2a\n2b\n"; let content4 = "1a\n4\n2a\n"; let content5 = "1b\n5\n2b\n"; let content6 = "1a\n4\n1b\n6\n2a\n5\n2b\n"; let tree1 = create_tree(repo, &[(file_path, content1)]); let tree2 = create_tree(repo, &[(file_path, content2)]); let tree3 = create_tree(repo, &[(file_path, content3)]); let tree4 = create_tree(repo, &[(file_path, content4)]); let tree5 = create_tree(repo, &[(file_path, content5)]); let tree6 = create_tree(repo, &[(file_path, content6)]); let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); let commit2 = create_commit("commit2", &[root_commit_id], tree2.id()); let commit3 = create_commit("commit3", &[commit1.id(), commit2.id()], tree3.id()); let commit4 = create_commit("commit4", &[commit3.id()], tree4.id()); let commit5 = create_commit("commit5", &[commit3.id()], tree5.id()); let commit6 = create_commit("commit6", &[commit4.id(), commit5.id()], tree6.id()); drop(create_commit); insta::assert_snapshot!(annotate(tx.repo(), &commit6, file_path), @r" commit1:1 : 1a commit4:2 : 4 commit1:2 : 1b commit6:4 : 6 commit2:1 : 2a commit5:2 : 5 commit2:2 : 2b ");} #[test]fn test_annotate_merge_dup() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let root_commit_id = repo.store().root_commit_id(); let file_path = repo_path("file"); // 4 "2 1 1 3 4" // |\ // | 3 "1 3" // | | // 2 | "2 1" // |/ // 1 "1" let mut tx = repo.start_transaction(); let mut create_commit = create_commit_fn(tx.repo_mut()); let content1 = "1\n"; let content2 = "2\n1\n"; let content3 = "1\n3\n"; let content4 = "2\n1\n1\n3\n4\n"; let tree1 = create_tree(repo, &[(file_path, content1)]); let tree2 = create_tree(repo, &[(file_path, content2)]); let tree3 = create_tree(repo, &[(file_path, content3)]); let tree4 = create_tree(repo, &[(file_path, content4)]); let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); let commit3 = create_commit("commit3", &[commit1.id()], tree3.id()); let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id()); drop(create_commit); // Both "1"s can be propagated to commit1 through commit2 and commit3. // Alternatively, it's also good to interpret that one of the "1"s was // produced at commit2, commit3, or commit4. insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r" commit2:1 : 2 commit1:1 : 1 commit1:1 : 1 commit3:2 : 3 commit4:5 : 4 "); // For example, the parent tree of commit4 doesn't contain multiple "1"s. // If annotation were computed compared to the parent tree, not trees of the // parent commits, "1" would be inserted at commit4. insta::assert_snapshot!(annotate_parent_tree(tx.repo(), &commit4, file_path), @r" commit2:1 : 2 commit1:1 : 1 commit3:2 : 3 ");} #[test]fn test_annotate_file_directory_transition() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let root_commit_id = repo.store().root_commit_id(); let file_path1 = repo_path("file/was_dir"); let file_path2 = repo_path("file"); let mut tx = repo.start_transaction(); let mut create_commit = create_commit_fn(tx.repo_mut()); let tree1 = create_tree(repo, &[(file_path1, "1\n")]); let tree2 = create_tree(repo, &[(file_path2, "2\n")]); let commit1 = create_commit("commit1", &[root_commit_id], tree1.id()); let commit2 = create_commit("commit2", &[commit1.id()], tree2.id()); drop(create_commit); insta::assert_snapshot!(annotate(tx.repo(), &commit2, file_path2), @"commit2:1 : 2");}
// Copyright 2024 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fmt::Write as _;
use std::rc::Rc;
use itertools::Itertools as _;
use jj_lib::annotate::FileAnnotation;
use jj_lib::annotate::FileAnnotator;
use jj_lib::backend::CommitId;
use jj_lib::backend::MergedTreeId;
use jj_lib::backend::MillisSinceEpoch;
use jj_lib::backend::Signature;
use jj_lib::backend::Timestamp;
use jj_lib::backend::TreeValue;
use jj_lib::commit::Commit;
use jj_lib::repo::MutableRepo;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPath;
use jj_lib::revset::ResolvedRevsetExpression;
use jj_lib::revset::RevsetExpression;
use testutils::create_tree;
use testutils::read_file;
use testutils::repo_path;
use testutils::TestRepo;
fn create_commit_fn(
mut_repo: &mut MutableRepo,
) -> impl FnMut(&str, &[&CommitId], MergedTreeId) -> Commit + use<'_> {
// stabilize commit IDs for ease of debugging
let signature = Signature {
name: "Some One".to_owned(),
email: "some.one@example.com".to_owned(),
timestamp: Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
},
};
move |description, parent_ids, tree_id| {
let parent_ids = parent_ids.iter().map(|&id| id.clone()).collect();
mut_repo
.new_commit(parent_ids, tree_id)
.set_author(signature.clone())
.set_committer(signature.clone())
.set_description(description)
.write()
.unwrap()
}
}
fn annotate(repo: &dyn Repo, commit: &Commit, file_path: &RepoPath) -> String {
let domain = RevsetExpression::all();
annotate_within(repo, commit, &domain, file_path)
}
fn annotate_within(
repo: &dyn Repo,
commit: &Commit,
domain: &Rc<ResolvedRevsetExpression>,
file_path: &RepoPath,
) -> String {
let mut annotator = FileAnnotator::from_commit(commit, file_path).unwrap();
annotator.compute(repo, domain).unwrap();
format_annotation(repo, &annotator.to_annotation())
}
fn annotate_parent_tree(repo: &dyn Repo, commit: &Commit, file_path: &RepoPath) -> String {
let tree = commit.parent_tree(repo).unwrap();
let text = match tree.path_value(file_path).unwrap().into_resolved().unwrap() {
Some(TreeValue::File { id, .. }) => read_file(repo.store(), file_path, &id),
value => panic!("unexpected path value: {value:?}"),
};
let mut annotator = FileAnnotator::with_file_content(commit.id(), file_path, text);
annotator.compute(repo, &RevsetExpression::all()).unwrap();
format_annotation(repo, &annotator.to_annotation())
}
fn format_annotation(repo: &dyn Repo, annotation: &FileAnnotation) -> String {
let mut output = String::new();
for (origin, line) in annotation.line_origins() {
let line_origin = origin.unwrap_or_else(|line_origin| line_origin);
let line_number = line_origin.line_number + 1;
let commit = repo.store().get_commit(&line_origin.commit_id).unwrap();
let desc = commit.description().trim_end();
let sigil = if origin.is_err() { '*' } else { ' ' };
write!(output, "{desc}:{line_number}{sigil}: {line}").unwrap();
}
output
}
#[test]
fn test_annotate_linear() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id();
let file_path = repo_path("file");
let mut tx = repo.start_transaction();
let mut create_commit = create_commit_fn(tx.repo_mut());
let content1 = "";
let content2 = "2a\n2b\n";
let content3 = "2b\n3\n";
let tree1 = create_tree(repo, &[(file_path, content1)]);
let tree2 = create_tree(repo, &[(file_path, content2)]);
let tree3 = create_tree(repo, &[(file_path, content3)]);
let commit1 = create_commit("commit1", &[root_commit_id], tree1.id());
let commit2 = create_commit("commit2", &[commit1.id()], tree2.id());
let commit3 = create_commit("commit3", &[commit2.id()], tree3.id());
let commit4 = create_commit("commit4", &[commit3.id()], tree3.id()); // empty commit
drop(create_commit);
insta::assert_snapshot!(annotate(tx.repo(), &commit1, file_path), @"");
insta::assert_snapshot!(annotate(tx.repo(), &commit2, file_path), @r"
commit2:1 : 2a
commit2:2 : 2b
");
insta::assert_snapshot!(annotate(tx.repo(), &commit3, file_path), @r"
commit2:2 : 2b
commit3:2 : 3
");
insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r"
commit2:2 : 2b
commit3:2 : 3
");
}
#[test]
fn test_annotate_merge_simple() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id();
let file_path = repo_path("file");
// 4 "2 1 3"
// |\
// | 3 "1 3"
// | |
// 2 | "2 1"
// |/
// 1 "1"
let mut tx = repo.start_transaction();
let mut create_commit = create_commit_fn(tx.repo_mut());
let content1 = "1\n";
let content2 = "2\n1\n";
let content3 = "1\n3\n";
let content4 = "2\n1\n3\n";
let tree1 = create_tree(repo, &[(file_path, content1)]);
let tree2 = create_tree(repo, &[(file_path, content2)]);
let tree3 = create_tree(repo, &[(file_path, content3)]);
let tree4 = create_tree(repo, &[(file_path, content4)]);
let commit1 = create_commit("commit1", &[root_commit_id], tree1.id());
let commit2 = create_commit("commit2", &[commit1.id()], tree2.id());
let commit3 = create_commit("commit3", &[commit1.id()], tree3.id());
let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id());
drop(create_commit);
insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r"
commit2:1 : 2
commit1:1 : 1
commit3:2 : 3
");
// Exclude the fork commit and its ancestors.
let domain = RevsetExpression::commit(commit1.id().clone())
.ancestors()
.negated();
insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r"
commit2:1 : 2
commit1:1*: 1
commit3:2 : 3
");
// Exclude one side of the merge and its ancestors.
let domain = RevsetExpression::commit(commit2.id().clone())
.ancestors()
.negated();
insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r"
commit2:1*: 2
commit2:2*: 1
commit3:2 : 3
");
// Exclude both sides of the merge and their ancestors.
let domain = RevsetExpression::commit(commit4.id().clone());
insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r"
commit2:1*: 2
commit2:2*: 1
commit3:2*: 3
");
// Exclude intermediate commit, which is useless but works.
let domain = RevsetExpression::commit(commit3.id().clone()).negated();
insta::assert_snapshot!(annotate_within(tx.repo(), &commit4, &domain, file_path), @r"
commit2:1 : 2
commit1:1 : 1
commit4:3 : 3
");
// Calculate incrementally
let mut annotator = FileAnnotator::from_commit(&commit4, file_path).unwrap();
assert_eq!(annotator.pending_commits().collect_vec(), [commit4.id()]);
insta::assert_snapshot!(format_annotation(tx.repo(), &annotator.to_annotation()), @r"
commit4:1*: 2
commit4:2*: 1
commit4:3*: 3
");
annotator
.compute(
tx.repo(),
&RevsetExpression::commits(vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]),
)
.unwrap();
assert_eq!(annotator.pending_commits().collect_vec(), [commit1.id()]);
insta::assert_snapshot!(format_annotation(tx.repo(), &annotator.to_annotation()), @r"
commit2:1 : 2
commit1:1*: 1
commit3:2 : 3
");
annotator
.compute(
tx.repo(),
&RevsetExpression::commits(vec![commit1.id().clone()]),
)
.unwrap();
assert!(annotator.pending_commits().next().is_none());
insta::assert_snapshot!(format_annotation(tx.repo(), &annotator.to_annotation()), @r"
commit2:1 : 2
commit1:1 : 1
commit3:2 : 3
");
}
#[test]
fn test_annotate_merge_split() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id();
let file_path = repo_path("file");
// 4 "2 1a 1b 3 4"
// |\
// | 3 "1b 3"
// | |
// 2 | "2 1a"
// |/
// 1 "1a 1b"
let mut tx = repo.start_transaction();
let mut create_commit = create_commit_fn(tx.repo_mut());
let content1 = "1a\n1b\n";
let content2 = "2\n1a\n";
let content3 = "1b\n3\n";
let content4 = "2\n1a\n1b\n3\n4\n";
let tree1 = create_tree(repo, &[(file_path, content1)]);
let tree2 = create_tree(repo, &[(file_path, content2)]);
let tree3 = create_tree(repo, &[(file_path, content3)]);
let tree4 = create_tree(repo, &[(file_path, content4)]);
let commit1 = create_commit("commit1", &[root_commit_id], tree1.id());
let commit2 = create_commit("commit2", &[commit1.id()], tree2.id());
let commit3 = create_commit("commit3", &[commit1.id()], tree3.id());
let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id());
drop(create_commit);
insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r"
commit2:1 : 2
commit1:1 : 1a
commit1:2 : 1b
commit3:2 : 3
commit4:5 : 4
");
}
#[test]
fn test_annotate_merge_split_interleaved() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id();
let file_path = repo_path("file");
// 6 "1a 4 1b 6 2a 5 2b"
// |\
// | 5 "1b 5 2b"
// | |
// 4 | "1a 4 2a"
// |/
// 3 "1a 1b 2a 2b"
// |\
// | 2 "2a 2b"
// |
// 1 "1a 1b"
let mut tx = repo.start_transaction();
let mut create_commit = create_commit_fn(tx.repo_mut());
let content1 = "1a\n1b\n";
let content2 = "2a\n2b\n";
let content3 = "1a\n1b\n2a\n2b\n";
let content4 = "1a\n4\n2a\n";
let content5 = "1b\n5\n2b\n";
let content6 = "1a\n4\n1b\n6\n2a\n5\n2b\n";
let tree1 = create_tree(repo, &[(file_path, content1)]);
let tree2 = create_tree(repo, &[(file_path, content2)]);
let tree3 = create_tree(repo, &[(file_path, content3)]);
let tree4 = create_tree(repo, &[(file_path, content4)]);
let tree5 = create_tree(repo, &[(file_path, content5)]);
let tree6 = create_tree(repo, &[(file_path, content6)]);
let commit1 = create_commit("commit1", &[root_commit_id], tree1.id());
let commit2 = create_commit("commit2", &[root_commit_id], tree2.id());
let commit3 = create_commit("commit3", &[commit1.id(), commit2.id()], tree3.id());
let commit4 = create_commit("commit4", &[commit3.id()], tree4.id());
let commit5 = create_commit("commit5", &[commit3.id()], tree5.id());
let commit6 = create_commit("commit6", &[commit4.id(), commit5.id()], tree6.id());
drop(create_commit);
insta::assert_snapshot!(annotate(tx.repo(), &commit6, file_path), @r"
commit1:1 : 1a
commit4:2 : 4
commit1:2 : 1b
commit6:4 : 6
commit2:1 : 2a
commit5:2 : 5
commit2:2 : 2b
");
}
#[test]
fn test_annotate_merge_dup() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id();
let file_path = repo_path("file");
// 4 "2 1 1 3 4"
// |\
// | 3 "1 3"
// | |
// 2 | "2 1"
// |/
// 1 "1"
let mut tx = repo.start_transaction();
let mut create_commit = create_commit_fn(tx.repo_mut());
let content1 = "1\n";
let content2 = "2\n1\n";
let content3 = "1\n3\n";
let content4 = "2\n1\n1\n3\n4\n";
let tree1 = create_tree(repo, &[(file_path, content1)]);
let tree2 = create_tree(repo, &[(file_path, content2)]);
let tree3 = create_tree(repo, &[(file_path, content3)]);
let tree4 = create_tree(repo, &[(file_path, content4)]);
let commit1 = create_commit("commit1", &[root_commit_id], tree1.id());
let commit2 = create_commit("commit2", &[commit1.id()], tree2.id());
let commit3 = create_commit("commit3", &[commit1.id()], tree3.id());
let commit4 = create_commit("commit4", &[commit2.id(), commit3.id()], tree4.id());
drop(create_commit);
// Both "1"s can be propagated to commit1 through commit2 and commit3.
// Alternatively, it's also good to interpret that one of the "1"s was
// produced at commit2, commit3, or commit4.
insta::assert_snapshot!(annotate(tx.repo(), &commit4, file_path), @r"
commit2:1 : 2
commit1:1 : 1
commit1:1 : 1
commit3:2 : 3
commit4:5 : 4
");
// For example, the parent tree of commit4 doesn't contain multiple "1"s.
// If annotation were computed compared to the parent tree, not trees of the
// parent commits, "1" would be inserted at commit4.
insta::assert_snapshot!(annotate_parent_tree(tx.repo(), &commit4, file_path), @r"
commit2:1 : 2
commit1:1 : 1
commit3:2 : 3
");
}
#[test]
fn test_annotate_file_directory_transition() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id();
let file_path1 = repo_path("file/was_dir");
let file_path2 = repo_path("file");
let mut tx = repo.start_transaction();
let mut create_commit = create_commit_fn(tx.repo_mut());
let tree1 = create_tree(repo, &[(file_path1, "1\n")]);
let tree2 = create_tree(repo, &[(file_path2, "2\n")]);
let commit1 = create_commit("commit1", &[root_commit_id], tree1.id());
let commit2 = create_commit("commit2", &[commit1.id()], tree2.id());
drop(create_commit);
insta::assert_snapshot!(annotate(tx.repo(), &commit2, file_path2), @"commit2:1 : 2");
}