1263 lines · 45.1 KB · Rust
RawDownload
// 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.