OXYGENHUB

Design: revsets (refs #65) 8ad9dfe16303

Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-03 20:10 UTC
Signature
✔ signed by Andy McCall
Hash
8ad9dfe163037131b832947cf9da6e2aff8d84587974504901c6139a98aa27d8
Depends on
ff0af1ee299d
Depended on by
bf3a67819b59 543bbf64d127 2c9ddac3c387 47ce8b8e2d4b
In bookmarks
agents-in-readme bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
In tags
v0.10.0 v0.11.0 v0.4.0 v0.4.1 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
Edits
1 atom, 1 tree operation, 1 file

Browse the tree at this change

added docs/revset_query_language.md +148 −0
1 +
# Oxygen (`oxy`): Revsets Specification
2 +
3 +
| | |
4 +
|---|---|
5 +
| **Status** | Draft 1.1 |
6 +
| **Date** | 2026-10-03 |
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 +
**Today** no command takes `-r`. Commands take a single change (a
28 +
ChangeId prefix, a `#hash` prefix or `@`) or a bookmark or tag name.
29 +
Everything here is new, except that symbols resolve exactly as they do
30 +
today.
31 +
32 +
## 2. Syntax
33 +
34 +
```sh
35 +
oxy log -r 'main..feature & author(andy)'
36 +
oxy log -r 'touches("src/**") & date(-7d..)'
37 +
```
38 +
39 +
### 2.1 Symbols
40 +
41 +
Symbols resolve in today's order: bookmark names, then tag names, then
42 +
change prefixes.
43 +
44 +
| Symbol | Means |
45 +
|---|---|
46 +
| `main`, `v0.3.1` | the heads of that bookmark or tag |
47 +
| `origin/main` | the heads of a remote-tracking bookmark |
48 +
| `8c5ea12a` | the change with that ChangeId prefix |
49 +
| `#fc68459c` | the change with that hash prefix |
50 +
| `@` | the working copy's base heads |
51 +
| `all()`, `none()` | every stored change, no change |
52 +
53 +
### 2.2 Relations
54 +
55 +
Each function takes a set and returns a set.
56 +
57 +
| Expression | Returns |
58 +
|---|---|
59 +
| `deps(X)` | the immediate dependencies of X |
60 +
| `dependents(X)` | changes that immediately depend on X |
61 +
| `::X` | X and its whole dependency closure, which is the *state* X names |
62 +
| `X::` | X and everything that transitively depends on it |
63 +
| `X::Y` | changes that are both in `X::` and in `::Y` |
64 +
| `X..Y` | `::Y ~ ::X`: what state Y has that state X lacks. This is what a proposal from X to Y adds. |
65 +
| `heads(X)`, `roots(X)` | members of X that no other member depends on, and members that depend on no other member |
66 +
| `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. |
67 +
| `latest(X, n)` | the n most recent members of X, by hybrid logical clock |
68 +
69 +
### 2.3 Content and metadata
70 +
71 +
| Expression | Selects |
72 +
|---|---|
73 +
| `author(P)` | changes whose `author` identity matches name, email or KeyId P |
74 +
| `description(T)` | changes whose message contains T (`re:` for a regular expression) |
75 +
| `date(RANGE)` | changes by HLC wall time: `2026-10-01..`, `-7d..`, `..2026-09-30` |
76 +
| `kind(K)` | `normal`, `resolution` or `working-copy` |
77 +
| `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. |
78 +
| `conflicts(S)` | changes that contributed atoms to a conflict in state S (default `@`). Conflicts belong to states, not changes. |
79 +
80 +
### 2.4 Trust: unique to Oxygen
81 +
82 +
| Expression | Selects |
83 +
|---|---|
84 +
| `signed_by(K)` | changes whose signature or seal is by key or identity K, including a delegate such as an agent key |
85 +
| `delegated()` | changes signed by a delegate rather than their author |
86 +
| `unsigned()` | changes with no valid signature or seal |
87 +
| `revoked()` | changes signed by a key now revoked |
88 +
| `attested(KIND)` | changes in states carrying an attestation of that kind, such as `tag` or `receipt` |
89 +
| `built(JOB)` | changes in states with a valid receipt for Flow job JOB |
90 +
91 +
For example, `release..main & (unsigned() | revoked())` asks: "is
92 +
anything about to ship without a trustworthy signature?"
93 +
94 +
### 2.5 Operators and precedence
95 +
96 +
From tightest to loosest:
97 +
98 +
1. `::` and `..`
99 +
2. `~` (difference)
100 +
3. `&` (intersection)
101 +
4. `|` (union)
102 +
103 +
Parentheses group. Strings with spaces or operators are quoted.
104 +
`~` is difference, as in Jujutsu. There is no unary not: write `all() ~ X`.
105 +
106 +
## 3. Where revsets are used
107 +
108 +
- **Commands that show history** take `-r`: `log`, `show` (several
109 +
  changes), `verify`, and `diff -r` (the combined edit of a set).
110 +
- **Commands that act on changes** take `-r` too: `abandon`, the agent
111 +
  workflow's `fold`, and `push -r` (push only these heads). Evaluating
112 +
  the revset never changes anything. A command that acts prints the
113 +
  selected set and refuses to act if it is empty. Above a threshold it
114 +
  asks first, or proceeds with `--yes`.
115 +
- **The hub API** takes `?r=` on history endpoints, evaluated only over
116 +
  what the caller can read.
117 +
- **Saved revsets:** `oxy config revset.mine 'author(me) & ::@'` lets
118 +
  you write `mine()`.
119 +
120 +
## 4. Evaluation
121 +
122 +
- **Read-only.** Evaluation never mutates the graph or any reference.
123 +
- **On the state index.** Relations use the dependency index that
124 +
  `oxy log` already uses. Unions and intersections stream. Difference,
125 +
  `heads` and `roots` need their operands' sets, bounded by the
126 +
  closure being walked.
127 +
- **Checkpoints** stand for a state's bulk without visiting each change,
128 +
  so a closure walk stops at the newest checkpoint it reaches.
129 +
- **Budgets.** Evaluation has a time and size budget, and a query that
130 +
  exceeds it fails with a message saying what to narrow. The hub
131 +
  enforces a stricter one, so a query cannot be used to load the
132 +
  server.
133 +
- **Targets.** A first page of `oxy log -r` for a simple revset should
134 +
  appear in under 100 ms, in line with the existing log target.
135 +
  `touches()` and the trust predicates are slower and are cached by
136 +
  state hash, since a state never changes.
137 +
138 +
## 5. Profiles (§22)
139 +
140 +
- **Full and Lite** evaluate locally, within their memory targets.
141 +
- **Thin clients** (68000-class, 1 MiB RAM) hold no history. A Thin
142 +
  client sends the revset text to its trusted Full-profile proxy, which
143 +
  evaluates it against its graph and returns a page of ChangeIds and
144 +
  metadata.
145 +
  - The proxy applies the same budget as the hub.
146 +
  - Over **ZCT (§15.3) the query and its results travel in plaintext**,
147 +
    like everything else on that transport. ZCT stays off by default,
148 +
    and the client warns on every use.