diff --git a/CHANGELOG.md b/CHANGELOG.md index 85192a89dd..7548cc62e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### New features +* The `ancestors()` revset function now takes an optional `depth` argument + to limit the depth of the ancestor set. For example, use `jj log -r + 'ancestors(@, 5)` to view the last 5 commits. + ### Fixed bugs ## [0.9.0] - 2023-09-06 diff --git a/docs/revsets.md b/docs/revsets.md index b6ee6f97fd..c8101552c0 100644 --- a/docs/revsets.md +++ b/docs/revsets.md @@ -79,7 +79,9 @@ revsets (expressions) as arguments. * `parents(x)`: Same as `x-`. * `children(x)`: Same as `x+`. -* `ancestors(x)`: Same as `:x`. +* `ancestors(x[, depth])`: `ancestors(x)` is the same as `::x`. + `ancestors(x, depth)` returns the ancestors of `x` limited to the given + `depth`. * `descendants(x)`: Same as `x::`. * `connected(x)`: Same as `x::x`. Useful when `x` includes several commits. * `all()`: All visible commits in the repo. diff --git a/lib/src/revset.rs b/lib/src/revset.rs index abc78d8e32..aa899d52bc 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -524,11 +524,7 @@ impl RevsetExpression { /// Ancestors of `self`, including `self`. pub fn ancestors(self: &Rc) -> Rc { - Rc::new(RevsetExpression::Ancestors { - heads: self.clone(), - generation: GENERATION_RANGE_FULL, - is_legacy: false, - }) + self.ancestors_range(GENERATION_RANGE_FULL) } fn legacy_ancestors(self: &Rc) -> Rc { Rc::new(RevsetExpression::Ancestors { @@ -540,9 +536,17 @@ impl RevsetExpression { /// Ancestors of `self`, including `self` until `generation` back. pub fn ancestors_at(self: &Rc, generation: u64) -> Rc { + self.ancestors_range(generation..(generation + 1)) + } + + /// Ancestors of `self` in the given range. + pub fn ancestors_range( + self: &Rc, + generation_range: Range, + ) -> Rc { Rc::new(RevsetExpression::Ancestors { heads: self.clone(), - generation: generation..(generation + 1), + generation: generation_range, is_legacy: false, }) } @@ -1167,9 +1171,15 @@ static BUILTIN_FUNCTION_MAP: Lazy> = Lazy: Ok(expression.children()) }); map.insert("ancestors", |name, arguments_pair, state| { - let arg = expect_one_argument(name, arguments_pair)?; - let expression = parse_expression_rule(arg.into_inner(), state)?; - Ok(expression.ancestors()) + let ([heads_arg], [depth_opt_arg]) = expect_arguments(name, arguments_pair)?; + let heads = parse_expression_rule(heads_arg.into_inner(), state)?; + let generation = if let Some(depth_arg) = depth_opt_arg { + let depth = parse_function_argument_as_literal("integer", name, depth_arg, state)?; + 0..depth + } else { + GENERATION_RANGE_FULL + }; + Ok(heads.ancestors_range(generation)) }); map.insert("descendants", |name, arguments_pair, state| { let arg = expect_one_argument(name, arguments_pair)?; diff --git a/lib/tests/test_revset.rs b/lib/tests/test_revset.rs index e6696f1ae7..1dea99f10d 100644 --- a/lib/tests/test_revset.rs +++ b/lib/tests/test_revset.rs @@ -1205,6 +1205,24 @@ fn test_evaluate_expression_ancestors(use_git: bool) { root_commit.id().clone(), ] ); + + // Can find last n ancestors of a commit + assert_eq!( + resolve_commit_ids(mut_repo, &format!("ancestors({}, 0)", commit2.id().hex())), + vec![] + ); + assert_eq!( + resolve_commit_ids(mut_repo, &format!("ancestors({}, 1)", commit3.id().hex())), + vec![commit3.id().clone()] + ); + assert_eq!( + resolve_commit_ids(mut_repo, &format!("ancestors({}, 3)", commit3.id().hex())), + vec![ + commit3.id().clone(), + commit2.id().clone(), + commit1.id().clone(), + ] + ); } #[test_case(false ; "local backend")]