| 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 |
**A state is its heads, not a history.** If you are coming from Git, this |
| 107 |
is the one thing to know. A bookmark holds its heads and what they depend |
| 108 |
on, and a change depends only on the changes it builds on (the files it |
| 109 |
touched), not on everything recorded before it. So on a bookmark with |
| 110 |
several heads, a new bookmark at one change can be a much older-looking |
| 111 |
tree. `oxy bookmark create NAME CHANGE` refuses when that would leave out |
| 112 |
earlier work and lists what it would lack. Then pick one: |
| 113 |
|
| 114 |
``` |
| 115 |
oxy bookmark create logo 745307b4 --upto # everything here up to that change, as a Git branch would be |
| 116 |
oxy bookmark create logo 745307b4 --only # just that change and what it depends on |
| 117 |
oxy bookmark create logo # the whole current state, usually what you want |
| 118 |
``` |
| 119 |
|
| 120 |
`oxy bookmark set NAME CHANGE` asks the same when the move would lose |
| 121 |
changes the bookmark holds that were recorded before CHANGE, and takes |
| 122 |
`--upto` and `--only` too. After merging a proposal's target into its |
| 123 |
bookmark to resolve conflicts, `oxy bookmark set NAME CHANGE --upto` |
| 124 |
keeps everything the bookmark held. |
| 125 |
|
| 126 |
`oxy bookmark switch` says when the bookmark you switch to lacks changes |
| 127 |
that the one you left has. |
| 128 |
|
| 129 |
### A develop / feature flow |
| 130 |
|
| 131 |
Names with `/` work everywhere, pushing included: |
| 132 |
|
| 133 |
``` |
| 134 |
oxy bookmark create feature/42-login # at the working copy's base |
| 135 |
oxy bookmark switch feature/42-login --carry # started already? take the edits along |
| 136 |
oxy commit -F msg.txt # a long message from a file |
| 137 |
oxy push -b feature/42-login |
| 138 |
oxy bookmark switch develop |
| 139 |
oxy merge feature/42-login # the working copy follows when it was clean |
| 140 |
oxy push # develop, with the feature in it |
| 141 |
oxy bookmark delete feature/42-login # oxy push --delete feature/42-login on the remote |
| 142 |
``` |
| 143 |
|
| 144 |
`bookmark switch` stops when the working copy has uncommitted edits, so |
| 145 |
nothing is lost by accident. There are two ways past it: |
| 146 |
- `--carry` takes the edits along. It is refused only when the target |
| 147 |
bookmark has other versions of the files they touch. |
| 148 |
- `--discard` drops the edits. |
| 149 |
|
| 150 |
`oxy describe` names what the working copy holds, for build stamps: |
| 151 |
- a tag, when a tag holds exactly this state; |
| 152 |
- otherwise the nearest tag with the distance from it and the change; |
| 153 |
- or, with no tag behind it, the bookmark and the change; |
| 154 |
- with `-dirty` added when there are uncommitted edits. |
| 155 |
|
| 156 |
## 4. Fix up history |
| 157 |
|
| 158 |
Oxygen keeps its own record of every operation, so all of these are safe to |
| 159 |
try and to undo. |
| 160 |
|
| 161 |
``` |
| 162 |
oxy describe -m "Better message" # reword the head the working copy sits on |
| 163 |
oxy describe 658990aa -m "..." # reword any change; dependents are rewritten |
| 164 |
oxy amend # fold the working copy into the head |
| 165 |
oxy amend --into 658990aa # fold it into an older change |
| 166 |
oxy split 658990aa src/parser.rs # split a head: those paths become a separate first change |
| 167 |
oxy abandon 658990aa # drop a change; its content stays on disk |
| 168 |
oxy squash 658990aa --into 9df9ac29 # fold a change into one it depends on |
| 169 |
oxy rebase 658990aa --onto feature # re-express a change against another base |
| 170 |
oxy rebase 658990aa --onto develop --into-working-copy # conflicted? resolve it here, then commit |
| 171 |
oxy undo # undo the last operation, whatever it was |
| 172 |
oxy op log # the operation log |
| 173 |
``` |
| 174 |
|
| 175 |
Rewriting a change that others depend on rewrites them too, keeping their |
| 176 |
ChangeIds. Because unrelated changes already coexist, `rebase` is rarely |
| 177 |
needed; its job is to change what a change is said to build on, for instance |
| 178 |
to drop a dependency on something you are about to abandon. Where a dependent referred to a line that no longer exists, the |
| 179 |
reference is re-anchored to the nearest surviving line and `oxy abandon` |
| 180 |
prints a note about it. |
| 181 |
|
| 182 |
## 4a. Who wrote this, and what broke it |
| 183 |
|
| 184 |
``` |
| 185 |
oxy blame src/parser.rs # every line: the change that introduced it, author, age |
| 186 |
oxy blame src/parser.rs --json |
| 187 |
``` |
| 188 |
|
| 189 |
Blame is exact: each line is an atom that remembers the change that |
| 190 |
introduced it, so renames cost nothing and there is no heuristic. |
| 191 |
|
| 192 |
``` |
| 193 |
oxy bisect start |
| 194 |
oxy bisect good v1.2 # a change, bookmark or tag known to be fine |
| 195 |
oxy bisect bad main # and one known to be broken |
| 196 |
oxy bisect run make test # exit 0 good, 125 skip, anything else bad |
| 197 |
oxy bisect reset # back to where you were |
| 198 |
``` |
| 199 |
|
| 200 |
Without `run`, each step checks out a state to test and you answer with |
| 201 |
`oxy bisect good`, `bad`, or `skip`. The candidates are the changes the bad |
| 202 |
state has that the good one lacks, ordered so every tested state is |
| 203 |
well-formed; the answer is the first change whose inclusion breaks things. |
| 204 |
|
| 205 |
## 5. Move around |
| 206 |
|
| 207 |
``` |
| 208 |
oxy checkout # the bookmark's heads |
| 209 |
oxy checkout 658990aa # the state of one change and its dependencies |
| 210 |
oxy checkout v1.2 # a tag |
| 211 |
oxy checkout --discard ... # throw away uncommitted work first |
| 212 |
``` |
| 213 |
|
| 214 |
Checking out a single older change gives you the files that change and its |
| 215 |
dependencies touch, not a full snapshot from that date: in Oxygen a change |
| 216 |
depends only on what it actually builds on. |
| 217 |
|
| 218 |
## 6. Conflicts |
| 219 |
|
| 220 |
A conflict never stops a commit, fetch, or pull. It is recorded in the |
| 221 |
state and shown by `oxy status`, which exits with status 2 while any |
| 222 |
conflict remains. Conflicted text files are written with markers: |
| 223 |
|
| 224 |
``` |
| 225 |
<<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 "Add retry loop" |
| 226 |
... lines ... |
| 227 |
======= oxy conflict 1 of 2, side 2: change q3x8w1 "Refactor client" |
| 228 |
... lines ... |
| 229 |
>>>>>>> oxy conflict 1 of 2 end |
| 230 |
``` |
| 231 |
|
| 232 |
Edit the file to what it should be (keep one side, both, or neither) and |
| 233 |
remove the markers. Tree conflicts, such as two files wanting the same |
| 234 |
name, appear as `<name>.oxy-conflict-<n>` candidates: keep the one you |
| 235 |
want under the right name and delete the rest. |
| 236 |
|
| 237 |
``` |
| 238 |
oxy resolve # which conflicts your edits resolve, which still have markers |
| 239 |
oxy commit -m "Resolve retry vs refactor" |
| 240 |
``` |
| 241 |
|
| 242 |
The commit is recorded as a *resolution*, a change that depends on every |
| 243 |
side of the conflict; anyone who fetches it gets the conflict resolved. |
| 244 |
|
| 245 |
## 7. Work with others |
| 246 |
|
| 247 |
Run a server (any machine with the repositories under one directory): |
| 248 |
|
| 249 |
``` |
| 250 |
oxy serve --root /srv/oxy --bind 0.0.0.0:8890 --token SECRET \ |
| 251 |
--tls-cert cert.pem --tls-key key.pem |
| 252 |
``` |
| 253 |
|
| 254 |
Without `--tls-*` the server speaks plain HTTP; keep that on a loopback |
| 255 |
address or behind a TLS terminator. `--token` makes pushes require a bearer |
| 256 |
token; reads stay open. |
| 257 |
|
| 258 |
On your machine: |
| 259 |
|
| 260 |
``` |
| 261 |
oxy clone https://host:8890/project |
| 262 |
cd project |
| 263 |
oxy config remote.origin.token SECRET # or export OXY_TOKEN |
| 264 |
oxy config remote.origin.ca my-ca.pem # self-signed server: extra roots to trust (or OXY_CA) |
| 265 |
oxy push |
| 266 |
oxy pull |
| 267 |
``` |
| 268 |
|
| 269 |
- `oxy fetch` updates `origin/main` without touching your bookmark. |
| 270 |
- `oxy pull` fetches, then makes your bookmark the union of both sides. |
| 271 |
There is no merge commit; unrelated changes simply coexist, and a real |
| 272 |
conflict is recorded as in section 6. The working copy follows when it was |
| 273 |
clean. |
| 274 |
- `oxy push` sends your changes and moves the remote bookmark. If the remote |
| 275 |
moved since you last fetched, the push is refused: pull and push again. |
| 276 |
`--force` overrides the compare-and-swap. |
| 277 |
- `oxy remote add|remove|list` manages remotes; the clone source is `origin`. |
| 278 |
|
| 279 |
A repository can also be cloned from a local path, which is handy for tests |
| 280 |
and for a bare "hub" on a shared disk. |
| 281 |
|
| 282 |
## 8. Decide who may publish: the trust policy |
| 283 |
|
| 284 |
Without a policy a repository is *unverified*: everything is accepted and |
| 285 |
every remote command says so. A policy names root keys and the keys allowed |
| 286 |
to update each bookmark; servers and clients enforce it. |
| 287 |
|
| 288 |
``` |
| 289 |
oxy policy init # your key becomes the only root; push publishes it |
| 290 |
oxy policy show |
| 291 |
oxy policy grant alice.identity # alice.identity came from her `oxy identity export` |
| 292 |
oxy policy grant ci.identity --pattern 'release/*' |
| 293 |
oxy policy revoke 9458b656 # by KeyId prefix |
| 294 |
oxy verify # signature status of every change in the log |
| 295 |
``` |
| 296 |
|
| 297 |
Several people can hold root keys: |
| 298 |
|
| 299 |
``` |
| 300 |
oxy policy root add bob.identity |
| 301 |
oxy policy root threshold 2 # future updates need two root signatures |
| 302 |
``` |
| 303 |
|
| 304 |
Once the threshold is above one, an update you make alone becomes a |
| 305 |
*proposal file* (`policy-<serial>.proposal`, or `--out`). Send it to another |
| 306 |
root holder, who runs `oxy policy sign policy-7.proposal`; whoever completes |
| 307 |
the threshold applies it, and the next push publishes it. Everyone else |
| 308 |
picks it up on fetch, verifying each step from the policy they already |
| 309 |
trust. |
| 310 |
|
| 311 |
## 9. Keys over time |
| 312 |
|
| 313 |
``` |
| 314 |
oxy identity rotate # fresh key pair; old signatures stay valid |
| 315 |
oxy identity renew --expires 365 # same keys, new expiry in days (0 = never) |
| 316 |
oxy identity delegate add ci.identity # let another key sign on your behalf |
| 317 |
oxy identity delegate remove 3f2a… |
| 318 |
oxy identity revoke # retire your own key |
| 319 |
oxy identity revoke 3f2a… --reason "laptop stolen" # any key, if you hold a root |
| 320 |
``` |
| 321 |
|
| 322 |
Rotation, renewal, and delegation all issue a successor identity that names |
| 323 |
the previous one. If a policy names your key, it is updated to the successor |
| 324 |
in the same step (as a proposal when more root signatures are needed). |
| 325 |
Changes signed before the rotation remain authorised; a revoked key |
| 326 |
authorises nothing. `oxy verify` shows signatures by revoked keys as valid |
| 327 |
but revoked. |
| 328 |
|
| 329 |
## 10. Tags and workspaces |
| 330 |
|
| 331 |
``` |
| 332 |
oxy tag create v1.2 -m "First release" # the current heads; or name changes |
| 333 |
oxy tag list |
| 334 |
oxy workspace add ../project-hotfix # a second checkout sharing the repository |
| 335 |
oxy workspace list |
| 336 |
oxy workspace forget ../project-hotfix |
| 337 |
``` |
| 338 |
|
| 339 |
A tag is an immutable state, signed when you have a key, and travels with |
| 340 |
push and fetch. Workspaces have their own working copy and bookmark |
| 341 |
(`workspace.bookmark`) but share objects and history. |
| 342 |
|
| 343 |
### Receipts from CI |
| 344 |
|
| 345 |
A runner such as OxygenFlow signs a receipt for every job it runs: what ran, |
| 346 |
in what environment, on which files, and the result. Receipts are kept with |
| 347 |
the state they ran on. |
| 348 |
|
| 349 |
``` |
| 350 |
oxy receipt list # receipts for the bookmark's state |
| 351 |
oxy receipt show 3f2a9c # one in full, with its signature checked |
| 352 |
oxy receipt export 3f2a9c build.receipt # hand it to someone |
| 353 |
oxy receipt verify build.receipt # check one without keeping it |
| 354 |
oxy receipt import build.receipt # verify and keep it |
| 355 |
``` |
| 356 |
|
| 357 |
## 11. Big repositories: partial, sparse, shallow |
| 358 |
|
| 359 |
``` |
| 360 |
oxy clone URL --max-blob 1M # files above 1 MiB are fetched on first read |
| 361 |
oxy clone URL --sparse src --sparse docs # materialise only these subtrees |
| 362 |
oxy clone URL --depth 50 # only the last 50 levels of history |
| 363 |
oxy sparse src docs tests # change the filter later |
| 364 |
oxy deepen 100 # fetch more history below the boundary |
| 365 |
``` |
| 366 |
|
| 367 |
Lazy blobs and sparse trees do not affect commits: what you do not have is |
| 368 |
left exactly as it was. A shallow clone starts from a *checkpoint* of the |
| 369 |
state, so status and commit work at full speed from the first minute. |
| 370 |
|
| 371 |
## 11a. Nested repositories (submodules) |
| 372 |
|
| 373 |
A subrepo is another repository checked out at a path and pinned to a state, |
| 374 |
Oxygen or Git. The pin is versioned like a file, so two people pinning |
| 375 |
different states get an ordinary conflict. |
| 376 |
|
| 377 |
``` |
| 378 |
oxy submodule add https://host/lib.git libs/lib # Git or Oxygen URL; records .oxymodules |
| 379 |
oxy commit -m "Add lib" # the pin is committed with the parent |
| 380 |
oxy submodule status # pin vs what is on disk |
| 381 |
oxy submodule update --init # after a clone: fetch nested repositories |
| 382 |
oxy submodule remove libs/lib # drop the node; the directory stays |
| 383 |
``` |
| 384 |
|
| 385 |
Committing the parent records whatever the nested repository is at, and |
| 386 |
checking out an older parent state moves the nested repository back, unless |
| 387 |
it has uncommitted changes, which are never overwritten. An absent nested |
| 388 |
repository keeps its pin until you run `update --init`. Git submodules |
| 389 |
survive `oxy git import` as Git pins and go back out as gitlinks on export. |
| 390 |
|
| 391 |
## 12. Git interoperability |
| 392 |
|
| 393 |
``` |
| 394 |
oxy git import ../project.git ./project-oxy --branch main # convert a Git history |
| 395 |
oxy git fetch https://github.com/you/project.git # bring new Git commits in |
| 396 |
oxy git push https://github.com/you/project.git # publish your changes to Git |
| 397 |
oxy git export ../mirror.git # a plain Git mirror of the bookmark |
| 398 |
``` |
| 399 |
|
| 400 |
Import turns each commit into one change (merge commits become |
| 401 |
resolutions), keeps authors and hashes as `Git-Author`/`Git-Commit` |
| 402 |
trailers, detects renames, and skips submodules. `git fetch`/`git push` keep |
| 403 |
a bare mirror under `.oxy/git/`; a push is refused until the remote's tip |
| 404 |
has been fetched, so it always fast-forwards. Exported commits carry |
| 405 |
`Change-Id` and `Oxy-Hash` trailers. |
| 406 |
|
| 407 |
## 13. Automate: hooks |
| 408 |
|
| 409 |
Executables in `.oxy/hooks/<event>` (never cloned) receive one JSON object |
| 410 |
on stdin. `pre-commit`, `pre-push` and other `pre-*` hooks veto by exiting |
| 411 |
non-zero, and their stderr becomes the error message. Events: |
| 412 |
`pre-commit`, `post-commit`, `pre-push`, `post-fetch`, `post-checkout`, and |
| 413 |
`semantic-deps`, which runs at commit when `hooks.semantic-deps = true` and |
| 414 |
answers `{"deps": ["<hash>", …]}` to make the change depend on more than it |
| 415 |
textually references. |
| 416 |
|
| 417 |
## 14. Per-path attributes, ignores, locks |
| 418 |
|
| 419 |
`.oxyattributes` uses gitattributes syntax: |
| 420 |
|
| 421 |
``` |
| 422 |
*.png binary |
| 423 |
*.txt text eol=lf |
| 424 |
*.psd lockable nocompress |
| 425 |
``` |
| 426 |
|
| 427 |
`binary`/`text` override content sniffing, `eol=lf|crlf` normalises line |
| 428 |
endings in the store and on checkout, `nocompress` stores chunks raw, and |
| 429 |
`lockable` files are checked out read-only until you take the local lock: |
| 430 |
|
| 431 |
``` |
| 432 |
oxy lock assets/logo.psd |
| 433 |
oxy unlock assets/logo.psd |
| 434 |
``` |
| 435 |
|
| 436 |
Locks are advisory and local; server-side locks are planned. |
| 437 |
|
| 438 |
## 15. Configuration |
| 439 |
|
| 440 |
Two files, both `key = "value"` lines: `~/.config/oxy/config.toml` |
| 441 |
(`oxy config --user KEY VALUE`) and `.oxy/config` (`oxy config KEY VALUE`). |
| 442 |
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. |
| 443 |
|
| 444 |
|
| 445 |
|
| 446 |
| `author.name`, `author.email`, `author.key` | who you are; `key` is set by `identity create` | |
| 447 |
| `core.sign` | `change` to sign every commit; otherwise seals at push | |
| 448 |
| `core.op-retention` | days for which old operations keep their states alive (90) | |
| 449 |
| `core.gc-grace` | days an unreachable object survives (14) | |
| 450 |
| `core.checkpoint-interval` | changes between automatic checkpoints (256) | |
| 451 |
| `filter.max-blob`, `filter.paths` | lazy blobs and sparse subtrees (section 11) | |
| 452 |
| `hooks.semantic-deps` | run the semantic-deps hook at commit | |
| 453 |
| `workspace.bookmark` | this workspace's bookmark (`main`) | |
| 454 |
| `remote.<name>.url`, `.token`, `.ca` | remotes; token and CA fall back to `OXY_TOKEN` and `OXY_CA` | |
| 455 |
|
| 456 |
## 16. Shell prompt |
| 457 |
|
| 458 |
`oxy prompt` prints `main 658990aa*+2` (bookmark, working copy, `*` dirty, |
| 459 |
`!` conflicted, ahead/behind origin) in a few milliseconds and records |
| 460 |
nothing. See [prompt.md](prompt.md) for bash, oh-my-bash, zsh, and fish |
| 461 |
snippets and the `--format` placeholders. |
| 462 |
|
| 463 |
## 17. Housekeeping |
| 464 |
|
| 465 |
``` |
| 466 |
oxy gc # prune unreachable objects past the grace period, repack into one pack |
| 467 |
oxy gc --now # ignore the grace period (only when nothing else is writing) |
| 468 |
oxy fsck # check every object, the op log, references, signatures, checkpoints |
| 469 |
oxy doctor # check identity, config and remotes, with what to do about each problem |
| 470 |
oxy checkpoint # serialise the bookmark's state so large histories load instantly |
| 471 |
``` |
| 472 |
|
| 473 |
Both run automatically: packing when loose objects pile up, checkpoints |
| 474 |
every `core.checkpoint-interval` changes. |
| 475 |
|
| 476 |
## 17a. Scripting and editors |
| 477 |
|
| 478 |
Every reporting command takes `--json` (`status`, `log --path`, `show`, |
| 479 |
`diff`, `hunks`, `blame`, `resolve`, `op log`, `bookmark list`, `remote list`, |
| 480 |
`fetch`, `pull`, `push`, `submodule status`, `bisect status`, `verify`, |
| 481 |
`policy show`, `root`, `version`). `oxy cat PATH -r CHANGE` prints a file as |
| 482 |
it was, `oxy discard PATH..` restores files to the base, and `oxy hunks` |
| 483 |
numbers the working copy's hunks for `commit --hunks` and `discard --hunks`. |
| 484 |
|
| 485 |
## 18. If something looks wrong |
| 486 |
|
| 487 |
- **"the remote has change … which you do not; pull first"**: someone |
| 488 |
pushed since your last fetch. `oxy pull`, then push. |
| 489 |
- **"policy needs N root signature(s)"** on fetch: the server's policy was |
| 490 |
updated in a way your copy cannot verify. Ask a root holder whether the |
| 491 |
chain of updates was published; a fresh clone trusts the current policy. |
| 492 |
- **`oxy status` exits 2**: conflicts remain; see section 6. |
| 493 |
- **Many heads on `main`**: independent changes that do not reference one |
| 494 |
another. Nothing to fix; the state is their union. |
| 495 |
- **Undo anything**: `oxy undo` reverts the last operation, including |
| 496 |
pulls, checkouts, and rewrites. |
| 497 |
|
| 498 |
## Shell completions and man pages |
| 499 |
|
| 500 |
```sh |
| 501 |
oxy completions bash > ~/.local/share/bash-completion/completions/oxy # bash |
| 502 |
oxy completions zsh > "${fpath[1]}/_oxy" # zsh |
| 503 |
oxy completions fish > ~/.config/fish/completions/oxy.fish # fish |
| 504 |
oxy man --dir ~/.local/share/man/man1 # then: man oxy-commit |
| 505 |
``` |
| 506 |
|
| 507 |
The oh-my-bash plugin in `contrib/oh-my-bash` loads the bash completions itself. |