OXYGENHUB
# 360aa74a6277 oxygen / docs / guide.md
22.0 KiB · Markdown · last changed Projects coming from Git: .gitignore honoured, nested Git repositories named (closes #43, closes #44) 1 month ago renderedsource blame history raw
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
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
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.
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
64
oxy show 658990aa          # one change by ChangeId prefix, #hash prefix, or @
65
```
66
67
Commit part of what changed with `oxy commit PATH.. -m "..."` (only those
68
paths; the rest stays as uncommitted work) or `oxy commit -i -m "..."`, which
69
offers each hunk in turn like `git add -p`; a hunk that another chosen hunk
70
needs, such as the directory a new file lives in, comes along by itself.
71
72
`status --json` prints the same for tools, with every conflict's kind, path and
73
contributing ChangeIds. On a terminal, `status` lists each path with a mark: `✚ added`, `✎ modified`, `✖ removed`,
74
`➜ renamed`, `⚙ mode`. Renames are detected: a removed file and an added
75
file of the same kind whose contents are at least half alike are recorded
76
as a move of the same file, so identity survives a rename.
77
78
Files containing NUL bytes are stored whole ("opaque"); text files are
79
stored line by line and merge line by line. `.oxyattributes` overrides the
80
guess (section 14).
81
82
Every change has two names: a **ChangeId** (`658990aa…`) that survives
83
rewrites, and a content hash (`#7469d16c…`) that changes when the change is
84
edited. Use whichever you have; prefixes work.
85
86
## 3a. Bookmarks: branches without merges
87
88
A bookmark names a state; each workspace works on one (`main` to begin
89
with). Because changes commute, "merging" is the union of two bookmarks'
90
heads, with no merge change and any real conflict recorded as in section 6.
91
92
```
93
oxy bookmark create feature        # at the working copy's base
94
oxy bookmark switch feature        # check it out; commits now go here
95
oxy commit -m "Feature work"
96
oxy bookmark switch main
97
oxy merge feature                  # main now holds both; working copy follows if clean
98
oxy bookmark list                  # ● current, heads, ↑n/↓n against origin
99
oxy push -b feature                # publish a bookmark other than the current one
100
oxy bookmark set feature 658990aa  # move it; oxy bookmark delete feature to drop it
101
```
102
103
`oxy checkout feature` materialises a bookmark without switching to it, and
104
a bookmark name works wherever a change is expected when it has one head.
105
Each workspace remembers its own bookmark, so two directories can sit on
106
two bookmarks of one repository.
107
108
**A state is its heads, not a history.** If you are coming from Git, this
109
is the one thing to know. A bookmark holds its heads and what they depend
110
on, and a change depends only on the changes it builds on (the files it
111
touched), not on everything recorded before it. So on a bookmark with
112
several heads, a new bookmark at one change can be a much older-looking
113
tree. `oxy bookmark create NAME CHANGE` refuses when that would leave out
114
earlier work and lists what it would lack. Then pick one:
115
116
```
117
oxy bookmark create logo 745307b4 --upto  # everything here up to that change, as a Git branch would be
118
oxy bookmark create logo 745307b4 --only  # just that change and what it depends on
119
oxy bookmark create logo                  # the whole current state, usually what you want
120
```
121
122
`oxy bookmark set NAME CHANGE` asks the same when the move would lose
123
changes the bookmark holds that were recorded before CHANGE, and takes
124
`--upto` and `--only` too. After merging a proposal's target into its
125
bookmark to resolve conflicts, `oxy bookmark set NAME CHANGE --upto`
126
keeps everything the bookmark held.
127
128
`oxy bookmark switch` says when the bookmark you switch to lacks changes
129
that the one you left has.
130
131
### A develop / feature flow
132
133
Names with `/` work everywhere, pushing included:
134
135
```
136
oxy bookmark create feature/42-login          # at the working copy's base
137
oxy bookmark switch feature/42-login --carry  # started already? take the edits along
138
oxy commit -F msg.txt                         # a long message from a file
139
oxy push -b feature/42-login
140
oxy bookmark switch develop
141
oxy merge feature/42-login                    # the working copy follows when it was clean
142
oxy push                                      # develop, with the feature in it
143
oxy bookmark delete feature/42-login          # oxy push --delete feature/42-login on the remote
144
```
145
146
`bookmark switch` stops when the working copy has uncommitted edits, so
147
nothing is lost by accident. There are two ways past it:
148
- `--carry` takes the edits along. It is refused only when the target
149
  bookmark has other versions of the files they touch.
150
- `--discard` drops the edits.
151
152
`oxy describe` names what the working copy holds, for build stamps:
153
- a tag, when a tag holds exactly this state;
154
- otherwise the nearest tag with the distance from it and the change;
155
- or, with no tag behind it, the bookmark and the change;
156
- with `-dirty` added when there are uncommitted edits.
157
158
## 4. Fix up history
159
160
Oxygen keeps its own record of every operation, so all of these are safe to
161
try and to undo.
162
163
```
164
oxy describe -m "Better message"        # reword the head the working copy sits on
165
oxy describe 658990aa -m "..."          # reword any change; dependents are rewritten
166
oxy amend                               # fold the working copy into the head
167
oxy amend --into 658990aa               # fold it into an older change
168
oxy split 658990aa src/parser.rs        # split a head: those paths become a separate first change
169
oxy abandon 658990aa                    # drop a change; its content stays on disk
170
oxy squash 658990aa --into 9df9ac29     # fold a change into one it depends on
171
oxy rebase 658990aa --onto feature      # re-express a change against another base
172
oxy rebase 658990aa --onto develop --into-working-copy   # conflicted? resolve it here, then commit
173
oxy undo                                # undo the last operation, whatever it was
174
oxy op log                              # the operation log
175
```
176
177
Rewriting a change that others depend on rewrites them too, keeping their
178
ChangeIds. Because unrelated changes already coexist, `rebase` is rarely
179
needed; its job is to change what a change is said to build on, for instance
180
to drop a dependency on something you are about to abandon. Where a dependent referred to a line that no longer exists, the
181
reference is re-anchored to the nearest surviving line and `oxy abandon`
182
prints a note about it.
183
184
## 4a. Who wrote this, and what broke it
185
186
```
187
oxy blame src/parser.rs          # every line: the change that introduced it, author, age
188
oxy blame src/parser.rs --json
189
```
190
191
Blame is exact: each line is an atom that remembers the change that
192
introduced it, so renames cost nothing and there is no heuristic.
193
194
```
195
oxy bisect start
196
oxy bisect good v1.2             # a change, bookmark or tag known to be fine
197
oxy bisect bad main              # and one known to be broken
198
oxy bisect run make test         # exit 0 good, 125 skip, anything else bad
199
oxy bisect reset                 # back to where you were
200
```
201
202
Without `run`, each step checks out a state to test and you answer with
203
`oxy bisect good`, `bad`, or `skip`. The candidates are the changes the bad
204
state has that the good one lacks, ordered so every tested state is
205
well-formed; the answer is the first change whose inclusion breaks things.
206
207
## 5. Move around
208
209
```
210
oxy checkout                    # the bookmark's heads
211
oxy checkout 658990aa           # the state of one change and its dependencies
212
oxy checkout v1.2               # a tag
213
oxy checkout --discard ...      # throw away uncommitted work first
214
```
215
216
Checking out a single older change gives you the files that change and its
217
dependencies touch, not a full snapshot from that date: in Oxygen a change
218
depends only on what it actually builds on.
219
220
## 6. Conflicts
221
222
A conflict never stops a commit, fetch, or pull. It is recorded in the
223
state and shown by `oxy status`, which exits with status 2 while any
224
conflict remains. Conflicted text files are written with markers:
225
226
```
227
<<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 "Add retry loop"
228
... lines ...
229
======= oxy conflict 1 of 2, side 2: change q3x8w1 "Refactor client"
230
... lines ...
231
>>>>>>> oxy conflict 1 of 2 end
232
```
233
234
Edit the file to what it should be (keep one side, both, or neither) and
235
remove the markers. Tree conflicts, such as two files wanting the same
236
name, appear as `<name>.oxy-conflict-<n>` candidates: keep the one you
237
want under the right name and delete the rest.
238
239
```
240
oxy resolve      # which conflicts your edits resolve, which still have markers
241
oxy commit -m "Resolve retry vs refactor"
242
```
243
244
The commit is recorded as a *resolution*, a change that depends on every
245
side of the conflict; anyone who fetches it gets the conflict resolved.
246
247
## 7. Work with others
248
249
Run a server (any machine with the repositories under one directory):
250
251
```
252
oxy serve --root /srv/oxy --bind 0.0.0.0:8890 --token SECRET \
253
          --tls-cert cert.pem --tls-key key.pem
254
```
255
256
Without `--tls-*` the server speaks plain HTTP; keep that on a loopback
257
address or behind a TLS terminator. `--token` makes pushes require a bearer
258
token; reads stay open.
259
260
On your machine:
261
262
```
263
oxy clone https://host:8890/project
264
cd project
265
oxy config remote.origin.token SECRET   # or export OXY_TOKEN
266
oxy config remote.origin.ca my-ca.pem   # self-signed server: extra roots to trust (or OXY_CA)
267
oxy push
268
oxy pull
269
```
270
271
- `oxy fetch` updates `origin/main` without touching your bookmark.
272
- `oxy pull` fetches, then makes your bookmark the union of both sides.
273
  There is no merge commit; unrelated changes simply coexist, and a real
274
  conflict is recorded as in section 6. The working copy follows when it was
275
  clean.
276
- `oxy push` sends your changes and moves the remote bookmark. If the remote
277
  moved since you last fetched, the push is refused: pull and push again.
278
  `--force` overrides the compare-and-swap.
279
- `oxy remote add|remove|list` manages remotes; the clone source is `origin`.
280
281
A repository can also be cloned from a local path, which is handy for tests
282
and for a bare "hub" on a shared disk.
283
284
## 8. Decide who may publish: the trust policy
285
286
Without a policy a repository is *unverified*: everything is accepted and
287
every remote command says so. A policy names root keys and the keys allowed
288
to update each bookmark; servers and clients enforce it.
289
290
```
291
oxy policy init                    # your key becomes the only root; push publishes it
292
oxy policy show
293
oxy policy grant alice.identity    # alice.identity came from her `oxy identity export`
294
oxy policy grant ci.identity --pattern 'release/*'
295
oxy policy revoke 9458b656         # by KeyId prefix
296
oxy verify                         # signature status of every change in the log
297
```
298
299
Several people can hold root keys:
300
301
```
302
oxy policy root add bob.identity
303
oxy policy root threshold 2        # future updates need two root signatures
304
```
305
306
Once the threshold is above one, an update you make alone becomes a
307
*proposal file* (`policy-<serial>.proposal`, or `--out`). Send it to another
308
root holder, who runs `oxy policy sign policy-7.proposal`; whoever completes
309
the threshold applies it, and the next push publishes it. Everyone else
310
picks it up on fetch, verifying each step from the policy they already
311
trust.
312
313
## 9. Keys over time
314
315
```
316
oxy identity rotate                   # fresh key pair; old signatures stay valid
317
oxy identity renew --expires 365      # same keys, new expiry in days (0 = never)
318
oxy identity delegate add ci.identity # let another key sign on your behalf
319
oxy identity delegate remove 3f2a…
320
oxy identity revoke                   # retire your own key
321
oxy identity revoke 3f2a… --reason "laptop stolen"   # any key, if you hold a root
322
```
323
324
Rotation, renewal, and delegation all issue a successor identity that names
325
the previous one. If a policy names your key, it is updated to the successor
326
in the same step (as a proposal when more root signatures are needed).
327
Changes signed before the rotation remain authorised; a revoked key
328
authorises nothing. `oxy verify` shows signatures by revoked keys as valid
329
but revoked.
330
331
## 10. Tags and workspaces
332
333
```
334
oxy tag create v1.2 -m "First release"   # the current heads; or name changes
335
oxy tag list
336
oxy workspace add ../project-hotfix      # a second checkout sharing the repository
337
oxy workspace list
338
oxy workspace forget ../project-hotfix
339
```
340
341
A tag is an immutable state, signed when you have a key, and travels with
342
push and fetch. Workspaces have their own working copy and bookmark
343
(`workspace.bookmark`) but share objects and history.
344
345
### Receipts from CI
346
347
A runner such as OxygenFlow signs a receipt for every job it runs: what ran,
348
in what environment, on which files, and the result. Receipts are kept with
349
the state they ran on.
350
351
```
352
oxy receipt list                         # receipts for the bookmark's state
353
oxy receipt show 3f2a9c                  # one in full, with its signature checked
354
oxy receipt export 3f2a9c build.receipt  # hand it to someone
355
oxy receipt verify build.receipt         # check one without keeping it
356
oxy receipt import build.receipt         # verify and keep it
357
```
358
359
## 11. Big repositories: partial, sparse, shallow
360
361
```
362
oxy clone URL --max-blob 1M          # files above 1 MiB are fetched on first read
363
oxy clone URL --sparse src --sparse docs   # materialise only these subtrees
364
oxy clone URL --depth 50             # only the last 50 levels of history
365
oxy sparse src docs tests            # change the filter later
366
oxy deepen 100                       # fetch more history below the boundary
367
```
368
369
Lazy blobs and sparse trees do not affect commits: what you do not have is
370
left exactly as it was. A shallow clone starts from a *checkpoint* of the
371
state, so status and commit work at full speed from the first minute.
372
373
## 11a. Nested repositories (submodules)
374
375
A subrepo is another repository checked out at a path and pinned to a state,
376
Oxygen or Git. The pin is versioned like a file, so two people pinning
377
different states get an ordinary conflict.
378
379
```
380
oxy submodule add https://host/lib.git libs/lib     # Git or Oxygen URL; records .oxymodules
381
oxy commit -m "Add lib"                              # the pin is committed with the parent
382
oxy submodule status                                 # pin vs what is on disk
383
oxy submodule update --init                          # after a clone: fetch nested repositories
384
oxy submodule remove libs/lib                        # drop the node; the directory stays
385
```
386
387
Committing the parent records whatever the nested repository is at, and
388
checking out an older parent state moves the nested repository back, unless
389
it has uncommitted changes, which are never overwritten. An absent nested
390
repository keeps its pin until you run `update --init`. Git submodules
391
survive `oxy git import` as Git pins and go back out as gitlinks on export.
392
393
## 12. Git interoperability
394
395
```
396
oxy git import ../project.git ./project-oxy --branch main   # convert a Git history
397
oxy git fetch https://github.com/you/project.git            # bring new Git commits in
398
oxy git push  https://github.com/you/project.git            # publish your changes to Git
399
oxy git export ../mirror.git                                # a plain Git mirror of the bookmark
400
```
401
402
Import turns each commit into one change (merge commits become
403
resolutions), keeps authors and hashes as `Git-Author`/`Git-Commit`
404
trailers, detects renames, and skips submodules. `git fetch`/`git push` keep
405
a bare mirror under `.oxy/git/`; a push is refused until the remote's tip
406
has been fetched, so it always fast-forwards. Exported commits carry
407
`Change-Id` and `Oxy-Hash` trailers.
408
409
## 13. Automate: hooks
410
411
Executables in `.oxy/hooks/<event>` (never cloned) receive one JSON object
412
on stdin. `pre-commit`, `pre-push` and other `pre-*` hooks veto by exiting
413
non-zero, and their stderr becomes the error message. Events:
414
`pre-commit`, `post-commit`, `pre-push`, `post-fetch`, `post-checkout`, and
415
`semantic-deps`, which runs at commit when `hooks.semantic-deps = true` and
416
answers `{"deps": ["<hash>", …]}` to make the change depend on more than it
417
textually references.
418
419
## 14. Per-path attributes, ignores, locks
420
421
`.oxyattributes` uses gitattributes syntax:
422
423
```
424
*.png     binary
425
*.txt     text eol=lf
426
*.psd     lockable nocompress
427
```
428
429
`binary`/`text` override content sniffing, `eol=lf|crlf` normalises line
430
endings in the store and on checkout, `nocompress` stores chunks raw, and
431
`lockable` files are checked out read-only until you take the local lock:
432
433
```
434
oxy lock assets/logo.psd
435
oxy unlock assets/logo.psd
436
```
437
438
Locks are advisory and local; server-side locks are planned.
439
440
## 15. Configuration
441
442
Two files, both `key = "value"` lines: `~/.config/oxy/config.toml`
443
(`oxy config --user KEY VALUE`) and `.oxy/config` (`oxy config KEY VALUE`).
444
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.
445
446
| Key | Meaning |
447
|---|---|
448
| `author.name`, `author.email`, `author.key` | who you are; `key` is set by `identity create` |
449
| `core.sign` | `change` to sign every commit; otherwise seals at push |
450
| `core.op-retention` | days for which old operations keep their states alive (90) |
451
| `core.gc-grace` | days an unreachable object survives (14) |
452
| `core.checkpoint-interval` | changes between automatic checkpoints (256) |
453
| `filter.max-blob`, `filter.paths` | lazy blobs and sparse subtrees (section 11) |
454
| `hooks.semantic-deps` | run the semantic-deps hook at commit |
455
| `workspace.bookmark` | this workspace's bookmark (`main`) |
456
| `remote.<name>.url`, `.token`, `.ca` | remotes; token and CA fall back to `OXY_TOKEN` and `OXY_CA` |
457
458
## 16. Shell prompt
459
460
`oxy prompt` prints `main 658990aa*+2` (bookmark, working copy, `*` dirty,
461
`!` conflicted, ahead/behind origin) in a few milliseconds and records
462
nothing. See [prompt.md]prompt.md for bash, oh-my-bash, zsh, and fish
463
snippets and the `--format` placeholders.
464
465
## 17. Housekeeping
466
467
```
468
oxy gc              # prune unreachable objects past the grace period, repack into one pack
469
oxy gc --now        # ignore the grace period (only when nothing else is writing)
470
oxy fsck            # check every object, the op log, references, signatures, checkpoints
471
oxy doctor          # check identity, config and remotes, with what to do about each problem
472
oxy checkpoint      # serialise the bookmark's state so large histories load instantly
473
```
474
475
Both run automatically: packing when loose objects pile up, checkpoints
476
every `core.checkpoint-interval` changes.
477
478
## 17a. Scripting and editors
479
480
Every reporting command takes `--json` (`status`, `log --path`, `show`,
481
`diff`, `hunks`, `blame`, `resolve`, `op log`, `bookmark list`, `remote list`,
482
`fetch`, `pull`, `push`, `submodule status`, `bisect status`, `verify`,
483
`policy show`, `root`, `version`). `oxy cat PATH -r CHANGE` prints a file as
484
it was, `oxy discard PATH..` restores files to the base, and `oxy hunks`
485
numbers the working copy's hunks for `commit --hunks` and `discard --hunks`.
486
487
## 18. If something looks wrong
488
489
- **"the remote has change … which you do not; pull first"**: someone
490
  pushed since your last fetch. `oxy pull`, then push.
491
- **"policy needs N root signature(s)"** on fetch: the server's policy was
492
  updated in a way your copy cannot verify. Ask a root holder whether the
493
  chain of updates was published; a fresh clone trusts the current policy.
494
- **`oxy status` exits 2**: conflicts remain; see section 6.
495
- **Many heads on `main`**: independent changes that do not reference one
496
  another. Nothing to fix; the state is their union.
497
- **Undo anything**: `oxy undo` reverts the last operation, including
498
  pulls, checkouts, and rewrites.
499
500
## Shell completions and man pages
501
502
```sh
503
oxy completions bash > ~/.local/share/bash-completion/completions/oxy   # bash
504
oxy completions zsh > "${fpath[1]}/_oxy"                                 # zsh
505
oxy completions fish > ~/.config/fish/completions/oxy.fish               # fish
506
oxy man --dir ~/.local/share/man/man1                                    # then: man oxy-commit
507
```
508
509
The oh-my-bash plugin in `contrib/oh-my-bash` loads the bash completions itself.