2799 lines · 87.3 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::path::Path; use assert_matches::assert_matches;use itertools::Itertools;use jj_lib::backend::{CommitId, MillisSinceEpoch, Signature, Timestamp};use jj_lib::commit::Commit;use jj_lib::git;use jj_lib::git_backend::GitBackend;use jj_lib::object_id::ObjectId;use jj_lib::op_store::{RefTarget, RemoteRef, RemoteRefState, WorkspaceId};use jj_lib::repo::Repo;use jj_lib::repo_path::RepoPath;use jj_lib::revset::{    optimize, parse, DefaultSymbolResolver, FailingSymbolResolver, ResolvedExpression, Revset,    RevsetAliasesMap, RevsetExpression, RevsetFilterPredicate, RevsetParseContext,    RevsetResolutionError, RevsetWorkspaceContext,};use jj_lib::revset_graph::{ReverseRevsetGraphIterator, RevsetGraphEdge};use jj_lib::settings::GitSettings;use jj_lib::workspace::Workspace;use test_case::test_case;use testutils::{    create_random_commit, create_tree, write_random_commit, CommitGraphBuilder, TestRepo,    TestRepoBackend, TestWorkspace,}; fn resolve_symbol(repo: &dyn Repo, symbol: &str) -> Result<Vec<CommitId>, RevsetResolutionError> {    let context = RevsetParseContext {        aliases_map: &RevsetAliasesMap::new(),        user_email: String::new(),        workspace: None,    };    let expression = parse(symbol, &context).unwrap();    assert_matches!(*expression, RevsetExpression::CommitRef(_));    let symbol_resolver = DefaultSymbolResolver::new(repo);    match expression.resolve_user_expression(repo, &symbol_resolver)? {        ResolvedExpression::Commits(commits) => Ok(commits),        expression => panic!("symbol resolved to compound expression: {expression:?}"),    }} fn revset_for_commits<'index>(    repo: &'index dyn Repo,    commits: &[&Commit],) -> Box<dyn Revset + 'index> {    let symbol_resolver = DefaultSymbolResolver::new(repo);    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(&settings);    let mut_repo = tx.mut_repo();    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(                &settings,                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");     // 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());    let context = RevsetParseContext {        aliases_map: &RevsetAliasesMap::new(),        user_email: settings.user_email(),        workspace: None,    };    assert_matches!(        optimize(parse("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) {    let settings = testutils::user_settings();    let git_settings = GitSettings::default();    // Test only with git so we can get predictable change ids    let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git);    let repo = &test_repo.repo;     let git_repo = repo        .store()        .backend_impl()        .downcast_ref::<GitBackend>()        .unwrap()        .open_git_repo()        .unwrap();    // Add some commits that will end up having change ids with common prefixes    let empty_tree_id = git_repo.treebuilder(None).unwrap().write().unwrap();    let git_author = git2::Signature::new(        "git author",        "git.author@example.com",        &git2::Time::new(1000, 60),    )    .unwrap();    let git_committer = git2::Signature::new(        "git committer",        "git.committer@example.com",        &git2::Time::new(2000, -480),    )    .unwrap();    let git_tree = git_repo.find_tree(empty_tree_id).unwrap();    let mut git_commit_ids = vec![];    for i in &[133, 664, 840, 5085] {        let git_commit_id = git_repo            .commit(                Some(&format!("refs/heads/branch{i}")),                &git_author,                &git_committer,                &format!("test {i}"),                &git_tree,                &[],            )            .unwrap();        git_commit_ids.push(git_commit_id);    }     let mut tx = repo.start_transaction(&settings);    git::import_refs(tx.mut_repo(), &git_settings).unwrap();     // Test the test setup    assert_eq!(        hex::encode(git_commit_ids[0]),        // "04e12a5467bba790efb88a9870894ec208b16bf1" reversed        "8fd68d104372910e19511df709e5dde62a548720"    );    assert_eq!(        hex::encode(git_commit_ids[1]),        // "040b3ba3a51d8edbc4c5855cbd09de71d4c29cca" reversed        "5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"    );    assert_eq!(        hex::encode(git_commit_ids[2]),        // "04e1c7082e4e34f3f371d8a1a46770b861b9b547" reversed        "e2ad9d861d0ee625851b8ecfcf2c727410e38720"    );    assert_eq!(        hex::encode(git_commit_ids[3]),        // "911d7e52fd5ba04b8f289e14c3d30b52d38c0020" reversed        "040031cb4ad0cbc3287914f1d205dabf4a7eb889"    );     let _readonly_repo;    let repo: &dyn Repo = if readonly {        _readonly_repo = tx.commit("test");        _readonly_repo.as_ref()    } else {        tx.mut_repo()    };     // Test lookup by full change id    assert_eq!(        resolve_symbol(repo, "zvlyxpuvtsoopsqzlkorrpqrszrqvlnx").unwrap(),        vec![CommitId::from_hex(            "8fd68d104372910e19511df709e5dde62a548720"        )]    );    assert_eq!(        resolve_symbol(repo, "zvzowopwpuymrlmonvnuruunomzqmlsy").unwrap(),        vec![CommitId::from_hex(            "5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"        )]    );    assert_eq!(        resolve_symbol(repo, "zvlynszrxlvlwvkwkwsymrpypvtsszor").unwrap(),        vec![CommitId::from_hex(            "e2ad9d861d0ee625851b8ecfcf2c727410e38720"        )]    );     // Test change id prefix    assert_eq!(        resolve_symbol(repo, "zvlyx").unwrap(),        vec![CommitId::from_hex(            "8fd68d104372910e19511df709e5dde62a548720"        )]    );    assert_eq!(        resolve_symbol(repo, "zvlyn").unwrap(),        vec![CommitId::from_hex(            "e2ad9d861d0ee625851b8ecfcf2c727410e38720"        )]    );    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![CommitId::from_hex(            "040031cb4ad0cbc3287914f1d205dabf4a7eb889"        )]    );    assert_eq!(        resolve_symbol(repo, "zvz").unwrap(),        vec![CommitId::from_hex(            "5339432b8e7b90bd3aa1a323db71b8a5c5dcd020"        )]    );     // 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_working_copy() {    let settings = testutils::user_settings();    let test_repo = TestRepo::init();    let repo = &test_repo.repo;     let mut tx = repo.start_transaction(&settings);    let mut_repo = tx.mut_repo();     let commit1 = write_random_commit(mut_repo, &settings);    let commit2 = write_random_commit(mut_repo, &settings);     let ws1 = WorkspaceId::new("ws1".to_string());    let ws2 = WorkspaceId::new("ws2".to_string());     // 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"    );     // 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 = |ws_id: WorkspaceId| -> Vec<CommitId> {        RevsetExpression::working_copy(ws_id)            .evaluate_programmatic(mut_repo)            .unwrap()            .iter()            .collect()    };     // Can resolve "@" shorthand with a default workspace ID    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_symbol_branches() {    let settings = testutils::user_settings();    let test_repo = TestRepo::init();    let repo = &test_repo.repo;    let new_remote_ref = |target| RemoteRef {        target,        state: RemoteRefState::New,    };    let tracking_remote_ref = |target| RemoteRef {        target,        state: RemoteRefState::Tracking,    };    let normal_tracking_remote_ref =        |id: &CommitId| tracking_remote_ref(RefTarget::normal(id.clone()));     let mut tx = repo.start_transaction(&settings);    let mut_repo = tx.mut_repo();     let commit1 = write_random_commit(mut_repo, &settings);    let commit2 = write_random_commit(mut_repo, &settings);    let commit3 = write_random_commit(mut_repo, &settings);    let commit4 = write_random_commit(mut_repo, &settings);    let commit5 = write_random_commit(mut_repo, &settings);     mut_repo.set_local_branch_target("local", RefTarget::normal(commit1.id().clone()));    mut_repo.set_remote_branch("remote", "origin", normal_tracking_remote_ref(commit2.id()));    mut_repo.set_local_branch_target("local-remote", RefTarget::normal(commit3.id().clone()));    mut_repo.set_remote_branch(        "local-remote",        "origin",        normal_tracking_remote_ref(commit4.id()),    );    mut_repo.set_local_branch_target(        "local-remote@origin", // not a remote branch        RefTarget::normal(commit5.id().clone()),    );    mut_repo.set_remote_branch(        "local-remote",        "mirror",        tracking_remote_ref(mut_repo.get_local_branch("local-remote")),    );    mut_repo.set_remote_branch(        "local-remote",        "untracked",        new_remote_ref(mut_repo.get_local_branch("local-remote")),    );    mut_repo.set_remote_branch(        "local-remote",        git::REMOTE_NAME_FOR_LOCAL_GIT_REPO,        tracking_remote_ref(mut_repo.get_local_branch("local-remote")),    );     mut_repo.set_local_branch_target(        "local-conflicted",        RefTarget::from_legacy_form(            [commit1.id().clone()],            [commit3.id().clone(), commit2.id().clone()],        ),    );    mut_repo.set_remote_branch(        "remote-conflicted",        "origin",        tracking_remote_ref(RefTarget::from_legacy_form(            [commit3.id().clone()],            [commit5.id().clone(), commit4.id().clone()],        )),    );     // Local only    assert_eq!(        resolve_symbol(mut_repo, "local").unwrap(),        vec![commit1.id().clone()],    );    insta::assert_debug_snapshot!(        resolve_symbol(mut_repo, "local@origin").unwrap_err(), @r###"    NoSuchRevision {        name: "local@origin",        candidates: [            "local",            "local-remote@git",            "local-remote@mirror",            "local-remote@origin",            "remote@origin",        ],    }    "###);     // Remote only (or locally deleted)    insta::assert_debug_snapshot!(        resolve_symbol(mut_repo, "remote").unwrap_err(), @r###"    NoSuchRevision {        name: "remote",        candidates: [            "remote-conflicted@origin",            "remote@origin",        ],    }    "###);    assert_eq!(        resolve_symbol(mut_repo, "remote@origin").unwrap(),        vec![commit2.id().clone()],    );     // Local/remote/git    assert_eq!(        resolve_symbol(mut_repo, "local-remote").unwrap(),        vec![commit3.id().clone()],    );    assert_eq!(        resolve_symbol(mut_repo, "local-remote@origin").unwrap(),        vec![commit4.id().clone()],    );    assert_eq!(        resolve_symbol(mut_repo, r#""local-remote@origin""#).unwrap(),        vec![commit5.id().clone()],    );    assert_eq!(        resolve_symbol(mut_repo, "local-remote@mirror").unwrap(),        vec![commit3.id().clone()],    );    assert_eq!(        resolve_symbol(mut_repo, "local-remote@git").unwrap(),        vec![commit3.id().clone()],    );     // Conflicted

Lines 1–500 of 2799.