forked from mirrors/jj
revsets: add union operator
This commit is contained in:
parent
2ac5d1f912
commit
332580918c
3 changed files with 163 additions and 2 deletions
|
@ -20,8 +20,9 @@ parents = { ":" }
|
|||
ancestors = { "*:" }
|
||||
prefix_operator = _{ parents | ancestors }
|
||||
|
||||
union = { "|" }
|
||||
difference = { "-" }
|
||||
infix_operator = _{ difference }
|
||||
infix_operator = _{ union| difference }
|
||||
|
||||
function_name = @{ (ASCII_ALPHANUMERIC | "_")+ }
|
||||
// The grammar accepts a string literal or an expression for function
|
||||
|
|
|
@ -107,6 +107,7 @@ pub enum RevsetExpression {
|
|||
needle: String,
|
||||
base_expression: Box<RevsetExpression>,
|
||||
},
|
||||
Union(Box<RevsetExpression>, Box<RevsetExpression>),
|
||||
Difference(Box<RevsetExpression>, Box<RevsetExpression>),
|
||||
}
|
||||
|
||||
|
@ -131,6 +132,9 @@ fn parse_infix_expression_rule(
|
|||
while let Some(operator) = pairs.next() {
|
||||
let expression2 = parse_prefix_expression_rule(pairs.next().unwrap().into_inner())?;
|
||||
match operator.as_rule() {
|
||||
Rule::union => {
|
||||
expression1 = RevsetExpression::Union(Box::new(expression1), Box::new(expression2))
|
||||
}
|
||||
Rule::difference => {
|
||||
expression1 =
|
||||
RevsetExpression::Difference(Box::new(expression1), Box::new(expression2))
|
||||
|
@ -398,6 +402,44 @@ impl<'repo> Iterator for RevWalkRevsetIterator<'repo> {
|
|||
}
|
||||
}
|
||||
|
||||
struct UnionRevset<'revset, 'repo: 'revset> {
|
||||
set1: Box<dyn Revset<'repo> + 'revset>,
|
||||
set2: Box<dyn Revset<'repo> + 'revset>,
|
||||
}
|
||||
|
||||
impl<'repo> Revset<'repo> for UnionRevset<'_, 'repo> {
|
||||
fn iter<'revset>(&'revset self) -> Box<dyn Iterator<Item = IndexEntry<'repo>> + 'revset> {
|
||||
Box::new(UnionRevsetIterator {
|
||||
iter1: self.set1.iter().peekable(),
|
||||
iter2: self.set2.iter().peekable(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct UnionRevsetIterator<'revset, 'repo> {
|
||||
iter1: Peekable<Box<dyn Iterator<Item = IndexEntry<'repo>> + 'revset>>,
|
||||
iter2: Peekable<Box<dyn Iterator<Item = IndexEntry<'repo>> + 'revset>>,
|
||||
}
|
||||
|
||||
impl<'revset, 'repo> Iterator for UnionRevsetIterator<'revset, 'repo> {
|
||||
type Item = IndexEntry<'repo>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (self.iter1.peek(), self.iter2.peek()) {
|
||||
(None, _) => self.iter2.next(),
|
||||
(_, None) => self.iter1.next(),
|
||||
(Some(entry1), Some(entry2)) => match entry1.position().cmp(&entry2.position()) {
|
||||
Ordering::Less => self.iter2.next(),
|
||||
Ordering::Equal => {
|
||||
self.iter1.next();
|
||||
self.iter2.next()
|
||||
}
|
||||
Ordering::Greater => self.iter1.next(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DifferenceRevset<'revset, 'repo: 'revset> {
|
||||
// The minuend (what to subtract from)
|
||||
set1: Box<dyn Revset<'repo> + 'revset>,
|
||||
|
@ -512,6 +554,11 @@ pub fn evaluate_expression<'revset, 'repo: 'revset>(
|
|||
index_entries.sort_by_key(|b| Reverse(b.position()));
|
||||
Ok(Box::new(EagerRevset { index_entries }))
|
||||
}
|
||||
RevsetExpression::Union(expression1, expression2) => {
|
||||
let set1 = evaluate_expression(repo, expression1.as_ref())?;
|
||||
let set2 = evaluate_expression(repo, expression2.as_ref())?;
|
||||
Ok(Box::new(UnionRevset { set1, set2 }))
|
||||
}
|
||||
RevsetExpression::Difference(expression1, expression2) => {
|
||||
let set1 = evaluate_expression(repo, expression1.as_ref())?;
|
||||
let set2 = evaluate_expression(repo, expression2.as_ref())?;
|
||||
|
|
|
@ -394,6 +394,7 @@ fn test_evaluate_expression_parents(use_git: bool) {
|
|||
let mut tx = repo.start_transaction("test");
|
||||
let mut_repo = tx.mut_repo();
|
||||
|
||||
let root_commit = repo.store().root_commit();
|
||||
let commit1 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo);
|
||||
let commit2 = testutils::create_random_commit(&settings, &repo)
|
||||
.set_parents(vec![commit1.id().clone()])
|
||||
|
@ -402,6 +403,9 @@ fn test_evaluate_expression_parents(use_git: bool) {
|
|||
let commit4 = testutils::create_random_commit(&settings, &repo)
|
||||
.set_parents(vec![commit2.id().clone(), commit3.id().clone()])
|
||||
.write_to_repo(mut_repo);
|
||||
let commit5 = testutils::create_random_commit(&settings, &repo)
|
||||
.set_parents(vec![commit2.id().clone()])
|
||||
.write_to_repo(mut_repo);
|
||||
|
||||
// The root commit has no parents
|
||||
assert_eq!(resolve_commit_ids(mut_repo.as_repo_ref(), ":root"), vec![]);
|
||||
|
@ -416,7 +420,34 @@ fn test_evaluate_expression_parents(use_git: bool) {
|
|||
// Can find parents of a merge commit
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), &format!(":{}", commit4.id().hex())),
|
||||
vec![commit3.id().clone(), commit2.id().clone(),]
|
||||
vec![commit3.id().clone(), commit2.id().clone()]
|
||||
);
|
||||
|
||||
// Parents of all commits in input are returned
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!(":({} | {})", commit2.id().hex(), commit3.id().hex())
|
||||
),
|
||||
vec![commit1.id().clone(), root_commit.id().clone()]
|
||||
);
|
||||
|
||||
// Parents already in input set are returned
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!(":*:{}", commit2.id().hex())
|
||||
),
|
||||
vec![commit1.id().clone(), root_commit.id().clone()]
|
||||
);
|
||||
|
||||
// Parents shared among commits in input are not repeated
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!(":({} | {})", commit4.id().hex(), commit5.id().hex())
|
||||
),
|
||||
vec![commit3.id().clone(), commit2.id().clone()]
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
|
@ -582,6 +613,88 @@ fn test_evaluate_expression_description(use_git: bool) {
|
|||
tx.discard();
|
||||
}
|
||||
|
||||
#[test_case(false ; "local store")]
|
||||
#[test_case(true ; "git store")]
|
||||
fn test_evaluate_expression_union(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 root_commit = repo.store().root_commit();
|
||||
let commit1 = testutils::create_random_commit(&settings, &repo).write_to_repo(mut_repo);
|
||||
let commit2 = testutils::create_random_commit(&settings, &repo)
|
||||
.set_parents(vec![commit1.id().clone()])
|
||||
.write_to_repo(mut_repo);
|
||||
let commit3 = testutils::create_random_commit(&settings, &repo)
|
||||
.set_parents(vec![commit2.id().clone()])
|
||||
.write_to_repo(mut_repo);
|
||||
let commit4 = testutils::create_random_commit(&settings, &repo)
|
||||
.set_parents(vec![commit3.id().clone()])
|
||||
.write_to_repo(mut_repo);
|
||||
let commit5 = testutils::create_random_commit(&settings, &repo)
|
||||
.set_parents(vec![commit2.id().clone()])
|
||||
.write_to_repo(mut_repo);
|
||||
|
||||
// Union between ancestors
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("*:{} | *:{}", commit4.id().hex(), commit5.id().hex())
|
||||
),
|
||||
vec![
|
||||
commit5.id().clone(),
|
||||
commit4.id().clone(),
|
||||
commit3.id().clone(),
|
||||
commit2.id().clone(),
|
||||
commit1.id().clone(),
|
||||
root_commit.id().clone()
|
||||
]
|
||||
);
|
||||
|
||||
// Unioning can add back commits removed by difference
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!(
|
||||
"(*:{} - *:{}) | *:{}",
|
||||
commit4.id().hex(),
|
||||
commit2.id().hex(),
|
||||
commit5.id().hex()
|
||||
)
|
||||
),
|
||||
vec![
|
||||
commit5.id().clone(),
|
||||
commit4.id().clone(),
|
||||
commit3.id().clone(),
|
||||
commit2.id().clone(),
|
||||
commit1.id().clone(),
|
||||
root_commit.id().clone(),
|
||||
]
|
||||
);
|
||||
|
||||
// Unioning of disjoint sets
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!(
|
||||
"(*:{} - *:{}) | {}",
|
||||
commit4.id().hex(),
|
||||
commit2.id().hex(),
|
||||
commit5.id().hex(),
|
||||
)
|
||||
),
|
||||
vec![
|
||||
commit5.id().clone(),
|
||||
commit4.id().clone(),
|
||||
commit3.id().clone()
|
||||
]
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
#[test_case(false ; "local store")]
|
||||
#[test_case(true ; "git store")]
|
||||
fn test_evaluate_expression_difference(use_git: bool) {
|
||||
|
|
Loading…
Reference in a new issue