Skip to content

Commit

Permalink
revset: add coalesce(revsets...)
Browse files Browse the repository at this point in the history
  • Loading branch information
bnjmnt4n committed Oct 14, 2024
1 parent e761844 commit 1a8f649
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

* New template function `raw_escape_sequence(...)` preserves escape sequences.

* New `coalesce(revsets...)` revset which returns commits in the first revset
in the `revsets` list that does not evaluate to `none()`.

### Fixed bugs

* Error on `trunk()` revset resolution is now handled gracefully.
Expand Down
4 changes: 4 additions & 0 deletions docs/revsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ given [string pattern](#string-patterns).
example, `at_operation(@-, visible_heads())` will return all heads which were
visible at the previous operation.

* `coalesce(revsets...)`: Commits in the first revset in the list of `revsets`
which does not evaluate to `none()`. If all revsets evaluate to `none()`, then
the result of `coalesce` will also be `none()`.

[operation]: glossary.md#operation

??? examples
Expand Down
8 changes: 8 additions & 0 deletions lib/src/default_index/revset_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,14 @@ impl<'index> EvaluationContext<'index> {
self.take_latest_revset(candidate_set.as_ref(), *count),
))
}
ResolvedExpression::Coalesce(expression1, expression2) => {
let set1 = self.evaluate(expression1)?;
if set1.positions().attach(index).next().is_some() {
Ok(set1)
} else {
self.evaluate(expression2)
}
}
ResolvedExpression::Union(expression1, expression2) => {
let set1 = self.evaluate(expression1)?;
let set2 = self.evaluate(expression2)?;
Expand Down
26 changes: 26 additions & 0 deletions lib/src/revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub enum RevsetExpression {
/// Copy of `repo.view().heads()`, should be set by `resolve_symbols()`.
visible_heads: Option<Vec<CommitId>>,
},
Coalesce(Rc<Self>, Rc<Self>),
Present(Rc<Self>),
NotIn(Rc<Self>),
Union(Rc<Self>, Rc<Self>),
Expand Down Expand Up @@ -528,6 +529,7 @@ pub enum ResolvedExpression {
candidates: Box<Self>,
count: usize,
},
Coalesce(Box<Self>, Box<Self>),
Union(Box<Self>, Box<Self>),
/// Intersects `candidates` with `predicate` by filtering.
FilterWithin {
Expand Down Expand Up @@ -853,6 +855,17 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
visible_heads: None,
}))
});
map.insert("coalesce", |diagnostics, function, context| {
let ([], args) = function.expect_some_arguments()?;
let mut revset = RevsetExpression::none();
for arg in args.iter().rev() {
revset = Rc::new(RevsetExpression::Coalesce(
lower_expression(diagnostics, arg, context)?,
revset,
));
}
Ok(revset)
});
map
});

Expand Down Expand Up @@ -1171,6 +1184,12 @@ fn try_transform_expression<E>(
visible_heads: visible_heads.clone(),
}
}),
RevsetExpression::Coalesce(expression1, expression2) => transform_rec_pair(
(expression1, expression2),
pre,
post,
)?
.map(|(expression1, expression2)| RevsetExpression::Coalesce(expression1, expression2)),
RevsetExpression::Present(candidates) => {
transform_rec(candidates, pre, post)?.map(RevsetExpression::Present)
}
Expand Down Expand Up @@ -2050,6 +2069,10 @@ impl VisibilityResolutionContext<'_> {
let context = VisibilityResolutionContext { visible_heads };
context.resolve(candidates)
}
RevsetExpression::Coalesce(expression1, expression2) => ResolvedExpression::Coalesce(
self.resolve(expression1).into(),
self.resolve(expression2).into(),
),
RevsetExpression::Present(_) => {
panic!("Expression '{expression:?}' should have been resolved by caller");
}
Expand Down Expand Up @@ -2130,6 +2153,9 @@ impl VisibilityResolutionContext<'_> {
RevsetExpression::AtOperation { .. } => {
ResolvedPredicateExpression::Set(self.resolve(expression).into())
}
RevsetExpression::Coalesce(_, _) => {
ResolvedPredicateExpression::Set(self.resolve(expression).into())
}
RevsetExpression::Present(_) => {
panic!("Expression '{expression:?}' should have been resolved by caller")
}
Expand Down
78 changes: 78 additions & 0 deletions lib/tests/test_revset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3029,6 +3029,84 @@ fn test_evaluate_expression_at_operation() {
);
}

#[test]
fn test_evaluate_expression_coalesce() {
let settings = testutils::user_settings();
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(&settings);
let mut_repo = tx.repo_mut();
let mut graph_builder = CommitGraphBuilder::new(&settings, mut_repo);
let commit1 = graph_builder.initial_commit();
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
mut_repo.set_local_bookmark_target("commit1", RefTarget::normal(commit1.id().clone()));
mut_repo.set_local_bookmark_target("commit2", 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()]
);
// Should resolve invalid symbols regardless of whether a specific revset is
// evaluated.
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 settings = testutils::user_settings();
Expand Down

0 comments on commit 1a8f649

Please sign in to comment.