mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-12 07:14:38 +00:00
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:
parent
fa6b14f166
commit
9ff21d8924
4 changed files with 110 additions and 2 deletions
|
@ -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,
|
`ui.editor` config. There is also a new `$JJ_EDITOR` environment variable,
|
||||||
which has even higher priority than the config.
|
which has even higher priority than the config.
|
||||||
|
|
||||||
|
* The new revset function `connected(x)` is the same as `x:x`.
|
||||||
|
|
||||||
### Fixed bugs
|
### Fixed bugs
|
||||||
|
|
||||||
* When rebasing a conflict where one side modified a file and the other side
|
* When rebasing a conflict where one side modified a file and the other side
|
||||||
|
|
|
@ -85,6 +85,7 @@ revsets (expressions) as arguments.
|
||||||
* `children(x)`: Same as `x+`.
|
* `children(x)`: Same as `x+`.
|
||||||
* `ancestors(x)`: Same as `:x`.
|
* `ancestors(x)`: Same as `:x`.
|
||||||
* `descendants(x)`: Same as `x:`.
|
* `descendants(x)`: Same as `x:`.
|
||||||
|
* `connected(x)`: Same as `x:x`.
|
||||||
* `all()`: All visible commits in the repo.
|
* `all()`: All visible commits in the repo.
|
||||||
* `none()`: No commits. This function is rarely useful; it is provided for
|
* `none()`: No commits. This function is rarely useful; it is provided for
|
||||||
completeness.
|
completeness.
|
||||||
|
|
|
@ -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`.
|
/// Commits reachable from `heads` but not from `self`.
|
||||||
pub fn range(
|
pub fn range(
|
||||||
self: &Rc<RevsetExpression>,
|
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" => {
|
"none" => {
|
||||||
if arg_count == 0 {
|
if arg_count == 0 {
|
||||||
Ok(RevsetExpression::none())
|
Ok(RevsetExpression::none())
|
||||||
|
@ -1351,6 +1369,13 @@ mod tests {
|
||||||
heads: checkout_symbol.clone(),
|
heads: checkout_symbol.clone(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
foo_symbol.connected(),
|
||||||
|
Rc::new(RevsetExpression::DagRange {
|
||||||
|
roots: foo_symbol.clone(),
|
||||||
|
heads: foo_symbol.clone(),
|
||||||
|
})
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
foo_symbol.range(&checkout_symbol),
|
foo_symbol.range(&checkout_symbol),
|
||||||
Rc::new(RevsetExpression::Range {
|
Rc::new(RevsetExpression::Range {
|
||||||
|
|
|
@ -765,7 +765,7 @@ fn test_evaluate_expression_dag_range(use_git: bool) {
|
||||||
mut_repo.as_repo_ref(),
|
mut_repo.as_repo_ref(),
|
||||||
&format!("{}:{}", root_commit_id.hex(), commit2.id().hex())
|
&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
|
// 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!(
|
assert_eq!(
|
||||||
resolve_commit_ids(
|
resolve_commit_ids(
|
||||||
mut_repo.as_repo_ref(),
|
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(false ; "local backend")]
|
||||||
#[test_case(true ; "git backend")]
|
#[test_case(true ; "git backend")]
|
||||||
fn test_evaluate_expression_descendants(use_git: bool) {
|
fn test_evaluate_expression_descendants(use_git: bool) {
|
||||||
|
|
Loading…
Reference in a new issue