isomorphic-jj 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +203 -1
- package/README.md +134 -23
- package/bin/isojj.js +4 -1
- package/dist/index.cjs +515 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +515 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -4
- package/src/api/repository.js +205 -9
- package/src/backend/isomorphic-git-backend.js +59 -3
- package/src/browser/helpers.js +2 -2
- package/src/core/jj-operation-store.js +5 -2
- package/src/core/jj-tree-state.js +3 -2
- package/src/core/jj-view-store.js +3 -2
- package/src/core/revset-engine.js +48 -3
- package/src/core/storage-manager.js +47 -10
- package/src/core/tag-store.js +40 -10
- package/src/utils/atomic-write.js +44 -0
- package/src/utils/mutex.js +63 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,205 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-09-02
|
|
4
|
+
|
|
5
|
+
The first `@johnhenry/isomorphic-jj` release since 0.1.0, and the first
|
|
6
|
+
scoped release to carry the audit fixes from PR #19 below — **the published
|
|
7
|
+
0.1.0 tarball on npm predates #19** and does not include them.
|
|
8
|
+
|
|
9
|
+
### Fixed (PR #19 — audit findings)
|
|
10
|
+
|
|
11
|
+
Fixes all 8 findings from a bug-hunt audit of the storage/operation-log,
|
|
12
|
+
undo/redo, revset, and Git-push layers:
|
|
13
|
+
|
|
14
|
+
- **Same-process storage locking.** `Storage.appendLine()`/`write()` did
|
|
15
|
+
read-then-write with no lock, so concurrent same-process calls (e.g. a
|
|
16
|
+
user command racing `BackgroundOps`'s autosnapshot) could silently drop an
|
|
17
|
+
entry. Added a path-keyed in-process async mutex (`src/utils/mutex.js`)
|
|
18
|
+
that serializes the full critical section for both methods. Scoped to
|
|
19
|
+
same-process concurrency per the audit's guidance; cross-process locking
|
|
20
|
+
is a follow-up.
|
|
21
|
+
- **`undo()` now reverts in-place content mutations.** Previously it never
|
|
22
|
+
restored `ChangeGraph`/tag mutations made in place. `describe()` now
|
|
23
|
+
snapshots the affected `ChangeGraph` node before mutating it (mirroring
|
|
24
|
+
the existing `fileSnapshot` pattern), and `undo()` restores it.
|
|
25
|
+
`operations.revert()` now also diffs/restores `tags`, not just
|
|
26
|
+
`bookmarks`.
|
|
27
|
+
- **Ambiguous change-id prefixes now throw `AMBIGUOUS_ID`.** `show()`
|
|
28
|
+
previously resolved an ambiguous prefix silently to the first match; it
|
|
29
|
+
now throws `AMBIGUOUS_ID` listing all matches.
|
|
30
|
+
- **`push()` surfaces categorized error codes.** Previously all per-ref
|
|
31
|
+
errors were swallowed into a bare ref name. Rejected refs now carry a
|
|
32
|
+
categorized `code` (`AUTH_FAILED`/`NETWORK_ERROR`/`NON_FAST_FORWARD`/
|
|
33
|
+
`OTHER`) and `reason` so callers can tell an auth failure apart from a
|
|
34
|
+
non-fast-forward rejection instead of guessing.
|
|
35
|
+
- **Conflict-marker newline fix.** Markers could fuse content onto boundary
|
|
36
|
+
markers when content lacked a trailing newline; now forces a newline
|
|
37
|
+
separator for non-empty content.
|
|
38
|
+
- **Atomic protobuf writes.** `jj-operation-store.js`, `jj-view-store.js`,
|
|
39
|
+
and `jj-tree-state.js` risked a truncated/undecodable file on a mid-write
|
|
40
|
+
crash. Now route through a shared `atomicWriteFile()` temp+rename helper
|
|
41
|
+
(`src/utils/atomic-write.js`).
|
|
42
|
+
- **Weak temp-file naming fixed alongside storage locking** by adding a
|
|
43
|
+
random suffix, so two concurrent writes can't collide on the same temp
|
|
44
|
+
filename.
|
|
45
|
+
- **Open-ended revset ranges** (`..b`, `a..`, `::`) failed with a
|
|
46
|
+
misleading "invalid IDs" error; they now delegate to the existing
|
|
47
|
+
`ancestors()`/`descendants()`/`all()` implementations.
|
|
48
|
+
|
|
49
|
+
Closes #11–#18. See [PR #19](https://github.com/johnhenry/isomorphic-jj/pull/19).
|
|
50
|
+
|
|
51
|
+
### CI (PR #20)
|
|
52
|
+
|
|
53
|
+
- Dropped macOS from the test matrix — hosted macOS runners are scarce/slow
|
|
54
|
+
to schedule and this is a pure-JS library with no native code path; Linux
|
|
55
|
+
+ Windows already cover realistic cross-platform risk. See
|
|
56
|
+
[PR #20](https://github.com/johnhenry/isomorphic-jj/pull/20).
|
|
57
|
+
|
|
58
|
+
### Documentation overhaul: examples suite
|
|
59
|
+
|
|
60
|
+
- **New `examples/` directory: 12 numbered, runnable examples** covering the
|
|
61
|
+
core loop (init/describe/new/log), stable-ID stacked changes, history
|
|
62
|
+
editing (split/squash/abandon/duplicate), branching and merging (including
|
|
63
|
+
`new({ parents })` for true merge changes), first-class conflicts, revsets,
|
|
64
|
+
bookmarks + tags (with the v0.44 `tag.track()` surface), Git interop,
|
|
65
|
+
configuration layers, undo/oplog/time travel, the `file.*` namespace, and
|
|
66
|
+
browser usage. Each example runs in a temp directory, cleans up after
|
|
67
|
+
itself, and *asserts* what it demonstrates, so the set doubles as a smoke
|
|
68
|
+
test. `npm run examples` runs the loop; `npm run example:NN` runs one.
|
|
69
|
+
- **Example 08 exercises real `git.clone()`/`git.push()`/`git.fetch()` with
|
|
70
|
+
zero network**: it builds a bare fixture repo with the git CLI and serves
|
|
71
|
+
it on 127.0.0.1 through `git http-backend` (buffering request bodies so
|
|
72
|
+
CGI gets a `CONTENT_LENGTH` — isomorphic-git sends chunked bodies). Skips
|
|
73
|
+
cleanly where the git CLI is unavailable.
|
|
74
|
+
- **CI**: the test workflow now runs the examples loop as a smoke step on
|
|
75
|
+
one matrix leg (ubuntu-latest / Node 22), deliberately a different leg
|
|
76
|
+
than the coverage gate.
|
|
77
|
+
- Companion docs for the site (opensource.johnhenry.me) were written from
|
|
78
|
+
this repo's README/API/MIGRATION/CONFIGURATION material in the same pass;
|
|
79
|
+
behavioral quirks the examples surfaced (e.g. `file()` matching snapshots
|
|
80
|
+
rather than modifications, duration revsets needing committer timestamps,
|
|
81
|
+
`init()` after `git.clone()` re-rooting `refs/heads/main`) are documented
|
|
82
|
+
there rather than papered over.
|
|
83
|
+
|
|
84
|
+
### Publishing
|
|
85
|
+
|
|
86
|
+
- `@johnhenry/isomorphic-jj@0.2.0` is published from `main`. A final
|
|
87
|
+
unscoped bridge release, `isomorphic-jj@1.8.0` (same code as this
|
|
88
|
+
release), will follow so existing unscoped installs get one more version
|
|
89
|
+
pointing at the new package before the unscoped name is deprecated.
|
|
90
|
+
|
|
91
|
+
## 0.1.0 — Track jj through v0.44
|
|
92
|
+
|
|
93
|
+
Jujutsu shipped v0.44.0 (2026-08-05) since isomorphic-jj's last parity pass
|
|
94
|
+
(v1.5.0/README, which tracked jj through v0.43). This release closes the gap
|
|
95
|
+
for the parts of v0.43–v0.44 that are in scope for a headless, browser-safe
|
|
96
|
+
reimplementation of jj's model — a library, not the `jj` CLI's terminal UX or
|
|
97
|
+
its Rust process/Git-plumbing internals.
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- **`tag.track({ name, remote })` / `tag.untrack({ name })`** — jj v0.44 made
|
|
102
|
+
tags remote-trackable the same way bookmarks already were, and added `jj
|
|
103
|
+
tag track`/`untrack` to manage that per-tag. `TagStore` now persists
|
|
104
|
+
tracking state (`{ remote, remoteName }` per local tag name) the same way
|
|
105
|
+
`BookmarkStore` already did for bookmarks, and `tag.list()` includes a
|
|
106
|
+
`tracking: { remote, ref }` field once a tag is tracked. The on-disk
|
|
107
|
+
`tags.json` format gained a `{ tags, tracked }` envelope; the old flat
|
|
108
|
+
`{ name: changeId }` format is still read for backward compatibility.
|
|
109
|
+
- **`builtin_log()` revset function** — jj v0.44 added `builtin_log()` as an
|
|
110
|
+
alias for the revset the built-in `jj log` uses by default, so a custom
|
|
111
|
+
`revsets.log` config can extend it instead of duplicating it. isomorphic-jj's
|
|
112
|
+
`log()` has always defaulted to `all()` when no revset is given, so
|
|
113
|
+
`builtin_log()` is wired to the same set and can be composed like any other
|
|
114
|
+
revset function (`builtin_log() & mine()`).
|
|
115
|
+
- **`file.search({ ..., nameOnly: true })`** — jj v0.44 changed `jj file
|
|
116
|
+
search`'s default CLI output to print every matched *line* (prefixed with
|
|
117
|
+
its file path), moving the old "just the file paths" behavior behind a new
|
|
118
|
+
`--name-only` flag. isomorphic-jj's `file.search()` already returned
|
|
119
|
+
structured per-line matches (`{ path, lineNumber, line }`) — that was
|
|
120
|
+
already the new default — so the only gap was the old, path-only shape;
|
|
121
|
+
`{ nameOnly: true }` now returns a deduplicated `string[]` of matching
|
|
122
|
+
paths, matching `--name-only`.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- **`git_refs()` / `git_head()` revsets are now documented as deprecated.**
|
|
127
|
+
Upstream jj deprecated both in favor of `bookmarks()`/`tags()`/`@`, then
|
|
128
|
+
removed them outright in jj v0.43 (calling them in real jj now errors).
|
|
129
|
+
isomorphic-jj keeps them working — removing a function outright is a
|
|
130
|
+
breaking API change this pass didn't want to force on existing callers
|
|
131
|
+
unilaterally — but they're no longer listed in the revset engine's
|
|
132
|
+
"did you mean" suggestion text, and their JSDoc now calls out the upstream
|
|
133
|
+
removal and recommends `bookmarks()`/`tags()`/`@` instead. See "Not
|
|
134
|
+
changed" below for the case for a harder break in a future major version.
|
|
135
|
+
|
|
136
|
+
### Testing
|
|
137
|
+
|
|
138
|
+
1714 tests passing (was 1701); lint (0 errors), format:check, typecheck, and
|
|
139
|
+
build all still green.
|
|
140
|
+
|
|
141
|
+
### Not changed (needs a human design call)
|
|
142
|
+
|
|
143
|
+
jj v0.43–v0.44 included several changes this pass deliberately left alone:
|
|
144
|
+
|
|
145
|
+
- **`jj run`** (v0.43 new command, gained `--passthrough`/`--ignore-changes`/
|
|
146
|
+
`--ignore-errors`/oldest-first-ordering in v0.44) — runs an arbitrary
|
|
147
|
+
subprocess against each revision's own private working copy. isomorphic-jj
|
|
148
|
+
doesn't shell out to anything and has to keep working in browsers with no
|
|
149
|
+
process/filesystem access; "run a command" doesn't have an isomorphic
|
|
150
|
+
equivalent. Adding it would mean either a Node-only stub (inconsistent with
|
|
151
|
+
the rest of the API surface) or scope-creeping into a job-runner. Flagging
|
|
152
|
+
for a maintainer decision rather than guessing.
|
|
153
|
+
- **Auto-importing remote tags/bookmarks during `git.fetch()`.** jj v0.44's
|
|
154
|
+
headline change is that `jj git fetch` now fetches tags the same way it
|
|
155
|
+
fetches bookmarks — as `<name>@<remote>`, auto-tracked by default. Wiring
|
|
156
|
+
that up properly for tags would need `git.fetch()` to actually populate
|
|
157
|
+
`TagStore`'s (and, by the same logic, `BookmarkStore`'s) remote-tracking
|
|
158
|
+
state from the fetched refs. Investigating this surfaced a **pre-existing**
|
|
159
|
+
gap: `git.fetch()` already doesn't call `bookmarks.setRemote()` for fetched
|
|
160
|
+
bookmarks today, even though `BookmarkStore.setRemote()`/`getRemote()` have
|
|
161
|
+
existed since v0.4 — remote ref sync was never wired all the way through at
|
|
162
|
+
the fetch call site. Building tag-fetch-tracking on top of that gap would
|
|
163
|
+
mean either (a) fixing the older bookmark gap too as a drive-by, our
|
|
164
|
+
papering over an inconsistency where tags "worked" and bookmarks didn't, or
|
|
165
|
+
(b) building a second, tag-only special case. Both are judgment calls
|
|
166
|
+
about a real architectural gap, not a mechanical port of one version's
|
|
167
|
+
changelog — left for a maintainer to decide. `tag.track()`/`untrack()`
|
|
168
|
+
(added this pass) at least let callers record and query tracking intent by
|
|
169
|
+
hand in the meantime.
|
|
170
|
+
- **`jj git clone --tag=PATTERN` / `--fetch-tags` removal.** Real jj replaced
|
|
171
|
+
`--fetch-tags=all|none|included` with `--tag=PATTERN` in v0.44. isojj's
|
|
172
|
+
`git.clone()`/`git.fetch()` never exposed CLI-shaped `--fetch-tags` flags in
|
|
173
|
+
the first place (just a boolean `noTags`), so there's no removed flag to
|
|
174
|
+
mirror; a `PATTERN`-based tag filter for clone/fetch would be new surface
|
|
175
|
+
area tied to the fetch-tracking gap above.
|
|
176
|
+
- **`jj git push --allow-conflicts`.** Real jj normally refuses to push
|
|
177
|
+
commits containing conflicts unless this flag is passed. isojj's
|
|
178
|
+
`git.push()` doesn't currently check for conflicts before pushing at all —
|
|
179
|
+
there's no existing guard to add a bypass flag for. Adding the guard itself
|
|
180
|
+
(and then the bypass) is more surface area than a one-version parity pass.
|
|
181
|
+
- **CLI argument parsing "last occurrence wins" (v0.44).** Checked
|
|
182
|
+
`bin/isojj.js`'s `parseArgs()`: it already stores each `--flag` into a
|
|
183
|
+
plain object keyed by flag name, so repeating a flag already naturally
|
|
184
|
+
overwrites the earlier value with no error — this was already jj v0.44's
|
|
185
|
+
new behavior by construction. No change needed.
|
|
186
|
+
- **`try(expr, fallback...)` template function, `jj workspace list` root
|
|
187
|
+
display, `diff.stat.max-bar-width`, `colors.crossed-out`, Gerrit/config-gc/
|
|
188
|
+
`/etc/jj` config-discovery changes.** These are jj's template engine and
|
|
189
|
+
terminal/config-file layers, which isomorphic-jj's CLI (a thin, generic
|
|
190
|
+
API-argument passthrough — see `bin/isojj.js`) doesn't attempt to replicate
|
|
191
|
+
1:1 for any command. Out of scope for this pass; noted here for
|
|
192
|
+
completeness of the v0.43/v0.44 diff.
|
|
193
|
+
|
|
194
|
+
## 0.0.0
|
|
195
|
+
|
|
196
|
+
- **Renamed: `isomorphic-jj` is now `@johnhenry/isomorphic-jj`, restarted at 0.0.0.** Same library, same API — a new address and era, not a maturity signal (1.7.0 lineage).
|
|
197
|
+
|
|
198
|
+
```sh
|
|
199
|
+
npm install @johnhenry/isomorphic-jj
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
|
|
3
203
|
All notable changes to isomorphic-jj are documented here. The project tracks the
|
|
4
204
|
[Jujutsu (jj)](https://github.com/jj-vcs/jj) CLI; each release notes the upstream
|
|
5
205
|
jj version whose semantics it targets.
|
|
@@ -171,7 +371,9 @@ adds a batch of revset functions and commands, and cleans up the toolchain.
|
|
|
171
371
|
- `forks()` — changes with more than one child.
|
|
172
372
|
- `first_parent(x)` / `first_ancestors(x)` — first-parent navigation (jj v0.32).
|
|
173
373
|
- `fork_point(x)` — youngest common ancestor of a set (jj v0.32).
|
|
174
|
-
- `merge_point(x)` — youngest common descendant of a set
|
|
374
|
+
- `merge_point(x)` — youngest common descendant of a set (implemented ahead of
|
|
375
|
+
upstream; jj stabilized its own `merge_point()` in v0.44 with matching
|
|
376
|
+
semantics — "the point where multiple branches merge").
|
|
175
377
|
- `exactly(x, n)` — the set, but errors unless it has exactly `n` elements (jj v0.34).
|
|
176
378
|
- `present(x)` — evaluate without erroring on unknown symbols.
|
|
177
379
|
- `coalesce(a, b, …)` — the first argument that resolves to a non-empty set.
|
package/README.md
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
# isomorphic-jj
|
|
1
|
+
# @johnhenry/isomorphic-jj
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/johnhenry/isomorphic-jj)
|
|
5
|
-
[](LICENSE)
|
|
3
|
+
> **This package has moved to `@johnhenry/isomorphic-jj`.** This is the final release under the old name.
|
|
6
4
|
|
|
7
5
|
> **Jujutsu version control for JavaScript**—stable change IDs, fearless undo, and no staging area. Works in Node.js and browsers.
|
|
8
6
|
|
|
7
|
+
Full documentation: [opensource.johnhenry.me/isomorphic-jj](https://opensource.johnhenry.me/isomorphic-jj/)
|
|
8
|
+
|
|
9
|
+
> Previously published as `isomorphic-jj` (final release 1.8.0, deprecated in
|
|
10
|
+
> favor of this package). Renamed to `@johnhenry/isomorphic-jj` and restarted
|
|
11
|
+
> at 0.0.0 on import into the @johnhenry family — a new address and era, not
|
|
12
|
+
> a maturity signal.
|
|
13
|
+
|
|
14
|
+
[](https://www.npmjs.com/package/@johnhenry/isomorphic-jj)
|
|
15
|
+
[](https://github.com/johnhenry/isomorphic-jj/actions/workflows/test.yml)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
|
|
9
18
|
**What makes it different:**
|
|
10
19
|
- 🎯 **Stable change IDs** that survive rebases/squashes
|
|
11
20
|
- ↩️ **Complete undo** for any operation (not just commits)
|
|
@@ -14,7 +23,7 @@
|
|
|
14
23
|
- 🌐 **True isomorphic**—same API in Node, browsers, and Web Workers
|
|
15
24
|
|
|
16
25
|
```javascript
|
|
17
|
-
import { createJJ } from 'isomorphic-jj';
|
|
26
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
18
27
|
import git from 'isomorphic-git';
|
|
19
28
|
import fs from 'fs';
|
|
20
29
|
|
|
@@ -30,6 +39,60 @@ await jj.undo();
|
|
|
30
39
|
|
|
31
40
|
---
|
|
32
41
|
|
|
42
|
+
## Try it in 60 seconds
|
|
43
|
+
|
|
44
|
+
### CLI
|
|
45
|
+
|
|
46
|
+
Every method is reachable from the `isojj` CLI (`bin/isojj.js`). Run it with
|
|
47
|
+
`npx`, or install it globally (`npm i -g @johnhenry/isomorphic-jj`) and call
|
|
48
|
+
`isojj` directly:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx @johnhenry/isomorphic-jj init
|
|
52
|
+
echo "# hello" > README.md
|
|
53
|
+
npx @johnhenry/isomorphic-jj describe -m "Initial commit"
|
|
54
|
+
npx @johnhenry/isomorphic-jj new -m "Add a feature"
|
|
55
|
+
npx @johnhenry/isomorphic-jj log
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Change History:
|
|
60
|
+
|
|
61
|
+
cb7b4e93fd23 Add a feature
|
|
62
|
+
You 9/2/2026, 7:20:30 AM
|
|
63
|
+
|
|
64
|
+
a2f6f5af8107 Initial commit
|
|
65
|
+
You 9/2/2026, 7:20:30 AM
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run `npx @johnhenry/isomorphic-jj --help` for the full command list.
|
|
69
|
+
|
|
70
|
+
### Node.js
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
74
|
+
import fs from 'fs';
|
|
75
|
+
|
|
76
|
+
const jj = await createJJ({ fs, dir: './my-repo' });
|
|
77
|
+
await jj.init({ userName: 'You', userEmail: 'you@example.com' });
|
|
78
|
+
await jj.write({ path: 'README.md', data: '# hello\n' });
|
|
79
|
+
await jj.describe({ message: 'Initial commit' });
|
|
80
|
+
console.log(await jj.log({ limit: 5 }));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Browser
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
87
|
+
import { createBrowserFS } from '@johnhenry/isomorphic-jj/browser';
|
|
88
|
+
|
|
89
|
+
const fs = createBrowserFS({ name: 'my-repo' });
|
|
90
|
+
const jj = await createJJ({ fs, dir: '/repo' });
|
|
91
|
+
// Same API as Node from here on — see examples/12-browser.mjs.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
33
96
|
## Quick Start
|
|
34
97
|
|
|
35
98
|
**Coming from isomorphic-git?** Read the [Migration Guide](./MIGRATION_FROM_ISOMORPHIC_GIT.md) to see how isomorphic-jj simplifies your JavaScript version control workflow.
|
|
@@ -42,6 +105,43 @@ This showcases all 17 feature categories (including v1.0 enhancements) in ~3 sec
|
|
|
42
105
|
|
|
43
106
|
---
|
|
44
107
|
|
|
108
|
+
## Examples
|
|
109
|
+
|
|
110
|
+
The [`examples/`](./examples) directory has 12 numbered, runnable, self-asserting
|
|
111
|
+
scripts — each creates a repo in a temp dir, demonstrates one area, and cleans
|
|
112
|
+
up after itself. Run one with `npm run example:01` (etc.), or the whole set
|
|
113
|
+
(the same loop CI runs) with:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm run examples
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| # | What it shows |
|
|
120
|
+
|---|----------------|
|
|
121
|
+
| [01](./examples/01-init-commit-log.mjs) | The core loop: init → write → `describe()` → `new()` → `log()` |
|
|
122
|
+
| [02](./examples/02-stacked-changes.mjs) | Stable change IDs across a 3-layer stack; `edit()`+`amend()` a bottom layer |
|
|
123
|
+
| [03](./examples/03-history-editing.mjs) | `split()`, `squash({ into })`, `abandon()`/`unabandon()`, `duplicate()` |
|
|
124
|
+
| [04](./examples/04-branching-and-merging.mjs) | Diverging without branches; content merge vs. true merge changes |
|
|
125
|
+
| [05](./examples/05-conflicts.mjs) | First-class conflicts: dry-run preview, `conflicts.list()`/`resolve()` |
|
|
126
|
+
| [06](./examples/06-revsets.mjs) | The revset query language, end to end |
|
|
127
|
+
| [07](./examples/07-bookmarks-and-tags.mjs) | Bookmarks, tags, and remote tracking |
|
|
128
|
+
| [08](./examples/08-git-interop.mjs) | Real `git.clone()`/`push()`/`fetch()` against a local fixture, zero network |
|
|
129
|
+
| [09](./examples/09-config.mjs) | Config layers: persisted, programmatic, deep-merge, reset |
|
|
130
|
+
| [10](./examples/10-undo-and-oplog.mjs) | `undo()`/`redo()`, `operations.at()` time travel, `obslog()` |
|
|
131
|
+
| [11](./examples/11-file-operations.mjs) | The `file.*` namespace: historical `show()`, `annotate()`, `search()` |
|
|
132
|
+
| [12](./examples/12-browser.mjs) | Browser usage (IndexedDB fs, persistence, CORS proxy) as a reference |
|
|
133
|
+
|
|
134
|
+
See [`examples/README.md`](./examples/README.md) for details and conventions.
|
|
135
|
+
|
|
136
|
+
Three fuller example applications live under [`example/`](./example) — each
|
|
137
|
+
exercises the library from a different angle and documents the bugs it found:
|
|
138
|
+
|
|
139
|
+
- [`jj-review-tool`](./example/jj-review-tool) — a code-review CLI built on stacked changes and bookmarks.
|
|
140
|
+
- [`jj-storage-server`](./example/jj-storage-server) — a REST API using isomorphic-jj as a versioned document store.
|
|
141
|
+
- [`jj-wiki`](./example/jj-wiki) — a collaborative wiki exercising workspaces and conflict resolution.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
45
145
|
## What's New in v1.0
|
|
46
146
|
|
|
47
147
|
isomorphic-jj v1.0 achieves **complete JJ CLI semantic compatibility** while maintaining 100% backward compatibility:
|
|
@@ -76,6 +176,7 @@ isomorphic-jj v1.0 now implements virtually all commonly-used JJ CLI commands:
|
|
|
76
176
|
- **`git.remote.*`** - Complete remote management (list, add, remove, rename, setUrl)
|
|
77
177
|
- **`bookmark.rename()`** - Rename bookmarks
|
|
78
178
|
- **`bookmark.track/untrack/forget()`** - Remote bookmark management
|
|
179
|
+
- **`tag.track/untrack()`** - Remote tag management (jj v0.44)
|
|
79
180
|
- **`config.*`** - Configuration management (get, set, list)
|
|
80
181
|
- **`diff()`** - Show file differences between revisions
|
|
81
182
|
- **`next()` / `prev()`** - Navigate between parent/child revisions
|
|
@@ -85,10 +186,10 @@ isomorphic-jj v1.0 now implements virtually all commonly-used JJ CLI commands:
|
|
|
85
186
|
- **`operations.show/diff/restore()`** - Advanced operation log features
|
|
86
187
|
- **`remote.*`** - Convenience aliases for git operations (push, fetch, add)
|
|
87
188
|
|
|
88
|
-
See [
|
|
189
|
+
See [ROADMAP.md](./ROADMAP.md) for the release-by-release feature comparison and what's still planned.
|
|
89
190
|
|
|
90
191
|
### Testing & Quality
|
|
91
|
-
- **
|
|
192
|
+
- **1730 tests** passing — ~97% statement / 91% branch coverage
|
|
92
193
|
- **100% backward compatible** - All existing code continues to work
|
|
93
194
|
- **Zero breaking changes** - Deprecated features show warnings but still function
|
|
94
195
|
|
|
@@ -97,7 +198,7 @@ See [JJ_CLI_PARITY.md](./JJ_CLI_PARITY.md) for complete feature comparison.
|
|
|
97
198
|
### Installation
|
|
98
199
|
|
|
99
200
|
```bash
|
|
100
|
-
npm install isomorphic-jj isomorphic-git
|
|
201
|
+
npm install @johnhenry/isomorphic-jj isomorphic-git
|
|
101
202
|
```
|
|
102
203
|
|
|
103
204
|
For browsers, also install:
|
|
@@ -108,7 +209,7 @@ npm install @isomorphic-git/lightning-fs
|
|
|
108
209
|
### Basic Workflow
|
|
109
210
|
|
|
110
211
|
```javascript
|
|
111
|
-
import { createJJ } from 'isomorphic-jj';
|
|
212
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
112
213
|
import git from 'isomorphic-git';
|
|
113
214
|
import fs from 'fs';
|
|
114
215
|
import http from 'isomorphic-git/http/node';
|
|
@@ -147,8 +248,8 @@ console.log(log);
|
|
|
147
248
|
### Browser Usage
|
|
148
249
|
|
|
149
250
|
```javascript
|
|
150
|
-
import { createJJ } from 'isomorphic-jj';
|
|
151
|
-
import { createBrowserFS } from 'isomorphic-jj/browser';
|
|
251
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
252
|
+
import { createBrowserFS } from '@johnhenry/isomorphic-jj/browser';
|
|
152
253
|
import git from 'isomorphic-git';
|
|
153
254
|
import http from 'isomorphic-git/http/web';
|
|
154
255
|
|
|
@@ -494,7 +595,7 @@ await jj.undo();
|
|
|
494
595
|
Merge drivers enable smart merging of structured files like JSON, package.json, and YAML:
|
|
495
596
|
|
|
496
597
|
```javascript
|
|
497
|
-
import { jsonDriver, packageJsonDriver, yamlDriver, markdownDriver } from 'isomorphic-jj';
|
|
598
|
+
import { jsonDriver, packageJsonDriver, yamlDriver, markdownDriver } from '@johnhenry/isomorphic-jj';
|
|
498
599
|
|
|
499
600
|
// Register merge drivers for different file types
|
|
500
601
|
jj.mergeDrivers.register({
|
|
@@ -625,7 +726,7 @@ const bookmarks = await jj.bookmark.list();
|
|
|
625
726
|
### Browser Support
|
|
626
727
|
|
|
627
728
|
```javascript
|
|
628
|
-
import { createBrowserFS, requestPersistentStorage } from 'isomorphic-jj/browser';
|
|
729
|
+
import { createBrowserFS, requestPersistentStorage } from '@johnhenry/isomorphic-jj/browser';
|
|
629
730
|
|
|
630
731
|
// Request persistent storage (prevents eviction)
|
|
631
732
|
const persistent = await requestPersistentStorage();
|
|
@@ -634,14 +735,14 @@ const persistent = await requestPersistentStorage();
|
|
|
634
735
|
const fs = createBrowserFS({ backend: 'idb', name: 'my-repo' });
|
|
635
736
|
|
|
636
737
|
// Check browser capabilities
|
|
637
|
-
import { detectCapabilities } from 'isomorphic-jj/browser';
|
|
738
|
+
import { detectCapabilities } from '@johnhenry/isomorphic-jj/browser';
|
|
638
739
|
const caps = detectCapabilities();
|
|
639
740
|
if (caps.indexedDB && caps.serviceWorker) {
|
|
640
741
|
// Enable offline support
|
|
641
742
|
}
|
|
642
743
|
|
|
643
744
|
// Get storage quota
|
|
644
|
-
import { getStorageQuota } from 'isomorphic-jj/browser';
|
|
745
|
+
import { getStorageQuota } from '@johnhenry/isomorphic-jj/browser';
|
|
645
746
|
const quota = await getStorageQuota();
|
|
646
747
|
console.log(`Using ${quota.percentage}% of available storage`);
|
|
647
748
|
```
|
|
@@ -794,7 +895,7 @@ Full API documentation available in [TypeScript definitions](./src/types.d.ts).
|
|
|
794
895
|
### Main Interface
|
|
795
896
|
|
|
796
897
|
```typescript
|
|
797
|
-
import { createJJ, type JJ, type CreateJJOptions } from 'isomorphic-jj';
|
|
898
|
+
import { createJJ, type JJ, type CreateJJOptions } from '@johnhenry/isomorphic-jj';
|
|
798
899
|
|
|
799
900
|
const jj: JJ = await createJJ(options: CreateJJOptions);
|
|
800
901
|
```
|
|
@@ -898,10 +999,10 @@ repo/
|
|
|
898
999
|
|
|
899
1000
|
## Project Status
|
|
900
1001
|
|
|
901
|
-
**Current Version**: v1.7.0
|
|
902
|
-
**Test Coverage**:
|
|
903
|
-
**JJ CLI parity**: tracks Jujutsu through v0.
|
|
904
|
-
**Status**:
|
|
1002
|
+
**Current Version**: 0.2.0 (`@johnhenry/isomorphic-jj`; same library/API lineage as the old `isomorphic-jj`, which reached v1.7.0 before the rename — see the provenance note at the top of this file)
|
|
1003
|
+
**Test Coverage**: 1730 tests passing, ~97% statements / 91% branches
|
|
1004
|
+
**JJ CLI parity**: tracks Jujutsu through v0.44
|
|
1005
|
+
**Status**: Actively developed; broad JJ CLI semantic parity, used in the examples/apps in this repo and in [JJHub](#related-projects). Not yet 1.0 — API surface can still shift; pin a version and read the CHANGELOG before upgrading.
|
|
905
1006
|
|
|
906
1007
|
**Completed:**
|
|
907
1008
|
- ✅ v0.1: Core JJ experience (stable IDs, undo, bookmarks, revsets)
|
|
@@ -911,6 +1012,8 @@ repo/
|
|
|
911
1012
|
- ✅ v0.5: Custom merge drivers, enhanced revsets (time-based, graph analytics), conflict resolution enhancements
|
|
912
1013
|
- ✅ v1.0: Full JJ CLI semantic parity, complete `file.*`/`operations.*`/`git.*` namespaces
|
|
913
1014
|
- ✅ v1.5: Parity refresh tracking jj through **v0.43** — new revsets (`change_id`, `commit_id`, `subject`, `author_name`/`email`, `committer*`, `signed`, `divergent`, `merges`, `forks`, `first_parent`, `first_ancestors`, `fork_point`, `merge_point`, `exactly`, `present`, `coalesce`, `remote_tags`, `ancestors(x, depth)`), working `tags()`/`remote_tags()` revsets, and new commands: `revert()`, `redo()`, `sign()`/`unsign()`, `file.search()`, `bookmark.advance()`, `tag.set()`. See [CHANGELOG.md](./CHANGELOG.md).
|
|
1015
|
+
- ✅ 0.1.0 (post-rename): Parity refresh tracking jj through **v0.44** — `tag.track()`/`tag.untrack()`, the `builtin_log()` revset alias, and `file.search({ nameOnly: true })`; `git_refs()`/`git_head()` documented as deprecated (jj removed both in v0.43). See [CHANGELOG.md](./CHANGELOG.md) for what was deliberately left for a maintainer call (`jj run`, fetch-time remote tag/bookmark import).
|
|
1016
|
+
- ✅ 0.2.0: Storage-layer audit fixes — same-process storage locking, `undo()` reverting in-place graph/tag mutations, ambiguous change-id detection, categorized `push()` error codes, a conflict-marker newline fix, atomic protobuf writes, and open-ended revset ranges (`..b`, `a..`, `::`). See [CHANGELOG.md](./CHANGELOG.md).
|
|
914
1017
|
|
|
915
1018
|
See [ROADMAP.md](./ROADMAP.md) for detailed plans, and [CHANGELOG.md](./CHANGELOG.md) for the release history.
|
|
916
1019
|
|
|
@@ -982,7 +1085,7 @@ A: Yes! Colocated repos expose normal Git commits. Git users never see JJ metada
|
|
|
982
1085
|
A: Comparable to isomorphic-git for Git operations. JJ metadata (JSON) is fast in Node, acceptable in browsers. Large histories need pagination.
|
|
983
1086
|
|
|
984
1087
|
**Q: Does this support all JJ features?**
|
|
985
|
-
A:
|
|
1088
|
+
A: Broad semantic parity (tracks Jujutsu through v0.44), not every CLI/UX detail — it's a library, not a terminal UI. See [ROADMAP.md](./ROADMAP.md) for what's planned and what's deliberately out of scope.
|
|
986
1089
|
|
|
987
1090
|
**Q: Can I migrate my Git repo?**
|
|
988
1091
|
A: Yes! `jj.git.init()` works on existing Git repositories.
|
|
@@ -994,6 +1097,16 @@ A: JJ's model genuinely improves common workflows—stable change IDs, fearless
|
|
|
994
1097
|
|
|
995
1098
|
## Related Projects
|
|
996
1099
|
|
|
1100
|
+
### JJHub
|
|
1101
|
+
|
|
1102
|
+
[JJHub](https://jjhub.erisera.com) is a Jujutsu-native overlay on GitHub, built
|
|
1103
|
+
on this library (it uses `createJJ` directly): stable change IDs and stacked
|
|
1104
|
+
PRs on top of your existing GitHub repos, platform-wide undo, and an MCP
|
|
1105
|
+
server so coding agents get the same fearless-undo, no-staging-area workflow.
|
|
1106
|
+
Install the CLI with `npm i -g jjhub`.
|
|
1107
|
+
|
|
1108
|
+
### Others
|
|
1109
|
+
|
|
997
1110
|
- [isomorphic-git](https://isomorphic-git.org/) - Pure JS Git implementation (our foundation)
|
|
998
1111
|
- [Jujutsu](https://jj-vcs.github.io/jj/) - The original JJ version control system
|
|
999
1112
|
- [simple-git](https://github.com/steveukx/git-js) - Git wrapper for Node.js
|
|
@@ -1013,5 +1126,3 @@ Built on the shoulders of:
|
|
|
1013
1126
|
- [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) by William Hilton
|
|
1014
1127
|
- [Jujutsu](https://github.com/martinvonz/jj) by Martin von Zweigbergk
|
|
1015
1128
|
- The Git and JavaScript communities
|
|
1016
|
-
|
|
1017
|
-
**Status**: v1.0.0 | **Tests**: 371 passing | **Ready for**: Production use
|
package/bin/isojj.js
CHANGED
|
@@ -24,6 +24,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
24
24
|
'noTags',
|
|
25
25
|
'allowNew',
|
|
26
26
|
'preserveSnapshot',
|
|
27
|
+
'nameOnly',
|
|
27
28
|
]);
|
|
28
29
|
|
|
29
30
|
/** Coerce a raw flag-value string to a real boolean when it looks like one. */
|
|
@@ -179,7 +180,7 @@ History editing:
|
|
|
179
180
|
Files:
|
|
180
181
|
file.list [--revision <id>] List tracked files
|
|
181
182
|
file.show <path> Show file contents
|
|
182
|
-
file.search --pattern <re>
|
|
183
|
+
file.search --pattern <re> [--nameOnly] Search file contents (jj file search)
|
|
183
184
|
file.annotate --path <p> Blame-style annotation
|
|
184
185
|
|
|
185
186
|
Bookmarks & tags:
|
|
@@ -188,6 +189,8 @@ Bookmarks & tags:
|
|
|
188
189
|
bookmark.list
|
|
189
190
|
tag.set --name <n> --changeId <id> Create or move a tag
|
|
190
191
|
tag.list
|
|
192
|
+
tag.track --name <n> [--remote <r>] Track a remote tag (jj v0.44)
|
|
193
|
+
tag.untrack --name <n> Stop tracking a remote tag
|
|
191
194
|
|
|
192
195
|
Signing:
|
|
193
196
|
sign [--revision <id>] [--backend ssh --key <k>]
|