mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-24 12:48:55 +00:00
revset: add a roots()
function
This commit is contained in:
parent
9ff21d8924
commit
7e79f25508
4 changed files with 105 additions and 1 deletions
|
@ -28,6 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
* The new revset function `connected(x)` is the same as `x:x`.
|
||||
|
||||
* The new revset function `roots(x)` finds commits in the set that are not
|
||||
descendants of other commits in the set.
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
* When rebasing a conflict where one side modified a file and the other side
|
||||
|
|
|
@ -101,6 +101,7 @@ revsets (expressions) as arguments.
|
|||
* `heads([x])`: Commits in `x` that are not ancestors of other commits in `x`.
|
||||
If `x` was not specified, it selects all visible heads (as if you had said
|
||||
`heads(all())`).
|
||||
* `roots(x)`: Commits in `x` that are not descendants of other commits in `x`.
|
||||
* `merges([x])`: Merge commits within `x`. If `x` was not specified, it selects
|
||||
all visible merge commits (as if you had said `merges(all())`).
|
||||
* `description(needle[, x])`: Commits with the given string in their
|
||||
|
|
|
@ -213,8 +213,9 @@ pub enum RevsetExpression {
|
|||
roots: Rc<RevsetExpression>,
|
||||
heads: Rc<RevsetExpression>,
|
||||
},
|
||||
VisibleHeads,
|
||||
Heads(Rc<RevsetExpression>),
|
||||
Roots(Rc<RevsetExpression>),
|
||||
VisibleHeads,
|
||||
PublicHeads,
|
||||
Branches,
|
||||
RemoteBranches,
|
||||
|
@ -298,6 +299,11 @@ impl RevsetExpression {
|
|||
Rc::new(RevsetExpression::Heads(self.clone()))
|
||||
}
|
||||
|
||||
/// Commits in `self` that don't have ancestors in `self`.
|
||||
pub fn roots(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
|
||||
Rc::new(RevsetExpression::Roots(self.clone()))
|
||||
}
|
||||
|
||||
/// Parents of `self`.
|
||||
pub fn parents(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
|
||||
Rc::new(RevsetExpression::Parents(self.clone()))
|
||||
|
@ -652,6 +658,18 @@ fn parse_function_expression(
|
|||
})
|
||||
}
|
||||
}
|
||||
"roots" => {
|
||||
if arg_count == 1 {
|
||||
let candidates =
|
||||
parse_expression_rule(argument_pairs.next().unwrap().into_inner())?;
|
||||
Ok(candidates.roots())
|
||||
} else {
|
||||
Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name,
|
||||
message: "Expected 1 argument".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
"public_heads" => {
|
||||
if arg_count == 0 {
|
||||
Ok(RevsetExpression::public_heads())
|
||||
|
@ -1185,6 +1203,22 @@ pub fn evaluate_expression<'repo>(
|
|||
&repo.index().heads(&candidate_ids),
|
||||
))
|
||||
}
|
||||
RevsetExpression::Roots(candidates) => {
|
||||
let connected_set = candidates.connected().evaluate(repo, workspace_id)?;
|
||||
let filled: HashSet<_> = connected_set.iter().map(|entry| entry.position()).collect();
|
||||
let mut index_entries = vec![];
|
||||
let candidate_set = candidates.evaluate(repo, workspace_id)?;
|
||||
for candidate in candidate_set.iter() {
|
||||
if !candidate
|
||||
.parent_positions()
|
||||
.iter()
|
||||
.any(|parent| filled.contains(parent))
|
||||
{
|
||||
index_entries.push(candidate);
|
||||
}
|
||||
}
|
||||
Ok(Box::new(EagerRevset { index_entries }))
|
||||
}
|
||||
RevsetExpression::ParentCount {
|
||||
candidates,
|
||||
parent_count_range,
|
||||
|
@ -1343,6 +1377,10 @@ mod tests {
|
|||
checkout_symbol.heads(),
|
||||
Rc::new(RevsetExpression::Heads(checkout_symbol.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
checkout_symbol.roots(),
|
||||
Rc::new(RevsetExpression::Roots(checkout_symbol.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
checkout_symbol.parents(),
|
||||
Rc::new(RevsetExpression::Parents(checkout_symbol.clone()))
|
||||
|
|
|
@ -522,6 +522,68 @@ fn test_evaluate_expression_heads(use_git: bool) {
|
|||
);
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_evaluate_expression_roots(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 = repo.store().root_commit();
|
||||
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]);
|
||||
|
||||
// Roots of an empty set is an empty set
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "roots(none())"),
|
||||
vec![]
|
||||
);
|
||||
|
||||
// Roots of the root is the root
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "roots(root)"),
|
||||
vec![root_commit.id().clone()]
|
||||
);
|
||||
|
||||
// Roots of a single commit is that commit
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("roots({})", commit2.id().hex())
|
||||
),
|
||||
vec![commit2.id().clone()]
|
||||
);
|
||||
|
||||
// Roots of a parent and a child is the parent
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("roots({} | {})", commit2.id().hex(), commit3.id().hex())
|
||||
),
|
||||
vec![commit2.id().clone()]
|
||||
);
|
||||
|
||||
// Roots of a grandparent and a grandchild is the grandparent (unlike
|
||||
// Mercurial's roots() revset, which would include both)
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("roots({} | {})", commit1.id().hex(), commit3.id().hex())
|
||||
),
|
||||
vec![commit1.id().clone()]
|
||||
);
|
||||
|
||||
// Roots of all commits is the root commit
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "roots(all())"),
|
||||
vec![root_commit.id().clone()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_evaluate_expression_parents(use_git: bool) {
|
||||
|
|
Loading…
Reference in a new issue