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