use itertools::Itertools as _;
use jj_lib::backend::CommitId;
use jj_lib::commit::Commit;
use jj_lib::default_index::DefaultReadonlyIndex;
use jj_lib::default_index::DefaultReadonlyIndexRevset;
use jj_lib::graph::GraphEdge;
use jj_lib::repo::ReadonlyRepo;
use jj_lib::repo::Repo as _;
use jj_lib::revset::ResolvedExpression;
use pollster::FutureExt as _;
use test_case::test_case;
use testutils::TestRepo;
use testutils::TestResult;
use testutils::write_random_commit;
use testutils::write_random_commit_with_parents;
fn revset_for_commits(repo: &ReadonlyRepo, commits: &[&Commit]) -> DefaultReadonlyIndexRevset {
let index: &DefaultReadonlyIndex = repo.readonly_index().downcast_ref().unwrap();
let expression =
ResolvedExpression::Commits(commits.iter().map(|commit| commit.id().clone()).collect());
index
.evaluate_revset_impl(&expression, repo.store())
.unwrap()
}
fn direct(commit: &Commit) -> GraphEdge<CommitId> {
GraphEdge::direct(commit.id().clone())
}
fn indirect(commit: &Commit) -> GraphEdge<CommitId> {
GraphEdge::indirect(commit.id().clone())
}
fn missing(commit: &Commit) -> GraphEdge<CommitId> {
GraphEdge::missing(commit.id().clone())
}
#[test_case(false, 0; "keep transitive edges")]
#[test_case(true, 0; "skip transitive edges")]
#[test_matrix(true, 60..64; "skip transitive edges")]
fn test_graph_iterator_linearized(skip_transitive_edges: bool, padding: u32) -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
for _ in 0..padding {
write_random_commit(tx.repo_mut());
}
let commit_a = write_random_commit(tx.repo_mut());
let commit_b = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]);
let commit_c = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]);
let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]);
let repo = tx.commit("test").block_on()?;
let root_commit = repo.store().root_commit();
let revset = revset_for_commits(repo.as_ref(), &[&commit_a, &commit_d]);
let commits: Vec<_> = revset
.iter_graph_impl(skip_transitive_edges)
.try_collect()?;
assert_eq!(commits.len(), 2);
assert_eq!(commits[0].0, *commit_d.id());
assert_eq!(commits[1].0, *commit_a.id());
assert_eq!(commits[0].1, vec![indirect(&commit_a)]);
assert_eq!(commits[1].1, vec![missing(&root_commit)]);
Ok(())
}
#[test_case(false, 0; "keep transitive edges")]
#[test_case(true, 0; "skip transitive edges")]
#[test_matrix(true, 58..64; "skip transitive edges")]
fn test_graph_iterator_virtual_octopus(skip_transitive_edges: bool, padding: u32) -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
for _ in 0..padding {
write_random_commit(tx.repo_mut());
}
let commit_a = write_random_commit(tx.repo_mut());
let commit_b = write_random_commit(tx.repo_mut());
let commit_c = write_random_commit(tx.repo_mut());
let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a, &commit_b]);
let commit_e = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]);
let commit_f = write_random_commit_with_parents(tx.repo_mut(), &[&commit_d, &commit_e]);
let repo = tx.commit("test").block_on()?;
let root_commit = repo.store().root_commit();
let revset = revset_for_commits(repo.as_ref(), &[&commit_a, &commit_b, &commit_c, &commit_f]);
let commits: Vec<_> = revset
.iter_graph_impl(skip_transitive_edges)
.try_collect()?;
assert_eq!(commits.len(), 4);
assert_eq!(commits[0].0, *commit_f.id());
assert_eq!(commits[1].0, *commit_c.id());
assert_eq!(commits[2].0, *commit_b.id());
assert_eq!(commits[3].0, *commit_a.id());
assert_eq!(
commits[0].1,
vec![
indirect(&commit_a),
indirect(&commit_b),
indirect(&commit_c),
]
);
assert_eq!(commits[1].1, vec![missing(&root_commit)]);
assert_eq!(commits[2].1, vec![missing(&root_commit)]);
assert_eq!(commits[3].1, vec![missing(&root_commit)]);
Ok(())
}
#[test_case(false, 0; "keep transitive edges")]
#[test_case(true, 0; "skip transitive edges")]
#[test_matrix(true, 59..64; "skip transitive edges")]
fn test_graph_iterator_simple_fork(skip_transitive_edges: bool, padding: u32) -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
for _ in 0..padding {
write_random_commit(tx.repo_mut());
}
let commit_a = write_random_commit(tx.repo_mut());
let commit_b = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]);
let commit_c = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b]);
let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b]);
let commit_e = write_random_commit_with_parents(tx.repo_mut(), &[&commit_d]);
let repo = tx.commit("test").block_on()?;
let root_commit = repo.store().root_commit();
let revset = revset_for_commits(repo.as_ref(), &[&commit_a, &commit_c, &commit_e]);
let commits: Vec<_> = revset
.iter_graph_impl(skip_transitive_edges)
.try_collect()?;
assert_eq!(commits.len(), 3);
assert_eq!(commits[0].0, *commit_e.id());
assert_eq!(commits[1].0, *commit_c.id());
assert_eq!(commits[2].0, *commit_a.id());
assert_eq!(commits[0].1, vec![indirect(&commit_a)]);
assert_eq!(commits[1].1, vec![indirect(&commit_a)]);
assert_eq!(commits[2].1, vec![missing(&root_commit)]);
Ok(())
}
#[test_case(false, 0; "keep transitive edges")]
#[test_case(true, 0; "skip transitive edges")]
#[test_matrix(true, 58..64; "skip transitive edges")]
fn test_graph_iterator_multiple_missing(skip_transitive_edges: bool, padding: u32) -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
for _ in 0..padding {
write_random_commit(tx.repo_mut());
}
let commit_a = write_random_commit(tx.repo_mut());
let commit_b = write_random_commit(tx.repo_mut());
let commit_c = write_random_commit(tx.repo_mut());
let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a, &commit_b]);
let commit_e = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]);
let commit_f = write_random_commit_with_parents(tx.repo_mut(), &[&commit_d, &commit_e]);
let repo = tx.commit("test").block_on()?;
let root_commit = repo.store().root_commit();
let revset = revset_for_commits(repo.as_ref(), &[&commit_b, &commit_f]);
let commits: Vec<_> = revset
.iter_graph_impl(skip_transitive_edges)
.try_collect()?;
assert_eq!(commits.len(), 2);
assert_eq!(commits[0].0, *commit_f.id());
assert_eq!(commits[1].0, *commit_b.id());
assert_eq!(
commits[0].1,
vec![missing(&commit_a), indirect(&commit_b), missing(&commit_c)]
);
assert_eq!(commits[1].1, vec![missing(&root_commit)]);
Ok(())
}
#[test_case(false, 0; "keep transitive edges")]
#[test_case(true, 0; "skip transitive edges")]
#[test_matrix(true, 58..64; "skip transitive edges")]
fn test_graph_iterator_edge_to_ancestor(skip_transitive_edges: bool, padding: u32) -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
for _ in 0..padding {
write_random_commit(tx.repo_mut());
}
let commit_a = write_random_commit(tx.repo_mut());
let commit_b = write_random_commit(tx.repo_mut());
let commit_c = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]);
let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b, &commit_c]);
let commit_e = write_random_commit_with_parents(tx.repo_mut(), &[&commit_c]);
let commit_f = write_random_commit_with_parents(tx.repo_mut(), &[&commit_d, &commit_e]);
let repo = tx.commit("test").block_on()?;
let revset = revset_for_commits(repo.as_ref(), &[&commit_c, &commit_d, &commit_f]);
let commits: Vec<_> = revset
.iter_graph_impl(skip_transitive_edges)
.try_collect()?;
assert_eq!(commits.len(), 3);
assert_eq!(commits[0].0, *commit_f.id());
assert_eq!(commits[1].0, *commit_d.id());
assert_eq!(commits[2].0, *commit_c.id());
if skip_transitive_edges {
assert_eq!(commits[0].1, vec![direct(&commit_d)]);
} else {
assert_eq!(commits[0].1, vec![direct(&commit_d), indirect(&commit_c),]);
}
assert_eq!(commits[1].1, vec![missing(&commit_b), direct(&commit_c)]);
assert_eq!(commits[2].1, vec![missing(&commit_a)]);
Ok(())
}
#[test_case(false, 0; "keep transitive edges")]
#[test_case(true, 0; "skip transitive edges")]
#[test_matrix(true, 54..64; "skip transitive edges")]
fn test_graph_iterator_edge_escapes_from_(skip_transitive_edges: bool, padding: u32) -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
for _ in 0..padding {
write_random_commit(tx.repo_mut());
}
let commit_a = write_random_commit(tx.repo_mut());
let commit_b = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]);
let commit_c = write_random_commit_with_parents(tx.repo_mut(), &[&commit_a]);
let commit_d = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b]);
let commit_e = write_random_commit_with_parents(tx.repo_mut(), &[&commit_d]);
let commit_f = write_random_commit_with_parents(tx.repo_mut(), &[&commit_d, &commit_c]);
let commit_g = write_random_commit_with_parents(tx.repo_mut(), &[&commit_b]);
let commit_h = write_random_commit_with_parents(tx.repo_mut(), &[&commit_f]);
let commit_i = write_random_commit_with_parents(tx.repo_mut(), &[&commit_e, &commit_h]);
let commit_j = write_random_commit_with_parents(tx.repo_mut(), &[&commit_g, &commit_i]);
let repo = tx.commit("test").block_on()?;
let root_commit = repo.store().root_commit();
let revset = revset_for_commits(
repo.as_ref(),
&[&commit_a, &commit_d, &commit_g, &commit_h, &commit_j],
);
let commits: Vec<_> = revset
.iter_graph_impl(skip_transitive_edges)
.try_collect()?;
assert_eq!(commits.len(), 5);
assert_eq!(commits[0].0, *commit_j.id());
assert_eq!(commits[1].0, *commit_h.id());
assert_eq!(commits[2].0, *commit_g.id());
assert_eq!(commits[3].0, *commit_d.id());
assert_eq!(commits[4].0, *commit_a.id());
if skip_transitive_edges {
assert_eq!(commits[0].1, vec![direct(&commit_g), indirect(&commit_h)]);
assert_eq!(commits[1].1, vec![indirect(&commit_d)]);
} else {
assert_eq!(
commits[0].1,
vec![direct(&commit_g), indirect(&commit_d), indirect(&commit_h)]
);
assert_eq!(commits[1].1, vec![indirect(&commit_d), indirect(&commit_a)]);
}
assert_eq!(commits[2].1, vec![indirect(&commit_a)]);
assert_eq!(commits[3].1, vec![indirect(&commit_a)]);
assert_eq!(commits[4].1, vec![missing(&root_commit)]);
Ok(())
}