forked from mirrors/jj
revset: make head()
accept candidate set to find heads within
With this change, you can do e.g. `heads(remote_branches())`. That should currently be the same as `public_heads()`, except that we don't yet remove public heads when remote branches have been updated. Having this support should be generally useful, but I may use it in the short term specifically for depending less on the public heads, until I get around to keeping them up to date.
This commit is contained in:
parent
aebdd4e8fd
commit
d8c0282873
2 changed files with 89 additions and 2 deletions
|
@ -196,6 +196,7 @@ pub enum RevsetExpression {
|
|||
heads: Rc<RevsetExpression>,
|
||||
},
|
||||
Heads,
|
||||
HeadsOf(Rc<RevsetExpression>),
|
||||
PublicHeads,
|
||||
Branches,
|
||||
RemoteBranches,
|
||||
|
@ -259,6 +260,13 @@ impl RevsetExpression {
|
|||
Rc::new(RevsetExpression::GitRefs)
|
||||
}
|
||||
|
||||
/// Commits in `self` that don't have descendants in `self`.
|
||||
// TODO: Perhaps this should be renamed to just `heads()` and the current
|
||||
// `heads()` should become `visible_heads()` or `current_heads()`.
|
||||
pub fn heads_of(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
|
||||
Rc::new(RevsetExpression::HeadsOf(self.clone()))
|
||||
}
|
||||
|
||||
/// Parents of `self`.
|
||||
pub fn parents(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
|
||||
Rc::new(RevsetExpression::Parents(self.clone()))
|
||||
|
@ -274,7 +282,7 @@ impl RevsetExpression {
|
|||
Rc::new(RevsetExpression::Children(self.clone()))
|
||||
}
|
||||
|
||||
/// Descendants of `self`.
|
||||
/// Descendants of `self`, including `self`.
|
||||
pub fn descendants(self: &Rc<RevsetExpression>) -> Rc<RevsetExpression> {
|
||||
self.dag_range_to(&RevsetExpression::heads())
|
||||
}
|
||||
|
@ -575,10 +583,14 @@ fn parse_function_expression(
|
|||
"heads" => {
|
||||
if arg_count == 0 {
|
||||
Ok(RevsetExpression::heads())
|
||||
} else if arg_count == 1 {
|
||||
let candidates =
|
||||
parse_expression_rule(argument_pairs.next().unwrap().into_inner())?;
|
||||
Ok(candidates.heads_of())
|
||||
} else {
|
||||
Err(RevsetParseError::InvalidFunctionArguments {
|
||||
name,
|
||||
message: "Expected 0 arguments".to_string(),
|
||||
message: "Expected 0 or 1 arguments".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1089,6 +1101,14 @@ pub fn evaluate_expression<'repo>(
|
|||
repo,
|
||||
&repo.view().heads().iter().cloned().collect_vec(),
|
||||
)),
|
||||
RevsetExpression::HeadsOf(candidates) => {
|
||||
let candidate_set = candidates.evaluate(repo)?;
|
||||
let candidate_ids = candidate_set.iter().commit_ids().collect_vec();
|
||||
Ok(revset_for_commit_ids(
|
||||
repo,
|
||||
&repo.index().heads(&candidate_ids),
|
||||
))
|
||||
}
|
||||
RevsetExpression::ParentCount {
|
||||
candidates,
|
||||
parent_count_range,
|
||||
|
@ -1210,6 +1230,10 @@ mod tests {
|
|||
checkout_symbol,
|
||||
Rc::new(RevsetExpression::Symbol("@".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
checkout_symbol.heads_of(),
|
||||
Rc::new(RevsetExpression::HeadsOf(checkout_symbol.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
checkout_symbol.parents(),
|
||||
Rc::new(RevsetExpression::Parents(checkout_symbol.clone()))
|
||||
|
|
|
@ -419,6 +419,69 @@ fn test_evaluate_expression_root_and_checkout(use_git: bool) {
|
|||
tx.discard();
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_evaluate_expression_heads_of(use_git: bool) {
|
||||
let settings = testutils::user_settings();
|
||||
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
||||
|
||||
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]);
|
||||
|
||||
// Heads of an empty set is an empty set
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "heads(none())"),
|
||||
vec![]
|
||||
);
|
||||
|
||||
// Heads of the root is the root
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "heads(root)"),
|
||||
vec![root_commit.id().clone()]
|
||||
);
|
||||
|
||||
// Heads of a single commit is that commit
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("heads({})", commit2.id().hex())
|
||||
),
|
||||
vec![commit2.id().clone()]
|
||||
);
|
||||
|
||||
// Heads of a parent and a child is the child
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("heads({} | {})", commit2.id().hex(), commit3.id().hex())
|
||||
),
|
||||
vec![commit3.id().clone()]
|
||||
);
|
||||
|
||||
// Heads of a grandparent and a grandchild is the grandchild (unlike Mercurial's
|
||||
// heads() revset, which would include both)
|
||||
assert_eq!(
|
||||
resolve_commit_ids(
|
||||
mut_repo.as_repo_ref(),
|
||||
&format!("heads({} | {})", commit1.id().hex(), commit3.id().hex())
|
||||
),
|
||||
vec![commit3.id().clone()]
|
||||
);
|
||||
|
||||
// Heads of all commits is the set of heads in the repo
|
||||
assert_eq!(
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "heads(all())"),
|
||||
resolve_commit_ids(mut_repo.as_repo_ref(), "heads()")
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_evaluate_expression_parents(use_git: bool) {
|
||||
|
|
Loading…
Reference in a new issue