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