forked from mirrors/jj
revsets: add revset yielding merge commits
This commit is contained in:
parent
aef27d5701
commit
7065cecfdc
2 changed files with 100 additions and 1 deletions
|
@ -15,6 +15,7 @@
|
||||||
use std::cmp::{Ordering, Reverse};
|
use std::cmp::{Ordering, Reverse};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use pest::iterators::Pairs;
|
use pest::iterators::Pairs;
|
||||||
|
@ -122,6 +123,10 @@ pub enum RevsetExpression {
|
||||||
PublicHeads,
|
PublicHeads,
|
||||||
GitRefs,
|
GitRefs,
|
||||||
NonObsoleteHeads(Rc<RevsetExpression>),
|
NonObsoleteHeads(Rc<RevsetExpression>),
|
||||||
|
ParentCount {
|
||||||
|
candidates: Rc<RevsetExpression>,
|
||||||
|
parent_count_range: Range<u32>,
|
||||||
|
},
|
||||||
Description {
|
Description {
|
||||||
needle: String,
|
needle: String,
|
||||||
candidates: Rc<RevsetExpression>,
|
candidates: Rc<RevsetExpression>,
|
||||||
|
@ -422,6 +427,25 @@ fn parse_function_expression(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"merges" => {
|
||||||
|
if arg_count > 1 {
|
||||||
|
return Err(RevsetParseError::InvalidFunctionArguments {
|
||||||
|
name,
|
||||||
|
message: "Expected 0 or 1 arguments".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let candidates = if arg_count == 0 {
|
||||||
|
RevsetExpression::non_obsolete_commits()
|
||||||
|
} else {
|
||||||
|
Rc::new(parse_expression_rule(
|
||||||
|
argument_pairs.next().unwrap().into_inner(),
|
||||||
|
)?)
|
||||||
|
};
|
||||||
|
Ok(RevsetExpression::ParentCount {
|
||||||
|
candidates,
|
||||||
|
parent_count_range: 2..u32::MAX,
|
||||||
|
})
|
||||||
|
}
|
||||||
"description" => {
|
"description" => {
|
||||||
if !(1..=2).contains(&arg_count) {
|
if !(1..=2).contains(&arg_count) {
|
||||||
return Err(RevsetParseError::InvalidFunctionArguments {
|
return Err(RevsetParseError::InvalidFunctionArguments {
|
||||||
|
@ -586,6 +610,39 @@ impl<'repo> Iterator for ChildrenRevsetIterator<'_, 'repo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Generalize this to be take a predicate to apply to an &IndexEntry.
|
||||||
|
struct ParentCountRevset<'revset, 'repo: 'revset> {
|
||||||
|
candidates: Box<dyn Revset<'repo> + 'revset>,
|
||||||
|
parent_count_range: Range<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'repo> Revset<'repo> for ParentCountRevset<'_, 'repo> {
|
||||||
|
fn iter<'revset>(&'revset self) -> RevsetIterator<'revset, 'repo> {
|
||||||
|
RevsetIterator::new(Box::new(ParentCountRevsetIterator {
|
||||||
|
iter: self.candidates.iter(),
|
||||||
|
parent_count_range: self.parent_count_range.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ParentCountRevsetIterator<'revset, 'repo> {
|
||||||
|
iter: RevsetIterator<'revset, 'repo>,
|
||||||
|
parent_count_range: Range<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'revset, 'repo> Iterator for ParentCountRevsetIterator<'revset, 'repo> {
|
||||||
|
type Item = IndexEntry<'repo>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while let Some(next) = self.iter.next() {
|
||||||
|
if self.parent_count_range.contains(&next.num_parents()) {
|
||||||
|
return Some(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct UnionRevset<'revset, 'repo: 'revset> {
|
struct UnionRevset<'revset, 'repo: 'revset> {
|
||||||
set1: Box<dyn Revset<'repo> + 'revset>,
|
set1: Box<dyn Revset<'repo> + 'revset>,
|
||||||
set2: Box<dyn Revset<'repo> + 'revset>,
|
set2: Box<dyn Revset<'repo> + 'revset>,
|
||||||
|
@ -808,6 +865,16 @@ pub fn evaluate_expression<'revset, 'repo: 'revset>(
|
||||||
let base_set = evaluate_expression(repo, base_expression.as_ref())?;
|
let base_set = evaluate_expression(repo, base_expression.as_ref())?;
|
||||||
Ok(non_obsolete_heads(repo, base_set))
|
Ok(non_obsolete_heads(repo, base_set))
|
||||||
}
|
}
|
||||||
|
RevsetExpression::ParentCount {
|
||||||
|
candidates,
|
||||||
|
parent_count_range,
|
||||||
|
} => {
|
||||||
|
let candidates = evaluate_expression(repo, candidates.as_ref())?;
|
||||||
|
Ok(Box::new(ParentCountRevset {
|
||||||
|
candidates,
|
||||||
|
parent_count_range: parent_count_range.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
RevsetExpression::PublicHeads => {
|
RevsetExpression::PublicHeads => {
|
||||||
let index = repo.index();
|
let index = repo.index();
|
||||||
let heads = repo.view().public_heads();
|
let heads = repo.view().public_heads();
|
||||||
|
|
|
@ -769,6 +769,38 @@ fn test_evaluate_expression_obsolete(use_git: bool) {
|
||||||
tx.discard();
|
tx.discard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(false ; "local store")]
|
||||||
|
#[test_case(true ; "git store")]
|
||||||
|
fn test_evaluate_expression_merges(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 mut graph_builder = CommitGraphBuilder::new(&settings, mut_repo);
|
||||||
|
let commit1 = graph_builder.initial_commit();
|
||||||
|
let commit2 = graph_builder.initial_commit();
|
||||||
|
let commit3 = graph_builder.initial_commit();
|
||||||
|
let commit4 = graph_builder.commit_with_parents(&[&commit1, &commit2]);
|
||||||
|
let commit5 = graph_builder.commit_with_parents(&[&commit1, &commit2, &commit3]);
|
||||||
|
|
||||||
|
// Finds all merges by default
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(mut_repo.as_repo_ref(), "merges()"),
|
||||||
|
vec![commit5.id().clone(), commit4.id().clone(),]
|
||||||
|
);
|
||||||
|
// Searches only among candidates if specified
|
||||||
|
assert_eq!(
|
||||||
|
resolve_commit_ids(
|
||||||
|
mut_repo.as_repo_ref(),
|
||||||
|
&format!("merges(,,{})", commit5.id().hex())
|
||||||
|
),
|
||||||
|
vec![commit5.id().clone()]
|
||||||
|
);
|
||||||
|
|
||||||
|
tx.discard();
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case(false ; "local store")]
|
#[test_case(false ; "local store")]
|
||||||
#[test_case(true ; "git store")]
|
#[test_case(true ; "git store")]
|
||||||
fn test_evaluate_expression_description(use_git: bool) {
|
fn test_evaluate_expression_description(use_git: bool) {
|
||||||
|
@ -804,7 +836,7 @@ fn test_evaluate_expression_description(use_git: bool) {
|
||||||
resolve_commit_ids(mut_repo.as_repo_ref(), "description(\"commit 2\")"),
|
resolve_commit_ids(mut_repo.as_repo_ref(), "description(\"commit 2\")"),
|
||||||
vec![commit2.id().clone()]
|
vec![commit2.id().clone()]
|
||||||
);
|
);
|
||||||
// Searches only in given base set if specified
|
// Searches only among candidates if specified
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve_commit_ids(
|
resolve_commit_ids(
|
||||||
mut_repo.as_repo_ref(),
|
mut_repo.as_repo_ref(),
|
||||||
|
|
Loading…
Reference in a new issue