# 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, per directory, negations with `!`) for files that should never be tracked; `/target` and `*.tmp` are typical entries. As in Git, ignore rules only affect untracked files: a file already in history stays tracked until you delete it. A project's `.gitignore` files are honoured too, so moving a Git project to Oxygen does not commit its build output; `.oxyignore` comes first, and a `!` rule there brings back what `.gitignore` leaves out. 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 log -r 'main.. & touches("src/**")' # a revset (docs/revset_query_language.md) oxy show 658990aa # one change by ChangeId prefix, #hash prefix, or @ ``` `oxy log` draws the history as a graph, newest first, two lines per change: ``` ◉ 2f8518c0 Andy McCall just now │ (working copy) ● 1b5888b1 Andy McCall 2 minutes ago main #cc23b999 ✔ signed │ Edit b │ ● d6f52eca Andy McCall 3 minutes ago main #ec6a7dc4 ✔ signed ├─╯ Edit a ○ c0593116 Andy McCall 5 minutes ago #eeac7940 ✔ signed Both files ``` The bright start of each ID is the shortest prefix that names it. `●` marks a head, `◉` the working copy. Bookmarks and tags show beside their heads, unless they have more than three. A lane follows the change each one most directly builds on, its newest dependency. Two edits that build on the same change take two lanes, which join above it, so independent work shows side by side. `~` means the change builds on something further down than the graph draws, or not shown; `-v` lists every dependency. Commit part of what changed with `oxy commit PATH.. -m "..."` (only those paths; the rest stays as uncommitted work) or `oxy commit -i -m "..."`, which offers each hunk in turn like `git add -p`; a hunk that another chosen hunk needs, such as the directory a new file lives in, comes along by itself. `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 bookmark show feature # each head, newest first (list -v: every bookmark's) 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. In a revset it means the bookmark's heads (`oxy log -r feature`); `bookmark(feature)` means only the bookmark, never a tag or change prefix. Each workspace remembers its own bookmark, so two directories can sit on two bookmarks of one repository. **A state is its heads, not a history.** If you are coming from Git, this is the one thing to know. A bookmark holds its heads and what they depend on, and a change depends only on the changes it builds on (the files it touched), not on everything recorded before it. So on a bookmark with several heads, a new bookmark at one change can be a much older-looking tree. `oxy bookmark create NAME CHANGE` refuses when that would leave out earlier work and lists what it would lack. Then pick one: ``` oxy bookmark create logo 745307b4 --upto # everything here up to that change, as a Git branch would be oxy bookmark create logo 745307b4 --only # just that change and what it depends on oxy bookmark create logo # the whole current state, usually what you want ``` `oxy bookmark set NAME CHANGE` asks the same when the move would lose changes the bookmark holds that were recorded before CHANGE, and takes `--upto` and `--only` too. After merging a proposal's target into its bookmark to resolve conflicts, `oxy bookmark set NAME CHANGE --upto` keeps everything the bookmark held. `oxy bookmark switch` says when the bookmark you switch to lacks changes that the one you left has. **A remote's bookmarks.** A clone fetches every change, checks out the remote's default bookmark, and names the others `origin/NAME`: use them anywhere a bookmark is expected (`oxy log origin/develop`, `oxy diff main origin/develop`). `oxy bookmark list --all` shows them. `oxy bookmark switch develop` makes a local `develop` from `origin/develop` when there is none, and `oxy pull` then keeps every local bookmark up to date with the remote's of the same name, not only the current one. Pulling is a union, so this never loses work. A local bookmark that is only behind its remote (`-3` in `bookmark list`) is caught up when you switch to it. ``` oxy clone https://oxygenhub.io/owner/game --recursive # subrepos too oxy bookmark list --all oxy bookmark switch develop # from origin/develop oxy pull # updates develop, main and the rest ``` ### A develop / feature flow Names with `/` work everywhere, pushing included: ``` oxy bookmark create feature/42-login # at the working copy's base oxy bookmark switch feature/42-login --carry # started already? take the edits along oxy commit -F msg.txt # a long message from a file oxy push -b feature/42-login oxy bookmark switch develop oxy merge feature/42-login # the working copy follows when it was clean oxy push # develop, with the feature in it oxy bookmark delete feature/42-login # oxy push --delete feature/42-login on the remote ``` `bookmark switch` stops when the working copy has uncommitted edits, so nothing is lost by accident. There are two ways past it: - `--carry` takes the edits along. It is refused only when the target bookmark has other versions of the files they touch. - `--discard` drops the edits. `oxy describe` names what the working copy holds, for build stamps: - a tag, when a tag holds exactly this state; - otherwise the nearest tag with the distance from it and the change; - or, with no tag behind it, the bookmark and the change; - with `-dirty` added when there are uncommitted edits. ## 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 squash 658990aa --into 9df9ac29 # fold a change into one it depends on oxy rebase 658990aa --onto feature # re-express a change against another base oxy rebase 658990aa --onto develop --into-working-copy # conflicted? resolve it here, then commit oxy undo # undo the last operation, whatever it was oxy op log # every operation, what it changed, and its command ``` ### The operation log Every command that changes the repository records an operation: the bookmarks, tags and working copy as they were after it. `oxy op log` lists them newest first, each with what it changed and the command that did it; working-copy snapshots fold into one row. ``` @ 13adb0d0b8bf just now │ main: 1 → 2 heads; working copy now 91ab4d50 │ oxy commit -m "Third" ○ fa21727d70da just now │ created bookmark test (1 head) │ oxy bookmark create test ○ 2d15930afdef … 830168a3ddcd just now │ 2 working-copy snapshots ``` Any operation can be looked at, gone back to, or undone alone: ``` oxy --at-op fa21727d log # the log as it was after that operation; any read-only command works oxy op restore fa21727d # bookmarks and working copy as they were then oxy op revert fa21727d # undo just that operation, keeping everything after it oxy undo # takes back the last command, a restore included ``` `op restore` and `op revert` are operations themselves, so `oxy undo` takes them back. `op revert` puts back each bookmark the operation moved, unless it has moved again since. Tags and the trust policy never go backwards. `undo` brings the files back as well when nothing uncommitted would be lost; an undone commit's edits stay on disk as uncommitted work. Commands that move the working copy end by saying where it is: ``` * Committed 95245a21 #35bbf5097e0c Third Working copy 91ab4d50 Bookmark main: 2 heads, newest 95245a21 Third ``` Rewriting a change that others depend on rewrites them too, keeping their ChangeIds. Because unrelated changes already coexist, `rebase` is rarely needed; its job is to change what a change is said to build on, for instance to drop a dependency on something you are about to abandon. 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. ## 4a. Who wrote this, and what broke it ``` oxy blame src/parser.rs # every line: the change that introduced it, author, age oxy blame src/parser.rs --json ``` Blame is exact: each line is an atom that remembers the change that introduced it, so renames cost nothing and there is no heuristic. ``` oxy bisect start oxy bisect good v1.2 # a change, bookmark or tag known to be fine oxy bisect bad main # and one known to be broken oxy bisect run make test # exit 0 good, 125 skip, anything else bad oxy bisect reset # back to where you were ``` Without `run`, each step checks out a state to test and you answer with `oxy bisect good`, `bad`, or `skip`. The candidates are the changes the bad state has that the good one lacks, ordered so every tested state is well-formed; the answer is the first change whose inclusion breaks things. ## 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: ``` echo SECRET | oxy auth login https://host:8890 --with-token --ca my-ca.pem oxy clone https://host:8890/project cd project oxy push oxy pull ``` `--ca` is only for a server with a self-signed certificate. On a hub such as OxygenHub, `oxy auth login https://oxygenhub.io` signs in through the browser instead: it shows a code to approve on the hub, and the hub issues a token that lasts 90 days past its last use. ### Signing in `oxy auth login HUB` keeps one token per hub (scheme, host and port) in `~/.config/oxy/credentials.toml`, readable by you only, and every repository on that hub uses it: clone, fetch, push and subrepos alike. Nothing secret is written into a checkout. `oxy auth status` lists the hubs you are signed in to and checks each token with its hub; `oxy auth logout HUB` forgets the token and revokes it on the hub. To keep tokens in the system keyring instead, name a Git credential helper: `oxy config --user auth.helper libsecret` (Linux), `osxkeychain` (macOS) or `manager` (Windows). The file then keeps only the account and CA. A token is found in this order: the remote's own `remote..token`, then `$OXY_TOKEN` (for CI), then the store, then a remote on the same hub in an enclosing checkout (so a subrepo uses its parent's). When the token came from the store or an enclosing checkout and the hub refuses it (a stale login, or another account's), oxy tries the other tokens enclosing checkouts hold for that hub before giving up, and says so once; a remote's own token and `$OXY_TOKEN` are never swapped for another. oxy sends a token over `https` only, or over plain `http` to this machine; it is never sent to another host after a redirect, and a credentials file that others can read is ignored. - `oxy fetch` updates `origin/main` without touching your bookmark. - `oxy pull` fetches, then makes your bookmark, and every other local bookmark the remote also has, 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, taking uncommitted work along when the pulled changes leave its files alone; otherwise it stays and `oxy checkout` updates it. A working copy that had fallen behind its bookmark catches up on the next pull too, and `oxy status` says when it is behind. - `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`. Commands that take a remote use `origin`, or the only remote there is; with several and none named `origin`, name one or set `oxy config core.remote NAME`. `bookmark list` compares each bookmark with a remote that has it, and names the remote when there is more than one. 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 agent.identity --expires 8h # sign changes for me today oxy identity delegate add ci.identity --scope changes,refs # sign and push for me oxy identity delegate remove 3f2a… oxy identity revoke 3f2a… --reason "agent host compromised" # a key you delegated to ``` Setting an agent up takes a home of its own, so it never holds your secret key. In the agent's shell, inside any checkout: ``` export OXY_HOME=~/.oxy-agents/claude oxy identity create --name "Claude Code on my laptop" --email claude@agents.local # prints the agent's KeyId; its identity file is keys/.identity in that home oxy config --user author.name "Your Name" # the agent authors as you… oxy config --user author.email "you@example.org" oxy config --user author.key oxy config --user author.signer # …and signs as itself oxy config --user core.sign change ``` Then, in your own shell, in the repository: `oxy identity delegate add ~/.oxy-agents/claude/keys/.identity --scope changes --expires 8h` (several files at once are one rotation). This rotates your identity: the new key is written to your user config, and to this repository's file too if it pinned the old one, so other checkouts follow; put the new KeyId in each agent's `author.key`, and re-link it on your hub. `oxy identity show` warns if a repository still pins an older key (`oxy config --unset author.key` there). Every commit the agent makes carries you as author and its signature: `oxy log -r 'delegated()'` lists them, `oxy verify` names the agent, and its own `oxy push` is refused under any policy, so you land its work. **Keyless agents** (on an OxygenHub) hold no key at all. You give the agent a grant instead, signed by your key, and the hub certifies a fresh key for it each time it signs: ``` oh agent grant claude --for 30d # your shell: prints the agent's token, once oxy auth grant < token # the agent's shell (its own OXY_HOME) oxy config --user author.key oxy config --user author.signer keyless ``` Each certificate lasts ten minutes and carries your grant, so the agent's commits count as yours, and a push seals them with the fresh key. A repository with a trust policy also names the agent: `oxy policy grant-subject agent:claude@YOU --issuer hub.identity --scope changes` (add `refs` to let it push), with the hub's identity from `/.well-known/oxyhub-key`. `oh agent list` shows your grants and `oh agent revoke ID` stops one at once; there is no key to rotate. ``` 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. ### Receipts from CI A runner such as OxygenFlow signs a receipt for every job it runs: what ran, in what environment, on which files, and the result. Receipts are kept with the state they ran on. ``` oxy receipt list # receipts for the bookmark's state oxy receipt show 3f2a9c # one in full, with its signature checked oxy receipt export 3f2a9c build.receipt # hand it to someone oxy receipt verify build.receipt # check one without keeping it oxy receipt import build.receipt # verify and keep it oxy receipt export 3f2a9c prov.json --format slsa # SLSA provenance, signed with your key oxy identity export --pem me.pem # the key cosign checks it with ``` The SLSA form is for tools outside Oxygen: in-toto and SLSA verifiers, and `cosign verify-blob-attestation --key me.pem --type slsaprovenance1 --check-claims=false --signature prov.json`. Its subjects are named by Oxygen hashes, which cosign cannot compute from a file, hence `--check-claims=false`. ## 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. ## 11a. Nested repositories (submodules) A subrepo is another repository checked out at a path and pinned to a state, Oxygen or Git. The pin is versioned like a file, so two people pinning different states get an ordinary conflict. ``` oxy submodule add https://host/lib.git libs/lib # Git or Oxygen URL; records .oxymodules oxy commit -m "Add lib" # the pin is committed with the parent oxy submodule status # pin vs what is on disk oxy submodule update --init # after a clone: fetch nested repositories oxy submodule remove libs/lib # drop the node; the directory stays ``` Committing the parent records whatever the nested repository is at, and checking out an older parent state moves the nested repository back, unless it has uncommitted changes, which are never overwritten. An absent nested repository keeps its pin until you run `update --init`. A `.oxymodules` section written by hand has no pin until `oxy submodule add` clones it; `status` and `update` name such entries, and `update` exits non-zero. Git submodules survive `oxy git import` as Git pins and go back out as gitlinks on export. ## 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. A long import moves its bookmark to the commits done so far every 2000 commits, so if it stops, `oxy git fetch GIT_DIR --branch BRANCH` in the new repository carries on from there. One stopped before its first checkpoint (or by an older oxy) is recovered first with `oxy git recover`, which indexes the objects it had written and points the bookmark at them. ## 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. `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. Built with `cargo build --release --features alloc-stats`, oxy also reports each phase's live and peak memory and what it allocated, and a summary at the end. | Key | Meaning | |---|---| | `author.name`, `author.email`, `author.key` | who you are; `key` is set by `identity create` | | `author.signer` | the key that signs instead of `author.key`: an agent's own key, which your identity lists as a delegate; or `keyless`, for fresh keys the hub certifies under the agent's grant (also `$OXY_SIGNER`) | | `keyless.hub` | the hub that certifies keyless keys (default: the default remote's hub) | | `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`, then `oxy auth login`'s store | | `auth.helper` (user file) | a Git credential helper to keep `oxy auth` tokens in, such as `libsecret` | ## 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 # prune unreachable objects past the grace period, repack into one pack oxy gc --now # ignore the grace period (only when nothing else is writing) oxy fsck # check every object, the op log, references, signatures, checkpoints oxy doctor # check identity, config and remotes, with what to do about each problem 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. ## 17a. Scripting and editors Every reporting command takes `--json` (`status`, `log --path`, `show`, `diff`, `hunks`, `blame`, `resolve`, `op log`, `bookmark list`, `remote list`, `fetch`, `pull`, `push`, `submodule status`, `bisect status`, `verify`, `policy show`, `root`, `version`). `oxy cat PATH -r CHANGE` prints a file as it was, `oxy discard PATH..` restores files to the base, and `oxy hunks` numbers the working copy's hunks for `commit --hunks` and `discard --hunks`. ## 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. ## Shell completions and man pages ```sh oxy completions bash > ~/.local/share/bash-completion/completions/oxy # bash oxy completions zsh > "${fpath[1]}/_oxy" # zsh oxy completions fish > ~/.config/fish/completions/oxy.fish # fish oxy man --dir ~/.local/share/man/man1 # then: man oxy-commit ``` The oh-my-bash plugin in `contrib/oh-my-bash` loads the bash completions itself.