// Copyright 2020 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::collections::HashSet;use std::fs;use std::sync::Arc; use assert_matches::assert_matches;use itertools::Itertools as _;use jj_lib::backend::ChangeId;use jj_lib::backend::CommitId;use jj_lib::commit::Commit;use jj_lib::default_index::DefaultIndexStore;use jj_lib::default_index::DefaultIndexStoreError;use jj_lib::default_index::DefaultMutableIndex;use jj_lib::default_index::DefaultReadonlyIndex;use jj_lib::index::Index;use jj_lib::object_id::HexPrefix;use jj_lib::object_id::ObjectId as _;use jj_lib::object_id::PrefixResolution;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::ref_name::RefName;use jj_lib::ref_name::RemoteName;use jj_lib::ref_name::RemoteRefSymbol;use jj_lib::repo::MutableRepo;use jj_lib::repo::ReadonlyRepo;use jj_lib::repo::Repo as _;use jj_lib::repo_path::RepoPathBuf;use jj_lib::revset::GENERATION_RANGE_FULL;use jj_lib::revset::PARENTS_RANGE_FULL;use jj_lib::revset::ResolvedExpression;use maplit::hashset;use pollster::FutureExt as _;use test_case::test_case;use testutils::TestRepo;use testutils::commit_transactions;use testutils::create_tree;use testutils::repo_path;use testutils::repo_path_buf;use testutils::test_backend::TestBackend;use testutils::write_random_commit;use testutils::write_random_commit_with_parents; fn remote_symbol<'a, N, M>(name: &'a N, remote: &'a M) -> RemoteRefSymbol<'a>where N: AsRef<RefName> + ?Sized, M: AsRef<RemoteName> + ?Sized,{ RemoteRefSymbol { name: name.as_ref(), remote: remote.as_ref(), }} fn enable_changed_path_index(repo: &ReadonlyRepo) -> Arc<ReadonlyRepo> { let default_index_store: &DefaultIndexStore = repo.index_store().downcast_ref().unwrap(); default_index_store .build_changed_path_index_at_operation(repo.op_id(), repo.store(), 0) .block_on() .unwrap(); repo.reload_at(repo.operation()).unwrap()} fn collect_changed_paths(repo: &ReadonlyRepo, commit_id: &CommitId) -> Option<Vec<RepoPathBuf>> { repo.index() .changed_paths_in_commit(commit_id) .unwrap() .map(|paths| paths.collect())} fn index_has_id(index: &dyn Index, commit_id: &CommitId) -> bool { index.has_id(commit_id).unwrap()} #[test]fn test_index_commits_empty_repo() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let index = as_readonly_index(repo); // There should be just the root commit assert_eq!(index.num_commits(), 1); // Check the generation numbers of the root and the working copy assert_eq!( index .generation_number(repo.store().root_commit_id()) .unwrap(), 0 );} #[test]fn test_index_commits_standard_cases() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // o H // o | G // o | F // |\| // | o E // | o D // | o C // o | B // |/ // o A // | o working copy // |/ // o root let root_commit_id = repo.store().root_commit_id(); let mut tx = repo.start_transaction(); 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_c]); 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_b, &commit_e]); let commit_g = write_random_commit_with_parents(tx.repo_mut(), &[&commit_f]); let commit_h = write_random_commit_with_parents(tx.repo_mut(), &[&commit_e]); let repo = tx.commit("test").unwrap(); let index = as_readonly_index(&repo); // There should be the root commit, plus 8 more assert_eq!(index.num_commits(), 1 + 8); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 8); assert_eq!(stats.num_merges, 1); assert_eq!(stats.max_generation_number, 6); assert_eq!(index.generation_number(root_commit_id).unwrap(), 0); assert_eq!(index.generation_number(commit_a.id()).unwrap(), 1); assert_eq!(index.generation_number(commit_b.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_c.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_d.id()).unwrap(), 3); assert_eq!(index.generation_number(commit_e.id()).unwrap(), 4); assert_eq!(index.generation_number(commit_f.id()).unwrap(), 5); assert_eq!(index.generation_number(commit_g.id()).unwrap(), 6); assert_eq!(index.generation_number(commit_h.id()).unwrap(), 5); assert!(index.is_ancestor(root_commit_id, commit_a.id())); assert!(!index.is_ancestor(commit_a.id(), root_commit_id)); assert!(index.is_ancestor(root_commit_id, commit_b.id())); assert!(!index.is_ancestor(commit_b.id(), root_commit_id)); assert!(!index.is_ancestor(commit_b.id(), commit_c.id())); assert!(index.is_ancestor(commit_a.id(), commit_b.id())); assert!(index.is_ancestor(commit_a.id(), commit_e.id())); assert!(index.is_ancestor(commit_a.id(), commit_f.id())); assert!(index.is_ancestor(commit_a.id(), commit_g.id())); assert!(index.is_ancestor(commit_a.id(), commit_h.id()));} #[test]fn test_index_commits_criss_cross() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let num_generations = 50; // Create a long chain of criss-crossed merges. If they were traversed without // keeping track of visited nodes, it would be 2^50 visits, so if this test // finishes in reasonable time, we know that we don't do a naive traversal. let mut tx = repo.start_transaction(); let mut left_commits = vec![write_random_commit(tx.repo_mut())]; let mut right_commits = vec![write_random_commit(tx.repo_mut())]; for generation in 1..num_generations { let new_left = write_random_commit_with_parents( tx.repo_mut(), &[ &left_commits[generation - 1], &right_commits[generation - 1], ], ); let new_right = write_random_commit_with_parents( tx.repo_mut(), &[ &left_commits[generation - 1], &right_commits[generation - 1], ], ); left_commits.push(new_left); right_commits.push(new_right); } let repo = tx.commit("test").unwrap(); let index = as_readonly_index(&repo); // There should the root commit, plus 2 for each generation assert_eq!(index.num_commits(), 1 + 2 * (num_generations as u32)); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 2 * (num_generations as u32)); // The first generations are not merges assert_eq!(stats.num_merges, 2 * (num_generations as u32 - 1)); assert_eq!(stats.max_generation_number, num_generations as u32); // Check generation numbers for generation in 0..num_generations { assert_eq!( index .generation_number(left_commits[generation].id()) .unwrap(), (generation as u32) + 1 ); assert_eq!( index .generation_number(right_commits[generation].id()) .unwrap(), (generation as u32) + 1 ); } // The left and right commits of the same generation should not be ancestors of // each other for generation in 0..num_generations { assert!(!index.is_ancestor( left_commits[generation].id(), right_commits[generation].id() )); assert!(!index.is_ancestor( right_commits[generation].id(), left_commits[generation].id() )); } // Both sides of earlier generations should be ancestors. Check a few different // earlier generations. for generation in 1..num_generations { for ancestor_side in &[&left_commits, &right_commits] { for descendant_side in &[&left_commits, &right_commits] { assert!(index.is_ancestor(ancestor_side[0].id(), descendant_side[generation].id())); assert!(index.is_ancestor( ancestor_side[generation - 1].id(), descendant_side[generation].id() )); assert!(index.is_ancestor( ancestor_side[generation / 2].id(), descendant_side[generation].id() )); } } } let count_revs = |wanted: &[CommitId], unwanted: &[CommitId], generation| { // Constructs ResolvedExpression directly to bypass tree optimization. let expression = ResolvedExpression::Range { roots: ResolvedExpression::Commits(unwanted.to_vec()).into(), heads: ResolvedExpression::Commits(wanted.to_vec()).into(), generation, parents_range: PARENTS_RANGE_FULL, }; let revset = index.evaluate_revset(&expression, repo.store()).unwrap(); // Don't switch to more efficient .count() implementation. Here we're // testing the iterator behavior. revset.iter().count() }; // RevWalk deduplicates chains by entry. assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[], GENERATION_RANGE_FULL, ), 2 * num_generations ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[], GENERATION_RANGE_FULL, ), 2 * num_generations ); assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[left_commits[num_generations - 2].id().clone()], GENERATION_RANGE_FULL, ), 2 ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[right_commits[num_generations - 2].id().clone()], GENERATION_RANGE_FULL, ), 2 ); // RevWalkGenerationRange deduplicates chains by (entry, generation), which may // be more expensive than RevWalk, but should still finish in reasonable time. assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[], 0..(num_generations + 1) as u64, ), 2 * num_generations ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[], 0..(num_generations + 1) as u64, ), 2 * num_generations ); assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[left_commits[num_generations - 2].id().clone()], 0..(num_generations + 1) as u64, ), 2 ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[right_commits[num_generations - 2].id().clone()], 0..(num_generations + 1) as u64, ), 2 );} #[test]fn test_index_commits_previous_operations() { // Test that commits visible only in previous operations are indexed. let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Remove commit B and C in one operation and make sure they're still // visible in the index after that operation. // o C // o B // o A // | o working copy // |/ // o root let mut tx = repo.start_transaction(); 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 repo = tx.commit("test").unwrap(); let mut tx = repo.start_transaction(); tx.repo_mut().remove_head(commit_c.id()); let repo = tx.commit("test").unwrap(); // Delete index from disk let default_index_store: &DefaultIndexStore = repo.index_store().downcast_ref().unwrap(); default_index_store.reinit().unwrap(); let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path()); let index = as_readonly_index(&repo); // There should be the root commit, plus 3 more assert_eq!(index.num_commits(), 1 + 3); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 3); assert_eq!(stats.num_merges, 0); assert_eq!(stats.max_generation_number, 3); assert_eq!(index.generation_number(commit_a.id()).unwrap(), 1); assert_eq!(index.generation_number(commit_b.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_c.id()).unwrap(), 3);} #[test]fn test_index_commits_hidden_but_referenced() { // Test that hidden-but-referenced commits are indexed. let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Remote bookmarks are usually visible at a certain point in operation // history, but that's not guaranteed if old operations have been discarded. // This can also happen if imported remote bookmarks get immediately // abandoned because the other bookmark has moved. let mut tx = repo.start_transaction(); 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()); tx.repo_mut().remove_head(commit_a.id()); tx.repo_mut().remove_head(commit_b.id()); tx.repo_mut().remove_head(commit_c.id()); tx.repo_mut().set_remote_bookmark( remote_symbol("bookmark", "origin"), RemoteRef { target: RefTarget::from_legacy_form( [commit_a.id().clone()], [commit_b.id().clone(), commit_c.id().clone()], ), state: jj_lib::op_store::RemoteRefState::New, }, ); let repo = tx.commit("test").unwrap(); // All commits should be indexed assert!(index_has_id(repo.index(), commit_a.id())); assert!(index_has_id(repo.index(), commit_b.id())); assert!(index_has_id(repo.index(), commit_c.id())); // Delete index from disk let default_index_store: &DefaultIndexStore = repo.index_store().downcast_ref().unwrap(); default_index_store.reinit().unwrap(); let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path()); // All commits should be reindexed assert!(index_has_id(repo.index(), commit_a.id())); assert!(index_has_id(repo.index(), commit_b.id())); assert!(index_has_id(repo.index(), commit_c.id()));} #[test]fn test_index_commits_incremental() { let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Create A in one operation, then B and C in another. Check that the index is // valid after. // o C // o B // o A // | o working copy // |/ // o root let root_commit = repo.store().root_commit(); let mut tx = repo.start_transaction(); let commit_a = write_random_commit_with_parents(tx.repo_mut(), &[]); let repo = tx.commit("test").unwrap(); let index = as_readonly_index(&repo); // There should be the root commit, plus 1 more assert_eq!(index.num_commits(), 1 + 1); let mut tx = repo.start_transaction(); 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]); tx.commit("test").unwrap(); let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path()); let index = as_readonly_index(&repo); // There should be the root commit, plus 3 more assert_eq!(index.num_commits(), 1 + 3); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 3); assert_eq!(stats.num_merges, 0); assert_eq!(stats.max_generation_number, 3); assert_eq!(stats.commit_levels.len(), 1); assert_eq!(stats.commit_levels[0].num_commits, 4); assert_eq!(index.generation_number(root_commit.id()).unwrap(), 0); assert_eq!(index.generation_number(commit_a.id()).unwrap(), 1); assert_eq!(index.generation_number(commit_b.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_c.id()).unwrap(), 3);} #[test]fn test_index_commits_incremental_empty_transaction() { let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Create A in one operation, then just an empty transaction. Check that the // index is valid after. // o A // | o working copy // |/ // o root let root_commit = repo.store().root_commit(); let mut tx = repo.start_transaction();Lines 1–500 of 1263. Load more
// Copyright 2020 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::collections::HashSet; use std::fs; use std::sync::Arc; use assert_matches::assert_matches; use itertools::Itertools as _; use jj_lib::backend::ChangeId; use jj_lib::backend::CommitId; use jj_lib::commit::Commit; use jj_lib::default_index::DefaultIndexStore; use jj_lib::default_index::DefaultIndexStoreError; use jj_lib::default_index::DefaultMutableIndex; use jj_lib::default_index::DefaultReadonlyIndex; use jj_lib::index::Index; use jj_lib::object_id::HexPrefix; use jj_lib::object_id::ObjectId as _; use jj_lib::object_id::PrefixResolution; use jj_lib::op_store::RefTarget; use jj_lib::op_store::RemoteRef; use jj_lib::ref_name::RefName; use jj_lib::ref_name::RemoteName; use jj_lib::ref_name::RemoteRefSymbol; use jj_lib::repo::MutableRepo; use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::Repo as _; use jj_lib::repo_path::RepoPathBuf; use jj_lib::revset::GENERATION_RANGE_FULL; use jj_lib::revset::PARENTS_RANGE_FULL; use jj_lib::revset::ResolvedExpression; use maplit::hashset; use pollster::FutureExt as _; use test_case::test_case; use testutils::TestRepo; use testutils::commit_transactions; use testutils::create_tree; use testutils::repo_path; use testutils::repo_path_buf; use testutils::test_backend::TestBackend; use testutils::write_random_commit; use testutils::write_random_commit_with_parents; fn remote_symbol<'a, N, M>(name: &'a N, remote: &'a M) -> RemoteRefSymbol<'a> where N: AsRef<RefName> + ?Sized, M: AsRef<RemoteName> + ?Sized, { RemoteRefSymbol { name: name.as_ref(), remote: remote.as_ref(), } } fn enable_changed_path_index(repo: &ReadonlyRepo) -> Arc<ReadonlyRepo> { let default_index_store: &DefaultIndexStore = repo.index_store().downcast_ref().unwrap(); default_index_store .build_changed_path_index_at_operation(repo.op_id(), repo.store(), 0) .block_on() .unwrap(); repo.reload_at(repo.operation()).unwrap() } fn collect_changed_paths(repo: &ReadonlyRepo, commit_id: &CommitId) -> Option<Vec<RepoPathBuf>> { repo.index() .changed_paths_in_commit(commit_id) .unwrap() .map(|paths| paths.collect()) } fn index_has_id(index: &dyn Index, commit_id: &CommitId) -> bool { index.has_id(commit_id).unwrap() } #[test] fn test_index_commits_empty_repo() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let index = as_readonly_index(repo); // There should be just the root commit assert_eq!(index.num_commits(), 1); // Check the generation numbers of the root and the working copy assert_eq!( index .generation_number(repo.store().root_commit_id()) .unwrap(), 0 ); } #[test] fn test_index_commits_standard_cases() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; // o H // o | G // o | F // |\| // | o E // | o D // | o C // o | B // |/ // o A // | o working copy // |/ // o root let root_commit_id = repo.store().root_commit_id(); let mut tx = repo.start_transaction(); 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_c]); 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_b, &commit_e]); let commit_g = write_random_commit_with_parents(tx.repo_mut(), &[&commit_f]); let commit_h = write_random_commit_with_parents(tx.repo_mut(), &[&commit_e]); let repo = tx.commit("test").unwrap(); let index = as_readonly_index(&repo); // There should be the root commit, plus 8 more assert_eq!(index.num_commits(), 1 + 8); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 8); assert_eq!(stats.num_merges, 1); assert_eq!(stats.max_generation_number, 6); assert_eq!(index.generation_number(root_commit_id).unwrap(), 0); assert_eq!(index.generation_number(commit_a.id()).unwrap(), 1); assert_eq!(index.generation_number(commit_b.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_c.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_d.id()).unwrap(), 3); assert_eq!(index.generation_number(commit_e.id()).unwrap(), 4); assert_eq!(index.generation_number(commit_f.id()).unwrap(), 5); assert_eq!(index.generation_number(commit_g.id()).unwrap(), 6); assert_eq!(index.generation_number(commit_h.id()).unwrap(), 5); assert!(index.is_ancestor(root_commit_id, commit_a.id())); assert!(!index.is_ancestor(commit_a.id(), root_commit_id)); assert!(index.is_ancestor(root_commit_id, commit_b.id())); assert!(!index.is_ancestor(commit_b.id(), root_commit_id)); assert!(!index.is_ancestor(commit_b.id(), commit_c.id())); assert!(index.is_ancestor(commit_a.id(), commit_b.id())); assert!(index.is_ancestor(commit_a.id(), commit_e.id())); assert!(index.is_ancestor(commit_a.id(), commit_f.id())); assert!(index.is_ancestor(commit_a.id(), commit_g.id())); assert!(index.is_ancestor(commit_a.id(), commit_h.id())); } #[test] fn test_index_commits_criss_cross() { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let num_generations = 50; // Create a long chain of criss-crossed merges. If they were traversed without // keeping track of visited nodes, it would be 2^50 visits, so if this test // finishes in reasonable time, we know that we don't do a naive traversal. let mut tx = repo.start_transaction(); let mut left_commits = vec![write_random_commit(tx.repo_mut())]; let mut right_commits = vec![write_random_commit(tx.repo_mut())]; for generation in 1..num_generations { let new_left = write_random_commit_with_parents( tx.repo_mut(), &[ &left_commits[generation - 1], &right_commits[generation - 1], ], ); let new_right = write_random_commit_with_parents( tx.repo_mut(), &[ &left_commits[generation - 1], &right_commits[generation - 1], ], ); left_commits.push(new_left); right_commits.push(new_right); } let repo = tx.commit("test").unwrap(); let index = as_readonly_index(&repo); // There should the root commit, plus 2 for each generation assert_eq!(index.num_commits(), 1 + 2 * (num_generations as u32)); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 2 * (num_generations as u32)); // The first generations are not merges assert_eq!(stats.num_merges, 2 * (num_generations as u32 - 1)); assert_eq!(stats.max_generation_number, num_generations as u32); // Check generation numbers for generation in 0..num_generations { assert_eq!( index .generation_number(left_commits[generation].id()) .unwrap(), (generation as u32) + 1 ); assert_eq!( index .generation_number(right_commits[generation].id()) .unwrap(), (generation as u32) + 1 ); } // The left and right commits of the same generation should not be ancestors of // each other for generation in 0..num_generations { assert!(!index.is_ancestor( left_commits[generation].id(), right_commits[generation].id() )); assert!(!index.is_ancestor( right_commits[generation].id(), left_commits[generation].id() )); } // Both sides of earlier generations should be ancestors. Check a few different // earlier generations. for generation in 1..num_generations { for ancestor_side in &[&left_commits, &right_commits] { for descendant_side in &[&left_commits, &right_commits] { assert!(index.is_ancestor(ancestor_side[0].id(), descendant_side[generation].id())); assert!(index.is_ancestor( ancestor_side[generation - 1].id(), descendant_side[generation].id() )); assert!(index.is_ancestor( ancestor_side[generation / 2].id(), descendant_side[generation].id() )); } } } let count_revs = |wanted: &[CommitId], unwanted: &[CommitId], generation| { // Constructs ResolvedExpression directly to bypass tree optimization. let expression = ResolvedExpression::Range { roots: ResolvedExpression::Commits(unwanted.to_vec()).into(), heads: ResolvedExpression::Commits(wanted.to_vec()).into(), generation, parents_range: PARENTS_RANGE_FULL, }; let revset = index.evaluate_revset(&expression, repo.store()).unwrap(); // Don't switch to more efficient .count() implementation. Here we're // testing the iterator behavior. revset.iter().count() }; // RevWalk deduplicates chains by entry. assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[], GENERATION_RANGE_FULL, ), 2 * num_generations ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[], GENERATION_RANGE_FULL, ), 2 * num_generations ); assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[left_commits[num_generations - 2].id().clone()], GENERATION_RANGE_FULL, ), 2 ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[right_commits[num_generations - 2].id().clone()], GENERATION_RANGE_FULL, ), 2 ); // RevWalkGenerationRange deduplicates chains by (entry, generation), which may // be more expensive than RevWalk, but should still finish in reasonable time. assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[], 0..(num_generations + 1) as u64, ), 2 * num_generations ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[], 0..(num_generations + 1) as u64, ), 2 * num_generations ); assert_eq!( count_revs( &[left_commits[num_generations - 1].id().clone()], &[left_commits[num_generations - 2].id().clone()], 0..(num_generations + 1) as u64, ), 2 ); assert_eq!( count_revs( &[right_commits[num_generations - 1].id().clone()], &[right_commits[num_generations - 2].id().clone()], 0..(num_generations + 1) as u64, ), 2 ); } #[test] fn test_index_commits_previous_operations() { // Test that commits visible only in previous operations are indexed. let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Remove commit B and C in one operation and make sure they're still // visible in the index after that operation. // o C // o B // o A // | o working copy // |/ // o root let mut tx = repo.start_transaction(); 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 repo = tx.commit("test").unwrap(); let mut tx = repo.start_transaction(); tx.repo_mut().remove_head(commit_c.id()); let repo = tx.commit("test").unwrap(); // Delete index from disk let default_index_store: &DefaultIndexStore = repo.index_store().downcast_ref().unwrap(); default_index_store.reinit().unwrap(); let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path()); let index = as_readonly_index(&repo); // There should be the root commit, plus 3 more assert_eq!(index.num_commits(), 1 + 3); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 3); assert_eq!(stats.num_merges, 0); assert_eq!(stats.max_generation_number, 3); assert_eq!(index.generation_number(commit_a.id()).unwrap(), 1); assert_eq!(index.generation_number(commit_b.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_c.id()).unwrap(), 3); } #[test] fn test_index_commits_hidden_but_referenced() { // Test that hidden-but-referenced commits are indexed. let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Remote bookmarks are usually visible at a certain point in operation // history, but that's not guaranteed if old operations have been discarded. // This can also happen if imported remote bookmarks get immediately // abandoned because the other bookmark has moved. let mut tx = repo.start_transaction(); 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()); tx.repo_mut().remove_head(commit_a.id()); tx.repo_mut().remove_head(commit_b.id()); tx.repo_mut().remove_head(commit_c.id()); tx.repo_mut().set_remote_bookmark( remote_symbol("bookmark", "origin"), RemoteRef { target: RefTarget::from_legacy_form( [commit_a.id().clone()], [commit_b.id().clone(), commit_c.id().clone()], ), state: jj_lib::op_store::RemoteRefState::New, }, ); let repo = tx.commit("test").unwrap(); // All commits should be indexed assert!(index_has_id(repo.index(), commit_a.id())); assert!(index_has_id(repo.index(), commit_b.id())); assert!(index_has_id(repo.index(), commit_c.id())); // Delete index from disk let default_index_store: &DefaultIndexStore = repo.index_store().downcast_ref().unwrap(); default_index_store.reinit().unwrap(); let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path()); // All commits should be reindexed assert!(index_has_id(repo.index(), commit_a.id())); assert!(index_has_id(repo.index(), commit_b.id())); assert!(index_has_id(repo.index(), commit_c.id())); } #[test] fn test_index_commits_incremental() { let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Create A in one operation, then B and C in another. Check that the index is // valid after. // o C // o B // o A // | o working copy // |/ // o root let root_commit = repo.store().root_commit(); let mut tx = repo.start_transaction(); let commit_a = write_random_commit_with_parents(tx.repo_mut(), &[]); let repo = tx.commit("test").unwrap(); let index = as_readonly_index(&repo); // There should be the root commit, plus 1 more assert_eq!(index.num_commits(), 1 + 1); let mut tx = repo.start_transaction(); 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]); tx.commit("test").unwrap(); let repo = test_env.load_repo_at_head(&settings, test_repo.repo_path()); let index = as_readonly_index(&repo); // There should be the root commit, plus 3 more assert_eq!(index.num_commits(), 1 + 3); let stats = index.stats(); assert_eq!(stats.num_commits, 1 + 3); assert_eq!(stats.num_merges, 0); assert_eq!(stats.max_generation_number, 3); assert_eq!(stats.commit_levels.len(), 1); assert_eq!(stats.commit_levels[0].num_commits, 4); assert_eq!(index.generation_number(root_commit.id()).unwrap(), 0); assert_eq!(index.generation_number(commit_a.id()).unwrap(), 1); assert_eq!(index.generation_number(commit_b.id()).unwrap(), 2); assert_eq!(index.generation_number(commit_c.id()).unwrap(), 3); } #[test] fn test_index_commits_incremental_empty_transaction() { let settings = testutils::user_settings(); let test_repo = TestRepo::init(); let test_env = &test_repo.env; let repo = &test_repo.repo; // Create A in one operation, then just an empty transaction. Check that the // index is valid after. // o A // | o working copy // |/ // o root let root_commit = repo.store().root_commit(); let mut tx = repo.start_transaction();