Revset semantics — notes for the §8 subset
Spec §8 requires a deliberately small revset subset (author(), description(),
conflicted(), bookmarks(), open(), plus &, |, ~) parsed into SQL, and warns:
Do not attempt to implement jj's full revset language against a Git-backed index; it will not be faithful, and a subtly wrong revset is worse than an honestly limited one.
These are behaviours verified against jj 0.43.0 that a naive SQL translation gets wrong.
description() is an EXACT match, not a substring match
This is the trap. Verified directly:
$ jj log -r 'description("merge base")' # commit described exactly "merge base"
(empty result)
$ jj log -r 'description(substring:"base")'
mmnulkozlxru merge base
$ jj log -r 'subject("merge base")'
mmnulkozlxru merge base
The obvious implementation — WHERE description ILIKE '%' || $1 || '%' — is wrong, and
wrong in the most damaging direction: it silently returns more than the CLI would, so a
reviewer filtering a change list sees results their terminal does not. That is precisely the
"subtly wrong revset" the spec says is worse than no revset.
Correct mapping for the v1 subset:
| revset | SQL |
|---|---|
description("x") |
description = 'x' — exact, including jj's trailing newline handling |
description(substring:"x") |
description ILIKE '%x%' |
subject("x") |
exact match against the first line of the description |
If only one form is implemented in v1, implement description(substring:), because it is the
one users actually want — but then do not accept the bare description("x") form at all.
Reject it with the "unsupported expression" message rather than aliasing it to substring.
Accepting it with different semantics than the CLI is the failure mode to avoid.
Note also that jj descriptions carry a trailing newline. Normalise on ingest (store without it) so exact matching does not depend on invisible whitespace.
Prefix matching
Ambiguity handling matters. jj resolves the shortest unique prefix and errors on ambiguity;
spec §7 requires Dogfood render a disambiguation page rather than guess. The index has
changes_prefix_idx (repo_id, change_id text_pattern_ops) for this — use
change_id LIKE $1 || '%' and count results before choosing a page.
Functions deliberately NOT in the v1 subset
heads(), roots(), ancestors(), descendants(), ::, .., latest(), merges(),
file(), diff_contains() — all require graph traversal or content search that the SQL index
cannot answer faithfully. The parser must reject these by name with a clear message naming the
unsupported function, not fail to parse with a generic syntax error.
mine() / author()
author() in jj matches against the author email and name as a substring. Dogfood stores
revisions.author_email and author_name; match against both, and be explicit in the UI that
this is a substring match so it is not confused with description()'s exact semantics.