revset: add a connected() function

This introduces a `connected(x)` function, which is simply the same as
`x:x`. It's occasionally useful if `x` is a long expression. It's also
useful as a building block for `root(x)` (coming soon).
This commit is contained in:
Martin von Zweigbergk 2022-04-13 21:26:21 -07:00 committed by Martin von Zweigbergk
parent fa6b14f166
commit 9ff21d8924
4 changed files with 110 additions and 2 deletions

View file

@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`ui.editor` config. There is also a new `$JJ_EDITOR` environment variable,
which has even higher priority than the config.
* The new revset function `connected(x)` is the same as `x:x`.
### Fixed bugs
* When rebasing a conflict where one side modified a file and the other side

View file

@ -85,6 +85,7 @@ revsets (expressions) as arguments.
* `children(x)`: Same as `x+`.
* `ancestors(x)`: Same as `:x`.
* `descendants(x)`: Same as `x:`.
* `connected(x)`: Same as `x:x`.
* `all()`: All visible commits in the repo.
* `none()`: No commits. This function is rarely useful; it is provided for
completeness.

View file

@ -330,6 +330,12 @@ impl RevsetExpression {
})
}
/// Connects any ancestors and descendants in the set by adding the commits
/// between them.
pub fn connected(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
self.dag_range_to(self)
}
/// Commits reachable from `heads` but not from `self`.
pub fn range(
self: &Rc<RevsetExpression>,
@ -600,6 +606,18 @@ fn parse_function_expression(
})
}
}
"connected" => {
if arg_count == 1 {
let candidates =
parse_expression_rule(argument_pairs.next().unwrap().into_inner())?;
Ok(candidates.connected())
} else {
Err(RevsetParseError::InvalidFunctionArguments {
name,
message: "Expected 1 argument".to_string(),
})
}
}
"none" => {
if arg_count == 0 {
Ok(RevsetExpression::none())
@ -1351,6 +1369,13 @@ mod tests {
heads: checkout_symbol.clone(),
})
);
assert_eq!(
foo_symbol.connected(),
Rc::new(RevsetExpression::DagRange {
roots: foo_symbol.clone(),
heads: foo_symbol.clone(),
})
);
assert_eq!(
foo_symbol.range(&checkout_symbol),
Rc::new(RevsetExpression::Range {

View file

@ -765,7 +765,7 @@ fn test_evaluate_expression_dag_range(use_git: bool) {
mut_repo.as_repo_ref(),
&format!("{}:{}", root_commit_id.hex(), commit2.id().hex())
),
vec![commit2.id().clone(), commit1.id().clone(), root_commit_id,]
vec![commit2.id().clone(), commit1.id().clone(), root_commit_id]
);
// Empty range
@ -792,7 +792,7 @@ fn test_evaluate_expression_dag_range(use_git: bool) {
]
);
// Including a merge, but only ancestors only from one side
// Including a merge, but ancestors only from one side
assert_eq!(
resolve_commit_ids(
mut_repo.as_repo_ref(),
@ -806,6 +806,86 @@ fn test_evaluate_expression_dag_range(use_git: bool) {
);
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_evaluate_expression_connected(use_git: bool) {
let settings = testutils::user_settings();
let test_repo = testutils::init_repo(&settings, use_git);
let repo = &test_repo.repo;
let root_commit_id = repo.store().root_commit_id().clone();
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.commit_with_parents(&[&commit1]);
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
let commit4 = graph_builder.commit_with_parents(&[&commit1]);
let commit5 = graph_builder.commit_with_parents(&[&commit3, &commit4]);
// Connecting an empty set yields an empty set
assert_eq!(
resolve_commit_ids(mut_repo.as_repo_ref(), "connected(none())"),
vec![]
);
// Can connect just the root commit
assert_eq!(
resolve_commit_ids(mut_repo.as_repo_ref(), "connected(root)"),
vec![root_commit_id.clone()]
);
// Can connect linearly
assert_eq!(
resolve_commit_ids(
mut_repo.as_repo_ref(),
&format!(
"connected({} | {})",
root_commit_id.hex(),
commit2.id().hex()
)
),
vec![commit2.id().clone(), commit1.id().clone(), root_commit_id]
);
// Siblings don't get connected
assert_eq!(
resolve_commit_ids(
mut_repo.as_repo_ref(),
&format!("connected({} | {})", commit2.id().hex(), commit4.id().hex())
),
vec![commit4.id().clone(), commit2.id().clone()]
);
// Including a merge
assert_eq!(
resolve_commit_ids(
mut_repo.as_repo_ref(),
&format!("connected({} | {})", commit1.id().hex(), commit5.id().hex())
),
vec![
commit5.id().clone(),
commit4.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
commit1.id().clone(),
]
);
// Including a merge, but ancestors only from one side
assert_eq!(
resolve_commit_ids(
mut_repo.as_repo_ref(),
&format!("connected({} | {})", commit2.id().hex(), commit5.id().hex())
),
vec![
commit5.id().clone(),
commit3.id().clone(),
commit2.id().clone(),
]
);
}
#[test_case(false ; "local backend")]
#[test_case(true ; "git backend")]
fn test_evaluate_expression_descendants(use_git: bool) {