// 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()], ); // ConflictedLines 1–500 of 2799. Load more
// 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