ok/jj
1
0
Fork 0
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:
Martin von Zweigbergk 2021-10-10 12:30:00 -07:00
parent aebdd4e8fd
commit d8c0282873
2 changed files with 89 additions and 2 deletions

View file

@ -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()))

View file

@ -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) {