4075 lines · 126.8 KB · Rust
RawDownload
// Copyright 2021 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::HashMap;use std::iter;use std::path::Path; use assert_matches::assert_matches;use chrono::DateTime;use itertools::Itertools as _;use jj_lib::backend::ChangeId;use jj_lib::backend::CommitId;use jj_lib::backend::MillisSinceEpoch;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::commit::Commit;use jj_lib::fileset::FilesetExpression;use jj_lib::git;use jj_lib::graph::reverse_graph;use jj_lib::graph::GraphEdge;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::op_store::RemoteRefState;use jj_lib::ref_name::RefName;use jj_lib::ref_name::RemoteName;use jj_lib::ref_name::RemoteRefSymbol;use jj_lib::ref_name::WorkspaceName;use jj_lib::ref_name::WorkspaceNameBuf;use jj_lib::repo::Repo;use jj_lib::repo_path::RepoPath;use jj_lib::repo_path::RepoPathUiConverter;use jj_lib::revset::parse;use jj_lib::revset::DefaultSymbolResolver;use jj_lib::revset::FailingSymbolResolver;use jj_lib::revset::Revset;use jj_lib::revset::RevsetAliasesMap;use jj_lib::revset::RevsetDiagnostics;use jj_lib::revset::RevsetExpression;use jj_lib::revset::RevsetExtensions;use jj_lib::revset::RevsetFilterPredicate;use jj_lib::revset::RevsetParseContext;use jj_lib::revset::RevsetResolutionError;use jj_lib::revset::RevsetWorkspaceContext;use jj_lib::revset::SymbolResolver as _;use jj_lib::revset::SymbolResolverExtension;use jj_lib::workspace::Workspace;use test_case::test_case;use testutils::create_random_commit;use testutils::create_tree;use testutils::write_random_commit;use testutils::CommitGraphBuilder;use testutils::TestRepo;use testutils::TestRepoBackend;use testutils::TestWorkspace; 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 resolve_symbol_with_extensions(    repo: &dyn Repo,    extensions: &RevsetExtensions,    symbol: &str,) -> Result<Vec<CommitId>, RevsetResolutionError> {    let context = RevsetParseContext {        aliases_map: &RevsetAliasesMap::default(),        local_variables: HashMap::new(),        user_email: "",        date_pattern_context: chrono::Local::now().into(),        extensions,        workspace: None,    };    let expression = parse(&mut RevsetDiagnostics::new(), symbol, &context).unwrap();    assert_matches!(*expression, RevsetExpression::CommitRef(_));    let symbol_resolver = DefaultSymbolResolver::new(repo, extensions.symbol_resolvers());    match expression        .resolve_user_expression(repo, &symbol_resolver)?        .as_ref()    {        RevsetExpression::Commits(commits) => Ok(commits.clone()),        expression => panic!("symbol resolved to compound expression: {expression:?}"),    }} fn resolve_symbol(repo: &dyn Repo, symbol: &str) -> Result<Vec<CommitId>, RevsetResolutionError> {    resolve_symbol_with_extensions(repo, &RevsetExtensions::default(), symbol)} fn revset_for_commits<'index>(    repo: &'index dyn Repo,    commits: &[&Commit],) -> Box<dyn Revset + 'index> {    let symbol_resolver =        DefaultSymbolResolver::new(repo, &([] as [&Box<dyn SymbolResolverExtension>; 0]));    RevsetExpression::commits(commits.iter().map(|commit| commit.id().clone()).collect())        .resolve_user_expression(repo, &symbol_resolver)        .unwrap()        .evaluate(repo)        .unwrap()} #[test]fn test_resolve_symbol_empty_string() {    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     assert_matches!(        resolve_symbol(repo.as_ref(), r#""""#),        Err(RevsetResolutionError::EmptyString)    );} #[test]fn test_resolve_symbol_commit_id() {    let settings = testutils::user_settings();    // Test only with git so we can get predictable commit ids    let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git);    let repo = &test_repo.repo;     let mut tx = repo.start_transaction();    let mut_repo = tx.repo_mut();    let signature = Signature {        name: "test".to_string(),        email: "test".to_string(),        timestamp: Timestamp {            timestamp: MillisSinceEpoch(0),            tz_offset: 0,        },    };     let mut commits = vec![];    for i in &[1, 167, 895] {        let commit = mut_repo            .new_commit(                vec![repo.store().root_commit_id().clone()],                repo.store().empty_merged_tree_id(),            )            .set_description(format!("test {i}"))            .set_author(signature.clone())            .set_committer(signature.clone())            .write()            .unwrap();        commits.push(commit);    }    let repo = tx.commit("test").unwrap();     // Test the test setup    assert_eq!(        commits[0].id().hex(),        "0454de3cae04c46cda37ba2e8873b4c17ff51dcb"    );    assert_eq!(        commits[1].id().hex(),        "045f56cd1b17e8abde86771e2705395dcde6a957"    );    assert_eq!(        commits[2].id().hex(),        "0468f7da8de2ce442f512aacf83411d26cd2e0cf"    );     // Change ids should never have prefix "04"    insta::assert_snapshot!(commits[0].change_id().hex(), @"781199f9d55d18e855a7aa84c5e4b40d");    insta::assert_snapshot!(commits[1].change_id().hex(), @"a2c96fc88f32e487328f04927f20c4b1");    insta::assert_snapshot!(commits[2].change_id().hex(), @"4399e4f3123763dfe7d68a2809ecc01b");     // Test lookup by full commit id    assert_eq!(        resolve_symbol(repo.as_ref(), "0454de3cae04c46cda37ba2e8873b4c17ff51dcb",).unwrap(),        vec![commits[0].id().clone()]    );    assert_eq!(        resolve_symbol(repo.as_ref(), "045f56cd1b17e8abde86771e2705395dcde6a957",).unwrap(),        vec![commits[1].id().clone()]    );    assert_eq!(        resolve_symbol(repo.as_ref(), "0468f7da8de2ce442f512aacf83411d26cd2e0cf",).unwrap(),        vec![commits[2].id().clone()]    );     // Test commit id prefix    assert_eq!(        resolve_symbol(repo.as_ref(), "046").unwrap(),        vec![commits[2].id().clone()]    );    assert_matches!(        resolve_symbol(repo.as_ref(), "04"),        Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "04"    );    assert_matches!(        resolve_symbol(repo.as_ref(), "040"),        Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "040" && candidates.is_empty()    );     // Test non-hex string    assert_matches!(        resolve_symbol(repo.as_ref(), "foo"),        Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "foo" && candidates.is_empty()    );     // Test present() suppresses only NoSuchRevision error    assert_eq!(resolve_commit_ids(repo.as_ref(), "present(foo)"), []);    let symbol_resolver = DefaultSymbolResolver::new(        repo.as_ref(),        &([] as [&Box<dyn SymbolResolverExtension>; 0]),    );    let context = RevsetParseContext {        aliases_map: &RevsetAliasesMap::default(),        local_variables: HashMap::new(),        user_email: settings.user_email(),        date_pattern_context: chrono::Utc::now().fixed_offset().into(),        extensions: &RevsetExtensions::default(),        workspace: None,    };    assert_matches!(        parse(&mut RevsetDiagnostics::new(), "present(04)", &context).unwrap()            .resolve_user_expression(repo.as_ref(), &symbol_resolver),        Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "04"    );    assert_eq!(        resolve_commit_ids(repo.as_ref(), "present(046)"),        vec![commits[2].id().clone()]    );} #[test_case(false ; "mutable")]#[test_case(true ; "readonly")]fn test_resolve_symbol_change_id(readonly: bool) {    // Test only with git so we can get predictable commit ids    let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git);    let repo = &test_repo.repo;     // Add some commits that will end up having change ids with common prefixes    let author = Signature {        name: "git author".to_owned(),        email: "git.author@example.com".to_owned(),        timestamp: Timestamp {            timestamp: MillisSinceEpoch(1_000_000),            tz_offset: 60,        },    };    let committer = Signature {        name: "git committer".to_owned(),        email: "git.committer@example.com".to_owned(),        timestamp: Timestamp {            timestamp: MillisSinceEpoch(2_000_000),            tz_offset: -480,        },    };    let root_commit_id = repo.store().root_commit_id();    let empty_tree_id = repo.store().empty_merged_tree_id();    // These are change ids that would be generated for the imported commits,    // but that isn't important. Here we have common prefixes "04", "040",    // "04e1" across commit and change ids.    let change_ids = [        "04e12a5467bba790efb88a9870894ec2",        "040b3ba3a51d8edbc4c5855cbd09de71",        "04e1c7082e4e34f3f371d8a1a46770b8",        "911d7e52fd5ba04b8f289e14c3d30b52",    ]    .map(ChangeId::from_hex);    let mut commits = vec![];    let mut tx = repo.start_transaction();    for (i, change_id) in iter::zip([133, 664, 840, 5085], change_ids) {        let commit = tx            .repo_mut()            .new_commit(vec![root_commit_id.clone()], empty_tree_id.clone())            .set_change_id(change_id)            .set_description(format!("test {i}"))            .set_author(author.clone())            .set_committer(committer.clone())            .write()            .unwrap();        commits.push(commit);    }     // Test the test setup    insta::allow_duplicates! {        insta::assert_snapshot!(            commits.iter().map(|c| format!("{} {}\n", c.id(), c.change_id())).join(""), @r"        8fd68d104372910e19511df709e5dde62a548720 zvlyxpuvtsoopsqzlkorrpqrszrqvlnx        5339432b8e7b90bd3aa1a323db71b8a5c5dcd020 zvzowopwpuymrlmonvnuruunomzqmlsy        e2ad9d861d0ee625851b8ecfcf2c727410e38720 zvlynszrxlvlwvkwkwsymrpypvtsszor        040031cb4ad0cbc3287914f1d205dabf4a7eb889 qyymsluxkmuopzvorkxrqlyvnwmwzoux        ");    }     let _readonly_repo;    let repo: &dyn Repo = if readonly {        _readonly_repo = tx.commit("test").unwrap();        _readonly_repo.as_ref()    } else {        tx.repo_mut()    };     // Test lookup by full change id    assert_eq!(        resolve_symbol(repo, "zvlyxpuvtsoopsqzlkorrpqrszrqvlnx").unwrap(),        vec![commits[0].id().clone()]    );    assert_eq!(        resolve_symbol(repo, "zvzowopwpuymrlmonvnuruunomzqmlsy").unwrap(),        vec![commits[1].id().clone()]    );    assert_eq!(        resolve_symbol(repo, "zvlynszrxlvlwvkwkwsymrpypvtsszor").unwrap(),        vec![commits[2].id().clone()]    );     // Test change id prefix    assert_eq!(        resolve_symbol(repo, "zvlyx").unwrap(),        vec![commits[0].id().clone()]    );    assert_eq!(        resolve_symbol(repo, "zvlyn").unwrap(),        vec![commits[2].id().clone()]    );    assert_matches!(        resolve_symbol(repo, "zvly"),        Err(RevsetResolutionError::AmbiguousChangeIdPrefix(s)) if s == "zvly"    );    assert_matches!(        resolve_symbol(repo, "zvlyw"),        Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "zvlyw" && candidates.is_empty()    );     // Test that commit and changed id don't conflict ("040" and "zvz" are the    // same).    assert_eq!(        resolve_symbol(repo, "040").unwrap(),        vec![commits[3].id().clone()]    );    assert_eq!(        resolve_symbol(repo, "zvz").unwrap(),        vec![commits[1].id().clone()]    );     // Test non-hex string    assert_matches!(        resolve_symbol(repo, "foo"),        Err(RevsetResolutionError::NoSuchRevision{            name,            candidates        }) if name == "foo" && candidates.is_empty()    );} #[test]fn test_resolve_symbol_in_different_disambiguation_context() {    let test_repo = TestRepo::init();    let repo0 = &test_repo.repo;     let mut tx = repo0.start_transaction();    let commit1 = write_random_commit(tx.repo_mut());    // Create more commits that are likely to conflict with 1-char hex prefix.    for _ in 0..50 {        write_random_commit(tx.repo_mut());    }    let repo1 = tx.commit("test").unwrap();     let mut tx = repo1.start_transaction();    let commit2 = tx.repo_mut().rewrite_commit(&commit1).write().unwrap();    tx.repo_mut().rebase_descendants().unwrap();    let repo2 = tx.commit("test").unwrap();     // Set up disambiguation index which only contains the commit2.id().    let id_prefix_context = IdPrefixContext::new(Default::default())        .disambiguate_within(RevsetExpression::commit(commit2.id().clone()));    let symbol_resolver =        DefaultSymbolResolver::new(repo2.as_ref(), &[] as &[Box<dyn SymbolResolverExtension>])            .with_id_prefix_context(&id_prefix_context);     // Sanity check    let change_hex = commit2.change_id().reverse_hex();    assert_eq!(        symbol_resolver            .resolve_symbol(repo2.as_ref(), &change_hex[0..1])            .unwrap(),        vec![commit2.id().clone()]    );    assert_eq!(        symbol_resolver            .resolve_symbol(repo2.as_ref(), &commit2.id().hex()[0..1])            .unwrap(),        vec![commit2.id().clone()]    );     // Change ID is disambiguated within repo2, then resolved in repo1.    assert_eq!(        symbol_resolver            .resolve_symbol(repo1.as_ref(), &change_hex[0..1])            .unwrap(),        vec![commit1.id().clone()]    );     // Commit ID can be found in the disambiguation index, but doesn't exist in    // repo1.    assert_matches!(        symbol_resolver.resolve_symbol(repo1.as_ref(), &commit2.id().hex()[0..1]),        Err(RevsetResolutionError::NoSuchRevision { .. })    );} #[test]fn test_resolve_working_copy() {    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     let mut tx = repo.start_transaction();    let mut_repo = tx.repo_mut();     let commit1 = write_random_commit(mut_repo);    let commit2 = write_random_commit(mut_repo);     let ws1 = WorkspaceNameBuf::from("ws1");    let ws2 = WorkspaceNameBuf::from("ws2");     // Cannot resolve a working-copy commit for an unknown workspace    assert_matches!(        RevsetExpression::working_copy(ws1.clone())            .resolve_user_expression(mut_repo, &FailingSymbolResolver),        Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name }) if name == "ws1"    );     // The error can be suppressed by present()    assert_eq!(        RevsetExpression::working_copy(ws1.clone())            .present()            .resolve_user_expression(mut_repo, &FailingSymbolResolver)            .unwrap()            .evaluate(mut_repo)            .unwrap()            .iter()            .map(Result::unwrap)            .collect_vec(),        vec![]    );     // Add some workspaces    mut_repo        .set_wc_commit(ws1.clone(), commit1.id().clone())        .unwrap();    mut_repo        .set_wc_commit(ws2.clone(), commit2.id().clone())        .unwrap();    let resolve = |name: WorkspaceNameBuf| -> Vec<CommitId> {        RevsetExpression::working_copy(name)            .resolve_user_expression(mut_repo, &FailingSymbolResolver)            .unwrap()            .evaluate(mut_repo)            .unwrap()            .iter()            .map(Result::unwrap)            .collect()    };     // Can resolve "@" shorthand with a default workspace name    assert_eq!(resolve(ws1), vec![commit1.id().clone()]);    // Can resolve an explicit checkout    assert_eq!(resolve(ws2), vec![commit2.id().clone()]);} #[test]fn test_resolve_working_copies() {    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     let mut tx = repo.start_transaction();    let mut_repo = tx.repo_mut();     let commit1 = write_random_commit(mut_repo);    let commit2 = write_random_commit(mut_repo);     // Add some workspaces    let ws1 = WorkspaceNameBuf::from("ws1");    let ws2 = WorkspaceNameBuf::from("ws2");     // add one commit to each working copy    mut_repo        .set_wc_commit(ws1.clone(), commit1.id().clone())

Lines 1–500 of 4075.