Skip to content

Commit 8db40c7

Browse files
committed
revset: add change_id/commit_id(prefix) predicates
Basically, these functions work in the same way as bookmarks()/tags(). They restrict the namespace to search the specified symbol, and unmatched symbol isn't an error. One major difference is that ambiguous prefix triggers an error. That's because ambiguous prefix should logically select all matching entries, whereas the underlying functions don't provide this behavior. It's also unclear whether we would want to get all matching commits by commit_id(prefix:''). #5632
1 parent 16d5092 commit 8db40c7

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4040
`depth` argument. For instance, `parents(x, 3)` is equivalent to `x---`, and
4141
`children(x, 3)` is equivalent to `x+++`.
4242

43+
* New `change_id(prefix)`/`commit_id(prefix)` revset functions to explicitly
44+
query commits by change/commit ID prefix.
45+
4346
### Fixed bugs
4447

4548
* `jj file annotate` can now process files at a hidden revision.

cli/tests/test_revset_output.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,34 @@ fn test_bad_function_call() {
172172
[exit status: 1]
173173
");
174174

175+
let output = work_dir.run_jj(["log", "-r", "change_id(glob:a)"]);
176+
insta::assert_snapshot!(output, @r"
177+
------- stderr -------
178+
Error: Failed to parse revset: Expected change ID prefix
179+
Caused by: --> 1:11
180+
|
181+
1 | change_id(glob:a)
182+
| ^----^
183+
|
184+
= Expected change ID prefix
185+
[EOF]
186+
[exit status: 1]
187+
");
188+
189+
let output = work_dir.run_jj(["log", "-r", "commit_id(xyzzy)"]);
190+
insta::assert_snapshot!(output, @r"
191+
------- stderr -------
192+
Error: Failed to parse revset: Invalid commit ID prefix
193+
Caused by: --> 1:11
194+
|
195+
1 | commit_id(xyzzy)
196+
| ^---^
197+
|
198+
= Invalid commit ID prefix
199+
[EOF]
200+
[exit status: 1]
201+
");
202+
175203
let output = work_dir.run_jj(["log", "-r", "files(not::a-fileset)"]);
176204
insta::assert_snapshot!(output, @r"
177205
------- stderr -------

docs/revsets.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ revsets (expressions) as arguments.
211211
* `none()`: No commits. This function is rarely useful; it is provided for
212212
completeness.
213213

214+
* `change_id(prefix)`: Commits with the given change ID prefix. It is an error
215+
to use a non-unique prefix. Unmatched prefix isn't an error.
216+
217+
* `commit_id(prefix)`: Commits with the given commit ID prefix. It is an error
218+
to use a non-unique prefix. Unmatched prefix isn't an error.
219+
214220
* `bookmarks([pattern])`: All local bookmark targets. If `pattern` is specified,
215221
this selects the bookmarks whose name match the given [string
216222
pattern](#string-patterns). For example, `bookmarks(push)` would match the

lib/src/revset.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ pub enum RevsetCommitRef {
134134
WorkingCopies,
135135
Symbol(String),
136136
RemoteSymbol(RemoteRefSymbolBuf),
137+
ChangeId(HexPrefix),
138+
CommitId(HexPrefix),
137139
Bookmarks(StringPattern),
138140
RemoteBookmarks {
139141
bookmark_pattern: StringPattern,
@@ -366,6 +368,16 @@ impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
366368
Rc::new(Self::CommitRef(commit_ref))
367369
}
368370

371+
pub fn change_id_prefix(prefix: HexPrefix) -> Rc<Self> {
372+
let commit_ref = RevsetCommitRef::ChangeId(prefix);
373+
Rc::new(Self::CommitRef(commit_ref))
374+
}
375+
376+
pub fn commit_id_prefix(prefix: HexPrefix) -> Rc<Self> {
377+
let commit_ref = RevsetCommitRef::CommitId(prefix);
378+
Rc::new(Self::CommitRef(commit_ref))
379+
}
380+
369381
pub fn bookmarks(pattern: StringPattern) -> Rc<Self> {
370382
Rc::new(Self::CommitRef(RevsetCommitRef::Bookmarks(pattern)))
371383
}
@@ -764,6 +776,24 @@ static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy:
764776
function.expect_no_arguments()?;
765777
Ok(RevsetExpression::root())
766778
});
779+
map.insert("change_id", |diagnostics, function, _context| {
780+
let [arg] = function.expect_exact_arguments()?;
781+
let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
782+
let value = revset_parser::expect_string_literal("change ID prefix", arg)?;
783+
HexPrefix::try_from_reverse_hex(value)
784+
.ok_or_else(|| RevsetParseError::expression("Invalid change ID prefix", arg.span))
785+
})?;
786+
Ok(RevsetExpression::change_id_prefix(prefix))
787+
});
788+
map.insert("commit_id", |diagnostics, function, _context| {
789+
let [arg] = function.expect_exact_arguments()?;
790+
let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
791+
let value = revset_parser::expect_string_literal("commit ID prefix", arg)?;
792+
HexPrefix::new(value)
793+
.ok_or_else(|| RevsetParseError::expression("Invalid commit ID prefix", arg.span))
794+
})?;
795+
Ok(RevsetExpression::commit_id_prefix(prefix))
796+
});
767797
map.insert("bookmarks", |diagnostics, function, _context| {
768798
let ([], [opt_arg]) = function.expect_arguments()?;
769799
let pattern = if let Some(arg) = opt_arg {
@@ -2533,6 +2563,14 @@ fn resolve_commit_ref(
25332563
let wc_commits = repo.view().wc_commit_ids().values().cloned().collect_vec();
25342564
Ok(wc_commits)
25352565
}
2566+
RevsetCommitRef::ChangeId(prefix) => {
2567+
let resolver = &symbol_resolver.change_id_resolver;
2568+
Ok(resolver.try_resolve(repo, prefix)?.unwrap_or_else(Vec::new))
2569+
}
2570+
RevsetCommitRef::CommitId(prefix) => {
2571+
let resolver = &symbol_resolver.commit_id_resolver;
2572+
Ok(resolver.try_resolve(repo, prefix)?.into_iter().collect())
2573+
}
25362574
RevsetCommitRef::Bookmarks(pattern) => {
25372575
let commit_ids = repo
25382576
.view()
@@ -3720,6 +3758,32 @@ mod tests {
37203758
insta::assert_debug_snapshot!(parse("signed()").unwrap(), @"Filter(Signed)");
37213759
}
37223760

3761+
#[test]
3762+
fn test_parse_revset_change_commit_id_functions() {
3763+
let settings = insta_settings();
3764+
let _guard = settings.bind_to_scope();
3765+
3766+
insta::assert_debug_snapshot!(
3767+
parse("change_id(z)").unwrap(),
3768+
@r#"CommitRef(ChangeId(HexPrefix("0")))"#);
3769+
insta::assert_debug_snapshot!(
3770+
parse("change_id('zk')").unwrap(),
3771+
@r#"CommitRef(ChangeId(HexPrefix("0f")))"#);
3772+
insta::assert_debug_snapshot!(
3773+
parse("change_id(01234)").unwrap_err().kind(),
3774+
@r#"Expression("Invalid change ID prefix")"#);
3775+
3776+
insta::assert_debug_snapshot!(
3777+
parse("commit_id(0)").unwrap(),
3778+
@r#"CommitRef(CommitId(HexPrefix("0")))"#);
3779+
insta::assert_debug_snapshot!(
3780+
parse("commit_id('0f')").unwrap(),
3781+
@r#"CommitRef(CommitId(HexPrefix("0f")))"#);
3782+
insta::assert_debug_snapshot!(
3783+
parse("commit_id(xyzzy)").unwrap_err().kind(),
3784+
@r#"Expression("Invalid commit ID prefix")"#);
3785+
}
3786+
37233787
#[test]
37243788
fn test_parse_revset_author_committer_functions() {
37253789
let settings = insta_settings();

lib/tests/test_revset.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,32 @@ fn test_resolve_symbol_commit_id() {
224224
resolve_commit_ids(repo.as_ref(), "present(017)"),
225225
vec![commits[2].id().clone()]
226226
);
227+
228+
// Test commit_id() function, which is roughly equivalent to present(id)
229+
assert_eq!(
230+
resolve_symbol(
231+
repo.as_ref(),
232+
"commit_id(019f179b4479a4f3d1373b772866037929e4f63c)",
233+
)
234+
.unwrap(),
235+
vec![commits[0].id().clone()]
236+
);
237+
assert_eq!(
238+
resolve_symbol(repo.as_ref(), "commit_id(019f1)").unwrap(),
239+
vec![commits[0].id().clone()]
240+
);
241+
assert_eq!(
242+
resolve_symbol(repo.as_ref(), "commit_id(12345)").unwrap(),
243+
vec![]
244+
);
245+
assert_matches!(
246+
resolve_symbol(repo.as_ref(), "commit_id('')"),
247+
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s.is_empty()
248+
);
249+
assert_matches!(
250+
resolve_symbol(repo.as_ref(), "commit_id(0)"),
251+
Err(RevsetResolutionError::AmbiguousCommitIdPrefix(s)) if s == "0"
252+
);
227253
}
228254

229255
#[test_case(false ; "mutable")]
@@ -347,6 +373,25 @@ fn test_resolve_symbol_change_id(readonly: bool) {
347373
candidates
348374
}) if name == "foo" && candidates.is_empty()
349375
);
376+
377+
// Test change_id() function, which is roughly equivalent to present(id)
378+
assert_eq!(
379+
resolve_symbol(repo, "change_id(zvlyxpuvtsoopsqzlkorrpqrszrqvlnx)").unwrap(),
380+
vec![commits[0].id().clone()]
381+
);
382+
assert_eq!(
383+
resolve_symbol(repo, "change_id(zvlyx)").unwrap(),
384+
vec![commits[0].id().clone()]
385+
);
386+
assert_eq!(resolve_symbol(repo, "change_id(xyzzy)").unwrap(), vec![]);
387+
assert_matches!(
388+
resolve_symbol(repo, "change_id('')"),
389+
Err(RevsetResolutionError::AmbiguousChangeIdPrefix(s)) if s.is_empty()
390+
);
391+
assert_matches!(
392+
resolve_symbol(repo, "change_id(z)"),
393+
Err(RevsetResolutionError::AmbiguousChangeIdPrefix(s)) if s == "z"
394+
);
350395
}
351396

352397
#[test]

0 commit comments

Comments
 (0)