OXYGENHUB
# d6e0d5b8b339 oxygen / CHANGELOG.md
24.8 KiB · Markdown · last changed git import and fetch --claim: author your own commits with your key 26 days ago renderedsource blame history raw
1
# Changelog
2
3
## Unreleased
4
5
- **Claim your own commits in a Git import (#97).** `oxy git import --claim` and `oxy git fetch --claim` set `git.claim`: a commit whose Git author email is your identity's email is authored with your key (and sealed by you on push), so a forge credits it to your account; anyone else's commits keep the zero key, as before.
6
7
- **Git imports of long histories much faster (#96).** Rendering a file asks whether the changes that deleted and inserted neighbouring lines depend on each other; in a deep history each answer was a walk through thousands of changes, asked again for every line, under a lock every rendering thread queued on. Answers are now remembered for the file being rendered, the shared memos are read without excluding other threads, and a merge renders its files on every core. On VS Code past 66 000 commits, 500 commits imported in 261 s rather than 734 s, with the same history.
8
9
- **A long push goes in parts (#95).** `oxy push` sent a whole history in one request, held in memory on both sides; the full VS Code import would have passed a hub's 4 GiB limit. It now sends parts of about `push.batch-bytes` (default 256 MiB) in dependency order, objects alone, and the last part moves the bookmark; the spinner says which part is going. A server stores a part and reports it as `objects`.
10
11
- **A state index read only as far as it is used (#94).** Each file's history in the workspace's state index is stored after the rest with a hash of its own, and is read and checked only when a command builds that file, so a diff or a one-file cat no longer reads and hashes the histories of every file (67 MB of an 84 MB index on VS Code at 20 000 commits). Changes added on top of an index no longer decode the histories they touch. A history found damaged is an error that says so, and the index is removed and rebuilt by the next command. Under load on that repository, `diff` went from 85 ms to 63 ms and `cat`/`blame` from 102 ms to 80 ms.
12
13
- **Removing a file removes every copy of it (#88).** When two lines of history each put a file back in the same place, it has two placements there and shows as one file. Removing or moving it retired only one, so the other stayed behind, and once its directory went it was an orphan: a conflict in the state. It is the one difference the Git import of VS Code had in its first 31 000 commits. Both now go together, in a commit and in a Git import.
14
15
- **A quicker log and diff on long histories (#94, first part).** `oxy log` no longer reads the working copy's metadata through the store's cache, walks the history with a fast hash and without copying each change's metadata, and works out the shortest unique id prefixes for the changes it shows rather than for every change. The state index is read without copying the file histories in it, and its change list is checked on every core. On VS Code at 20 000 commits, `log -n 20` went from 74 ms to 40 ms and loading the state index from 43 ms to 28 ms, with the same output.
16
17
- **Faster commits, and the state index after a checkpoint (#93).** A state loaded through a checkpoint read the changes after it whole, text included, so it could never be saved as the workspace's state index and every command after a checkpoint loaded history the long way (25 s for the first commit on VS Code at 20 000 commits). It now reads them as the index needs them. `oxy commit PATHS` no longer takes a second snapshot to count what it left when it took everything. A one-file commit on VS Code went from about 450 ms to 300 ms; 70 ms of that is the store's fsync, which stays.
18
19
- **Reading one file reads one file (#92).** `oxy cat` rendered every file of the state to print one. It now builds only that file, from the workspace's state index when it reads the working copy's base or the state it is on. On VS Code at 20 000 commits, `cat package.json -r main` went from 26 s to 0.10 s and at a change from 2016 from 0.66 s to 0.16 s; on the full 166 000-change history it takes 1.9 s and 1.3 GB, where it ran for minutes and used 9 GB.
20
21
- **A file's history in a fraction of a second (#91).** `oxy log --path` rendered every file of the tree twice just to find the path, and both it and the `touches()` revset decoded every change of the history, its text included, to see what each one touched. They now read the path from the tree alone and keep the nodes each change touches in a cache beside the store (`.oxy/index/touched`, rebuilt if missing). On VS Code at 20 000 commits, `log --path package.json` went from 53.6 s to 0.16 s (Git: 0.15 s) and `touches("package.json")` from 3.4 s to 0.16 s, with the same output.
22
23
## 0.4.2 (2026-10-09)
24
25
- **`pull` brings a working copy that fell behind up to its bookmark (#89).** The working copy followed a pull only from exactly the bookmark's old heads, and not at all when the pull brought nothing new, so one left behind by `bookmark set`, a landing or a pull from another workspace stayed on old code while `status` said clean. It now follows whenever its base is part of the bookmark's state, clean or carrying uncommitted work, and `status` says when the working copy is behind its bookmark and by how many changes.
26
27
- **Faster merges in a Git import (#80).** A merge is built from only the paths it can change, as an ordinary commit is: on VS Code at 139 000 commits merges import 3.2 times faster (9.5 s against 30.9 s on average) and the import as a whole 2.7 times faster, with exactly the history the whole-tree path builds. `OXY_IMPORT_WHOLE_MERGES=1` takes the old path.
28
29
- **A binary file changed and made executable at once keeps both (#87).** `oxy commit` and the Git import dropped the executable bit when a binary file got new contents in the same change.
30
31
- **Several remotes and no origin (#83).** `core.remote` names the default remote; `bookmark list` compares with a remote that has the bookmark and names it; the error without a default says what to do.
32
33
- **No i386 notice from apt (#85).** The .deb's apt source names the machine's own architecture, and an upgrade adds it to a source written before, so a system with i386 enabled no longer warns on every update.
34
35
- **Clearer messages.** A missing author identity says which config set it and how to pin the right one (#82); `oxy git verify --max-differences N` and a summary of what differs by kind.
36
37
## 0.4.1 (2026-10-08)
38
39
- **The prompt agrees with status in a repository with subrepos (#75).** Straight after a commit or a merge, `oxy prompt` took a subrepo's directory for a recently changed file and showed `*` until a full `oxy status` ran. Subrepos are now compared by their pins.
40
41
- **Catching up a stale bookmark (#74).** `oxy bookmark switch` brings a bookmark that is only behind its remote up to it first, rather than checking out the stale state. `oxy pull` takes uncommitted work along when the pulled changes leave its files alone, instead of leaving the working copy behind, and `checkout --carry` no longer counts a subrepo already at the target's pin as a change.
42
43
- **`bookmark set` fast-forwards (#78).** Moving a bookmark onto work built on its newest head keeps the older changes it held that the new one doesn't depend on, as `--upto` does, and says so. A head the bookmark already holds is still refused without `--upto` or `--only`.
44
45
- **A bookmark's heads by name (#73).** `oxy bookmark show [NAME]` lists each head, newest first; `bookmark list -v` does it for every bookmark, and `bookmark list` names the newest head's subject. `bookmark(NAME)` in a revset is a local or remote bookmark's heads and nothing else.
46
47
- **Clearer messages.** `submodule status` and `update` name `.oxymodules` entries that were never added, with the command that pins them, and `update` exits 1 (#72). `oxy cat -r CHANGE`'s help and error say that a change means its dependency closure, and suggest `-r BOOKMARK` or `--at-op` (#76).
48
49
- **A long Git import can stop and carry on (#77, #79).** An import moves its bookmark to the commits done so far every 2 000 commits, so `oxy git fetch` resumes it. `oxy git recover` rescues an import that stopped before its first checkpoint, or ran on an older oxy: it indexes the objects already written (each checked, a torn last one cut off) and points the bookmark at them. It recovered 139 086 commits of VS Code in under two minutes. `oxy git fetch ../path` with a relative path works.
50
51
- **`oxy git verify` in bounded memory (#81).** It held the ancestry of every commit and ran out of memory on VS Code; it now holds only the commits whose children are still to come, and checks 120 000 commits in under 1 GB. On an import not finished yet it stops at the last imported commit, counts `--last` from there, and says how many are still to come.
52
53
- **Measuring an import (#80).** `OXY_IMPORT_STOP_AFTER=N` ends an import cleanly after N commits with its timings; the trace gives each commit's time and which path each merge took. Merges built from only the paths they can change are in, behind `OXY_IMPORT_PARTIAL_MERGES=1` until verified at depth: on VS Code a small merge goes from about 18 s to under a second. An import stopping early no longer dies of SIGPIPE.
54
55
- **Cloud stores take credentials handed over (#68).** `oxy_strata::Credentials` (an S3 access key, a Google service account key, an Azure service principal) can be given when a store is opened, and a program can register a lookup for the stores it opens by URL. With them, nothing from the environment or the providers' files is used, which lets a hub keep each repository's credentials itself. Their debug form never shows a secret.
56
57
- **A repository kept on a store (#68).** `oxy init --store URL` keeps a repository's objects and operation log on `dir://`, S3, GCS or Azure, behind a local cache of `storage.cache` bytes (2 GiB by default) that can be deleted at any time. Checked against the real services: a commit takes 0.5 to 0.9 seconds, and reopening with no cache about half a second.
58
59
- **A caching store, and the operation head on a store (#68).** `strata::CachingStore` puts a local directory in front of any store: reads are kept, writes go to the remote first, the cache stays under a size limit by removing what was used least recently, and references always come from the remote. A repository kept on a store other than this disk's directory now holds its operation head there, moved by compare-and-swap with merge and retry.
60
61
- **A large merge's file no longer imports empty (#71).** When a merge rendered its files in batches, a file whose graph the import had already built came out empty, so the merge recorded the whole file as inserted again and conflicted with itself: VS Code's history failed verification from 2016 on. Built graphs are now copied. The 10 412-commit reproducer imports and verifies every commit.
62
63
- **Git import holds a budget of graph memory (#25).** A long-lived file's graph keeps every line it ever had, and the import shed a kept state's graphs only past 2 000 of them; it now also sheds them past 1.5 million atoms, and gives freed memory back after each merge. A 28 637-commit slice of VS Code imports at 5.1 GB instead of 16.9 GB, in 36 minutes instead of 61, with the same verify result. The dependency check no longer memoises every queried change's whole ancestry.
64
65
- **A rotation follows you everywhere (#69).** `delegate add`, `delegate remove`, `rotate` and `renew` record the successor key in the user config when it named the old key, and in the repository's own file when that pinned it, so every checkout moves together. `oxy identity show` warns when a repository pins a key other than the user config's; `oxy config --unset KEY` drops a key; `oxy identity delegate add` takes several identity files in one rotation and reminds you that agents configured with your `author.key` need the new one.
66
67
## 0.4.0 (2026-10-04)
68
69
- **Oxygen Strata: a repository on a bucket (#63).** `oxy remote add NAME s3://BUCKET/PREFIX` (`--profile`, `--region`, `--endpoint`), `gs://BUCKET/PREFIX` (`--credentials`), `az://ACCOUNT/CONTAINER/PREFIX`, or `dir://PATH`, then `push`, `pull`, `clone` and `gc` against it as against any remote, with no server in between: objects are written once, the references record moves by compare-and-swap, and two pushers merge and retry rather than lose each other's work. Credentials come from each cloud's own chain. `oxy remote check` runs the conformance suite against a store before it is trusted. `docs/strata.md` is the guide; the bucket layout is not stable before 1.0.
70
71
- **The object store on any Store (#68).** `.oxy/objects` is now the same `Store` a bucket is, with the same keys and the same speed: the first step towards a repository that lives in its owner's bucket with the hub as one client of it. Signatures, seal and receipt entries are written once each, so two writers of a shared store never lose each other's; older repositories still read.
72
73
- **Scoped, expiring delegates (#67).** `oxy identity delegate add FILE --scope changes --expires 8h` lets an AI agent sign changes for you while it can never move a bookmark or tag, or seal, whatever you may do; a host that lands for you gets `changes,refs`. You can revoke a key you delegated to. Older identities still read: a bare key means every scope.
74
75
- **An agent signs as itself, you stay the author.** The agent's keys live in its own `$OXY_HOME`, and `author.signer` (or `$OXY_SIGNER`) names its key, so `oxy commit` in your checkout carries you as author and the agent's signature. `oxy log` says `via NAME` beside the author, `oxy show` has a `via` row with the agent's identity and the delegation it acted under, `oxy identity show` names each delegate, and `oxy log -r 'delegated()'` lists what agents did. The guide's §9 has the setup.
76
77
- **Revsets (#65).** `oxy log -r`, `show -r`, `verify -r` and `abandon -r` take an expression over history: symbols (bookmarks, `origin/main`, tags, change prefixes, `@`), `::X`, `X::`, `X..Y`, `|`, `&`, `~`, `deps`, `dependents`, `heads`, `roots`, `latest`, `independent`, `author`, `description`, `date` (`-7d..`), `kind`, `touches` (through renames), `conflicts`, and the trust predicates `signed_by`, `delegated`, `unsigned`, `revoked`, `attested`, `built`. Saved revsets: `oxy config revset.mine 'author(me) & ::@'` makes `mine()`. A first page takes about 10 ms on this repository; evaluation has a time budget. `docs/revset_query_language.md`.
78
79
- **`oxy gc` and `oxy git import` say what they are doing (#66, #25).** Each phase and count of a gc, and every 500 commits of an import with the commit in hand, through the spinner or as lines without a terminal.
80
81
- **Large merges import in batches (#25).** A merge touching thousands of files builds and renders its graphs 500 files at a time: a 10 000-commit slice of VS Code imports in 135 s at 1.6 GB (from 140 s at 1.6 GB, and far less for the merges alone). The whole history still needs more than 20 GB at its largest merges, which is the next piece of work.
82
83
- **Release builds link on every target.** The cloud stores send through `ureq`, so macOS and Windows builds no longer pull in a C library the cross-compiler cannot link.
84
85
## 0.3.1 (2026-10-02)
86
87
- **RISC-V (#59).** Builds for 64-bit RISC-V Linux (`linux-riscv64` and a `riscv64` .deb), tested natively on an OrangePi RV2.
88
89
- **apt (oxygenhub#85).** The .deb adds the repository at https://oxygenhub.io/apt on its first install, so `apt upgrade` keeps oxy current.
90
91
- **Built by OxygenFlow.** Every file of this release comes from the dist job of the release commit's run, signed by its runner: the release's trust page shows each as an attested build.
92
93
- **Being on the bookmark you asked for (#60).** `oxy checkout BOOKMARK` switches to that bookmark (as `oxy bookmark switch` does, with `--discard` or `--carry`), where it used to write its files under the current bookmark's name, so the next commit moved the wrong bookmark. `oxy workspace add PATH BOOKMARK` makes a workspace on that bookmark rather than this one's.
94
95
- **A new workspace has its subrepos (#61).** `oxy workspace add` brings each subrepo at its pin from this workspace's own copy, with no network or sign-in, its remote pointing where the subrepo comes from.
96
97
## 0.3.0 (2026-10-02)
98
99
- **Fetch says when a bookmark is behind (#56).** `oxy fetch` reports each bookmark against the remote's copy ("main is 1 change behind: oxy pull to take it", ahead, diverged) and says "up to date" only when they match; `--json` gives `ahead` and `behind`. A change that was fetched but not pulled resolves by id or hash, and `oxy show` says it is only in `origin/main`. With one remote not called `origin`, fetch, pull and push use it, and `bookmark list` and the prompt count against it.
100
101
- **Clones record local paths absolute (#57).** `oxy clone ../repo copy` and `oxy remote add NAME ../repo` store the absolute path, so fetch, pull and push still find the remote from inside the clone.
102
103
- **Faster commits (#41).** A commit writes the manifest once, its own snapshot neither records an operation nor encodes the change twice, syncs are batched, and edited files are diffed on every core: a one-file commit on the Linux tree takes 0.3 to 0.5 s (from about 3 s; Git 0.15 s, which does not sync), 1 000 files 0.8 s (from 4.9 s).
104
105
- **First commit of a large tree in half the time and less memory (#41).** The Linux tip commits in 12 to 17 s (from 38 s; Git 32 s), peaking at 2.6 GB resident (from 4.4 GB): line offsets take four bytes, inline lines are encoded without a value each, stored texts go at most 32 MiB a batch, and `oxy commit` (and describe, amend, abandon, split, squash, rebase) read only the new change's metadata to print it.
106
107
- **Diff and fsck (#41).** A diff answered from the status cache renders only the files it shows (one file on the Linux tree 0.15 s, from 1.2 s; 1 000 files level with Git), and fsck checks objects in parallel (8 to 10 s, Git 12 to 13 s).
108
109
- **Blame twice as fast (#41).** `oxy blame` prints through a buffered writer and formats each change once, and the state index's tree view loads in one step: blame of the Linux tree's MAINTAINERS (30 000 lines) is 0.08 s, from 0.16 s; tuned Git 0.06 s. The faster tree view also takes 16 ms off diff, commit and checkout.
110
111
- **`oxy log` in milliseconds (#41).** Log no longer checks the files on disk: its working-copy row shows the working copy as last recorded, "(working copy)" or "(working copy, uncommitted changes)", as Git's log never looks at the working tree; `oxy status` checks the disk. `log -n 100` is 0.03 s on the Linux tree (from 0.17 s) and 0.02 s on a 10 000-commit history.
112
113
- **Status twice as fast as Git on the Linux tree (#41).** With uncommitted work, `oxy status` answers from a status cache while every file is as the last snapshot left it, and the manifest keeps every directory's time, so the tree is listed only when a directory's entries changed. One edited file is 0.10 to 0.12 s (from 0.44 s; Git 0.22 s) and a clean status 0.09 to 0.12 s (Git 0.20 to 0.26 s), in 80 MB. `oxy diff` renders the working copy from the cache too (0.20 s, from 0.41 s; Git 0.08 s). A commit after a status takes that status's snapshot instead of walking the tree again (0.37 s, from 0.68 s; Git 0.15 s). A file written within the racy window is now hashed by the prompt's check too, as it was by status.
114
115
- **Quieter notes (#55).** Notes start with a dimmed `›` instead of `ℹ`, which many terminal fonts draw as a boxed or emoji-style "i". A spinner's final line no longer shows two ticks or runs into the next line.
116
117
## 0.2.0 (2026-10-01)
118
119
- **The operation log says what each operation did (#51).** `oxy op log` gives each operation a summary, such as "main: 1 → 2 heads; working copy now 91ab4d50", with its command dimmed beneath. Runs of working-copy snapshots fold into one row, and `-n` limits the listing. `--json` adds each operation's changes.
120
- **`oxy op restore`, `oxy op revert` and `--at-op` (#52).** `op restore OP` brings back the bookmarks and working copy as they were after OP. `op revert OP` undoes OP alone and keeps everything after it. `--at-op OP` runs any read-only command on the repository as it was then. Restore and revert are operations too, so `oxy undo` takes them back. `undo` now takes back a command's own checkout with it (pull and restore record one), and brings the files back when nothing uncommitted would be lost.
121
- **Commands say where they left you (#53).** Commit, checkout, switch, rebase, merge, amend, abandon, pull, undo, restore and revert end with the working copy and the bookmark's heads, newest first, as jj does.
122
123
- **`oxy log` is a graph (#50).** Each change takes two lines: its ID (the shortest unique prefix bright), author, time, bookmarks and tags, hash and signature, then its subject. Lanes follow the change each one most directly builds on, and join where two lines of work meet, so independent work shows side by side. A dependency further than twelve rows down is marked `~`, which keeps the graph to a few lanes even on a long history. `--json` is unchanged.
124
125
- **A remote's bookmarks work like local ones (#48).**
126
  - `origin/NAME` names a remote's bookmark wherever a bookmark is expected, and `oxy bookmark list --all` lists those with no local bookmark.
127
  - `oxy bookmark switch NAME` makes a local bookmark from `origin/NAME` when there is none (`REMOTE/NAME` picks another remote).
128
  - `oxy pull` updates every local bookmark the remote has too, not only the current one, and names the others it moved.
129
  - `oxy clone` checks out the remote's default bookmark (servers now name it in `refs`), says how many other bookmarks the remote has, and takes `--recursive` to clone the subrepos. A clone, switch or pull says when subrepos are not at their pins.
130
- **A newer repository format names the oxy that cannot read it (#49).** Instead of "unsupported format version 2", oxy says the data was written by a newer oxy, and gives its own version and path, which shows an older copy earlier on `PATH`.
131
132
- **Sign in once per hub: `oxy auth login`, `status` and `logout` (#47).** `oxy auth login https://oxygenhub.io` signs in through the browser, and every repository on that hub then clones, fetches and pushes with the token, subrepos included. Nothing secret goes into a checkout any more.
133
  - The token is kept in `~/.config/oxy/credentials.toml`, readable by you only, or in the system keyring through a Git credential helper (`oxy config --user auth.helper libsecret`).
134
  - `--with-token` reads a token from standard input, for CI and scripts. `oxy auth logout` also revokes the token on the hub.
135
  - A token goes over `https` only (plain `http` only to this machine), never to another host after a redirect. A credentials file that others can read is ignored.
136
  - Lookup order: `remote.<name>.token`, `OXY_TOKEN`, the store, then an enclosing checkout's remote on the same hub. Clones no longer copy a token into a subrepo's configuration.
137
  - Identity secret keys are now created owner-only, rather than tightened just after being written.
138
139
Measured on the Linux kernel's tree (95 943 files); `docs/benchmarks.md` has the tables.
140
141
- **Faster than Git on the first commit, a clean status, fsck, gc and the pack's size.**
142
  - **First commit:** 21.9 s (Git 33.7 s).
143
  - **Clean status:** 0.17 to 0.19 s (Git 0.22 s).
144
  - **fsck:** 8.2 s (Git 12.6 s).
145
  - **gc:** 33.7 s (Git 70 s).
146
  - **The pack:** 293.5 MB (Git's 295.5 MB).
147
- **Level with Git on a local clone (about 17 s).** Status with one file changed, diff, commit, log, blame and checkout are two to six times faster than at 0.1.0. They are still behind Git:
148
  - **Status with one file changed:** 0.41 s, against 0.22 s.
149
  - **Commit:** 0.67 s, against 0.21 s.
150
  - **Blame:** 0.16 s, against 0.07 s.
151
  - **Checkout:** 0.74 s, against 0.35 s.
152
- **A project's `.gitignore` files count**, below `.oxyignore`. A project coming from Git no longer commits its build output, and `!` in `.oxyignore` can bring back what `.gitignore` leaves out. Ignore rules still apply to untracked paths only.
153
- **A folder holding a repository of its own is named.** `oxy status`, `oxy commit` and `status --json` (`untracked_nested`) name a folder with its own `.git` or `.oxy`, rather than skipping it silently. They say how to track its files, keep it as a subrepo, or leave it out.
154
- **`oxy rebase CHANGE --onto T --into-working-copy`.** Where a rebase would be conflicted, the target is merged into the current bookmark instead, and the conflict is resolved in the working copy, as after `oxy merge`.
155
- **mimalloc is the allocator.** Most of the speed above came from it: status, walks and index loads allocate many small values from every thread.
156
- **Internals.** Each item below is a cache and is rebuilt when it does not check:
157
  - the workspace manifest is a checksummed binary file;
158
  - the state index is decoded lazily, caught up by replaying a few changes, and kept for the last two states;
159
  - the store caches change metadata;
160
  - a pack can carry a trained zstd dictionary.
161
162
## 0.1.0 (2026-09-30)
163
164
- **The first builds for people to download.**
165
  - Linux x86_64 and arm64 (glibc 2.28 or later) as `.tar.gz` and `.deb`.
166
  - macOS x86_64 and arm64.
167
  - Windows x86_64.
168
169
  All are cross-compiled from Linux with `scripts/dist.sh`, with SHA256SUMS. A prerelease: formats may still change before 1.0.