Oxygen (oxy): Revsets Specification
| Status | Draft 1.2: the core is implemented (oxy log -r), trust predicates and -r on other commands are next |
| Date | 2026-10-04 |
| Audience | Implementers and tooling developers |
| Depends on | spec.md §6 (changes), §8 (dependencies), §9 (conflicts), §10 (states), §11 (checkpoints), §14 (identity, attestations), §22 (profiles) |
1. Overview
A revset is an expression that selects a set of changes. Mercurial and Jujutsu users know the idea, so Oxygen keeps the name and much of the syntax. People should not have to learn a new language to ask familiar questions.
Oxygen's model makes revsets more natural than in Git:
- History is a set of changes, and a state is itself a set: the dependency closure of its heads. Revsets are set algebra over the dependency graph.
- Changes keep their identity across rebases and transport, so a saved revset means the same thing tomorrow.
Oxygen also has things no other system can query: signatures, delegates, attestations and build receipts. Section 2.4 adds those.
Implemented (2026-10-04, oxygen#65): oxy log -r, oxy show -r,
oxy verify -r and oxy abandon -r with the symbols, relations,
operators, author, description (substring), date, kind,
touches, latest, independent, conflicts and the trust predicates
of §2.4 below, saved revsets, and a time budget. Not yet:
description(re:…), diff -r, push -r, fold, and the hub's ?r=.
Other commands take a single change (a ChangeId prefix, a #hash
prefix or @) or a bookmark or tag name; symbols in a revset resolve
exactly as they do there.
2. Syntax
oxy log -r 'main..feature & author(andy)'
oxy log -r 'touches("src/**") & date(-7d..)'
2.1 Symbols
Symbols resolve in today's order: bookmark names, then tag names, then change prefixes.
| Symbol | Means |
|---|---|
main, v0.3.1 | the heads of that bookmark or tag |
bookmark(main), bookmark(origin/main) | the heads of that bookmark only; an error if there is none (a bare name falls through to tags and change prefixes) |
origin/main | the heads of a remote-tracking bookmark |
8c5ea12a | the change with that ChangeId prefix |
#fc68459c | the change with that hash prefix |
@ | the working copy's base heads |
all(), none() | every stored change, no change |
2.2 Relations
Each function takes a set and returns a set.
| Expression | Returns |
|---|---|
deps(X) | the immediate dependencies of X |
dependents(X) | changes that immediately depend on X |
::X | X and its whole dependency closure, which is the state X names |
X:: | X and everything that transitively depends on it |
X::Y | changes that are both in X:: and in ::Y |
X..Y | ::Y ~ ::X: what state Y has that state X lacks. This is what a proposal from X to Y adds. |
heads(X), roots(X) | members of X that no other member depends on, and members that depend on no other member |
independent(X, IN) | changes in IN that neither depend on X nor are depended on by X: what could ship without X. Only Oxygen can answer this, because changes commute. |
latest(X, n) | the n most recent members of X, by hybrid logical clock |
2.3 Content and metadata
| Expression | Selects |
|---|---|
author(P) | changes whose author identity matches name, email or KeyId P |
description(T) | changes whose message contains T (re: for a regular expression) |
date(RANGE) | changes by HLC wall time: 2026-10-01.., -7d.., ..2026-09-30 |
kind(K) | normal, resolution or working-copy |
touches(GLOB) | changes that edit any node whose path matches GLOB in the evaluation state. Nodes keep their identity through renames, so this follows a file's whole history under any name. |
conflicts(S) | changes that contributed atoms to a conflict in state S (default: the working copy's base). Conflicts belong to states, not changes. |
2.4 Trust: unique to Oxygen
| Expression | Selects |
|---|---|
signed_by(K) | changes whose signature or seal is by key or identity K (a key prefix, a name, an email, or me), including a delegate such as an agent key |
delegated() | changes signed by a delegate rather than their author |
unsigned() | changes with no valid signature or seal |
revoked() | changes signed by a key now revoked |
attested(KIND) | changes in states carrying an attestation of that kind: tag, receipt, or ref (states a remote attested, as its tracking bookmarks hold them) |
built(JOB) | changes in states with a valid receipt for Flow job JOB |
For example, release..main & (unsigned() | revoked()) asks: "is
anything about to ship without a trustworthy signature?"
2.5 Operators and precedence
From tightest to loosest:
::and..~(difference)&(intersection)|(union)
Parentheses group. Strings with spaces or operators are quoted.
~ is difference, as in Jujutsu. There is no unary not: write all() ~ X.
..Y is ::Y, and X.. is everything the repository has beyond X.
A date() argument is taken as written, so date(-7d..) needs no quotes.
3. Where revsets are used
- Commands that show history take
-r:log,show(several changes),verify, anddiff -r(the combined edit of a set). - Commands that act on changes take
-rtoo:abandon, the agent workflow'sfold, andpush -r(push only these heads). Evaluating the revset never changes anything. A command that acts prints the selected set and refuses to act if it is empty. Above a threshold (five, forabandon) it refuses unless given--yes.abandon -rdrops the newest first and finds each later member again by its ChangeId, since dropping a change rewrites what depended on it. - The hub API takes
?r=on history endpoints, evaluated only over what the caller can read. - Saved revsets:
oxy config revset.mine 'author(me) & ::@'lets you writemine().author(me)is the configured identity.
4. Evaluation
- Read-only. Evaluation never mutates the graph or any reference.
- On the state index. Relations use the dependency index that
oxy logalready uses. Unions and intersections stream. Difference,headsandrootsneed their operands' sets, bounded by the closure being walked. - Checkpoints stand for a state's bulk without visiting each change, so a closure walk stops at the newest checkpoint it reaches.
- Budgets. Evaluation has a time budget (10 s from the command
line;
Repo::revset_with_budgettakes another), and a query that exceeds it fails with a message saying what to narrow. The hub enforces a stricter one, so a query cannot be used to load the server. - Predicates range over
all()(every change a bookmark, remote bookmark, tag or the working copy reaches), except that inX & p()the predicate is evaluated overXalone, somain.. & touches("src/**")reads only the changes beyondmain. - Targets. A first page of
oxy log -rfor a simple revset should appear in under 100 ms, in line with the existing log target.touches()and the trust predicates are slower and are cached by state hash, since a state never changes.
5. Profiles (§22)
- Full and Lite evaluate locally, within their memory targets.
- Thin clients (68000-class, 1 MiB RAM) hold no history. A Thin
client sends the revset text to its trusted Full-profile proxy, which
evaluates it against its graph and returns a page of ChangeIds and
metadata.
- The proxy applies the same budget as the hub.
- Over ZCT (§15.3) the query and its results travel in plaintext, like everything else on that transport. ZCT stays off by default, and the client warns on every use.