| 1 |
# Oxygen (`oxy`): Revsets Specification |
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
| **Status** | Draft 1.2: the core is implemented (`oxy log -r`), trust predicates and `-r` on other commands are next | |
| 6 |
| **Date** | 2026-10-04 | |
| 7 |
| **Audience** | Implementers and tooling developers | |
| 8 |
| **Depends on** | spec.md §6 (changes), §8 (dependencies), §9 (conflicts), §10 (states), §11 (checkpoints), §14 (identity, attestations), §22 (profiles) | |
| 9 |
|
| 10 |
## 1. Overview |
| 11 |
|
| 12 |
A **revset** is an expression that selects a set of changes. Mercurial |
| 13 |
and Jujutsu users know the idea, so Oxygen keeps the name and much of |
| 14 |
the syntax. People should not have to learn a new language to ask |
| 15 |
familiar questions. |
| 16 |
|
| 17 |
Oxygen's model makes revsets more natural than in Git: |
| 18 |
- History is a set of changes, and a state is itself a set: the |
| 19 |
dependency closure of its heads. Revsets are set algebra over the |
| 20 |
dependency graph. |
| 21 |
- Changes keep their identity across rebases and transport, so a saved |
| 22 |
revset means the same thing tomorrow. |
| 23 |
|
| 24 |
Oxygen also has things no other system can query: signatures, delegates, |
| 25 |
attestations and build receipts. Section 2.4 adds those. |
| 26 |
|
| 27 |
**Implemented (2026-10-04, oxygen#65):** `oxy log -r REVSET` with the |
| 28 |
symbols, relations, operators, `author`, `description` (substring), |
| 29 |
`date`, `kind`, `touches` and `latest` below, saved revsets, and a |
| 30 |
time budget. Not yet: `independent()`, `conflicts()`, `description(re:…)`, |
| 31 |
the trust predicates of §2.4, `-r` on other commands, and the hub's |
| 32 |
`?r=`. Other commands still take a single change (a ChangeId prefix, a |
| 33 |
`#hash` prefix or `@`) or a bookmark or tag name; symbols in a revset |
| 34 |
resolve exactly as they do there. |
| 35 |
|
| 36 |
## 2. Syntax |
| 37 |
|
| 38 |
```sh |
| 39 |
oxy log -r 'main..feature & author(andy)' |
| 40 |
oxy log -r 'touches("src/**") & date(-7d..)' |
| 41 |
``` |
| 42 |
|
| 43 |
### 2.1 Symbols |
| 44 |
|
| 45 |
Symbols resolve in today's order: bookmark names, then tag names, then |
| 46 |
change prefixes. |
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
| `main`, `v0.3.1` | the heads of that bookmark or tag | |
| 51 |
| `origin/main` | the heads of a remote-tracking bookmark | |
| 52 |
| `8c5ea12a` | the change with that ChangeId prefix | |
| 53 |
| `#fc68459c` | the change with that hash prefix | |
| 54 |
| `@` | the working copy's base heads | |
| 55 |
| `all()`, `none()` | every stored change, no change | |
| 56 |
|
| 57 |
### 2.2 Relations |
| 58 |
|
| 59 |
Each function takes a set and returns a set. |
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
| `deps(X)` | the immediate dependencies of X | |
| 64 |
| `dependents(X)` | changes that immediately depend on X | |
| 65 |
| `::X` | X and its whole dependency closure, which is the *state* X names | |
| 66 |
| `X::` | X and everything that transitively depends on it | |
| 67 |
| `X::Y` | changes that are both in `X::` and in `::Y` | |
| 68 |
| `X..Y` | `::Y ~ ::X`: what state Y has that state X lacks. This is what a proposal from X to Y adds. | |
| 69 |
| `heads(X)`, `roots(X)` | members of X that no other member depends on, and members that depend on no other member | |
| 70 |
| `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. | |
| 71 |
| `latest(X, n)` | the n most recent members of X, by hybrid logical clock | |
| 72 |
|
| 73 |
### 2.3 Content and metadata |
| 74 |
|
| 75 |
|
| 76 |
|
| 77 |
| `author(P)` | changes whose `author` identity matches name, email or KeyId P | |
| 78 |
| `description(T)` | changes whose message contains T (`re:` for a regular expression) | |
| 79 |
| `date(RANGE)` | changes by HLC wall time: `2026-10-01..`, `-7d..`, `..2026-09-30` | |
| 80 |
| `kind(K)` | `normal`, `resolution` or `working-copy` | |
| 81 |
| `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. | |
| 82 |
| `conflicts(S)` | changes that contributed atoms to a conflict in state S (default `@`). Conflicts belong to states, not changes. | |
| 83 |
|
| 84 |
### 2.4 Trust: unique to Oxygen |
| 85 |
|
| 86 |
|
| 87 |
|
| 88 |
| `signed_by(K)` | changes whose signature or seal is by key or identity K, including a delegate such as an agent key | |
| 89 |
| `delegated()` | changes signed by a delegate rather than their author | |
| 90 |
| `unsigned()` | changes with no valid signature or seal | |
| 91 |
| `revoked()` | changes signed by a key now revoked | |
| 92 |
| `attested(KIND)` | changes in states carrying an attestation of that kind, such as `tag` or `receipt` | |
| 93 |
| `built(JOB)` | changes in states with a valid receipt for Flow job JOB | |
| 94 |
|
| 95 |
For example, `release..main & (unsigned() | revoked())` asks: "is |
| 96 |
anything about to ship without a trustworthy signature?" |
| 97 |
|
| 98 |
### 2.5 Operators and precedence |
| 99 |
|
| 100 |
From tightest to loosest: |
| 101 |
|
| 102 |
1. `::` and `..` |
| 103 |
2. `~` (difference) |
| 104 |
3. `&` (intersection) |
| 105 |
4. `|` (union) |
| 106 |
|
| 107 |
Parentheses group. Strings with spaces or operators are quoted. |
| 108 |
`~` is difference, as in Jujutsu. There is no unary not: write `all() ~ X`. |
| 109 |
`..Y` is `::Y`, and `X..` is everything the repository has beyond `X`. |
| 110 |
A `date()` argument is taken as written, so `date(-7d..)` needs no quotes. |
| 111 |
|
| 112 |
## 3. Where revsets are used |
| 113 |
|
| 114 |
- **Commands that show history** take `-r`: `log`, `show` (several |
| 115 |
changes), `verify`, and `diff -r` (the combined edit of a set). |
| 116 |
- **Commands that act on changes** take `-r` too: `abandon`, the agent |
| 117 |
workflow's `fold`, and `push -r` (push only these heads). Evaluating |
| 118 |
the revset never changes anything. A command that acts prints the |
| 119 |
selected set and refuses to act if it is empty. Above a threshold it |
| 120 |
asks first, or proceeds with `--yes`. |
| 121 |
- **The hub API** takes `?r=` on history endpoints, evaluated only over |
| 122 |
what the caller can read. |
| 123 |
- **Saved revsets:** `oxy config revset.mine 'author(me) & ::@'` lets |
| 124 |
you write `mine()`. `author(me)` is the configured identity. |
| 125 |
|
| 126 |
## 4. Evaluation |
| 127 |
|
| 128 |
- **Read-only.** Evaluation never mutates the graph or any reference. |
| 129 |
- **On the state index.** Relations use the dependency index that |
| 130 |
`oxy log` already uses. Unions and intersections stream. Difference, |
| 131 |
`heads` and `roots` need their operands' sets, bounded by the |
| 132 |
closure being walked. |
| 133 |
- **Checkpoints** stand for a state's bulk without visiting each change, |
| 134 |
so a closure walk stops at the newest checkpoint it reaches. |
| 135 |
- **Budgets.** Evaluation has a time budget (10 s from the command |
| 136 |
line; `Repo::revset_with_budget` takes another), and a query that |
| 137 |
exceeds it fails with a message saying what to narrow. The hub |
| 138 |
enforces a stricter one, so a query cannot be used to load the |
| 139 |
server. |
| 140 |
- **Predicates range over `all()`** (every change a bookmark, remote |
| 141 |
bookmark, tag or the working copy reaches), except that in `X & p()` |
| 142 |
the predicate is evaluated over `X` alone, so `main.. & touches("src/**")` |
| 143 |
reads only the changes beyond `main`. |
| 144 |
- **Targets.** A first page of `oxy log -r` for a simple revset should |
| 145 |
appear in under 100 ms, in line with the existing log target. |
| 146 |
`touches()` and the trust predicates are slower and are cached by |
| 147 |
state hash, since a state never changes. |
| 148 |
|
| 149 |
## 5. Profiles (§22) |
| 150 |
|
| 151 |
- **Full and Lite** evaluate locally, within their memory targets. |
| 152 |
- **Thin clients** (68000-class, 1 MiB RAM) hold no history. A Thin |
| 153 |
client sends the revset text to its trusted Full-profile proxy, which |
| 154 |
evaluates it against its graph and returns a page of ChangeIds and |
| 155 |
metadata. |
| 156 |
- The proxy applies the same budget as the hub. |
| 157 |
- Over **ZCT (§15.3) the query and its results travel in plaintext**, |
| 158 |
like everything else on that transport. ZCT stays off by default, |
| 159 |
and the client warns on every use. |