diff --git a/lib/src/revset.rs b/lib/src/revset.rs index 65dcc3487..86f2790f4 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -198,6 +198,7 @@ pub enum RevsetExpression { Heads, PublicHeads, Branches, + RemoteBranches, Tags, GitRefs, ParentCount { @@ -246,6 +247,10 @@ impl RevsetExpression { Rc::new(RevsetExpression::Branches) } + pub fn remote_branches() -> Rc { + Rc::new(RevsetExpression::RemoteBranches) + } + pub fn tags() -> Rc { Rc::new(RevsetExpression::Tags) } @@ -577,6 +582,16 @@ fn parse_function_expression( }) } } + "remote_branches" => { + if arg_count == 0 { + Ok(RevsetExpression::remote_branches()) + } else { + Err(RevsetParseError::InvalidFunctionArguments { + name, + message: "Expected 0 arguments".to_string(), + }) + } + } "tags" => { if arg_count == 0 { Ok(RevsetExpression::tags()) @@ -1098,6 +1113,15 @@ pub fn evaluate_expression<'repo>( index_entries.push(index.entry_by_id(&id).unwrap()); } } + } + index_entries.sort_by_key(|b| Reverse(b.position())); + index_entries.dedup(); + Ok(Box::new(EagerRevset { index_entries })) + } + RevsetExpression::RemoteBranches => { + let index = repo.index(); + let mut index_entries = vec![]; + for branch_target in repo.view().branches().values() { for remote_target in branch_target.remote_targets.values() { for id in remote_target.adds() { index_entries.push(index.entry_by_id(&id).unwrap()); diff --git a/lib/tests/test_revset.rs b/lib/tests/test_revset.rs index e2a17b9bb..7f63d1dca 100644 --- a/lib/tests/test_revset.rs +++ b/lib/tests/test_revset.rs @@ -890,6 +890,151 @@ fn test_evaluate_expression_git_refs(use_git: bool) { tx.discard(); } +#[test_case(false ; "local backend")] +#[test_case(true ; "git backend")] +fn test_evaluate_expression_branches(use_git: bool) { + let settings = testutils::user_settings(); + let (_temp_dir, repo) = testutils::init_repo(&settings, use_git); + + let mut tx = repo.start_transaction("test"); + let mut_repo = tx.mut_repo(); + + let commit1 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + let commit2 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + let commit3 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + let commit4 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + + // Can get branches when there are none + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "branches()"), + vec![] + ); + // Can get a few branches + mut_repo.set_local_branch( + "branch1".to_string(), + RefTarget::Normal(commit1.id().clone()), + ); + mut_repo.set_local_branch( + "branch2".to_string(), + RefTarget::Normal(commit2.id().clone()), + ); + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "branches()"), + vec![commit2.id().clone(), commit1.id().clone()] + ); + // Two branches pointing to the same commit does not result in a duplicate in + // the revset + mut_repo.set_local_branch( + "branch3".to_string(), + RefTarget::Normal(commit2.id().clone()), + ); + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "branches()"), + vec![commit2.id().clone(), commit1.id().clone()] + ); + // Can get branches when there are conflicted refs + mut_repo.set_local_branch( + "branch1".to_string(), + RefTarget::Conflict { + removes: vec![commit1.id().clone()], + adds: vec![commit2.id().clone(), commit3.id().clone()], + }, + ); + mut_repo.set_local_branch( + "branch2".to_string(), + RefTarget::Conflict { + removes: vec![commit2.id().clone()], + adds: vec![commit3.id().clone(), commit4.id().clone()], + }, + ); + mut_repo.remove_local_branch("branch3"); + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "branches()"), + vec![ + commit4.id().clone(), + commit3.id().clone(), + commit2.id().clone() + ] + ); + + tx.discard(); +} + +#[test_case(false ; "local backend")] +#[test_case(true ; "git backend")] +fn test_evaluate_expression_remote_branches(use_git: bool) { + let settings = testutils::user_settings(); + let (_temp_dir, repo) = testutils::init_repo(&settings, use_git); + + let mut tx = repo.start_transaction("test"); + let mut_repo = tx.mut_repo(); + + let commit1 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + let commit2 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + let commit3 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + let commit4 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo); + + // Can get branches when there are none + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "remote_branches()"), + vec![] + ); + // Can get a few branches + mut_repo.set_remote_branch( + "branch1".to_string(), + "origin".to_string(), + RefTarget::Normal(commit1.id().clone()), + ); + mut_repo.set_remote_branch( + "branch2".to_string(), + "private".to_string(), + RefTarget::Normal(commit2.id().clone()), + ); + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "remote_branches()"), + vec![commit2.id().clone(), commit1.id().clone()] + ); + // Two branches pointing to the same commit does not result in a duplicate in + // the revset + mut_repo.set_remote_branch( + "branch3".to_string(), + "origin".to_string(), + RefTarget::Normal(commit2.id().clone()), + ); + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "remote_branches()"), + vec![commit2.id().clone(), commit1.id().clone()] + ); + // Can get branches when there are conflicted refs + mut_repo.set_remote_branch( + "branch1".to_string(), + "origin".to_string(), + RefTarget::Conflict { + removes: vec![commit1.id().clone()], + adds: vec![commit2.id().clone(), commit3.id().clone()], + }, + ); + mut_repo.set_remote_branch( + "branch2".to_string(), + "private".to_string(), + RefTarget::Conflict { + removes: vec![commit2.id().clone()], + adds: vec![commit3.id().clone(), commit4.id().clone()], + }, + ); + mut_repo.remove_remote_branch("branch3", "origin"); + assert_eq!( + resolve_commit_ids(mut_repo.as_repo_ref(), "remote_branches()"), + vec![ + commit4.id().clone(), + commit3.id().clone(), + commit2.id().clone() + ] + ); + + tx.discard(); +} + #[test_case(false ; "local backend")] #[test_case(true ; "git backend")] fn test_evaluate_expression_merges(use_git: bool) {