# 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 REVSET` with the symbols, relations, operators, `author`, `description` (substring), `date`, `kind`, `touches` and `latest` below, saved revsets, and a time budget. Not yet: `independent()`, `conflicts()`, `description(re:…)`, the trust predicates of §2.4, `-r` on other commands, and the hub's `?r=`. Other commands still 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 ```sh 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 | | `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 `@`). 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, 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, such as `tag` or `receipt` | | `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: 1. `::` and `..` 2. `~` (difference) 3. `&` (intersection) 4. `|` (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`, and `diff -r` (the combined edit of a set). - **Commands that act on changes** take `-r` too: `abandon`, the agent workflow's `fold`, and `push -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 it asks first, or proceeds with `--yes`. - **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 write `mine()`. `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 log` already uses. Unions and intersections stream. Difference, `heads` and `roots` need 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_budget` takes 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 in `X & p()` the predicate is evaluated over `X` alone, so `main.. & touches("src/**")` reads only the changes beyond `main`. - **Targets.** A first page of `oxy log -r` for 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.