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