Jump to…
snowinitial commitqoxwzsukwmkx1mo

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.