# Oxygen user guide This guide is organised by task. It assumes the `oxy` binary is on your path (`cargo build --release` puts it in `target/release/`). The design behind every command is in [spec.md](spec.md); this guide only tells you what to type and what to expect. Oxygen tracks *changes* rather than snapshots: a change is the set of edits you made, it commutes with unrelated changes, and conflicts are recorded in history instead of blocking you. Two ideas recur below: - A **bookmark** (default `main`) names a *state*: the set of changes it contains. A state can have several **heads** when independent changes have not been related to each other; that is normal, not a problem. - The **working copy** is itself a change that Oxygen rewrites every time it looks at your files. `oxy commit` turns it into a real change. Output uses colour and glyphs on a terminal; pipe it, pass `--plain`, or set `NO_COLOR` for plain ASCII. ## 1. Set yourself up ``` oxy identity create --name "Ada Lovelace" --email ada@example.org oxy config --user core.sign change ``` `identity create` generates a composite Ed25519 + ML-DSA-65 key pair, keeps the secret in `~/.config/oxy/keys/`, and records you as the default author in `~/.config/oxy/config.toml`. With `core.sign = change` every commit is signed; without it, changes are sealed in batches when you push (also authenticated, just later). Use `--local` to record the identity in one repository only. `oxy identity show` prints the key, its expiry, delegates, and whether the secret is available. `oxy identity export` writes the public identity to a file to hand to whoever manages a trust policy (section 8). ## 2. Start a repository ``` mkdir project && cd project oxy init oxy status ``` `oxy init` creates `.oxy/`. Add `.oxyignore` (gitignore syntax) for files that should never be tracked; `/target` and `*.tmp` are typical entries. To take over an existing Git project, see section 12. ## 3. Everyday work ``` oxy status # what changed since the last commit oxy diff # the edits, file by file oxy commit -m "Add retry loop" oxy log # newest first; -n 10, -v for full messages oxy show 658990aa # one change by ChangeId prefix, #hash prefix, or @ ``` `status --json` prints the same for tools, with every conflict's kind, path and contributing ChangeIds. On a terminal, `status` lists each path with a mark: `✚ added`, `✎ modified`, `✖ removed`, `➜ renamed`, `⚙ mode`. Renames are detected: a removed file and an added file of the same kind whose contents are at least half alike are recorded as a move of the same file, so identity survives a rename. Files containing NUL bytes are stored whole ("opaque"); text files are stored line by line and merge line by line. `.oxyattributes` overrides the guess (section 14). Every change has two names: a **ChangeId** (`658990aa…`) that survives rewrites, and a content hash (`#7469d16c…`) that changes when the change is edited. Use whichever you have; prefixes work. ## 3a. Bookmarks: branches without merges A bookmark names a state; each workspace works on one (`main` to begin with). Because changes commute, "merging" is the union of two bookmarks' heads, with no merge change and any real conflict recorded as in section 6. ``` oxy bookmark create feature # at the working copy's base oxy bookmark switch feature # check it out; commits now go here oxy commit -m "Feature work" oxy bookmark switch main oxy merge feature # main now holds both; working copy follows if clean oxy bookmark list # ● current, heads, ↑n/↓n against origin oxy push -b feature # publish a bookmark other than the current one oxy bookmark set feature 658990aa # move it; oxy bookmark delete feature to drop it ``` `oxy checkout feature` materialises a bookmark without switching to it, and a bookmark name works wherever a change is expected when it has one head. Each workspace remembers its own bookmark, so two directories can sit on two bookmarks of one repository. ## 4. Fix up history Oxygen keeps its own record of every operation, so all of these are safe to try and to undo. ``` oxy describe -m "Better message" # reword the head the working copy sits on oxy describe 658990aa -m "..." # reword any change; dependents are rewritten oxy amend # fold the working copy into the head oxy amend --into 658990aa # fold it into an older change oxy split 658990aa src/parser.rs # split a head: those paths become a separate first change oxy abandon 658990aa # drop a change; its content stays on disk oxy undo # undo the last operation, whatever it was oxy op log # the operation log ``` Rewriting a change that others depend on rewrites them too, keeping their ChangeIds. Where a dependent referred to a line that no longer exists, the reference is re-anchored to the nearest surviving line and `oxy abandon` prints a note about it. ## 5. Move around ``` oxy checkout # the bookmark's heads oxy checkout 658990aa # the state of one change and its dependencies oxy checkout v1.2 # a tag oxy checkout --discard ... # throw away uncommitted work first ``` Checking out a single older change gives you the files that change and its dependencies touch, not a full snapshot from that date: in Oxygen a change depends only on what it actually builds on. ## 6. Conflicts A conflict never stops a commit, fetch, or pull. It is recorded in the state and shown by `oxy status`, which exits with status 2 while any conflict remains. Conflicted text files are written with markers: ``` <<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 "Add retry loop" ... lines ... ======= oxy conflict 1 of 2, side 2: change q3x8w1 "Refactor client" ... lines ... >>>>>>> oxy conflict 1 of 2 end ``` Edit the file to what it should be (keep one side, both, or neither) and remove the markers. Tree conflicts, such as two files wanting the same name, appear as `.oxy-conflict-` candidates: keep the one you want under the right name and delete the rest. ``` oxy resolve # which conflicts your edits resolve, which still have markers oxy commit -m "Resolve retry vs refactor" ``` The commit is recorded as a *resolution*, a change that depends on every side of the conflict; anyone who fetches it gets the conflict resolved. ## 7. Work with others Run a server (any machine with the repositories under one directory): ``` oxy serve --root /srv/oxy --bind 0.0.0.0:8890 --token SECRET \ --tls-cert cert.pem --tls-key key.pem ``` Without `--tls-*` the server speaks plain HTTP; keep that on a loopback address or behind a TLS terminator. `--token` makes pushes require a bearer token; reads stay open. On your machine: ``` oxy clone https://host:8890/project cd project oxy config remote.origin.token SECRET # or export OXY_TOKEN oxy config remote.origin.ca my-ca.pem # self-signed server: extra roots to trust (or OXY_CA) oxy push oxy pull ``` - `oxy fetch` updates `origin/main` without touching your bookmark. - `oxy pull` fetches, then makes your bookmark the union of both sides. There is no merge commit; unrelated changes simply coexist, and a real conflict is recorded as in section 6. The working copy follows when it was clean. - `oxy push` sends your changes and moves the remote bookmark. If the remote moved since you last fetched, the push is refused: pull and push again. `--force` overrides the compare-and-swap. - `oxy remote add|remove|list` manages remotes; the clone source is `origin`. A repository can also be cloned from a local path, which is handy for tests and for a bare "hub" on a shared disk. ## 8. Decide who may publish: the trust policy Without a policy a repository is *unverified*: everything is accepted and every remote command says so. A policy names root keys and the keys allowed to update each bookmark; servers and clients enforce it. ``` oxy policy init # your key becomes the only root; push publishes it oxy policy show oxy policy grant alice.identity # alice.identity came from her `oxy identity export` oxy policy grant ci.identity --pattern 'release/*' oxy policy revoke 9458b656 # by KeyId prefix oxy verify # signature status of every change in the log ``` Several people can hold root keys: ``` oxy policy root add bob.identity oxy policy root threshold 2 # future updates need two root signatures ``` Once the threshold is above one, an update you make alone becomes a *proposal file* (`policy-.proposal`, or `--out`). Send it to another root holder, who runs `oxy policy sign policy-7.proposal`; whoever completes the threshold applies it, and the next push publishes it. Everyone else picks it up on fetch, verifying each step from the policy they already trust. ## 9. Keys over time ``` oxy identity rotate # fresh key pair; old signatures stay valid oxy identity renew --expires 365 # same keys, new expiry in days (0 = never) oxy identity delegate add ci.identity # let another key sign on your behalf oxy identity delegate remove 3f2a… oxy identity revoke # retire your own key oxy identity revoke 3f2a… --reason "laptop stolen" # any key, if you hold a root ``` Rotation, renewal, and delegation all issue a successor identity that names the previous one. If a policy names your key, it is updated to the successor in the same step (as a proposal when more root signatures are needed). Changes signed before the rotation remain authorised; a revoked key authorises nothing. `oxy verify` shows signatures by revoked keys as valid but revoked. ## 10. Tags and workspaces ``` oxy tag create v1.2 -m "First release" # the current heads; or name changes oxy tag list oxy workspace add ../project-hotfix # a second checkout sharing the repository oxy workspace list oxy workspace forget ../project-hotfix ``` A tag is an immutable state, signed when you have a key, and travels with push and fetch. Workspaces have their own working copy and bookmark (`workspace.bookmark`) but share objects and history. ## 11. Big repositories: partial, sparse, shallow ``` oxy clone URL --max-blob 1M # files above 1 MiB are fetched on first read oxy clone URL --sparse src --sparse docs # materialise only these subtrees oxy clone URL --depth 50 # only the last 50 levels of history oxy sparse src docs tests # change the filter later oxy deepen 100 # fetch more history below the boundary ``` Lazy blobs and sparse trees do not affect commits: what you do not have is left exactly as it was. A shallow clone starts from a *checkpoint* of the state, so status and commit work at full speed from the first minute. ## 12. Git interoperability ``` oxy git import ../project.git ./project-oxy --branch main # convert a Git history oxy git fetch https://github.com/you/project.git # bring new Git commits in oxy git push https://github.com/you/project.git # publish your changes to Git oxy git export ../mirror.git # a plain Git mirror of the bookmark ``` Import turns each commit into one change (merge commits become resolutions), keeps authors and hashes as `Git-Author`/`Git-Commit` trailers, detects renames, and skips submodules. `git fetch`/`git push` keep a bare mirror under `.oxy/git/`; a push is refused until the remote's tip has been fetched, so it always fast-forwards. Exported commits carry `Change-Id` and `Oxy-Hash` trailers. ## 13. Automate: hooks Executables in `.oxy/hooks/` (never cloned) receive one JSON object on stdin. `pre-commit`, `pre-push` and other `pre-*` hooks veto by exiting non-zero, and their stderr becomes the error message. Events: `pre-commit`, `post-commit`, `pre-push`, `post-fetch`, `post-checkout`, and `semantic-deps`, which runs at commit when `hooks.semantic-deps = true` and answers `{"deps": ["", …]}` to make the change depend on more than it textually references. ## 14. Per-path attributes, ignores, locks `.oxyattributes` uses gitattributes syntax: ``` *.png binary *.txt text eol=lf *.psd lockable nocompress ``` `binary`/`text` override content sniffing, `eol=lf|crlf` normalises line endings in the store and on checkout, `nocompress` stores chunks raw, and `lockable` files are checked out read-only until you take the local lock: ``` oxy lock assets/logo.psd oxy unlock assets/logo.psd ``` Locks are advisory and local; server-side locks are planned. ## 15. Configuration Two files, both `key = "value"` lines: `~/.config/oxy/config.toml` (`oxy config --user KEY VALUE`) and `.oxy/config` (`oxy config KEY VALUE`). The repository file wins. `OXY_HOME` relocates the user directory. | Key | Meaning | |---|---| | `author.name`, `author.email`, `author.key` | who you are; `key` is set by `identity create` | | `core.sign` | `change` to sign every commit; otherwise seals at push | | `core.op-retention` | days for which old operations keep their states alive (90) | | `core.gc-grace` | days an unreachable object survives (14) | | `core.checkpoint-interval` | changes between automatic checkpoints (256) | | `filter.max-blob`, `filter.paths` | lazy blobs and sparse subtrees (section 11) | | `hooks.semantic-deps` | run the semantic-deps hook at commit | | `workspace.bookmark` | this workspace's bookmark (`main`) | | `remote..url`, `.token`, `.ca` | remotes; token and CA fall back to `OXY_TOKEN` and `OXY_CA` | ## 16. Shell prompt `oxy prompt` prints `main 658990aa*+2` (bookmark, working copy, `*` dirty, `!` conflicted, ahead/behind origin) in a few milliseconds and records nothing. See [prompt.md](prompt.md) for bash, oh-my-bash, zsh, and fish snippets and the `--format` placeholders. ## 17. Housekeeping ``` oxy gc # pack loose objects, prune unreachable ones past the grace period oxy gc --now # ignore the grace period (only when nothing else is writing) oxy checkpoint # serialise the bookmark's state so large histories load instantly ``` Both run automatically: packing when loose objects pile up, checkpoints every `core.checkpoint-interval` changes. ## 18. If something looks wrong - **"the remote has change … which you do not; pull first"**: someone pushed since your last fetch. `oxy pull`, then push. - **"policy needs N root signature(s)"** on fetch: the server's policy was updated in a way your copy cannot verify. Ask a root holder whether the chain of updates was published; a fresh clone trusts the current policy. - **`oxy status` exits 2**: conflicts remain; see section 6. - **Many heads on `main`**: independent changes that do not reference one another. Nothing to fix; the state is their union. - **Undo anything**: `oxy undo` reverts the last operation, including pulls, checkouts, and rewrites.