OXYGENHUB
◆ v0.7.0 oxygen / docs / guide.md
33.7 KiB · Markdown · last changed oxy resolve PATH settles a replace conflict with the working copy's content (closes #140) 10 days ago renderedsource blame history raw
ee06a6c5 Andy McCall ↶ 1
# Oxygen user guide
2
3
This guide is organised by task. It assumes the `oxy` binary is on your
4
path (`cargo build --release` puts it in `target/release/`). The design
5
behind every command is in [spec.md]spec.md; this guide only tells you
6
what to type and what to expect.
7
8
Oxygen tracks *changes* rather than snapshots: a change is the set of
9
edits you made, it commutes with unrelated changes, and conflicts are
10
recorded in history instead of blocking you. Two ideas recur below:
11
12
- A **bookmark** (default `main`) names a *state*: the set of changes it
13
  contains. A state can have several **heads** when independent changes
14
  have not been related to each other; that is normal, not a problem.
15
- The **working copy** is itself a change that Oxygen rewrites every time
16
  it looks at your files. `oxy commit` turns it into a real change.
17
18
Output uses colour and glyphs on a terminal; pipe it, pass `--plain`, or set
19
`NO_COLOR` for plain ASCII.
20
21
## 1. Set yourself up
22
23
```
24
oxy identity create --name "Ada Lovelace" --email [email protected]
25
oxy config --user core.sign change
26
```
27
28
`identity create` generates a composite Ed25519 + ML-DSA-65 key pair, keeps
29
the secret in `~/.config/oxy/keys/`, and records you as the default author
30
in `~/.config/oxy/config.toml`. With `core.sign = change` every commit is
31
signed; without it, changes are sealed in batches when you push (also
32
authenticated, just later). Use `--local` to record the identity in one
33
repository only.
34
35
`oxy identity show` prints the key, its expiry, delegates, and whether the
36
secret is available. `oxy identity export` writes the public identity to a
37
file to hand to whoever manages a trust policy (section 8).
38
39
## 2. Start a repository
40
41
```
42
mkdir project && cd project
43
oxy init
44
oxy status
45
```
46
9ac1fbe2 Andy McCall ↶ 47
`oxy init` creates `.oxy/`. Add `.oxyignore` (gitignore syntax, per
48
directory, negations with `!`) for files that should never be tracked;
49
`/target` and `*.tmp` are typical entries. As in Git, ignore rules only
50
affect untracked files: a file already in history stays tracked until you
d4dbab97 Andy McCall ↶ 51
delete it. A project's `.gitignore` files are honoured too, so moving a Git
52
project to Oxygen does not commit its build output; `.oxyignore` comes
53
first, and a `!` rule there brings back what `.gitignore` leaves out.
ee06a6c5 Andy McCall ↶ 54
55
To take over an existing Git project, see section 12.
56
57
## 3. Everyday work
58
59
```
60
oxy status                 # what changed since the last commit
61
oxy diff                   # the edits, file by file
62
oxy commit -m "Add retry loop"
63
oxy log                    # newest first; -n 10, -v for full messages
47ce8b8e Andy McCall ↶ 64
oxy log -r 'main.. & touches("src/**")'   # a revset (docs/revset_query_language.md)
ee06a6c5 Andy McCall ↶ 65
oxy show 658990aa          # one change by ChangeId prefix, #hash prefix, or @
66
```
67
2bf22305 Andy McCall ↶ 68
`oxy log` draws the history as a graph, newest first, two lines per change:
69
70
```
71
◉  2f8518c0 Andy McCall just now
33472ccc Andy McCall ↶ 72
│  (working copy)
2bf22305 Andy McCall ↶ 73
●  1b5888b1 Andy McCall 2 minutes ago main #cc23b999 ✔ signed
74
│  Edit b
75
│ ●  d6f52eca Andy McCall 3 minutes ago main #ec6a7dc4 ✔ signed
76
├─╯  Edit a
77
○  c0593116 Andy McCall 5 minutes ago #eeac7940 ✔ signed
78
   Both files
79
```
80
81
The bright start of each ID is the shortest prefix that names it. `●` marks
82
a head, `◉` the working copy. Bookmarks and tags show beside their heads,
83
unless they have more than three. A lane follows the change each one most
84
directly builds on, its newest dependency. Two edits that build on the same
85
change take two lanes, which join above it, so independent work shows side
86
by side. `~` means the change builds on something further down than the
87
graph draws, or not shown; `-v` lists every dependency.
88
957fee30 Andy McCall ↶ 89
Commit part of what changed with `oxy commit PATH.. -m "..."` (only those
90
paths; the rest stays as uncommitted work) or `oxy commit -i -m "..."`, which
91
offers each hunk in turn like `git add -p`; a hunk that another chosen hunk
92
needs, such as the directory a new file lives in, comes along by itself.
93
50a046e4 Andy McCall ↶ 94
`status --json` prints the same for tools, with every conflict's kind, path and
95
contributing ChangeIds. On a terminal, `status` lists each path with a mark: `✚ added`, `✎ modified`, `✖ removed`,
ee06a6c5 Andy McCall ↶ 96
`➜ renamed`, `⚙ mode`. Renames are detected: a removed file and an added
97
file of the same kind whose contents are at least half alike are recorded
98
as a move of the same file, so identity survives a rename.
99
100
Files containing NUL bytes are stored whole ("opaque"); text files are
101
stored line by line and merge line by line. `.oxyattributes` overrides the
102
guess (section 14).
103
104
Every change has two names: a **ChangeId** (`658990aa…`) that survives
105
rewrites, and a content hash (`#7469d16c…`) that changes when the change is
106
edited. Use whichever you have; prefixes work.
107
fbda43e7 Andy McCall ↶ 108
## 3a. Bookmarks: branches without merges
109
110
A bookmark names a state; each workspace works on one (`main` to begin
111
with). Because changes commute, "merging" is the union of two bookmarks'
112
heads, with no merge change and any real conflict recorded as in section 6.
113
114
```
115
oxy bookmark create feature        # at the working copy's base
116
oxy bookmark switch feature        # check it out; commits now go here
117
oxy commit -m "Feature work"
118
oxy bookmark switch main
119
oxy merge feature                  # main now holds both; working copy follows if clean
120
oxy bookmark list                  # ● current, heads, ↑n/↓n against origin
bf3a6781 Andy McCall ↶ 121
oxy bookmark show feature          # each head, newest first (list -v: every bookmark's)
fbda43e7 Andy McCall ↶ 122
oxy push -b feature                # publish a bookmark other than the current one
4afab467 Andy McCall ↶ 123
oxy bookmark set feature 658990aa  # move it (onto work built on it: kept heads stay); delete drops it
fbda43e7 Andy McCall ↶ 124
```
125
126
`oxy checkout feature` materialises a bookmark without switching to it, and
127
a bookmark name works wherever a change is expected when it has one head.
bf3a6781 Andy McCall ↶ 128
In a revset it means the bookmark's heads (`oxy log -r feature`);
129
`bookmark(feature)` means only the bookmark, never a tag or change prefix.
fbda43e7 Andy McCall ↶ 130
Each workspace remembers its own bookmark, so two directories can sit on
131
two bookmarks of one repository.
132
4fe7d604 Andy McCall ↶ 133
**A state is its heads, not a history.** If you are coming from Git, this
134
is the one thing to know. A bookmark holds its heads and what they depend
135
on, and a change depends only on the changes it builds on (the files it
136
touched), not on everything recorded before it. So on a bookmark with
137
several heads, a new bookmark at one change can be a much older-looking
138
tree. `oxy bookmark create NAME CHANGE` refuses when that would leave out
139
earlier work and lists what it would lack. Then pick one:
140
141
```
142
oxy bookmark create logo 745307b4 --upto  # everything here up to that change, as a Git branch would be
143
oxy bookmark create logo 745307b4 --only  # just that change and what it depends on
144
oxy bookmark create logo                  # the whole current state, usually what you want
145
```
146
06d16dc2 Andy McCall ↶ 147
`oxy bookmark set NAME CHANGE` asks the same when the move would lose
148
changes the bookmark holds that were recorded before CHANGE, and takes
149
`--upto` and `--only` too. After merging a proposal's target into its
150
bookmark to resolve conflicts, `oxy bookmark set NAME CHANGE --upto`
151
keeps everything the bookmark held.
152
b9bf7eec Andy McCall ↶ 153
`--only` is exact, and that can surprise: other changes to the same files
154
are left out, so a file can come out partly edited (a pipeline file that
155
no longer parses, say). To put one change on top of a bookmark's work,
156
give that bookmark's heads with it, as `oxy bookmark show main` prints
157
them: `oxy bookmark set fix <main's heads> 745307b4`.
158
4fe7d604 Andy McCall ↶ 159
`oxy bookmark switch` says when the bookmark you switch to lacks changes
160
that the one you left has.
161
14edbaaf Andy McCall ↶ 162
**A remote's bookmarks.** A clone fetches every change, checks out the
163
remote's default bookmark, and names the others `origin/NAME`: use them
164
anywhere a bookmark is expected (`oxy log origin/develop`,
165
`oxy diff main origin/develop`). `oxy bookmark list --all` shows them.
166
`oxy bookmark switch develop` makes a local `develop` from `origin/develop`
167
when there is none, and `oxy pull` then keeps every local bookmark up to
168
date with the remote's of the same name, not only the current one. Pulling
72484195 Andy McCall ↶ 169
is a union, so this never loses work. A local bookmark that is only behind
170
its remote (`-3` in `bookmark list`) is caught up when you switch to it.
14edbaaf Andy McCall ↶ 171
172
```
173
oxy clone https://oxygenhub.io/owner/game --recursive   # subrepos too
174
oxy bookmark list --all
175
oxy bookmark switch develop        # from origin/develop
176
oxy pull                           # updates develop, main and the rest
177
```
178
314db9b4 Andy McCall ↶ 179
### A develop / feature flow
180
181
Names with `/` work everywhere, pushing included:
182
183
```
184
oxy bookmark create feature/42-login          # at the working copy's base
185
oxy bookmark switch feature/42-login --carry  # started already? take the edits along
186
oxy commit -F msg.txt                         # a long message from a file
187
oxy push -b feature/42-login
188
oxy bookmark switch develop
189
oxy merge feature/42-login                    # the working copy follows when it was clean
190
oxy push                                      # develop, with the feature in it
191
oxy bookmark delete feature/42-login          # oxy push --delete feature/42-login on the remote
192
```
193
194
`bookmark switch` stops when the working copy has uncommitted edits, so
195
nothing is lost by accident. There are two ways past it:
196
- `--carry` takes the edits along. It is refused only when the target
197
  bookmark has other versions of the files they touch.
