use std::collections::HashMap;
use std::iter;
use std::path::Path;
use std::sync::Arc;
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::default_index::DefaultIndexStore;
use jj_lib::fileset::FilesetAliasesMap;
use jj_lib::fileset::FilesetExpression;
use jj_lib::git;
use jj_lib::graph::GraphEdge;
use jj_lib::graph::reverse_graph;
use jj_lib::id_prefix::IdPrefixContext;
use jj_lib::merge::Merge;
use jj_lib::merged_tree::MergedTree;
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::ReadonlyRepo;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPath;
use jj_lib::repo_path::RepoPathUiConverter;
use jj_lib::revset::ResolvedRevsetExpression;
use jj_lib::revset::Revset;
use jj_lib::revset::RevsetAliasesMap;
use jj_lib::revset::RevsetDiagnostics;
use jj_lib::revset::RevsetEvaluationError;
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;
use jj_lib::revset::SymbolResolverExtension;
use jj_lib::revset::parse;
use jj_lib::signing::SignBehavior;
use jj_lib::signing::Signer;
use jj_lib::test_signing_backend::TestSigningBackend;
use jj_lib::workspace::Workspace;
use pollster::FutureExt as _;
use test_case::test_case;
use testutils::CommitBuilderExt as _;
use testutils::TestRepo;
use testutils::TestRepoBackend;
use testutils::TestResult;
use testutils::TestWorkspace;
use testutils::create_random_commit;
use testutils::create_tree;
use testutils::create_tree_with;
use testutils::repo_path;
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 default_symbol_resolver(repo: &dyn Repo) -> SymbolResolver<'_> {
SymbolResolver::new(repo, &([] as [&Box<dyn SymbolResolverExtension>; 0]))
}
fn resolve_symbol(repo: &dyn Repo, 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(),
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
fileset_aliases_map: &FilesetAliasesMap::new(),
use_glob_by_default: true,
extensions: &RevsetExtensions::default(),
workspace: None,
};
let expression = parse(&mut RevsetDiagnostics::new(), symbol, &context).unwrap();
assert_matches!(*expression, RevsetExpression::CommitRef(_));
let symbol_resolver = default_symbol_resolver(repo);
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 revset_for_commits<'index>(
repo: &'index dyn Repo,
commits: &[&Commit],
) -> Box<dyn Revset + 'index> {
let symbol_resolver = default_symbol_resolver(repo);
RevsetExpression::commits(commits.iter().map(|commit| commit.id().clone()).collect())
.resolve_user_expression(repo, &symbol_resolver)
.unwrap()
.evaluate(repo)
.unwrap()
}
fn build_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(), u32::MAX, |_| ())
.block_on()
.unwrap();
repo.reload_at(repo.operation()).block_on().unwrap()
}
#[test]
fn test_resolve_symbol_empty_string() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
assert_matches!(
resolve_symbol(repo.as_ref(), r#""""#),
Err(RevsetResolutionError::EmptyString)
);
Ok(())
}
#[test]
fn test_resolve_symbol_commit_id() -> TestResult {
let settings = testutils::user_settings();
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 [156, 268, 869] {
let commit = mut_repo
.new_commit(
vec![repo.store().root_commit_id().clone()],
repo.store().empty_merged_tree(),
)
.set_change_id(ChangeId::from_hex("781199f9d55d18e855a7aa84c5e4b40d"))
.set_description(format!("test {i}"))
.set_author(signature.clone())
.set_committer(signature.clone())
.write_unwrap();
commits.push(commit);
}
let repo = tx.commit("test").block_on()?;
insta::assert_snapshot!(commits.iter().map(|c| c.id().hex()).join("\n"), @"
019f179b4479a4f3d1373b772866037929e4f63c
019fd357eb2a4904c348b62d1f4cc2ac222cdbc7
017dc442a1d77bb1620a1a32863580ae81543d7d
");
assert_eq!(
resolve_symbol(repo.as_ref(), "019f179b4479a4f3d1373b772866037929e4f63c",)?,
vec![commits[0].id().clone()]
);
assert_eq!(
resolve_symbol(repo.as_ref(), "019fd357eb2a4904c348b62d1f4cc2ac222cdbc7",)?,
vec![commits[1].id().clone()]
);
assert_eq!(
resolve_symbol(repo.as_ref(), "017dc442a1d77bb1620a1a32863580ae81543d7d",)?,
vec![commits[2].id().clone()]
);
assert_eq!(
resolve_symbol(repo.as_ref(), "017")?,
vec![commits[2].id().clone()]
);
assert_matches!(
resolve_symbol(repo.as_ref(), "01"),
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "01"
);
assert_matches!(
resolve_symbol(repo.as_ref(), "010"),
Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "010" && candidates.is_empty()
);
assert_matches!(
resolve_symbol(repo.as_ref(), "foo"),
Err(RevsetResolutionError::NoSuchRevision{name, candidates}) if name == "foo" && candidates.is_empty()
);
assert_eq!(resolve_commit_ids(repo.as_ref(), "present(foo)"), []);
let symbol_resolver = default_symbol_resolver(repo.as_ref());
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(),
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
fileset_aliases_map: &FilesetAliasesMap::new(),
use_glob_by_default: true,
extensions: &RevsetExtensions::default(),
workspace: None,
};
assert_matches!(
parse(&mut RevsetDiagnostics::new(), "present(01)", &context)?
.resolve_user_expression(repo.as_ref(), &symbol_resolver),
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "01"
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), "present(017)"),
vec![commits[2].id().clone()]
);
assert_eq!(
resolve_symbol(
repo.as_ref(),
"commit_id(019f179b4479a4f3d1373b772866037929e4f63c)",
)?,
vec![commits[0].id().clone()]
);
assert_eq!(
resolve_symbol(repo.as_ref(), "commit_id(019f1)")?,
vec![commits[0].id().clone()]
);
assert_eq!(resolve_symbol(repo.as_ref(), "commit_id(12345)")?, vec![]);
assert_matches!(
resolve_symbol(repo.as_ref(), "commit_id('')"),
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s.is_empty()
);
assert_matches!(
resolve_symbol(repo.as_ref(), "commit_id(0)"),
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "0"
);
Ok(())
}
#[test_case(false ; "mutable")]
#[test_case(true ; "readonly")]
fn test_resolve_symbol_change_id(readonly: bool) -> TestResult {
let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git);
let repo = &test_repo.repo;
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 = repo.store().empty_merged_tree();
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([0, 1, 2, 5359], change_ids) {
let commit = tx
.repo_mut()
.new_commit(vec![root_commit_id.clone()], empty_tree.clone())
.set_change_id(change_id)
.set_description(format!("test {i}"))
.set_author(author.clone())
.set_committer(committer.clone())
.write_unwrap();
commits.push(commit);
}
insta::allow_duplicates! {
insta::assert_snapshot!(
commits.iter().map(|c| format!("{} {}\n", c.id(), c.change_id())).join(""), @"
cd741d7f2c542e443df3c5bf2d4f8a15a2759e77 zvlyxpuvtsoopsqzlkorrpqrszrqvlnx
0af32dcddbdf49c132ad39c3623a6196c6c987a5 zvzowopwpuymrlmonvnuruunomzqmlsy
553ee869e64329d1022f5c00c63dff6621924c18 zvlynszrxlvlwvkwkwsymrpypvtsszor
0407d5eb08231b546a42518a50a835f17282eaef qyymsluxkmuopzvorkxrqlyvnwmwzoux
");
}
let _readonly_repo;
let repo: &dyn Repo = if readonly {
_readonly_repo = tx.commit("test").block_on()?;
_readonly_repo.as_ref()
} else {
tx.repo_mut()
};
assert_eq!(
resolve_symbol(repo, "zvlyxpuvtsoopsqzlkorrpqrszrqvlnx")?,
vec![commits[0].id().clone()]
);
assert_eq!(
resolve_symbol(repo, "zvzowopwpuymrlmonvnuruunomzqmlsy")?,
vec![commits[1].id().clone()]
);
assert_eq!(
resolve_symbol(repo, "zvlynszrxlvlwvkwkwsymrpypvtsszor")?,
vec![commits[2].id().clone()]
);
assert_eq!(
resolve_symbol(repo, "zvlyx")?,
vec![commits[0].id().clone()]
);
assert_eq!(
resolve_symbol(repo, "zvlyn")?,
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()
);
assert_eq!(resolve_symbol(repo, "040")?, vec![commits[3].id().clone()]);
assert_eq!(resolve_symbol(repo, "zvz")?, vec![commits[1].id().clone()]);
assert_matches!(
resolve_symbol(repo, "foo"),
Err(RevsetResolutionError::NoSuchRevision{
name,
candidates
}) if name == "foo" && candidates.is_empty()
);
assert_eq!(
resolve_symbol(repo, "change_id(zvlyxpuvtsoopsqzlkorrpqrszrqvlnx)")?,
vec![commits[0].id().clone()]
);
assert_eq!(
resolve_symbol(repo, "change_id(zvlyx)")?,
vec![commits[0].id().clone()]
);
assert_eq!(resolve_symbol(repo, "change_id(xyzzy)")?, vec![]);
assert_matches!(
resolve_symbol(repo, "change_id('')"),
Err(RevsetResolutionError::AmbiguousChangeIdPrefix(s)) if s.is_empty()
);
assert_matches!(
resolve_symbol(repo, "change_id(z)"),
Err(RevsetResolutionError::AmbiguousChangeIdPrefix(s)) if s == "z"
);
Ok(())
}
#[test]
fn test_resolve_symbol_divergent_change_id() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let commit1 = write_random_commit(tx.repo_mut());
let commit2 = create_random_commit(tx.repo_mut())
.set_change_id(commit1.change_id().clone())
.write_unwrap();
let change_id = commit1.change_id();
assert_matches!(
resolve_symbol(tx.repo(), &format!("{change_id}")),
Err(RevsetResolutionError::DivergentChangeId { symbol, visible_targets })
if symbol == change_id.to_string()
&& visible_targets == vec![(0, commit2.id().clone()), (1, commit1.id().clone())]
);
assert_eq!(
resolve_symbol(tx.repo(), &format!("{change_id}/0"))?,
vec![commit2.id().clone()]
);
assert_eq!(
resolve_symbol(tx.repo(), &format!("{change_id}/1"))?,
vec![commit1.id().clone()]
);
assert_matches!(
resolve_symbol(tx.repo(), &format!("{change_id}/2")),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
assert_eq!(
resolve_symbol(tx.repo(), &format!("change_id({change_id})"))?,
vec![commit2.id().clone(), commit1.id().clone()]
);
Ok(())
}
#[test]
fn test_resolve_symbol_hidden_change_id() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let commit1 = write_random_commit(tx.repo_mut());
let commit2 = tx
.repo_mut()
.rewrite_commit(&commit1)
.set_description("updated commit")
.write_unwrap();
tx.repo_mut().rebase_descendants().block_on()?;
let repo = tx.commit("rewrite commit").block_on()?;
let change_id = commit1.change_id();
assert_eq!(
resolve_symbol(repo.as_ref(), &format!("{change_id}"))?,
vec![commit2.id().clone()]
);
assert_eq!(
resolve_symbol(repo.as_ref(), &format!("{change_id}/0"))?,
vec![commit2.id().clone()]
);
assert_eq!(
resolve_symbol(repo.as_ref(), &format!("{change_id}/1"))?,
vec![commit1.id().clone()]
);
assert_matches!(
resolve_symbol(repo.as_ref(), &format!("{change_id}/2")),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
assert_eq!(
resolve_symbol(repo.as_ref(), &format!("change_id({change_id})"))?,
vec![commit2.id().clone()]
);
let mut tx = repo.start_transaction();
tx.repo_mut().record_abandoned_commit(&commit2);
tx.repo_mut().rebase_descendants().block_on()?;
let repo = tx.commit("abandon commit").block_on()?;
assert_matches!(
resolve_symbol(repo.as_ref(), &format!("{change_id}")),
Err(RevsetResolutionError::NoSuchRevision { name, candidates })
if name == change_id.to_string() && candidates.is_empty()
);
assert_eq!(
resolve_symbol(repo.as_ref(), &format!("{change_id}/0"))?,
vec![commit2.id().clone()]
);
assert_eq!(
resolve_symbol(repo.as_ref(), &format!("{change_id}/1"))?,
vec![commit1.id().clone()]
);
assert_matches!(
resolve_symbol(repo.as_ref(), &format!("{change_id}/2")),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
assert_eq!(
resolve_symbol(repo.as_ref(), &format!("change_id({change_id})"))?,
vec![]
);
Ok(())
}
#[test]
fn test_resolve_symbol_in_different_disambiguation_context() -> TestResult {
let test_repo = TestRepo::init();
let repo0 = &test_repo.repo;
let mut tx = repo0.start_transaction();
let commit1 = write_random_commit(tx.repo_mut());
for _ in 0..50 {
write_random_commit(tx.repo_mut());
}
let repo1 = tx.commit("test").block_on()?;
let mut tx = repo1.start_transaction();
let commit2 = tx.repo_mut().rewrite_commit(&commit1).write_unwrap();
tx.repo_mut().rebase_descendants().block_on()?;
let repo2 = tx.commit("test").block_on()?;
let id_prefix_context = IdPrefixContext::new(Arc::new(RevsetExtensions::default()))
.disambiguate_within(RevsetExpression::commit(commit2.id().clone()));
let symbol_resolver =
default_symbol_resolver(repo2.as_ref()).with_id_prefix_context(&id_prefix_context);
let change_hex = commit2.change_id().reverse_hex();
assert_eq!(
symbol_resolver.resolve_symbol(repo2.as_ref(), &change_hex[0..1])?,
commit2.id().clone()
);
assert_eq!(
symbol_resolver.resolve_symbol(repo2.as_ref(), &commit2.id().hex()[0..1])?,
commit2.id().clone()
);
assert_eq!(
symbol_resolver.resolve_symbol(repo1.as_ref(), &change_hex[0..1])?,
commit1.id().clone()
);
assert_matches!(
symbol_resolver.resolve_symbol(repo1.as_ref(), &commit2.id().hex()[0..1]),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
Ok(())
}
#[test]
fn test_resolve_working_copy() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let commit1 = write_random_commit(tx.repo_mut());
let commit2 = write_random_commit(tx.repo_mut());
let ws1 = WorkspaceNameBuf::from("ws1");
let ws2 = WorkspaceNameBuf::from("ws2");
let symbol_resolver = default_symbol_resolver(tx.repo());
assert_matches!(
RevsetExpression::working_copy(ws1.clone())
.resolve_user_expression(tx.repo(), &symbol_resolver),
Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name }) if name == "ws1"
);
assert_eq!(
RevsetExpression::working_copy(ws1.clone())
.present()
.resolve_user_expression(tx.repo(), &symbol_resolver)?
.evaluate(tx.repo())?
.iter()
.map(Result::unwrap)
.collect_vec(),
vec![]
);
drop(symbol_resolver);
tx.repo_mut()
.set_wc_commit(ws1.clone(), commit1.id().clone())?;
tx.repo_mut()
.set_wc_commit(ws2.clone(), commit2.id().clone())?;
let symbol_resolver = default_symbol_resolver(tx.repo());
let resolve = |name: WorkspaceNameBuf| -> Vec<CommitId> {
RevsetExpression::working_copy(name)
.resolve_user_expression(tx.repo(), &symbol_resolver)
.unwrap()
.evaluate(tx.repo())
.unwrap()
.iter()
.map(Result::unwrap)
.collect()
};
assert_eq!(resolve(ws1), vec![commit1.id().clone()]);
assert_eq!(resolve(ws2), vec![commit2.id().clone()]);
Ok(())
}
#[test]
fn test_resolve_working_copies() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let commit1 = write_random_commit(tx.repo_mut());
let commit2 = write_random_commit(tx.repo_mut());
let ws1 = WorkspaceNameBuf::from("ws1");
let ws2 = WorkspaceNameBuf::from("ws2");
tx.repo_mut()
.set_wc_commit(ws1.clone(), commit1.id().clone())?;
tx.repo_mut()
.set_wc_commit(ws2.clone(), commit2.id().clone())?;
let symbol_resolver = default_symbol_resolver(tx.repo());
let resolve = || -> Vec<CommitId> {
RevsetExpression::working_copies()
.resolve_user_expression(tx.repo(), &symbol_resolver)
.unwrap()
.evaluate(tx.repo())
.unwrap()
.iter()
.map(Result::unwrap)
.collect()
};
assert_eq!(resolve(), vec![commit2.id().clone(), commit1.id().clone()]);
Ok(())
}
#[test]
fn test_resolve_symbol_bookmarks_only() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let new_remote_ref = |target| RemoteRef {
target,
state: RemoteRefState::New,
};
let tracked_remote_ref = |target| RemoteRef {
target,
state: RemoteRefState::Tracked,
};
let normal_tracked_remote_ref =
|id: &CommitId| tracked_remote_ref(RefTarget::normal(id.clone()));
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
let commit5 = write_random_commit(mut_repo);
mut_repo.set_local_bookmark_target("local".as_ref(), RefTarget::normal(commit1.id().clone()));
mut_repo.set_remote_bookmark(
remote_symbol("remote", "origin"),
normal_tracked_remote_ref(commit2.id()),
);
mut_repo.set_local_bookmark_target(
"local-remote".as_ref(),
RefTarget::normal(commit3.id().clone()),
);
mut_repo.set_remote_bookmark(
remote_symbol("local-remote", "origin"),
normal_tracked_remote_ref(commit4.id()),
);
mut_repo.set_local_bookmark_target(
"local-remote@origin".as_ref(), RefTarget::normal(commit5.id().clone()),
);
mut_repo.set_remote_bookmark(
remote_symbol("local-remote", "mirror"),
tracked_remote_ref(mut_repo.get_local_bookmark("local-remote".as_ref())),
);
mut_repo.set_remote_bookmark(
remote_symbol("local-remote", "untracked"),
new_remote_ref(mut_repo.get_local_bookmark("local-remote".as_ref())),
);
mut_repo.set_remote_bookmark(
remote_symbol("local-remote", git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
tracked_remote_ref(mut_repo.get_local_bookmark("local-remote".as_ref())),
);
mut_repo.set_local_bookmark_target(
"local-conflicted".as_ref(),
RefTarget::from_legacy_form(
[commit1.id().clone()],
[commit3.id().clone(), commit2.id().clone()],
),
);
mut_repo.set_remote_bookmark(
remote_symbol("remote-conflicted", "origin"),
tracked_remote_ref(RefTarget::from_legacy_form(
[commit3.id().clone()],
[commit5.id().clone(), commit4.id().clone()],
)),
);
assert_eq!(
resolve_symbol(mut_repo, "local")?,
vec![commit1.id().clone()],
);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local@origin").unwrap_err(), @r#"
NoSuchRevision {
name: "local@origin",
candidates: [
"\"local-remote@origin\"",
"local",
"local-remote@git",
"local-remote@mirror",
"local-remote@origin",
"remote@origin",
],
}
"#);
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")?,
vec![commit2.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "local-remote")?,
vec![commit3.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "local-remote@origin")?,
vec![commit4.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, r#""local-remote@origin""#)?,
vec![commit5.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "local-remote@mirror")?,
vec![commit3.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "local-remote@git")?,
vec![commit3.id().clone()],
);
assert_matches!(
resolve_symbol(mut_repo, "local-conflicted"),
Err(RevsetResolutionError::ConflictedRef { kind: "bookmark", symbol, targets })
if symbol == "local-conflicted"
&& targets == vec![commit3.id().clone(), commit2.id().clone()]
);
assert_matches!(
resolve_symbol(mut_repo, "remote-conflicted@origin"),
Err(RevsetResolutionError::ConflictedRef { kind: "remote_bookmark", symbol, targets })
if symbol == "remote-conflicted@origin"
&& targets == vec![commit5.id().clone(), commit4.id().clone()]
);
assert_eq!(
resolve_symbol(mut_repo, "bookmarks(local-conflicted)")?,
vec![commit3.id().clone(), commit2.id().clone()],
);
assert_eq!(
resolve_symbol(mut_repo, "remote_bookmarks(remote-conflicted, origin)")?,
vec![commit5.id().clone(), commit4.id().clone()],
);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local-emote").unwrap_err(), @r#"
NoSuchRevision {
name: "local-emote",
candidates: [
"\"local-remote@origin\"",
"local",
"local-conflicted",
"local-remote",
"local-remote@origin",
"local-remote@untracked",
],
}
"#);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local-emote@origin").unwrap_err(), @r#"
NoSuchRevision {
name: "local-emote@origin",
candidates: [
"\"local-remote@origin\"",
"local",
"local-remote",
"local-remote@git",
"local-remote@mirror",
"local-remote@origin",
"local-remote@untracked",
"remote-conflicted@origin",
"remote@origin",
],
}
"#);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local-remote@origine").unwrap_err(), @r#"
NoSuchRevision {
name: "local-remote@origine",
candidates: [
"\"local-remote@origin\"",
"local",
"local-remote",
"local-remote@git",
"local-remote@mirror",
"local-remote@origin",
"local-remote@untracked",
"remote-conflicted@origin",
"remote@origin",
],
}
"#);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "remote@mirror").unwrap_err(), @r#"
NoSuchRevision {
name: "remote@mirror",
candidates: [
"local-remote@mirror",
"remote@origin",
],
}
"#);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "emote").unwrap_err(), @r#"
NoSuchRevision {
name: "emote",
candidates: [
"remote-conflicted@origin",
"remote@origin",
],
}
"#);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "emote@origin").unwrap_err(), @r#"
NoSuchRevision {
name: "emote@origin",
candidates: [
"\"local-remote@origin\"",
"local-remote@origin",
"remote@origin",
],
}
"#);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "remote@origine").unwrap_err(), @r#"
NoSuchRevision {
name: "remote@origine",
candidates: [
"\"local-remote@origin\"",
"local-remote@origin",
"remote-conflicted@origin",
"remote@origin",
],
}
"#);
Ok(())
}
#[test]
fn test_resolve_symbol_tags() -> TestResult {
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 commit3 = write_random_commit(mut_repo);
mut_repo.set_local_tag_target(
"tag-bookmark".as_ref(),
RefTarget::normal(commit1.id().clone()),
);
mut_repo.set_local_bookmark_target(
"tag-bookmark".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
mut_repo.set_git_ref_target(
"refs/tags/unimported".as_ref(),
RefTarget::normal(commit3.id().clone()),
);
assert_eq!(
resolve_symbol(mut_repo, "tag-bookmark")?,
vec![commit1.id().clone()],
);
assert_matches!(
resolve_symbol(mut_repo, "unimported"),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
let ws_name = WorkspaceName::DEFAULT.to_owned();
mut_repo.set_wc_commit(ws_name.clone(), commit1.id().clone())?;
mut_repo.set_local_tag_target("@".as_ref(), RefTarget::normal(commit2.id().clone()));
mut_repo.set_local_tag_target("root".as_ref(), RefTarget::normal(commit3.id().clone()));
assert_eq!(
resolve_symbol(mut_repo, r#""@""#)?,
vec![commit2.id().clone()]
);
assert_eq!(
resolve_symbol(mut_repo, "root")?,
vec![commit3.id().clone()]
);
Ok(())
}
#[test]
fn test_resolve_symbol_remote_tags_or_bookmarks() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let new_remote_ref = |target| RemoteRef {
target,
state: RemoteRefState::New,
};
let tracked_remote_ref = |target| RemoteRef {
target,
state: RemoteRefState::Tracked,
};
let normal_tracked_remote_ref =
|id: &CommitId| tracked_remote_ref(RefTarget::normal(id.clone()));
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
let commit5 = write_random_commit(mut_repo);
mut_repo.set_local_tag_target(
"tag-bookmark".as_ref(),
RefTarget::normal(commit1.id().clone()),
);
mut_repo.set_local_bookmark_target(
"tag-bookmark".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
mut_repo.set_remote_tag(
remote_symbol("tag-bookmark", "origin"),
normal_tracked_remote_ref(commit3.id()),
);
mut_repo.set_remote_bookmark(
remote_symbol("tag-bookmark", "origin"),
normal_tracked_remote_ref(commit4.id()),
);
mut_repo.set_local_tag_target(
"local-remote-tag".as_ref(),
RefTarget::normal(commit5.id().clone()),
);
mut_repo.set_remote_tag(
remote_symbol("local-remote-tag", "untracked"),
new_remote_ref(mut_repo.get_local_tag("local-remote-tag".as_ref())),
);
mut_repo.set_remote_tag(
remote_symbol("local-remote-tag", "tracked"),
tracked_remote_ref(mut_repo.get_local_tag("local-remote-tag".as_ref())),
);
assert_eq!(
resolve_symbol(mut_repo, "tag-bookmark@origin")?,
vec![commit3.id().clone()],
);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "tag-bookmark@orig").unwrap_err(), @r#"
NoSuchRevision {
name: "tag-bookmark@orig",
candidates: [
"tag-bookmark",
"tag-bookmark@origin",
],
}
"#);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "local-emote-tag").unwrap_err(), @r#"
NoSuchRevision {
name: "local-emote-tag",
candidates: [
"local-remote-tag",
"local-remote-tag@untracked",
],
}
"#);
Ok(())
}
#[test]
fn test_resolve_symbol_git_refs() -> TestResult {
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
let commit5 = write_random_commit(mut_repo);
mut_repo.set_git_ref_target(
"refs/heads/bookmark1".as_ref(),
RefTarget::normal(commit1.id().clone()),
);
mut_repo.set_git_ref_target(
"refs/heads/bookmark2".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
mut_repo.set_git_ref_target(
"refs/heads/conflicted".as_ref(),
RefTarget::from_legacy_form(
[commit2.id().clone()],
[commit1.id().clone(), commit3.id().clone()],
),
);
mut_repo.set_git_ref_target(
"refs/tags/tag1".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
mut_repo.set_git_ref_target(
"refs/tags/remotes/origin/bookmark1".as_ref(),
RefTarget::normal(commit3.id().clone()),
);
assert_matches!(
resolve_symbol(mut_repo, "nonexistent"),
Err(RevsetResolutionError::NoSuchRevision{name, candidates})
if name == "nonexistent" && candidates.is_empty()
);
mut_repo.set_git_ref_target(
"refs/heads/bookmark".as_ref(),
RefTarget::normal(commit4.id().clone()),
);
assert_eq!(
resolve_symbol(mut_repo, "refs/heads/bookmark")?,
vec![commit4.id().clone()]
);
mut_repo.set_git_ref_target(
"refs/heads/bookmark".as_ref(),
RefTarget::normal(commit5.id().clone()),
);
mut_repo.set_git_ref_target(
"refs/tags/bookmark".as_ref(),
RefTarget::normal(commit4.id().clone()),
);
insta::assert_debug_snapshot!(
resolve_symbol(mut_repo, "bookmark").unwrap_err(), @r#"
NoSuchRevision {
name: "bookmark",
candidates: [],
}
"#);
assert_eq!(
resolve_symbol(mut_repo, "heads/bookmark")?,
vec![commit5.id().clone()]
);
mut_repo.set_git_ref_target(
"refs/tags/tag".as_ref(),
RefTarget::normal(commit4.id().clone()),
);
assert_matches!(
resolve_symbol(mut_repo, "tag"),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
mut_repo.set_git_ref_target(
"refs/remotes/origin/remote-bookmark".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
assert_matches!(
resolve_symbol(mut_repo, "origin/remote-bookmark"),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
assert_matches!(
resolve_symbol(mut_repo, "refs/heads/conflicted"),
Err(RevsetResolutionError::ConflictedRef { kind: "git_ref", symbol, targets })
if symbol == "refs/heads/conflicted"
&& targets == vec![commit1.id().clone(), commit3.id().clone()]
);
Ok(())
}
fn resolve_commit_ids(repo: &dyn Repo, revset_str: &str) -> Vec<CommitId> {
try_resolve_commit_ids(repo, revset_str).unwrap()
}
fn try_resolve_expression(
repo: &dyn Repo,
revset_str: &str,
) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
let settings = testutils::user_settings();
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(),
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
fileset_aliases_map: &FilesetAliasesMap::new(),
use_glob_by_default: true,
extensions: &RevsetExtensions::default(),
workspace: None,
};
let expression = parse(&mut RevsetDiagnostics::new(), revset_str, &context).unwrap();
let symbol_resolver = default_symbol_resolver(repo);
expression.resolve_user_expression(repo, &symbol_resolver)
}
fn try_resolve_commit_ids(
repo: &dyn Repo,
revset_str: &str,
) -> Result<Vec<CommitId>, RevsetResolutionError> {
Ok(try_resolve_expression(repo, revset_str)?
.evaluate(repo)
.unwrap()
.iter()
.map(Result::unwrap)
.collect())
}
fn try_evaluate_expression<'index>(
repo: &'index dyn Repo,
revset_str: &str,
) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
try_resolve_expression(repo, revset_str)
.unwrap()
.evaluate(repo)
}
fn resolve_commit_ids_in_workspace(
repo: &dyn Repo,
revset_str: &str,
workspace: &Workspace,
cwd: Option<&Path>,
) -> Vec<CommitId> {
let settings = testutils::user_settings();
let path_converter = RepoPathUiConverter::Fs {
cwd: cwd.unwrap_or_else(|| workspace.workspace_root()).to_owned(),
base: workspace.workspace_root().to_owned(),
};
let workspace_ctx = RevsetWorkspaceContext {
path_converter: &path_converter,
workspace_name: workspace.workspace_name(),
};
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(),
default_ignored_remote: Some(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
fileset_aliases_map: &FilesetAliasesMap::new(),
use_glob_by_default: true,
extensions: &RevsetExtensions::default(),
workspace: Some(workspace_ctx),
};
let expression = parse(&mut RevsetDiagnostics::new(), revset_str, &context).unwrap();
let symbol_resolver = default_symbol_resolver(repo);
let expression = expression
.resolve_user_expression(repo, &symbol_resolver)
.unwrap();
expression
.evaluate(repo)
.unwrap()
.iter()
.map(Result::unwrap)
.collect()
}
#[test]
fn test_evaluate_expression_with_hidden_revisions() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id();
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 commit3 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit3]);
let repo = tx.commit("test").block_on()?;
let mut tx = repo.start_transaction();
tx.repo_mut().record_abandoned_commit(&commit3);
tx.repo_mut().record_abandoned_commit(&commit4);
tx.repo_mut().rebase_descendants().block_on()?;
let repo = tx.commit("test").block_on()?;
assert_eq!(
resolve_commit_ids(repo.as_ref(), "all()"),
[commit2.id(), commit1.id(), root_commit_id].map(Clone::clone)
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), &commit4.id().hex()),
[commit4.id()].map(Clone::clone)
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), &format!("all() | {}", commit3.id())),
[commit3.id(), commit2.id(), commit1.id(), root_commit_id].map(Clone::clone)
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), &format!("empty() | {}", commit3.id())),
[commit3.id(), root_commit_id].map(Clone::clone)
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), &format!("all() | {}", commit4.id())),
[
commit4.id(),
commit3.id(),
commit2.id(),
commit1.id(),
root_commit_id
]
.map(Clone::clone)
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), &format!("~{}", commit4.id())),
[commit3.id(), commit2.id(), commit1.id(), root_commit_id].map(Clone::clone)
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), &format!("all() | ::{}", commit4.id())),
[
commit4.id(),
commit3.id(),
commit2.id(),
commit1.id(),
root_commit_id
]
.map(Clone::clone)
);
Ok(())
}
#[test]
fn test_evaluate_expression_root_and_checkout() -> TestResult {
let test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let root_operation = repo.loader().root_operation().block_on();
let root_repo = repo.reload_at(&root_operation).block_on()?;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let root_commit = repo.store().root_commit();
let commit1 = write_random_commit(mut_repo);
assert_eq!(
resolve_commit_ids(mut_repo, "root()"),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(root_repo.as_ref(), "root()"),
vec![root_commit.id().clone()]
);
mut_repo.set_wc_commit(WorkspaceName::DEFAULT.to_owned(), commit1.id().clone())?;
assert_eq!(
resolve_commit_ids_in_workspace(mut_repo, "@", &test_workspace.workspace, None),
vec![commit1.id().clone()]
);
let symbol_resolver = default_symbol_resolver(tx.repo());
let expression = RevsetExpression::commit(commit1.id().clone())
.resolve_user_expression(tx.repo(), &symbol_resolver)?;
assert!(expression.evaluate(tx.base_repo().as_ref()).is_err());
Ok(())
}
#[test]
fn test_evaluate_expression_heads() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit3, &commit4]);
assert_eq!(resolve_commit_ids(mut_repo, "heads(none())"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, "heads(root())"),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("heads({})", commit2.id())),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("heads({} | {})", commit2.id(), commit3.id())
),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("heads({} | {})", commit1.id(), commit3.id())
),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("heads({} | {})", commit3.id(), commit4.id())
),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "heads(all())"),
resolve_commit_ids(mut_repo, "visible_heads()")
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("heads({}..{})", commit4.id(), commit3.id())
),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"heads({}..{} & ~{})",
commit4.id(),
commit3.id(),
commit3.id()
)
),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("heads(::{} ~ {})", commit5.id(), commit5.id())
),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"heads(first_ancestors({}) ~ {})",
commit5.id(),
commit5.id()
)
),
vec![commit3.id().clone()]
);
}
#[test]
fn test_evaluate_expression_roots() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
assert_eq!(resolve_commit_ids(mut_repo, "roots(none())"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, "roots(root())"),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("roots({})", commit2.id())),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("roots({} | {})", commit2.id(), commit3.id())
),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("roots({} | {})", commit1.id(), commit3.id())
),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "roots(all())"),
vec![root_commit.id().clone()]
);
}
#[test]
fn test_evaluate_expression_parents() -> TestResult {
let test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit2, &commit3]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit2]);
assert_eq!(resolve_commit_ids(mut_repo, "root()-"), vec![]);
mut_repo.set_wc_commit(WorkspaceName::DEFAULT.to_owned(), commit2.id().clone())?;
assert_eq!(
resolve_commit_ids_in_workspace(mut_repo, "@-", &test_workspace.workspace, None,),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}-", commit4.id())),
vec![commit3.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("({} | {})-", commit2.id(), commit3.id())),
vec![commit1.id().clone(), root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("({} | {})-", commit1.id(), commit2.id())),
vec![commit1.id().clone(), root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("({} | {})-", commit4.id(), commit5.id())),
vec![commit3.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}--", commit4.id())),
vec![commit1.id().clone(), root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("({} | {})--", commit4.id(), commit5.id())
),
vec![commit1.id().clone(), root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("({} | {})--", commit4.id(), commit2.id())
),
vec![commit1.id().clone(), root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("parents({}, 0)", commit5.id())),
vec![commit5.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("parents({}, 2)", commit4.id())),
vec![commit1.id().clone(), root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("parents({} | {}, 2)", commit4.id(), commit5.id())
),
vec![commit1.id().clone(), root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("parents({} | {}, 2)", commit4.id(), commit2.id())
),
vec![commit1.id().clone(), root_commit.id().clone()]
);
Ok(())
}
#[test]
fn test_evaluate_expression_children() {
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_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit3, &commit4]);
let commit6 = write_random_commit_with_parents(mut_repo, &[&commit5]);
assert_eq!(
resolve_commit_ids(mut_repo, "root()+"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("({} | {})+", commit1.id(), commit2.id())),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone()
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("({} | {})+", commit3.id(), commit4.id())),
vec![commit5.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "root()++"),
vec![commit4.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("(root() | {})++", commit1.id())),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("({} | {})++", commit4.id(), commit2.id())
),
vec![commit6.id().clone(), commit5.id().clone()]
);
assert_eq!(resolve_commit_ids(mut_repo, "none()+"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("children({}, 0)", commit1.id())),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "children(root(), 2)"),
vec![commit4.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("children(root() | {}, 2)", commit1.id())),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("children({} | {}, 2)", commit4.id(), commit2.id())
),
vec![commit6.id().clone(), commit5.id().clone()]
);
}
#[test]
fn test_evaluate_expression_ancestors() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit3]);
assert_eq!(
resolve_commit_ids(mut_repo, "::root()"),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("::{}", commit4.id())),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("::({}-)", commit4.id())),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("(::({}|{}))-", commit3.id(), commit2.id()),
),
vec![
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("::(({}|{})-)", commit3.id(), commit2.id()),
),
vec![
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("ancestors({}, 0)", commit2.id())),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("ancestors({}, 1)", commit3.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("ancestors({}, 3)", commit3.id())),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
}
#[test]
fn test_evaluate_expression_first_parent() {
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_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit3]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit4, &commit2]);
let commit6 = write_random_commit_with_parents(mut_repo, &[&commit5, &commit4, &commit2]);
assert_eq!(resolve_commit_ids(mut_repo, "first_parent(root())"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_parent({})", commit2.id().clone())),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_parent({})", commit5.id().clone())),
vec![commit4.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_parent({})", commit6.id().clone())),
vec![commit5.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("first_parent({} | {})", commit6.id(), commit5.id())
),
vec![commit5.id().clone(), commit4.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_parent({}, 0)", commit6.id())),
vec![commit6.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_parent({}, 2)", commit6.id())),
vec![commit4.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("first_parent({} | {}, 2)", commit6.id(), commit5.id())
),
vec![commit4.id().clone(), commit3.id().clone()]
);
}
#[test]
fn test_evaluate_expression_first_ancestors() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit2, &commit3]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit3, &commit4]);
assert_eq!(
resolve_commit_ids(mut_repo, "first_ancestors(root())"),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_ancestors({})", commit5.id())),
vec![
commit5.id().clone(),
commit3.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_ancestors({})", commit4.id())),
vec![
commit4.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("first_ancestors({} | {})", commit5.id(), commit2.id())
),
vec![
commit5.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_ancestors({}, 0)", commit5.id())),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_ancestors({}, 1)", commit5.id())),
vec![commit5.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("first_ancestors({}, 3)", commit5.id())),
vec![
commit5.id().clone(),
commit3.id().clone(),
commit1.id().clone(),
]
);
}
#[test]
fn test_evaluate_expression_range() {
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_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit3]);
assert_eq!(resolve_commit_ids(mut_repo, "root()..root()"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}..{}", commit1.id(), commit3.id())),
vec![commit3.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}..{}", commit3.id(), commit1.id())),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}..{}", commit1.id(), commit4.id())),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone()
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}--..{}", commit4.id(), commit3.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}..{}", commit2.id(), commit3.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("..{}", commit2.id())),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}..", commit2.id())),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, ".."),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
}
#[test]
fn test_evaluate_expression_dag_range() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id().clone();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit3, &commit4]);
assert_eq!(
resolve_commit_ids(mut_repo, "root()::root()"),
vec![root_commit_id.clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}::{}", root_commit_id, commit2.id())),
vec![
commit2.id().clone(),
commit1.id().clone(),
root_commit_id.clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}::{}", commit2.id(), commit4.id())),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("none()::{}", commit5.id())),
vec![],
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("({}|{})::{}", commit1.id(), commit2.id(), commit3.id())
),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone()
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}::{}", commit1.id(), commit5.id())),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}::{}", commit2.id(), commit5.id())),
vec![
commit5.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "::"),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit_id.clone(),
]
);
}
#[test]
fn test_evaluate_expression_connected() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id().clone();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit3, &commit4]);
assert_eq!(resolve_commit_ids(mut_repo, "connected(none())"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, "connected(root())"),
vec![root_commit_id.clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("connected({} | {})", root_commit_id, commit2.id())
),
vec![commit2.id().clone(), commit1.id().clone(), root_commit_id]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("connected({} | {})", commit2.id(), commit4.id())
),
vec![commit4.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("connected({} | {})", commit1.id(), commit5.id())
),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("connected({} | {})", commit2.id(), commit5.id())
),
vec![
commit5.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
}
#[test]
fn test_evaluate_expression_reachable() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let graph1commit1 = write_random_commit(mut_repo);
let graph2commit1 = write_random_commit(mut_repo);
let graph2commit2 = write_random_commit(mut_repo);
let graph2commit3 =
write_random_commit_with_parents(mut_repo, &[&graph2commit1, &graph2commit2]);
let graph1commit2 = write_random_commit_with_parents(mut_repo, &[&graph1commit1]);
let graph1commit3 = write_random_commit_with_parents(mut_repo, &[&graph1commit2]);
let graph3commit1 = write_random_commit(mut_repo);
let graph3commit2 = write_random_commit(mut_repo);
let graph3commit3 = write_random_commit(mut_repo);
let graph3commit4 =
write_random_commit_with_parents(mut_repo, &[&graph3commit1, &graph3commit2]);
let graph3commit5 =
write_random_commit_with_parents(mut_repo, &[&graph3commit2, &graph3commit3]);
let graph3commit6 = write_random_commit_with_parents(mut_repo, &[&graph3commit3]);
let graph3commit7 =
write_random_commit_with_parents(mut_repo, &[&graph3commit4, &graph3commit5]);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"reachable(ancestors({} | {}), root()..)",
graph1commit1.id(),
graph2commit1.id(),
)
),
vec![
graph1commit3.id().clone(),
graph1commit2.id().clone(),
graph2commit3.id().clone(),
graph2commit2.id().clone(),
graph2commit1.id().clone(),
graph1commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"reachable(ancestors({} | {}), {}..)",
graph1commit2.id(),
graph2commit1.id(),
graph1commit1.id(),
)
),
vec![
graph1commit3.id().clone(),
graph1commit2.id().clone(),
graph2commit3.id().clone(),
graph2commit2.id().clone(),
graph2commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"reachable({}, all() ~ ::{})",
graph1commit2.id(),
graph1commit1.id()
)
),
vec![graph1commit3.id().clone(), graph1commit2.id().clone(),]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"reachable({}, all() ~ ::{})",
graph1commit2.id(),
graph1commit3.id()
)
),
vec![]
);
for (i, commit) in [&graph1commit1, &graph1commit2, &graph1commit3]
.iter()
.enumerate()
{
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("reachable({}, all() ~ root())", commit.id())
),
vec![
graph1commit3.id().clone(),
graph1commit2.id().clone(),
graph1commit1.id().clone(),
],
"commit {}",
i + 1
);
}
for (i, commit) in [&graph2commit1, &graph2commit2, &graph2commit3]
.iter()
.enumerate()
{
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("reachable({}, all() ~ root())", commit.id())
),
vec![
graph2commit3.id().clone(),
graph2commit2.id().clone(),
graph2commit1.id().clone(),
],
"commit {}",
i + 1
);
}
for (i, commit) in [
&graph3commit1,
&graph3commit2,
&graph3commit3,
&graph3commit4,
&graph3commit5,
&graph3commit6,
&graph3commit7,
]
.iter()
.enumerate()
{
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("reachable({}, all() ~ root())", commit.id())
),
vec![
graph3commit7.id().clone(),
graph3commit6.id().clone(),
graph3commit5.id().clone(),
graph3commit4.id().clone(),
graph3commit3.id().clone(),
graph3commit2.id().clone(),
graph3commit1.id().clone(),
],
"commit {}",
i + 1
);
}
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"reachable({}, all() ~ ::{})",
graph3commit4.id(),
graph3commit5.id()
)
),
vec![
graph3commit7.id().clone(),
graph3commit4.id().clone(),
graph3commit1.id().clone(),
]
);
}
#[test]
fn test_evaluate_expression_descendants() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let root_commit_id = repo.store().root_commit_id().clone();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit3, &commit4]);
let commit6 = write_random_commit_with_parents(mut_repo, &[&commit5]);
assert_eq!(
resolve_commit_ids(mut_repo, "root()::"),
vec![
commit6.id().clone(),
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit_id,
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}::", commit2.id())),
vec![
commit6.id().clone(),
commit5.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("({}+)::", commit1.id())),
vec![
commit6.id().clone(),
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("({}++)::", commit1.id())),
vec![
commit6.id().clone(),
commit5.id().clone(),
commit3.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("(({}|{})::)+", commit4.id(), commit2.id()),
),
vec![
commit6.id().clone(),
commit5.id().clone(),
commit3.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("(({}|{})+)::", commit4.id(), commit2.id()),
),
vec![
commit6.id().clone(),
commit5.id().clone(),
commit3.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("descendants({}, 0)", commit2.id())),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("descendants({}, 1)", commit3.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("descendants({}, 3)", commit3.id())),
vec![
commit6.id().clone(),
commit5.id().clone(),
commit3.id().clone(),
]
);
}
#[test]
fn test_evaluate_expression_none() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
assert_eq!(resolve_commit_ids(repo.as_ref(), "none()"), vec![]);
}
#[test]
fn test_evaluate_expression_all() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let root_commit_id = repo.store().root_commit_id().clone();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit2, &commit3]);
assert_eq!(
resolve_commit_ids(mut_repo, "all()"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit_id,
]
);
}
#[test]
fn test_evaluate_expression_visible_heads() {
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_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit1]);
assert_eq!(
resolve_commit_ids(mut_repo, "visible_heads()"),
vec![commit3.id().clone(), commit2.id().clone()]
);
}
#[test]
fn test_evaluate_expression_git_refs() {
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
assert_eq!(resolve_commit_ids(mut_repo, "git_refs()"), vec![]);
mut_repo.set_git_ref_target(
"refs/heads/bookmark1".as_ref(),
RefTarget::normal(commit1.id().clone()),
);
mut_repo.set_git_ref_target(
"refs/tags/tag1".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "git_refs()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.set_git_ref_target(
"refs/tags/tag2".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "git_refs()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.set_git_ref_target(
"refs/heads/bookmark1".as_ref(),
RefTarget::from_legacy_form(
[commit1.id().clone()],
[commit2.id().clone(), commit3.id().clone()],
),
);
mut_repo.set_git_ref_target(
"refs/tags/tag1".as_ref(),
RefTarget::from_legacy_form(
[commit2.id().clone()],
[commit3.id().clone(), commit4.id().clone()],
),
);
mut_repo.set_git_ref_target("refs/tags/tag2".as_ref(), RefTarget::absent());
assert_eq!(
resolve_commit_ids(mut_repo, "git_refs()"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone()
]
);
}
#[test]
fn test_evaluate_expression_git_head() {
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);
assert_eq!(resolve_commit_ids(mut_repo, "git_head()"), vec![]);
mut_repo.set_git_head_target(RefTarget::normal(commit1.id().clone()));
assert_eq!(
resolve_commit_ids(mut_repo, "git_head()"),
vec![commit1.id().clone()]
);
}
#[test]
fn test_evaluate_expression_bookmarks() {
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
assert_eq!(resolve_commit_ids(mut_repo, "bookmarks()"), vec![]);
mut_repo.set_local_bookmark_target(
"bookmark1".as_ref(),
RefTarget::normal(commit1.id().clone()),
);
mut_repo.set_local_bookmark_target(
"bookmark2".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(bookmark1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(substring:bookmark)"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(exact:bookmark1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(bookmark* & ~bookmark1)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"bookmarks(glob:"Bookmark?")"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"bookmarks(glob-i:"Bookmark?")"#),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(regex:'ookmark')"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(regex:'^[Bb]ookmark1$')"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(regex-i:'BOOKmark')"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks(substring:bookmark3)"),
vec![]
);
assert_eq!(resolve_commit_ids(mut_repo, "bookmarks(ookmark1)"), vec![]);
mut_repo.set_local_bookmark_target(
"bookmark3".as_ref(),
RefTarget::normal(commit2.id().clone()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.set_local_bookmark_target(
"bookmark1".as_ref(),
RefTarget::from_legacy_form(
[commit1.id().clone()],
[commit2.id().clone(), commit3.id().clone()],
),
);
mut_repo.set_local_bookmark_target(
"bookmark2".as_ref(),
RefTarget::from_legacy_form(
[commit2.id().clone()],
[commit3.id().clone(), commit4.id().clone()],
),
);
mut_repo.set_local_bookmark_target("bookmark3".as_ref(), RefTarget::absent());
assert_eq!(
resolve_commit_ids(mut_repo, "bookmarks()"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone()
]
);
}
#[test]
fn test_evaluate_expression_remote_bookmarks() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let tracked_remote_ref = |target| RemoteRef {
target,
state: RemoteRefState::Tracked,
};
let normal_tracked_remote_ref =
|id: &CommitId| tracked_remote_ref(RefTarget::normal(id.clone()));
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
let commit_git_remote = write_random_commit(mut_repo);
assert_eq!(resolve_commit_ids(mut_repo, "remote_bookmarks()"), vec![]);
mut_repo.set_remote_bookmark(
remote_symbol("bookmark1", "origin"),
RemoteRef {
target: RefTarget::normal(commit1.id().clone()),
state: RemoteRefState::New,
},
);
mut_repo.set_remote_bookmark(
remote_symbol("bookmark2", "private"),
normal_tracked_remote_ref(commit2.id()),
);
mut_repo.set_remote_bookmark(
remote_symbol("bookmark", git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
normal_tracked_remote_ref(commit_git_remote.id()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(bookmark1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(substring:bookmark)"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(exact:bookmark1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"remote_bookmarks(*, origin)"#),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"remote_bookmarks(*, *ri*)"#),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"remote_bookmarks(*, origin)"#),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(bookmark1, *ri*)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(bookmark*, private)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(bookmark1, origin)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(remote=git)"),
vec![commit_git_remote.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(remote=*)"),
vec![
commit_git_remote.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_bookmarks()"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_bookmarks()"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_bookmarks(bookmark1, origin)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_bookmarks(bookmark2, private)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks(substring:bookmark3)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"remote_bookmarks("", upstream)"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"remote_bookmarks(bookmark1, private)"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"remote_bookmarks(ranch1, origin)"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"remote_bookmarks(bookmark1, orig)"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_bookmarks(bookmark1)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_bookmarks(bookmark2)"),
vec![]
);
mut_repo.set_remote_bookmark(
remote_symbol("bookmark3", "origin"),
normal_tracked_remote_ref(commit2.id()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.remove_head(commit2.id());
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.set_remote_bookmark(
remote_symbol("bookmark1", "origin"),
tracked_remote_ref(RefTarget::from_legacy_form(
[commit1.id().clone()],
[commit2.id().clone(), commit3.id().clone()],
)),
);
mut_repo.set_remote_bookmark(
remote_symbol("bookmark2", "private"),
tracked_remote_ref(RefTarget::from_legacy_form(
[commit2.id().clone()],
[commit3.id().clone(), commit4.id().clone()],
)),
);
mut_repo.set_remote_bookmark(remote_symbol("bookmark3", "origin"), RemoteRef::absent());
assert_eq!(
resolve_commit_ids(mut_repo, "remote_bookmarks()"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone()
]
);
}
#[test]
fn test_evaluate_expression_tags() {
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
assert_eq!(resolve_commit_ids(mut_repo, "tags()"), vec![]);
mut_repo.set_local_tag_target("tag1".as_ref(), RefTarget::normal(commit1.id().clone()));
mut_repo.set_local_tag_target("tag2".as_ref(), RefTarget::normal(commit2.id().clone()));
assert_eq!(
resolve_commit_ids(mut_repo, "tags()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tags(tag1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tags(substring:tag)"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tags(exact:tag1)"),
vec![commit1.id().clone()]
);
assert_eq!(resolve_commit_ids(mut_repo, r#"tags(glob:"Tag?")"#), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, r#"tags(glob-i:"Tag?")"#),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tags(regex:'ag')"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tags(regex:'^[Tt]ag1$')"),
vec![commit1.id().clone()]
);
assert_eq!(resolve_commit_ids(mut_repo, "tags(substring:tag3)"), vec![]);
assert_eq!(resolve_commit_ids(mut_repo, "tags(ag1)"), vec![]);
mut_repo.set_local_tag_target("tag3".as_ref(), RefTarget::normal(commit2.id().clone()));
assert_eq!(
resolve_commit_ids(mut_repo, "tags()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.set_local_tag_target(
"tag1".as_ref(),
RefTarget::from_legacy_form(
[commit1.id().clone()],
[commit2.id().clone(), commit3.id().clone()],
),
);
mut_repo.set_local_tag_target(
"tag2".as_ref(),
RefTarget::from_legacy_form(
[commit2.id().clone()],
[commit3.id().clone(), commit4.id().clone()],
),
);
mut_repo.set_local_tag_target("tag3".as_ref(), RefTarget::absent());
assert_eq!(
resolve_commit_ids(mut_repo, "tags()"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone()
]
);
}
#[test]
fn test_evaluate_expression_remote_tags() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let tracked_remote_ref = |target| RemoteRef {
target,
state: RemoteRefState::Tracked,
};
let normal_tracked_remote_ref =
|id: &CommitId| tracked_remote_ref(RefTarget::normal(id.clone()));
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit(mut_repo);
let commit_git_remote = write_random_commit(mut_repo);
assert_eq!(resolve_commit_ids(mut_repo, "remote_tags()"), vec![]);
mut_repo.set_remote_tag(
remote_symbol("tag1", "origin"),
RemoteRef {
target: RefTarget::normal(commit1.id().clone()),
state: RemoteRefState::New,
},
);
mut_repo.set_remote_tag(
remote_symbol("tag2", "private"),
normal_tracked_remote_ref(commit2.id()),
);
mut_repo.set_remote_tag(
remote_symbol("tag", git::REMOTE_NAME_FOR_LOCAL_GIT_REPO),
normal_tracked_remote_ref(commit_git_remote.id()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(tag1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(substring:tag)"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(exact:tag1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(*, origin)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(*, *ri*)"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(*, origin)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(tag1, *ri*)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(tag*, private)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(tag1, origin)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(remote=git)"),
vec![commit_git_remote.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(remote=*)"),
vec![
commit_git_remote.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_tags()"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_tags()"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_tags(tag1, origin)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_tags(tag2, private)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(substring:tag3)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags('', upstream)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(tag1, private)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(ranch1, origin)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags(tag1, orig)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "tracked_remote_tags(tag1)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "untracked_remote_tags(tag2)"),
vec![]
);
mut_repo.set_remote_tag(
remote_symbol("tag3", "origin"),
normal_tracked_remote_ref(commit2.id()),
);
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.remove_head(commit2.id());
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
mut_repo.set_remote_tag(
remote_symbol("tag1", "origin"),
tracked_remote_ref(RefTarget::from_legacy_form(
[commit1.id().clone()],
[commit2.id().clone(), commit3.id().clone()],
)),
);
mut_repo.set_remote_tag(
remote_symbol("tag2", "private"),
tracked_remote_ref(RefTarget::from_legacy_form(
[commit2.id().clone()],
[commit3.id().clone(), commit4.id().clone()],
)),
);
mut_repo.set_remote_tag(remote_symbol("tag3", "origin"), RemoteRef::absent());
assert_eq!(
resolve_commit_ids(mut_repo, "remote_tags()"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone()
]
);
}
#[test]
fn test_evaluate_expression_latest() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let mut write_commit_with_committer_timestamp = |sec: i64| {
let builder = create_random_commit(mut_repo);
let mut committer = builder.committer().clone();
committer.timestamp.timestamp = MillisSinceEpoch(sec * 1000);
builder.set_committer(committer).write_unwrap()
};
let commit1_t3 = write_commit_with_committer_timestamp(3);
let commit2_t2 = write_commit_with_committer_timestamp(2);
let commit3_t2 = write_commit_with_committer_timestamp(2);
let commit4_t1 = write_commit_with_committer_timestamp(1);
assert_eq!(
resolve_commit_ids(mut_repo, "latest(all())"),
vec![commit1_t3.id().clone()],
);
assert_eq!(resolve_commit_ids(mut_repo, "latest(all(), 0)"), vec![]);
assert_eq!(resolve_commit_ids(mut_repo, "latest(none())"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, "latest(all(), 1)"),
vec![commit1_t3.id().clone()],
);
assert_eq!(
resolve_commit_ids(mut_repo, "latest(all(), 2)"),
vec![commit3_t2.id().clone(), commit1_t3.id().clone()],
);
assert_eq!(
resolve_commit_ids(mut_repo, "latest(all(), 3)"),
vec![
commit3_t2.id().clone(),
commit2_t2.id().clone(),
commit1_t3.id().clone(),
],
);
assert_eq!(
resolve_commit_ids(mut_repo, "latest(all(), 4)"),
vec![
commit4_t1.id().clone(),
commit3_t2.id().clone(),
commit2_t2.id().clone(),
commit1_t3.id().clone(),
],
);
assert_eq!(
resolve_commit_ids(mut_repo, "latest(all(), 5)"),
vec![
commit4_t1.id().clone(),
commit3_t2.id().clone(),
commit2_t2.id().clone(),
commit1_t3.id().clone(),
mut_repo.store().root_commit_id().clone(),
],
);
assert_eq!(
resolve_commit_ids(mut_repo, "latest(~root(), 5)"),
vec![
commit4_t1.id().clone(),
commit3_t2.id().clone(),
commit2_t2.id().clone(),
commit1_t3.id().clone(),
],
);
}
#[test]
fn test_evaluate_expression_fork_point() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let root_commit = repo.store().root_commit();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit(mut_repo);
let commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit4]);
let commit6 = write_random_commit_with_parents(mut_repo, &[&commit4, &commit2]);
assert_eq!(resolve_commit_ids(mut_repo, "fork_point(none())"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, "fork_point(root())"),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("fork_point({})", commit1.id())),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("fork_point({})", commit2.id())),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("fork_point({})", commit3.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("fork_point({})", commit4.id())),
vec![commit4.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("fork_point({})", commit5.id())),
vec![commit5.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("fork_point({})", commit6.id())),
vec![commit6.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit1.id(), commit2.id())
),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit2.id(), commit3.id())
),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"fork_point({} | {} | {})",
commit1.id(),
commit2.id(),
commit3.id()
)
),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit1.id(), commit4.id())
),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit2.id(), commit5.id())
),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit3.id(), commit6.id())
),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit1.id(), commit5.id())
),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit4.id(), commit5.id())
),
vec![commit4.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit5.id(), commit6.id())
),
vec![commit4.id().clone()]
);
}
#[test]
fn test_evaluate_expression_fork_point_criss_cross() {
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 commit3 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit2]);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit3.id(), commit4.id())
),
vec![commit2.id().clone(), commit1.id().clone()]
);
}
#[test]
fn test_evaluate_expression_fork_point_merge_with_ancestor() {
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit2]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit2, &commit3]);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("fork_point({} | {})", commit4.id(), commit5.id())
),
vec![commit2.id().clone()]
);
}
#[test]
fn test_evaluate_expression_exactly() {
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_with_parents(mut_repo, &[&commit1]);
assert!(try_evaluate_expression(mut_repo, "exactly(none(), 0)").is_ok());
assert!(try_evaluate_expression(mut_repo, "exactly(none(), 1)").is_err());
assert!(try_evaluate_expression(mut_repo, &format!("exactly({}, 1)", commit1.id())).is_ok());
assert!(
try_evaluate_expression(
mut_repo,
&format!("exactly({}|{}, 2)", commit1.id(), commit2.id())
)
.is_ok()
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("exactly({}, 1)", commit1.id())),
vec![commit1.id().clone()]
);
}
#[test]
fn test_evaluate_expression_bisect_linear() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let root_commit = repo.store().root_commit();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit3]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit4]);
let commit6 = write_random_commit_with_parents(mut_repo, &[&commit5]);
let commit7 = write_random_commit_with_parents(mut_repo, &[&commit6]);
let resolve_ids = |input: &str| resolve_commit_ids(mut_repo, input);
assert_eq!(resolve_ids("bisect(none())"), vec![]);
assert_eq!(
resolve_ids("bisect(root())"),
vec![root_commit.id().clone()]
);
assert_eq!(
resolve_ids(&format!("bisect({})", commit3.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_ids(&format!("bisect({}|{})", commit3.id(), commit4.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_ids(&format!("bisect({}|{})", commit2.id(), commit7.id())),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_ids(&format!("bisect({}::{})", root_commit.id(), commit7.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_ids(&format!("bisect({}::{})", commit3.id(), commit7.id())),
vec![commit5.id().clone()]
);
}
#[test]
fn test_evaluate_expression_bisect_nonlinear() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let root_commit = repo.store().root_commit();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit(mut_repo);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit3]);
let commit6 = write_random_commit_with_parents(mut_repo, &[&commit4]);
let _commit7 = write_random_commit_with_parents(mut_repo, &[&commit5, &commit6]);
let resolve_ids = |input: &str| resolve_commit_ids(mut_repo, input);
assert_eq!(
resolve_ids(&format!("bisect({}::)", root_commit.id())),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_ids(&format!(
"bisect({}::{} | {})",
commit1.id(),
commit5.id(),
commit2.id()
)),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_ids(&format!("bisect({}|{})", commit3.id(), commit4.id())),
vec![commit3.id().clone()]
);
}
#[test]
fn test_evaluate_expression_merges() {
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 commit3 = write_random_commit(mut_repo);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit2]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit1, &commit2, &commit3]);
assert_eq!(
resolve_commit_ids(mut_repo, "merges()"),
vec![commit5.id().clone(), commit4.id().clone(),]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("::{} & merges()", commit5.id())),
vec![commit5.id().clone()]
);
}
#[test]
fn test_evaluate_expression_description() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = create_random_commit(mut_repo)
.set_description("commit 1\n")
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_description("commit 2\n\nblah blah...\n")
.write_unwrap();
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_description("commit 3\n")
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "description(substring:commit)"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone()
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "description('*commit 2*')"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "visible_heads() & description('*commit 2*')"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"description(exact:"commit 1\n")"#),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, r#"description(exact:"commit 2\n")"#),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "description('')"),
vec![mut_repo.store().root_commit_id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "description(~'')"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "subject('commit ?')"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "subject(substring:blah)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "subject('commit 2')"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "subject('')"),
vec![mut_repo.store().root_commit_id().clone()]
);
}
#[test]
fn test_evaluate_expression_author() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let timestamp = Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
};
let commit1 = create_random_commit(mut_repo)
.set_author(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp,
})
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_author(Signature {
name: "name2".to_string(),
email: "email2".to_string(),
timestamp,
})
.write_unwrap();
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_author(Signature {
name: "name3".to_string(),
email: "email3".to_string(),
timestamp,
})
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "author(substring:name)"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone()
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author(*name2*)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author(*email3*)"),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author(substring-i:Name)"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author_name(*name2*)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author_email(*name2*)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author_name(*email2*)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author_email(*email2*)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "visible_heads() & author(*name2*)"),
vec![]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("root().. & (author(*name1*) | {})", commit3.id())
),
vec![commit3.id().clone(), commit1.id().clone()]
);
}
fn parse_timestamp(s: &str) -> Timestamp {
Timestamp::from_datetime(s.parse::<DateTime<chrono::FixedOffset>>().unwrap())
}
#[test]
fn test_evaluate_expression_author_date() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let timestamp1 = parse_timestamp("2023-03-25T11:30:00Z");
let timestamp2 = parse_timestamp("2023-03-25T12:30:00Z");
let timestamp3 = parse_timestamp("2023-03-25T13:30:00Z");
let root_commit = repo.store().root_commit();
let commit1 = create_random_commit(mut_repo)
.set_author(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp1,
})
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp2,
})
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_author(Signature {
name: "name2".to_string(),
email: "email2".to_string(),
timestamp: timestamp2,
})
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp2,
})
.write_unwrap();
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_author(Signature {
name: "name3".to_string(),
email: "email3".to_string(),
timestamp: timestamp3,
})
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp2,
})
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "author_date(after:'2023-03-25 12:00')"),
vec![commit3.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "author_date(before:'2023-03-25 12:00')"),
vec![commit1.id().clone(), root_commit.id().clone()]
);
}
#[test]
fn test_evaluate_expression_committer_date() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let timestamp1 = parse_timestamp("2023-03-25T11:30:00Z");
let timestamp2 = parse_timestamp("2023-03-25T12:30:00Z");
let timestamp3 = parse_timestamp("2023-03-25T13:30:00Z");
let root_commit = repo.store().root_commit();
let commit1 = create_random_commit(mut_repo)
.set_author(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp2,
})
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp1,
})
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_author(Signature {
name: "name2".to_string(),
email: "email2".to_string(),
timestamp: timestamp2,
})
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp2,
})
.write_unwrap();
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_author(Signature {
name: "name3".to_string(),
email: "email3".to_string(),
timestamp: timestamp2,
})
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp: timestamp3,
})
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "committer_date(after:'2023-03-25 12:00')"),
vec![commit3.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer_date(before:'2023-03-25 12:00')"),
vec![commit1.id().clone(), root_commit.id().clone()]
);
}
#[test]
fn test_evaluate_expression_mine() {
let settings = testutils::user_settings();
let test_repo = TestRepo::init_with_settings(&settings);
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let timestamp = Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
};
let commit1 = create_random_commit(mut_repo)
.set_author(Signature {
name: settings.user_email().to_owned(),
email: "email1".to_string(),
timestamp,
})
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_author(Signature {
name: "name2".to_string(),
email: settings.user_email().to_owned(),
timestamp,
})
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "mine()"),
vec![commit2.id().clone()]
);
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_author(Signature {
name: "name3".to_string(),
email: settings.user_email().to_ascii_uppercase(),
timestamp,
})
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "mine()"),
vec![commit3.id().clone(), commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "visible_heads() & mine()"),
vec![commit3.id().clone()],
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("root().. & (mine() | {})", commit1.id())),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone()
]
);
}
#[test]
fn test_evaluate_expression_signed() {
let signer = Signer::new(Some(Box::new(TestSigningBackend)), vec![]);
let settings = testutils::user_settings();
let test_workspace =
TestWorkspace::init_with_backend_and_signer(TestRepoBackend::Test, signer, &settings);
let repo = &test_workspace.repo;
let repo = repo.clone();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let timestamp = Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
};
let commit1 = create_random_commit(mut_repo)
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp,
})
.set_sign_behavior(SignBehavior::Own)
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_committer(Signature {
name: "name2".to_string(),
email: "email2".to_string(),
timestamp,
})
.set_sign_behavior(SignBehavior::Drop)
.write_unwrap();
assert!(commit1.is_signed());
assert!(!commit2.is_signed());
let signed_commits = resolve_commit_ids(mut_repo, "signed()");
assert!(signed_commits.contains(commit1.id()));
assert!(!signed_commits.contains(commit2.id()));
let unsigned_commits = resolve_commit_ids(mut_repo, "~signed()");
assert!(!unsigned_commits.contains(commit1.id()));
assert!(unsigned_commits.contains(commit2.id()));
}
#[test]
fn test_evaluate_expression_committer() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let timestamp = Timestamp {
timestamp: MillisSinceEpoch(0),
tz_offset: 0,
};
let commit1 = create_random_commit(mut_repo)
.set_committer(Signature {
name: "name1".to_string(),
email: "email1".to_string(),
timestamp,
})
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_committer(Signature {
name: "name2".to_string(),
email: "email2".to_string(),
timestamp,
})
.write_unwrap();
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_committer(Signature {
name: "name3".to_string(),
email: "email3".to_string(),
timestamp,
})
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "committer(substring:name)"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone()
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer(*name2*)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer(*email3*)"),
vec![commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer(substring-i:Name)"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer_name(*name2*)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer_email(*name2*)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer_name(*email2*)"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "committer_email(*email2*)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "visible_heads() & committer(*name2*)"),
vec![]
);
}
#[test]
fn test_evaluate_expression_at_operation() -> TestResult {
let test_repo = TestRepo::init();
let repo0 = &test_repo.repo;
let root_commit = repo0.store().root_commit();
let mut tx = repo0.start_transaction();
let commit1_op1 = create_random_commit(tx.repo_mut())
.set_description("commit1@op1")
.write_unwrap();
let commit2_op1 = create_random_commit(tx.repo_mut())
.set_description("commit2@op1")
.write_unwrap();
tx.repo_mut().set_local_bookmark_target(
"commit1_ref".as_ref(),
RefTarget::normal(commit1_op1.id().clone()),
);
let repo1 = tx.commit("test").block_on()?;
let mut tx = repo1.start_transaction();
let commit1_op2 = tx
.repo_mut()
.rewrite_commit(&commit1_op1)
.set_description("commit1@op2")
.write_unwrap();
let commit3_op2 = create_random_commit(tx.repo_mut())
.set_description("commit3@op2")
.write_unwrap();
tx.repo_mut().rebase_descendants().block_on()?;
let repo2 = tx.commit("test").block_on()?;
let mut tx = repo2.start_transaction();
let _commit4_op3 = create_random_commit(tx.repo_mut())
.set_description("commit4@op3")
.write_unwrap();
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "at_operation(@, commit1_ref)"),
vec![commit1_op2.id().clone()]
);
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "at_operation(@-, commit1_ref)"),
vec![commit1_op1.id().clone()]
);
assert_matches!(
try_resolve_commit_ids(repo2.as_ref(), "at_operation(@--, commit1_ref)"),
Err(RevsetResolutionError::NoSuchRevision { .. })
);
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "present(at_operation(@--, commit1_ref))"),
vec![]
);
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "at_operation(@--, present(commit1_ref))"),
vec![]
);
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "at_operation(@, all())"),
vec![
commit3_op2.id().clone(),
commit1_op2.id().clone(),
commit2_op1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "at_operation(@-, all())"),
vec![
commit2_op1.id().clone(),
commit1_op1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "at_operation(@-, at_operation(@-, all()))"),
resolve_commit_ids(repo2.as_ref(), "at_operation(@--, all())")
);
assert_eq!(
resolve_commit_ids(tx.repo(), "at_operation(@, all())"),
vec![
commit3_op2.id().clone(),
commit1_op2.id().clone(),
commit2_op1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(repo2.as_ref(), "at_operation(@-, subject('commit*'))"),
vec![commit2_op1.id().clone(), commit1_op1.id().clone()]
);
assert_eq!(
resolve_commit_ids(
repo2.as_ref(),
"subject('commit1*') & at_operation(@-, subject('commit*'))"
),
vec![commit1_op1.id().clone()]
);
assert_eq!(
resolve_commit_ids(
repo2.as_ref(),
"::visible_heads() & subject('commit1*') & at_operation(@-, subject('commit*'))"
),
vec![]
);
assert_matches!(
try_resolve_commit_ids(repo2.as_ref(), "at_operation(000000000000-, all())"),
Err(RevsetResolutionError::Other(_))
);
Ok(())
}
#[test]
fn test_evaluate_expression_coalesce() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id().clone();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
mut_repo.set_local_bookmark_target("commit1".as_ref(), RefTarget::normal(commit1.id().clone()));
mut_repo.set_local_bookmark_target("commit2".as_ref(), RefTarget::normal(commit2.id().clone()));
assert_eq!(resolve_commit_ids(mut_repo, "coalesce()"), vec![]);
assert_eq!(resolve_commit_ids(mut_repo, "coalesce(none())"), vec![]);
assert_eq!(
resolve_commit_ids(mut_repo, "coalesce(all())"),
vec![
commit2.id().clone(),
commit1.id().clone(),
root_commit_id.clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "coalesce(all(), commit1)"),
vec![
commit2.id().clone(),
commit1.id().clone(),
root_commit_id.clone(),
]
);
assert_eq!(
resolve_commit_ids(mut_repo, "coalesce(none(), commit1)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "coalesce(commit1, commit2)"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "coalesce(none(), none(), commit2)"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "coalesce(none(), commit1, commit2)"),
vec![commit1.id().clone()]
);
assert_matches!(
try_resolve_commit_ids(mut_repo, "coalesce(all(), commit1_invalid)"),
Err(RevsetResolutionError::NoSuchRevision { name, .. })
if name == "commit1_invalid"
);
assert_matches!(
try_resolve_commit_ids(mut_repo, "coalesce(none(), commit1_invalid)"),
Err(RevsetResolutionError::NoSuchRevision { name, .. })
if name == "commit1_invalid"
);
assert_matches!(
try_resolve_commit_ids(mut_repo, "coalesce(all(), commit1, commit2_invalid)"),
Err(RevsetResolutionError::NoSuchRevision { name, .. })
if name == "commit2_invalid"
);
assert_matches!(
try_resolve_commit_ids(mut_repo, "coalesce(none(), commit1, commit2_invalid)"),
Err(RevsetResolutionError::NoSuchRevision { name, .. })
if name == "commit2_invalid"
);
assert_matches!(
try_resolve_commit_ids(mut_repo, "coalesce(none(), commit1, commit2, commit2_invalid)"),
Err(RevsetResolutionError::NoSuchRevision { name, .. })
if name == "commit2_invalid"
);
}
#[test]
fn test_evaluate_expression_union() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit3]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit2]);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("::{} | ::{}", commit4.id(), commit5.id())
),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone()
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"(::{} ~ ::{}) | ::{}",
commit4.id(),
commit2.id(),
commit5.id()
)
),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"(::{} ~ ::{}) | {}",
commit4.id(),
commit2.id(),
commit5.id(),
)
),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone()
]
);
}
#[test]
fn test_evaluate_expression_machine_generated_union() {
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_with_parents(mut_repo, &[&commit1]);
let revset_str =
std::iter::repeat_n(format!("({}::{})", commit1.id(), commit2.id()), 5000).join("|");
assert_eq!(
resolve_commit_ids(mut_repo, &revset_str),
vec![commit2.id().clone(), commit1.id().clone()]
);
}
#[test]
fn test_evaluate_expression_intersection() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit3]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit2]);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("::{} & ::{}", commit4.id(), commit5.id())
),
vec![
commit2.id().clone(),
commit1.id().clone(),
root_commit.id().clone()
]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{} & {}", commit4.id(), commit2.id())),
vec![]
);
}
#[test]
fn test_evaluate_expression_difference() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = write_random_commit_with_parents(mut_repo, &[&commit1]);
let commit3 = write_random_commit_with_parents(mut_repo, &[&commit2]);
let commit4 = write_random_commit_with_parents(mut_repo, &[&commit3]);
let commit5 = write_random_commit_with_parents(mut_repo, &[&commit2]);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("~::{}", commit5.id())),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("::{} ~ ::{}", commit4.id(), commit5.id())
),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("::{} ~ ::{}", commit5.id(), commit4.id())
),
vec![commit5.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("~::{} & ::{}", commit4.id(), commit5.id())
),
vec![commit5.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("::{} ~ ::{}", commit4.id(), commit2.id())
),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("::{} ~ {} ~ {}", commit4.id(), commit2.id(), commit3.id())
),
vec![
commit4.id().clone(),
commit1.id().clone(),
root_commit.id().clone(),
]
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!(
"(::{} ~ ::{}) ~ (::{} ~ ::{})",
commit4.id(),
commit1.id(),
commit3.id(),
commit1.id(),
)
),
vec![commit4.id().clone()]
);
}
#[test]
fn test_evaluate_expression_filter_combinator() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let root_commit_id = repo.store().root_commit_id();
let commit1 = create_random_commit(mut_repo)
.set_description("commit 1")
.write_unwrap();
let commit2 = create_random_commit(mut_repo)
.set_parents(vec![commit1.id().clone()])
.set_description("commit 2")
.write_unwrap();
let commit3 = create_random_commit(mut_repo)
.set_parents(vec![commit2.id().clone()])
.set_description("commit 3")
.write_unwrap();
assert_eq!(
resolve_commit_ids(mut_repo, "~subject(*1)"),
vec![
commit3.id().clone(),
commit2.id().clone(),
root_commit_id.clone(),
],
);
assert_eq!(
resolve_commit_ids(mut_repo, "subject(*1) | subject(*2)"),
vec![commit2.id().clone(), commit1.id().clone()],
);
assert_eq!(
resolve_commit_ids(mut_repo, "subject(commit*) ~ (subject(*2) | subject(*3))",),
vec![commit1.id().clone()],
);
assert_eq!(
resolve_commit_ids(mut_repo, "root().. & ~subject(*1)"),
vec![commit3.id().clone(), commit2.id().clone()],
);
assert_eq!(
resolve_commit_ids(
mut_repo,
".. & (subject(*1) & subject(commit*) | subject(*2))"
),
vec![commit2.id().clone(), commit1.id().clone()],
);
assert_eq!(
resolve_commit_ids(
mut_repo,
".. & (subject(*1) ~ subject(commit*) | subject(*2))"
),
vec![commit2.id().clone()],
);
assert_eq!(
resolve_commit_ids(
mut_repo,
&format!("{}.. & (subject(*1) | subject(*2))", commit1.id()),
),
vec![commit2.id().clone()],
);
}
#[test_case(false; "without changed-path index")]
#[test_case(true; "with changed-path index")]
fn test_evaluate_expression_file(indexed: bool) {
let test_workspace = TestWorkspace::init();
let repo = if indexed {
build_changed_path_index(&test_workspace.repo)
} else {
test_workspace.repo.clone()
};
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let added_clean_clean = repo_path("added_clean_clean");
let added_modified_clean = repo_path("added_modified_clean");
let added_modified_removed = repo_path("added_modified_removed");
let tree1 = create_tree(
&repo,
&[
(added_clean_clean, "1"),
(added_modified_clean, "1"),
(added_modified_removed, "1"),
],
);
let tree2 = create_tree(
&repo,
&[
(added_clean_clean, "1"),
(added_modified_clean, "2"),
(added_modified_removed, "2"),
],
);
let tree3 = create_tree(
&repo,
&[
(added_clean_clean, "1"),
(added_modified_clean, "2"),
],
);
let commit1 = mut_repo
.new_commit(vec![repo.store().root_commit_id().clone()], tree1)
.write_unwrap();
let commit2 = mut_repo
.new_commit(vec![commit1.id().clone()], tree2)
.write_unwrap();
let commit3 = mut_repo
.new_commit(vec![commit2.id().clone()], tree3.clone())
.write_unwrap();
let commit4 = mut_repo
.new_commit(vec![commit3.id().clone()], tree3)
.write_unwrap();
let resolve = |file_path: &RepoPath| -> Vec<CommitId> {
let mut_repo = &*mut_repo;
let expression = RevsetExpression::filter(RevsetFilterPredicate::File(
FilesetExpression::prefix_path(file_path.to_owned()),
));
let revset = expression.evaluate(mut_repo).unwrap();
revset.iter().map(Result::unwrap).collect()
};
assert_eq!(resolve(added_clean_clean), vec![commit1.id().clone()]);
assert_eq!(
resolve(added_modified_clean),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve(added_modified_removed),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone()
]
);
assert_eq!(
resolve_commit_ids_in_workspace(
mut_repo,
r#"files("repo/added_clean_clean")"#,
&test_workspace.workspace,
Some(test_workspace.workspace.workspace_root().parent().unwrap()),
),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids_in_workspace(
mut_repo,
r#"files("added_clean_clean"|"added_modified_clean")"#,
&test_workspace.workspace,
Some(test_workspace.workspace.workspace_root()),
),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids_in_workspace(
mut_repo,
&format!(r#"{}:: & files("added_modified_clean")"#, commit2.id()),
&test_workspace.workspace,
Some(test_workspace.workspace.workspace_root()),
),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, &format!("{}:: & empty()", commit1.id())),
vec![commit4.id().clone()]
);
}
#[test_case(false; "without changed-path index")]
#[test_case(true; "with changed-path index")]
fn test_evaluate_expression_diff_lines(indexed: bool) {
let test_workspace = TestWorkspace::init();
let repo = if indexed {
build_changed_path_index(&test_workspace.repo)
} else {
test_workspace.repo.clone()
};
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let empty_clean_inserted_deleted = repo_path("empty_clean_inserted_deleted");
let blank_clean_inserted_clean = repo_path("blank_clean_inserted_clean");
let noeol_modified_modified_clean = repo_path("noeol_modified_modified_clean");
let normal_inserted_modified_removed = repo_path("normal_inserted_modified_removed");
let tree1 = create_tree(
&repo,
&[
(empty_clean_inserted_deleted, ""),
(blank_clean_inserted_clean, "\n"),
(noeol_modified_modified_clean, "1"),
(normal_inserted_modified_removed, "1\n"),
],
);
let tree2 = create_tree(
&repo,
&[
(empty_clean_inserted_deleted, ""),
(blank_clean_inserted_clean, "\n"),
(noeol_modified_modified_clean, "2"),
(normal_inserted_modified_removed, "1\n2\n"),
],
);
let tree3 = create_tree(
&repo,
&[
(empty_clean_inserted_deleted, "3"),
(blank_clean_inserted_clean, "\n3\n"),
(noeol_modified_modified_clean, "2 3"),
(normal_inserted_modified_removed, "1 3\n2\n"),
],
);
let tree4 = create_tree(
&repo,
&[
(empty_clean_inserted_deleted, ""),
(blank_clean_inserted_clean, "\n3\n"),
(noeol_modified_modified_clean, "2 3"),
],
);
let commit1 = mut_repo
.new_commit(vec![repo.store().root_commit_id().clone()], tree1)
.write_unwrap();
let commit2 = mut_repo
.new_commit(vec![commit1.id().clone()], tree2)
.write_unwrap();
let commit3 = mut_repo
.new_commit(vec![commit2.id().clone()], tree3)
.write_unwrap();
let commit4 = mut_repo
.new_commit(vec![commit3.id().clone()], tree4)
.write_unwrap();
let query = |revset_str: &str| {
resolve_commit_ids_in_workspace(
mut_repo,
revset_str,
&test_workspace.workspace,
Some(test_workspace.workspace.workspace_root()),
)
};
assert_eq!(
query("diff_lines(*2*)"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
assert_eq!(
query("diff_lines_added(*2*)"),
vec![commit3.id().clone(), commit2.id().clone()]
);
assert_eq!(
query("diff_lines_removed(*2*)"),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
query("diff_lines(*3*)"),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(query("diff_lines_added(*3*)"), vec![commit3.id().clone()]);
assert_eq!(query("diff_lines_removed(*3*)"), vec![commit4.id().clone()]);
assert_eq!(query("diff_lines('*2 3*')"), vec![commit3.id().clone()]);
assert_eq!(
query("diff_lines_added('*2 3*')"),
vec![commit3.id().clone()]
);
assert_eq!(query("diff_lines_removed('*2 3*')"), vec![]);
assert_eq!(
query("diff_lines('*1 3*')"),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
query("diff_lines_added('*1 3*')"),
vec![commit3.id().clone()]
);
assert_eq!(
query("diff_lines_removed('*1 3*')"),
vec![commit4.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines('1', {normal_inserted_modified_removed:?})",
)),
vec![commit3.id().clone(), commit1.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines_added('1', {normal_inserted_modified_removed:?})",
)),
vec![commit1.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines_removed('1', {normal_inserted_modified_removed:?})",
)),
vec![commit3.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines('1', {noeol_modified_modified_clean:?})",
)),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines_added('1', {noeol_modified_modified_clean:?})",
)),
vec![commit1.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines_removed('1', {noeol_modified_modified_clean:?})",
)),
vec![commit2.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines(exact:'', {empty_clean_inserted_deleted:?})",
)),
vec![]
);
assert_eq!(
query(&format!(
"diff_lines(exact:'', {blank_clean_inserted_clean:?})",
)),
vec![commit1.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines(substring:'', {empty_clean_inserted_deleted:?})",
)),
vec![commit4.id().clone(), commit3.id().clone()]
);
assert_eq!(
query(&format!(
"diff_lines(substring:'', {blank_clean_inserted_clean:?})",
)),
vec![commit3.id().clone(), commit1.id().clone()]
);
}
#[test]
fn test_evaluate_expression_diff_lines_non_utf8() {
let test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let tree1 = create_tree_with(repo, |builder| {
builder.file(repo_path("file"), b"\x81\x40");
});
let commit1 = mut_repo
.new_commit(vec![repo.store().root_commit_id().clone()], tree1)
.write_unwrap();
let query = |revset_str: &str| resolve_commit_ids(mut_repo, revset_str);
assert_eq!(
query("diff_lines(regex:'(?-u)^.{2}$')"),
vec![commit1.id().clone()]
);
assert_eq!(query("diff_lines(regex:'(?-u)^.$')"), vec![]);
}
#[test_case(false; "without changed-path index")]
#[test_case(true; "with changed-path index")]
fn test_evaluate_expression_diff_lines_conflict(indexed: bool) -> TestResult {
let test_workspace = TestWorkspace::init();
let repo = if indexed {
build_changed_path_index(&test_workspace.repo)
} else {
test_workspace.repo.clone()
};
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let mut create_commit = |parent_ids, tree| mut_repo.new_commit(parent_ids, tree).write_unwrap();
let file_path = repo_path("file");
let tree1 = create_tree(&repo, &[(file_path, "0\n1\n")]);
let commit1 = create_commit(vec![repo.store().root_commit_id().clone()], tree1.clone());
let tree2 = create_tree(&repo, &[(file_path, "0\n2\n")]);
let tree3 = create_tree(&repo, &[(file_path, "0\n3\n")]);
let tree4 = MergedTree::merge(Merge::from_vec(vec![
(tree2, "tree 2".into()),
(tree1, "tree 1".into()),
(tree3, "tree 3".into()),
]))
.block_on()?;
let commit2 = create_commit(vec![commit1.id().clone()], tree4);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines('0')"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines_added('0')"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines_removed('0')"),
vec![]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines('1')"),
vec![commit2.id().clone(), commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines_added('1')"),
vec![commit1.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines_removed('1')"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines('2')"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines_added('2')"),
vec![commit2.id().clone()]
);
assert_eq!(
resolve_commit_ids(mut_repo, "diff_lines_removed('2')"),
vec![]
);
Ok(())
}
#[test_case(false; "without changed-path index")]
#[test_case(true; "with changed-path index")]
fn test_evaluate_expression_file_merged_parents(indexed: bool) {
let test_workspace = TestWorkspace::init();
let repo = if indexed {
build_changed_path_index(&test_workspace.repo)
} else {
test_workspace.repo.clone()
};
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let file_path1 = repo_path("file1");
let file_path2 = repo_path("file2");
let tree1 = create_tree(&repo, &[(file_path1, "1\n"), (file_path2, "1\n")]);
let tree2 = create_tree(&repo, &[(file_path1, "1\n2\n"), (file_path2, "2\n1\n")]);
let tree3 = create_tree(&repo, &[(file_path1, "1\n3\n"), (file_path2, "1\n3\n")]);
let tree4 = create_tree(&repo, &[(file_path1, "1\n4\n"), (file_path2, "2\n1\n3\n")]);
let mut create_commit = |parent_ids, tree| mut_repo.new_commit(parent_ids, tree).write_unwrap();
let commit1 = create_commit(vec![repo.store().root_commit_id().clone()], tree1);
let commit2 = create_commit(vec![commit1.id().clone()], tree2);
let commit3 = create_commit(vec![commit1.id().clone()], tree3);
let commit4 = create_commit(vec![commit2.id().clone(), commit3.id().clone()], tree4);
let query = |revset_str: &str| {
resolve_commit_ids_in_workspace(
mut_repo,
revset_str,
&test_workspace.workspace,
Some(test_workspace.workspace.workspace_root()),
)
};
assert_eq!(
query("files('file1')"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
query("files('file2')"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
query("diff_lines(regex:'[1234]', 'file1')"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
query("diff_lines_added(regex:'[1234]', 'file1')"),
vec![
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
query("diff_lines_removed(regex:'[1234]', 'file1')"),
vec![commit4.id().clone()]
);
assert_eq!(
query("diff_lines(regex:'[1234]', 'file2')"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(
query("diff_lines_added(regex:'[1234]', 'file2')"),
vec![
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
assert_eq!(query("diff_lines_removed(regex:'[1234]', 'file2')"), vec![]);
}
#[test]
fn test_evaluate_expression_conflict() -> TestResult {
let test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let mut create_commit = |parent_ids, tree| mut_repo.new_commit(parent_ids, tree).write_unwrap();
let file_path1 = repo_path("file1");
let file_path2 = repo_path("file2");
let tree1 = create_tree(repo, &[(file_path1, "1"), (file_path2, "1")]);
let commit1 = create_commit(vec![repo.store().root_commit_id().clone()], tree1.clone());
let tree2 = create_tree(repo, &[(file_path1, "2"), (file_path2, "2")]);
let commit2 = create_commit(vec![commit1.id().clone()], tree2.clone());
let tree3 = create_tree(repo, &[(file_path1, "3"), (file_path2, "1")]);
let commit3 = create_commit(vec![commit2.id().clone()], tree3.clone());
let tree4 = MergedTree::merge(Merge::from_vec(vec![
(tree2, "tree 2".into()),
(tree1, "tree 1".into()),
(tree3, "tree 3".into()),
]))
.block_on()?;
let commit4 = create_commit(vec![commit3.id().clone()], tree4);
assert_eq!(
resolve_commit_ids(mut_repo, "conflicts()"),
vec![commit4.id().clone()]
);
Ok(())
}
#[test]
fn test_evaluate_expression_divergent() -> TestResult {
let test_workspace = TestWorkspace::init();
let repo = &test_workspace.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit1 = write_random_commit(mut_repo);
let commit2 = create_random_commit(mut_repo)
.set_change_id(commit1.change_id().clone())
.write_unwrap();
let _commit3 = write_random_commit(mut_repo);
let _commit4 = write_random_commit(mut_repo);
let change_id = commit1.change_id();
let repo = tx.commit("Divergent commits").block_on()?;
assert_matches!(
resolve_symbol(repo.as_ref(), &format!("{change_id}")),
Err(RevsetResolutionError::DivergentChangeId { symbol, visible_targets })
if symbol == change_id.to_string()
&& visible_targets == vec![(0, commit2.id().clone()), (1, commit1.id().clone())]
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), "divergent()"),
vec![commit2.id().clone(), commit1.id().clone()]
);
let mut tx = repo.start_transaction();
tx.repo_mut().record_abandoned_commit(&commit1);
tx.repo_mut().rebase_descendants().block_on()?;
let repo = tx.commit("abandon commit").block_on()?;
assert_eq!(resolve_commit_ids(repo.as_ref(), "divergent()"), vec![]);
assert_eq!(
resolve_commit_ids(repo.as_ref(), &format!("{} & divergent()", commit1.id())),
vec![]
);
assert_eq!(
resolve_commit_ids(repo.as_ref(), "at_operation(@-, divergent())"),
vec![commit2.id().clone(), commit1.id().clone()]
);
Ok(())
}
#[test]
fn test_reverse_graph() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit_a = write_random_commit(mut_repo);
let commit_b = write_random_commit_with_parents(mut_repo, &[&commit_a]);
let commit_c = write_random_commit_with_parents(mut_repo, &[&commit_b]);
let commit_d = write_random_commit_with_parents(mut_repo, &[&commit_c]);
let commit_e = write_random_commit_with_parents(mut_repo, &[&commit_c]);
let commit_f = write_random_commit_with_parents(mut_repo, &[&commit_d, &commit_e]);
let repo = tx.commit("test").block_on()?;
let revset = revset_for_commits(
repo.as_ref(),
&[&commit_a, &commit_c, &commit_d, &commit_e, &commit_f],
);
let commits = reverse_graph(revset.iter_graph(), |id| id)?;
assert_eq!(commits.len(), 5);
assert_eq!(commits[0].0, *commit_a.id());
assert_eq!(commits[1].0, *commit_c.id());
assert_eq!(commits[2].0, *commit_d.id());
assert_eq!(commits[3].0, *commit_e.id());
assert_eq!(commits[4].0, *commit_f.id());
assert_eq!(
commits[0].1,
vec![GraphEdge::indirect(commit_c.id().clone())]
);
assert_eq!(
commits[1].1,
vec![
GraphEdge::direct(commit_e.id().clone()),
GraphEdge::direct(commit_d.id().clone()),
]
);
assert_eq!(commits[2].1, vec![GraphEdge::direct(commit_f.id().clone())]);
assert_eq!(commits[3].1, vec![GraphEdge::direct(commit_f.id().clone())]);
assert_eq!(commits[4].1, vec![]);
Ok(())
}
#[test]
fn test_no_such_revision_suggestion() {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit = write_random_commit(mut_repo);
for bookmark_name in ["foo", "bar", "baz"] {
mut_repo.set_local_bookmark_target(
bookmark_name.as_ref(),
RefTarget::normal(commit.id().clone()),
);
}
assert_matches!(resolve_symbol(mut_repo, "bar"), Ok(_));
assert_matches!(
resolve_symbol(mut_repo, "bax"),
Err(RevsetResolutionError::NoSuchRevision { name, candidates })
if name == "bax" && candidates == vec!["bar".to_string(), "baz".to_string()]
);
}
#[test]
fn test_revset_containing_fn() -> TestResult {
let test_repo = TestRepo::init();
let repo = &test_repo.repo;
let mut tx = repo.start_transaction();
let mut_repo = tx.repo_mut();
let commit_a = write_random_commit(mut_repo);
let commit_b = write_random_commit(mut_repo);
let commit_c = write_random_commit(mut_repo);
let commit_d = write_random_commit(mut_repo);
let repo = tx.commit("test").block_on()?;
let revset = revset_for_commits(repo.as_ref(), &[&commit_b, &commit_d]);
let revset_has_commit = revset.containing_fn();
assert!(!revset_has_commit(commit_a.id())?);
assert!(revset_has_commit(commit_b.id())?);
assert!(!revset_has_commit(commit_c.id())?);
assert!(revset_has_commit(commit_d.id())?);
Ok(())
}