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. |