198
- `--discard` drops the edits.
199
200
`oxy describe` names what the working copy holds, for build stamps:
201
- a tag, when a tag holds exactly this state;
202
- otherwise the nearest tag with the distance from it and the change;
203
- or, with no tag behind it, the bookmark and the change;
204
- with `-dirty` added when there are uncommitted edits.
205
ee06a6c5 Andy McCall ↶ 206
## 4. Fix up history
207
208
Oxygen keeps its own record of every operation, so all of these are safe to
209
try and to undo.
210
211
```
212
oxy describe -m "Better message"        # reword the head the working copy sits on
213
oxy describe 658990aa -m "..."          # reword any change; dependents are rewritten
214
oxy amend                               # fold the working copy into the head
215
oxy amend --into 658990aa               # fold it into an older change
216
oxy split 658990aa src/parser.rs        # split a head: those paths become a separate first change
217
oxy abandon 658990aa                    # drop a change; its content stays on disk
728da0f9 Andy McCall ↶ 218
oxy squash 658990aa --into 9df9ac29     # fold a change into one it depends on
219
oxy rebase 658990aa --onto feature      # re-express a change against another base
e266b7d4 Andy McCall ↶ 220
oxy rebase 658990aa --onto develop --into-working-copy   # conflicted? resolve it here, then commit
ee06a6c5 Andy McCall ↶ 221
oxy undo                                # undo the last operation, whatever it was
98a1620a Andy McCall ↶ 222
oxy op log                              # every operation, what it changed, and its command
ee06a6c5 Andy McCall ↶ 223
```
224
98a1620a Andy McCall ↶ 225
### The operation log
226
227
Every command that changes the repository records an operation: the
228
bookmarks, tags and working copy as they were after it. `oxy op log` lists
229
them newest first, each with what it changed and the command that did it;
230
working-copy snapshots fold into one row.
231
232
```
233
@  13adb0d0b8bf just now
234
│  main: 1 → 2 heads; working copy now 91ab4d50
235
│  oxy commit -m "Third"
236
○  fa21727d70da just now
237
│  created bookmark test (1 head)
238
│  oxy bookmark create test
239
○  2d15930afdef … 830168a3ddcd just now
240
│  2 working-copy snapshots
241
```
242
243
Any operation can be looked at, gone back to, or undone alone:
244
245
```
246
oxy --at-op fa21727d log                # the log as it was after that operation; any read-only command works
247
oxy op restore fa21727d                 # bookmarks and working copy as they were then
248
oxy op revert fa21727d                  # undo just that operation, keeping everything after it
249
oxy undo                                # takes back the last command, a restore included
250
```
251
252
`op restore` and `op revert` are operations themselves, so `oxy undo` takes
253
them back. `op revert` puts back each bookmark the operation moved, unless
254
it has moved again since. Tags and the trust policy never go backwards.
255
`undo` brings the files back as well when nothing uncommitted would be
256
lost; an undone commit's edits stay on disk as uncommitted work.
257
258
Commands that move the working copy end by saying where it is:
259
260
```
261
* Committed 95245a21 #35bbf5097e0c  Third
262
  Working copy  91ab4d50
263
  Bookmark      main: 2 heads, newest 95245a21 Third
264
```
265
ee06a6c5 Andy McCall ↶ 266
Rewriting a change that others depend on rewrites them too, keeping their
728da0f9 Andy McCall ↶ 267
ChangeIds. Because unrelated changes already coexist, `rebase` is rarely
268
needed; its job is to change what a change is said to build on, for instance
269
to drop a dependency on something you are about to abandon. Where a dependent referred to a line that no longer exists, the
ee06a6c5 Andy McCall ↶ 270
reference is re-anchored to the nearest surviving line and `oxy abandon`
271
prints a note about it.
272
957fee30 Andy McCall ↶ 273
## 4a. Who wrote this, and what broke it
274
275
```
276
oxy blame src/parser.rs          # every line: the change that introduced it, author, age
277
oxy blame src/parser.rs --json
278
```
279
280
Blame is exact: each line is an atom that remembers the change that
281
introduced it, so renames cost nothing and there is no heuristic.
282
283
```
284
oxy bisect start
285
oxy bisect good v1.2             # a change, bookmark or tag known to be fine
286
oxy bisect bad main              # and one known to be broken
287
oxy bisect run make test         # exit 0 good, 125 skip, anything else bad
288
oxy bisect reset                 # back to where you were
289
```
290
291
Without `run`, each step checks out a state to test and you answer with
292
`oxy bisect good`, `bad`, or `skip`. The candidates are the changes the bad
293
state has that the good one lacks, ordered so every tested state is
294
well-formed; the answer is the first change whose inclusion breaks things.
295
ee06a6c5 Andy McCall ↶ 296
## 5. Move around
297
298
```
299
oxy checkout                    # the bookmark's heads
300
oxy checkout 658990aa           # the state of one change and its dependencies
301
oxy checkout v1.2               # a tag
302
oxy checkout --discard ...      # throw away uncommitted work first
303
```
304
305
Checking out a single older change gives you the files that change and its
306
dependencies touch, not a full snapshot from that date: in Oxygen a change
307
depends only on what it actually builds on.
308
309
## 6. Conflicts
310
311
A conflict never stops a commit, fetch, or pull. It is recorded in the
312
state and shown by `oxy status`, which exits with status 2 while any
313
conflict remains. Conflicted text files are written with markers:
314
315
```
316
<<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 "Add retry loop"
317
... lines ...
318
======= oxy conflict 1 of 2, side 2: change q3x8w1 "Refactor client"
319
... lines ...
320
>>>>>>> oxy conflict 1 of 2 end
321
```
322
323
Edit the file to what it should be (keep one side, both, or neither) and
324
remove the markers. Tree conflicts, such as two files wanting the same
325
name, appear as `<name>.oxy-conflict-<n>` candidates: keep the one you
326
want under the right name and delete the rest.
327
157d02e9 Andy McCall ↶ 328
A binary file or a subrepo pin changed two ways has no markers to edit:
329
the checkout writes one version, and you say which content wins by
330
naming it. Put the bytes you want at the path (or move the subrepo to the
331
pin you want), then:
332
333
```
334
oxy resolve assets/logo.png deps/lib   # these paths' content settles them
335
```
336
ee06a6c5 Andy McCall ↶ 337
```
338
oxy resolve      # which conflicts your edits resolve, which still have markers
339
oxy commit -m "Resolve retry vs refactor"
340
```
341
342
The commit is recorded as a *resolution*, a change that depends on every
343
side of the conflict; anyone who fetches it gets the conflict resolved.
344
345
## 7. Work with others
346
347
Run a server (any machine with the repositories under one directory):
348
349
```
350
oxy serve --root /srv/oxy --bind 0.0.0.0:8890 --token SECRET \
351
          --tls-cert cert.pem --tls-key key.pem
352
```
353
354
Without `--tls-*` the server speaks plain HTTP; keep that on a loopback
355
address or behind a TLS terminator. `--token` makes pushes require a bearer
356
token; reads stay open.
357
358
On your machine:
359
360
```
ff1f62fe Andy McCall ↶ 361
echo SECRET | oxy auth login https://host:8890 --with-token --ca my-ca.pem
ee06a6c5 Andy McCall ↶ 362
oxy clone https://host:8890/project
363
cd project
364
oxy push
365
oxy pull
366
```
367
ff1f62fe Andy McCall ↶ 368
`--ca` is only for a server with a self-signed certificate. On a hub such as
369
OxygenHub, `oxy auth login https://oxygenhub.io` signs in through the
370
browser instead: it shows a code to approve on the hub, and the hub issues a
371
token that lasts 90 days past its last use.
372
373
### Signing in
374
375
`oxy auth login HUB` keeps one token per hub (scheme, host and port) in
376
`~/.config/oxy/credentials.toml`, readable by you only, and every repository
377
on that hub uses it: clone, fetch, push and subrepos alike. Nothing secret is
378
written into a checkout. `oxy auth status` lists the hubs you are signed in
379
to and checks each token with its hub; `oxy auth logout HUB` forgets the token
380
and revokes it on the hub.
381
382
To keep tokens in the system keyring instead, name a Git credential helper:
383
`oxy config --user auth.helper libsecret` (Linux), `osxkeychain` (macOS) or
384
`manager` (Windows). The file then keeps only the account and CA.
385
386
A token is found in this order: the remote's own `remote.<name>.token`, then
387
`$OXY_TOKEN` (for CI), then the store, then a remote on the same hub in an
68a92484 Andy McCall ↶ 388
enclosing checkout (so a subrepo uses its parent's). When the token came
389
from the store or an enclosing checkout and the hub refuses it (a stale login,
390
or another account's), oxy tries the other tokens enclosing checkouts hold for
391
that hub before giving up, and says so once; a remote's own token and
392
`$OXY_TOKEN` are never swapped for another. oxy sends a token over
ff1f62fe Andy McCall ↶ 393
`https` only, or over plain `http` to this machine; it is never sent to
394
another host after a redirect, and a credentials file that others can read
395
is ignored.
396
ee06a6c5 Andy McCall ↶ 397
- `oxy fetch` updates `origin/main` without touching your bookmark.
14edbaaf Andy McCall ↶ 398
- `oxy pull` fetches, then makes your bookmark, and every other local
399
  bookmark the remote also has, the union of both sides.
ee06a6c5 Andy McCall ↶ 400
  There is no merge commit; unrelated changes simply coexist, and a real
72484195 Andy McCall ↶ 401
  conflict is recorded as in section 6. The working copy follows, taking
402
  uncommitted work along when the pulled changes leave its files alone;
f6dccc45 Andy McCall ↶ 403
  otherwise it stays and `oxy checkout` updates it. A working copy that had
404
  fallen behind its bookmark catches up on the next pull too, and `oxy
405
  status` says when it is behind.
ee06a6c5 Andy McCall ↶ 406
- `oxy push` sends your changes and moves the remote bookmark. If the remote
407
  moved since you last fetched, the push is refused: pull and push again.
408
  `--force` overrides the compare-and-swap.
409
- `oxy remote add|remove|list` manages remotes; the clone source is `origin`.
42ccf504 Andy McCall ↶ 410
  Commands that take a remote use `origin`, or the only remote there is; with
411
  several and none named `origin`, name one or set `oxy config core.remote
412
  NAME`. `bookmark list` compares each bookmark with a remote that has it,
413
  and names the remote when there is more than one.
ee06a6c5 Andy McCall ↶ 414
415
A repository can also be cloned from a local path, which is handy for tests
416
and for a bare "hub" on a shared disk.
417
418
## 8. Decide who may publish: the trust policy
419
420
Without a policy a repository is *unverified*: everything is accepted and
421
every remote command says so. A policy names root keys and the keys allowed
422
to update each bookmark; servers and clients enforce it.
423
424
```
425
oxy policy init                    # your key becomes the only root; push publishes it
426
oxy policy show
427
oxy policy grant alice.identity    # alice.identity came from her `oxy identity export`
428
oxy policy grant ci.identity --pattern 'release/*'
429
oxy policy revoke 9458b656         # by KeyId prefix
430
oxy verify                         # signature status of every change in the log
431
```
432
433
Several people can hold root keys:
434
435
```
436
oxy policy root add bob.identity
437
oxy policy root threshold 2        # future updates need two root signatures
438
```
439
440
Once the threshold is above one, an update you make alone becomes a
441
*proposal file* (`policy-<serial>.proposal`, or `--out`). Send it to another
442
root holder, who runs `oxy policy sign policy-7.proposal`; whoever completes
443
the threshold applies it, and the next push publishes it. Everyone else
444
picks it up on fetch, verifying each step from the policy they already
445
trust.
446
447
## 9. Keys over time
448
449
```
450
oxy identity rotate                   # fresh key pair; old signatures stay valid
451
oxy identity renew --expires 365      # same keys, new expiry in days (0 = never)
7ba155f2 Andy McCall ↶ 452
oxy identity delegate add agent.identity --expires 8h       # sign changes for me today
453
oxy identity delegate add ci.identity --scope changes,refs  # sign and push for me
ee06a6c5 Andy McCall ↶ 454
oxy identity delegate remove 3f2a…
7ba155f2 Andy McCall ↶ 455
oxy identity revoke 3f2a… --reason "agent host compromised" # a key you delegated to
445ce913 Andy McCall ↶ 456
```
457
458
Setting an agent up takes a home of its own, so it never holds your
459
secret key. In the agent's shell, inside any checkout:
460
461
```
462
export OXY_HOME=~/.oxy-agents/claude
463
oxy identity create --name "Claude Code on my laptop" --email [email protected]
464
#   prints the agent's KeyId; its identity file is keys/<KeyId>.identity in that home
465
oxy config --user author.name  "Your Name"      # the agent authors as you…
466
oxy config --user author.email "[email protected]"
467
oxy config --user author.key    <YOUR KeyId>
468
oxy config --user author.signer <THE AGENT's KeyId>   # …and signs as itself
469
oxy config --user core.sign     change
470
```
471
472
Then, in your own shell, in the repository: `oxy identity delegate add
473
~/.oxy-agents/claude/keys/<KeyId>.identity --scope changes --expires 8h`
614f55e1 Andy McCall ↶ 474
(several files at once are one rotation). This rotates your identity:
475
the new key is written to your user config, and to this repository's
476
file too if it pinned the old one, so other checkouts follow; put the
477
new KeyId in each agent's `author.key`, and re-link it on your hub.
478
`oxy identity show` warns if a repository still pins an older key
479
(`oxy config --unset author.key` there). Every commit the agent makes
445ce913 Andy McCall ↶ 480
carries you as author and its signature: `oxy log -r 'delegated()'`
481
lists them, `oxy verify` names the agent, and its own `oxy push` is
482
refused under any policy, so you land its work.
483
c895bc52 Andy McCall ↶ 484
**Keyless agents** (on an OxygenHub) hold no key at all. You give the
485
agent a grant instead, signed by your key, and the hub certifies a fresh
486
key for it each time it signs:
487
488
```
489
oh agent grant claude --for 30d       # your shell: prints the agent's token, once
490
oxy auth grant < token                # the agent's shell (its own OXY_HOME)
491
oxy config --user author.key    <YOUR KeyId>
492
oxy config --user author.signer keyless
493
```
494
495
Each certificate lasts ten minutes and carries your grant, so the
496
agent's commits count as yours, and a push seals them with the fresh
497
key. A repository with a trust policy also names the agent:
498
`oxy policy grant-subject agent:claude@YOU --issuer hub.identity --scope
499
changes` (add `refs` to let it push), with the hub's identity from
500
`/.well-known/oxyhub-key`. `oh agent list` shows your grants and `oh
501
agent revoke ID` stops one at once; there is no key to rotate.
502
445ce913 Andy McCall ↶ 503
```
ee06a6c5 Andy McCall ↶ 504
oxy identity revoke                   # retire your own key
505
oxy identity revoke 3f2a… --reason "laptop stolen"   # any key, if you hold a root
506
```
507
508
Rotation, renewal, and delegation all issue a successor identity that names
509
the previous one. If a policy names your key, it is updated to the successor
510
in the same step (as a proposal when more root signatures are needed).
511
Changes signed before the rotation remain authorised; a revoked key
512
authorises nothing. `oxy verify` shows signatures by revoked keys as valid
513
but revoked.
514
515
## 10. Tags and workspaces
516
517
```
518
oxy tag create v1.2 -m "First release"   # the current heads; or name changes
519
oxy tag list
520
oxy workspace add ../project-hotfix      # a second checkout sharing the repository
521
oxy workspace list
522
oxy workspace forget ../project-hotfix
523
```
524
525
A tag is an immutable state, signed when you have a key, and travels with
526
push and fetch. Workspaces have their own working copy and bookmark
527
(`workspace.bookmark`) but share objects and history.
528
fc1a466a Andy McCall ↶ 529
### Receipts from CI
530
531
A runner such as OxygenFlow signs a receipt for every job it runs: what ran,
532
in what environment, on which files, and the result. Receipts are kept with
533
the state they ran on.
534
535
```
536
oxy receipt list                         # receipts for the bookmark's state
537
oxy receipt show 3f2a9c                  # one in full, with its signature checked
538
oxy receipt export 3f2a9c build.receipt  # hand it to someone
539
oxy receipt verify build.receipt         # check one without keeping it
540
oxy receipt import build.receipt         # verify and keep it
a39754d0 Andy McCall ↶ 541
oxy receipt export 3f2a9c prov.json --format slsa   # SLSA provenance, signed with your key
542
oxy identity export --pem me.pem         # the key cosign checks it with
fc1a466a Andy McCall ↶ 543
```
544
a39754d0 Andy McCall ↶ 545
The SLSA form is for tools outside Oxygen: in-toto and SLSA verifiers, and
546
`cosign verify-blob-attestation --key me.pem --type slsaprovenance1
5f661562 Andy McCall ↶ 547
--insecure-ignore-tlog=true --check-claims=false --signature prov.json`. Its subjects are named by
a39754d0 Andy McCall ↶ 548
Oxygen hashes, which cosign cannot compute from a file, hence
549
`--check-claims=false`.
550
ee06a6c5 Andy McCall ↶ 551
## 11. Big repositories: partial, sparse, shallow
552
553
```
554
oxy clone URL --max-blob 1M          # files above 1 MiB are fetched on first read
555
oxy clone URL --sparse src --sparse docs   # materialise only these subtrees
556
oxy clone URL --depth 50             # only the last 50 levels of history
557
oxy sparse src docs tests            # change the filter later
558
oxy deepen 100                       # fetch more history below the boundary
559
```
560
561
Lazy blobs and sparse trees do not affect commits: what you do not have is
562
left exactly as it was. A shallow clone starts from a *checkpoint* of the
563
state, so status and commit work at full speed from the first minute.
564
4dee277c Andy McCall ↶ 565
## 11a. Nested repositories (submodules)
566
567
A subrepo is another repository checked out at a path and pinned to a state,
568
Oxygen or Git. The pin is versioned like a file, so two people pinning
569
different states get an ordinary conflict.
570
571
```
572
oxy submodule add https://host/lib.git libs/lib     # Git or Oxygen URL; records .oxymodules
573
oxy commit -m "Add lib"                              # the pin is committed with the parent
574
oxy submodule status                                 # pin vs what is on disk
575
oxy submodule update --init                          # after a clone: fetch nested repositories
576
oxy submodule remove libs/lib                        # drop the node; the directory stays
577
```
578
579
Committing the parent records whatever the nested repository is at, and
580
checking out an older parent state moves the nested repository back, unless
581
it has uncommitted changes, which are never overwritten. An absent nested
7993d465 Andy McCall ↶ 582
repository keeps its pin until you run `update --init`. A `.oxymodules`
583
section written by hand has no pin until `oxy submodule add` clones it;
584
`status` and `update` name such entries, and `update` exits non-zero. Git
585
submodules survive `oxy git import` as Git pins and go back out as gitlinks
586
on export.
4dee277c Andy McCall ↶ 587
ee06a6c5 Andy McCall ↶ 588
## 12. Git interoperability
589
590
```
591
oxy git import ../project.git ./project-oxy --branch main   # convert a Git history
592
oxy git fetch https://github.com/you/project.git            # bring new Git commits in
593
oxy git push  https://github.com/you/project.git            # publish your changes to Git
594
oxy git export ../mirror.git                                # a plain Git mirror of the bookmark
595
```
596
597
Import turns each commit into one change (merge commits become
598
resolutions), keeps authors and hashes as `Git-Author`/`Git-Commit`
599
trailers, detects renames, and skips submodules. `git fetch`/`git push` keep
600
a bare mirror under `.oxy/git/`; a push is refused until the remote's tip
601
has been fetched, so it always fast-forwards. Exported commits carry
602
`Change-Id` and `Oxy-Hash` trailers.
603
54e2e630 Andy McCall ↶ 604
A long import moves its bookmark to the commits done so far every 2000
605
commits, so if it stops, `oxy git fetch GIT_DIR --branch BRANCH` in the new
606
repository carries on from there. One stopped before its first checkpoint
607
(or by an older oxy) is recovered first with `oxy git recover`, which
608
indexes the objects it had written and points the bookmark at them.
609
ee06a6c5 Andy McCall ↶ 610
## 13. Automate: hooks
611
612
Executables in `.oxy/hooks/<event>` (never cloned) receive one JSON object
613
on stdin. `pre-commit`, `pre-push` and other `pre-*` hooks veto by exiting
614
non-zero, and their stderr becomes the error message. Events:
615
`pre-commit`, `post-commit`, `pre-push`, `post-fetch`, `post-checkout`, and
616
`semantic-deps`, which runs at commit when `hooks.semantic-deps = true` and
617
answers `{"deps": ["<hash>", …]}` to make the change depend on more than it
618
textually references.
619
620
## 14. Per-path attributes, ignores, locks
621
622
`.oxyattributes` uses gitattributes syntax:
623
624
```
625
*.png     binary
626
*.txt     text eol=lf
627
*.psd     lockable nocompress
628
```
629
630
`binary`/`text` override content sniffing, `eol=lf|crlf` normalises line
631
endings in the store and on checkout, `nocompress` stores chunks raw, and
632
`lockable` files are checked out read-only until you take the local lock:
633
634
```
635
oxy lock assets/logo.psd
636
oxy unlock assets/logo.psd
637
```
638
639
Locks are advisory and local; server-side locks are planned.
640
641
## 15. Configuration
642
643
Two files, both `key = "value"` lines: `~/.config/oxy/config.toml`
644
(`oxy config --user KEY VALUE`) and `.oxy/config` (`oxy config KEY VALUE`).
d443639b Andy McCall ↶ 645
The repository file wins. `OXY_HOME` relocates the user directory. `OXY_TIMINGS=1` prints how long each phase of a snapshot takes (loading the base state, rendering it, reading the working copy, building the change) to standard error, for finding out where a slow command spends its time. Built with `cargo build --release --features alloc-stats`, oxy also reports each phase's live and peak memory and what it allocated, and a summary at the end.
ee06a6c5 Andy McCall ↶ 646
647
| Key | Meaning |
648
|---|---|
649
| `author.name`, `author.email`, `author.key` | who you are; `key` is set by `identity create` |
c895bc52 Andy McCall ↶ 650
| `author.signer` | the key that signs instead of `author.key`: an agent's own key, which your identity lists as a delegate; or `keyless`, for fresh keys the hub certifies under the agent's grant (also `$OXY_SIGNER`) |
651
| `keyless.hub` | the hub that certifies keyless keys (default: the default remote's hub) |
ee06a6c5 Andy McCall ↶ 652
| `core.sign` | `change` to sign every commit; otherwise seals at push |
653
| `core.op-retention` | days for which old operations keep their states alive (90) |
654
| `core.gc-grace` | days an unreachable object survives (14) |
655
| `core.checkpoint-interval` | changes between automatic checkpoints (256) |
656
| `filter.max-blob`, `filter.paths` | lazy blobs and sparse subtrees (section 11) |
657
| `hooks.semantic-deps` | run the semantic-deps hook at commit |
658
| `workspace.bookmark` | this workspace's bookmark (`main`) |
ff1f62fe Andy McCall ↶ 659
| `remote.<name>.url`, `.token`, `.ca` | remotes; token and CA fall back to `OXY_TOKEN` and `OXY_CA`, then `oxy auth login`'s store |
660
| `auth.helper` (user file) | a Git credential helper to keep `oxy auth` tokens in, such as `libsecret` |
ee06a6c5 Andy McCall ↶ 661
662
## 16. Shell prompt
663
664
`oxy prompt` prints `main 658990aa*+2` (bookmark, working copy, `*` dirty,
665
`!` conflicted, ahead/behind origin) in a few milliseconds and records
666
nothing. See [prompt.md]prompt.md for bash, oh-my-bash, zsh, and fish
667
snippets and the `--format` placeholders.
668
669
## 17. Housekeeping
670
671
```
da652d63 Andy McCall ↶ 672
oxy gc              # prune unreachable objects past the grace period, repack into one pack
ee06a6c5 Andy McCall ↶ 673
oxy gc --now        # ignore the grace period (only when nothing else is writing)
387335ff Andy McCall ↶ 674
oxy fsck            # check every object, the op log, references, signatures, checkpoints
ad2496f7 Andy McCall ↶ 675
oxy doctor          # check identity, config, trust policy and remotes, with what to do about each problem
ee06a6c5 Andy McCall ↶ 676
oxy checkpoint      # serialise the bookmark's state so large histories load instantly
677
```
678
679
Both run automatically: packing when loose objects pile up, checkpoints
680
every `core.checkpoint-interval` changes.
681
c6aa5bd9 Andy McCall ↶ 682
## 17a. Scripting and editors
683
684
Every reporting command takes `--json` (`status`, `log --path`, `show`,
685
`diff`, `hunks`, `blame`, `resolve`, `op log`, `bookmark list`, `remote list`,
686
`fetch`, `pull`, `push`, `submodule status`, `bisect status`, `verify`,
687
`policy show`, `root`, `version`). `oxy cat PATH -r CHANGE` prints a file as
b0a0c9af Andy McCall ↶ 688
it was in that change's dependency closure (the change and what it depends
689
on; `-r BOOKMARK` or a tag reads the whole state), `oxy discard PATH..`
690
restores files to the base, and `oxy hunks` numbers the working copy's hunks
691
for `commit --hunks` and `discard --hunks`.
c6aa5bd9 Andy McCall ↶ 692
ee06a6c5 Andy McCall ↶ 693
## 18. If something looks wrong
694
695
- **"the remote has change … which you do not; pull first"**: someone
696
  pushed since your last fetch. `oxy pull`, then push.
697
- **"policy needs N root signature(s)"** on fetch: the server's policy was
698
  updated in a way your copy cannot verify. Ask a root holder whether the
699
  chain of updates was published; a fresh clone trusts the current policy.
700
- **`oxy status` exits 2**: conflicts remain; see section 6.
701
- **Many heads on `main`**: independent changes that do not reference one
702
  another. Nothing to fix; the state is their union.
703
- **Undo anything**: `oxy undo` reverts the last operation, including
704
  pulls, checkouts, and rewrites.
d42369ab Andy McCall 705
706
## Shell completions and man pages
707
708
```sh
709
oxy completions bash > ~/.local/share/bash-completion/completions/oxy   # bash
710
oxy completions zsh > "${fpath[1]}/_oxy"                                 # zsh
711
oxy completions fish > ~/.config/fish/completions/oxy.fish               # fish
712
oxy man --dir ~/.local/share/man/man1                                    # then: man oxy-commit
713
```
714
715
The oh-my-bash plugin in `contrib/oh-my-bash` loads the bash completions itself.
e59512f2 Andy McCall ↶ 716
717
## Transparency logs
718
719
A hub that keeps a transparency log (OxygenHub does, at `/log`) logs every signed object it accepts. `oxy fetch` and `oxy pull` check it as they go. They remember the log's key and its last checkpoint, and fail if the hub ever shows a log that does not extend what you saw before. That is the sign of a hub showing different histories to different people. To audit a hub's whole log and see what it holds for this repository:
720
721
```
722
oxy tlog verify                         # this checkout's hub
723
oxy tlog verify https://oxygenhub.io --all
724
oxy tlog verify --key <KeyId>           # what a key signed
725
```
51d4862a Andy McCall ↶ 726
727
Anyone can **witness** a hub's log: a witness cosigns each checkpoint that extends the last one it cosigned, with its own key and clock, so a hub cannot show two histories without the witness noticing. The hub lists its witnesses in `hub.toml` (`[tlog] witnesses`), and its served checkpoint carries their cosignatures.
728
729
```
730
curl -fsSL https://oxygenhub.io/.well-known/oxyhub-key -o oxygenhub.identity
731
oxy witness serve --name witness.example.org --log oxygenhub.io/log=oxygenhub.identity \
732
    --bind 0.0.0.0:8891 --tls-cert cert.pem --tls-key key.pem
733
```
734
735
It signs with your oxy home's key and keeps what it cosigned in `--state` (default `witness-state`).