isomorphic-jj 1.7.0 → 1.9.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 +425 -1
- package/LICENSE +1 -1
- package/README.md +233 -35
- package/bin/isojj.js +4 -1
- package/dist/index.cjs +2419 -378
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2419 -377
- package/dist/index.mjs.map +1 -1
- package/package.json +24 -10
- package/src/api/repository.js +966 -153
- package/src/backend/isomorphic-git-backend.js +72 -15
- package/src/browser/helpers.js +40 -18
- package/src/browser/index.d.ts +9 -2
- package/src/core/change-graph.js +125 -4
- package/src/core/conflict-model.js +72 -7
- package/src/core/jj-checkout.js +5 -16
- package/src/core/jj-operation-store.js +11 -19
- package/src/core/jj-tree-state.js +8 -18
- package/src/core/jj-view-store.js +9 -19
- package/src/core/merge-driver-registry.js +11 -10
- package/src/core/revset-engine.js +63 -6
- package/src/core/storage-manager.js +56 -18
- package/src/core/tag-store.js +42 -15
- package/src/core/workspace-manager.js +4 -3
- package/src/protos/local_working_copy.json.js +146 -0
- package/src/protos/schema.js +49 -0
- package/src/protos/simple_op_store.json.js +374 -0
- package/src/types.d.ts +33 -0
- package/src/utils/atomic-write.js +46 -0
- package/src/utils/bytes.js +67 -0
- package/src/utils/id-generation.js +18 -4
- package/src/utils/mkdirp.js +41 -0
- package/src/utils/mutex.js +63 -0
- package/src/utils/posix-path.js +117 -0
package/README.md
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
# isomorphic-jj
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/isomorphic-jj)
|
|
4
|
-
[](https://github.com/johnhenry/isomorphic-jj)
|
|
5
|
-
[](LICENSE)
|
|
1
|
+
# @johnhenry/isomorphic-jj
|
|
6
2
|
|
|
7
3
|
> **Jujutsu version control for JavaScript**—stable change IDs, fearless undo, and no staging area. Works in Node.js and browsers.
|
|
8
4
|
|
|
5
|
+
Full documentation: [opensource.johnhenry.me/isomorphic-jj](https://opensource.johnhenry.me/isomorphic-jj/)
|
|
6
|
+
|
|
7
|
+
> Previously published as `isomorphic-jj` (final release 1.8.0, deprecated in
|
|
8
|
+
> favor of this package). Renamed to `@johnhenry/isomorphic-jj` and restarted
|
|
9
|
+
> at 0.0.0 on import into the @johnhenry family — a new address and era, not
|
|
10
|
+
> a maturity signal.
|
|
11
|
+
|
|
12
|
+
[](https://www.npmjs.com/package/@johnhenry/isomorphic-jj)
|
|
13
|
+
[](https://github.com/johnhenry/isomorphic-jj/actions/workflows/test.yml)
|
|
14
|
+
[](LICENSE)
|
|
15
|
+
|
|
9
16
|
**What makes it different:**
|
|
10
17
|
- 🎯 **Stable change IDs** that survive rebases/squashes
|
|
11
18
|
- ↩️ **Complete undo** for any operation (not just commits)
|
|
@@ -14,7 +21,7 @@
|
|
|
14
21
|
- 🌐 **True isomorphic**—same API in Node, browsers, and Web Workers
|
|
15
22
|
|
|
16
23
|
```javascript
|
|
17
|
-
import { createJJ } from 'isomorphic-jj';
|
|
24
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
18
25
|
import git from 'isomorphic-git';
|
|
19
26
|
import fs from 'fs';
|
|
20
27
|
|
|
@@ -30,6 +37,83 @@ await jj.undo();
|
|
|
30
37
|
|
|
31
38
|
---
|
|
32
39
|
|
|
40
|
+
## Contents
|
|
41
|
+
|
|
42
|
+
- [Try it in 60 seconds](#try-it-in-60-seconds)
|
|
43
|
+
- [Quick Start](#quick-start)
|
|
44
|
+
- [Examples](#examples)
|
|
45
|
+
- [What's New in v1.0](#whats-new-in-v10)
|
|
46
|
+
- [How is this Different?](#how-is-this-different)
|
|
47
|
+
- [Features](#features)
|
|
48
|
+
- [Use Cases](#use-cases)
|
|
49
|
+
- [API Reference](#api-reference)
|
|
50
|
+
- [Why isomorphic-jj?](#why-isomorphic-jj)
|
|
51
|
+
- [Architecture](#architecture)
|
|
52
|
+
- [Project Status](#project-status)
|
|
53
|
+
- [Installation & Environment](#installation--environment)
|
|
54
|
+
- [Security model](#security-model)
|
|
55
|
+
- [Contributing](#contributing)
|
|
56
|
+
- [FAQ](#faq)
|
|
57
|
+
- [Family](#family)
|
|
58
|
+
- [License](#license)
|
|
59
|
+
- [Acknowledgments](#acknowledgments)
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Try it in 60 seconds
|
|
64
|
+
|
|
65
|
+
### CLI
|
|
66
|
+
|
|
67
|
+
Every method is reachable from the `isojj` CLI (`bin/isojj.js`). Run it with
|
|
68
|
+
`npx`, or install it globally (`npm i -g @johnhenry/isomorphic-jj`) and call
|
|
69
|
+
`isojj` directly:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx @johnhenry/isomorphic-jj init
|
|
73
|
+
echo "# hello" > README.md
|
|
74
|
+
npx @johnhenry/isomorphic-jj describe -m "Initial commit"
|
|
75
|
+
npx @johnhenry/isomorphic-jj new -m "Add a feature"
|
|
76
|
+
npx @johnhenry/isomorphic-jj log
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Change History:
|
|
81
|
+
|
|
82
|
+
cb7b4e93fd23 Add a feature
|
|
83
|
+
You 9/2/2026, 7:20:30 AM
|
|
84
|
+
|
|
85
|
+
a2f6f5af8107 Initial commit
|
|
86
|
+
You 9/2/2026, 7:20:30 AM
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Run `npx @johnhenry/isomorphic-jj --help` for the full command list.
|
|
90
|
+
|
|
91
|
+
### Node.js
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
95
|
+
import fs from 'fs';
|
|
96
|
+
|
|
97
|
+
const jj = await createJJ({ fs, dir: './my-repo' });
|
|
98
|
+
await jj.init({ userName: 'You', userEmail: 'you@example.com' });
|
|
99
|
+
await jj.write({ path: 'README.md', data: '# hello\n' });
|
|
100
|
+
await jj.describe({ message: 'Initial commit' });
|
|
101
|
+
console.log(await jj.log({ limit: 5 }));
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Browser
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
108
|
+
import { createBrowserFS } from '@johnhenry/isomorphic-jj/browser';
|
|
109
|
+
|
|
110
|
+
const fs = await createBrowserFS({ name: 'my-repo' });
|
|
111
|
+
const jj = await createJJ({ fs, dir: '/repo' });
|
|
112
|
+
// Same API as Node from here on — see examples/12-browser.mjs.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
33
117
|
## Quick Start
|
|
34
118
|
|
|
35
119
|
**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 +126,43 @@ This showcases all 17 feature categories (including v1.0 enhancements) in ~3 sec
|
|
|
42
126
|
|
|
43
127
|
---
|
|
44
128
|
|
|
129
|
+
## Examples
|
|
130
|
+
|
|
131
|
+
The [`examples/`](./examples) directory has 12 numbered, runnable, self-asserting
|
|
132
|
+
scripts — each creates a repo in a temp dir, demonstrates one area, and cleans
|
|
133
|
+
up after itself. Run one with `npm run example:01` (etc.), or the whole set
|
|
134
|
+
(the same loop CI runs) with:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm run examples
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| # | What it shows |
|
|
141
|
+
|---|----------------|
|
|
142
|
+
| [01](./examples/01-init-commit-log.mjs) | The core loop: init → write → `describe()` → `new()` → `log()` |
|
|
143
|
+
| [02](./examples/02-stacked-changes.mjs) | Stable change IDs across a 3-layer stack; `edit()`+`amend()` a bottom layer |
|
|
144
|
+
| [03](./examples/03-history-editing.mjs) | `split()`, `squash({ into })`, `abandon()`/`unabandon()`, `duplicate()` |
|
|
145
|
+
| [04](./examples/04-branching-and-merging.mjs) | Diverging without branches; content merge vs. true merge changes |
|
|
146
|
+
| [05](./examples/05-conflicts.mjs) | First-class conflicts: dry-run preview, `conflicts.list()`/`resolve()` |
|
|
147
|
+
| [06](./examples/06-revsets.mjs) | The revset query language, end to end |
|
|
148
|
+
| [07](./examples/07-bookmarks-and-tags.mjs) | Bookmarks, tags, and remote tracking |
|
|
149
|
+
| [08](./examples/08-git-interop.mjs) | Real `git.clone()`/`push()`/`fetch()` against a local fixture, zero network |
|
|
150
|
+
| [09](./examples/09-config.mjs) | Config layers: persisted, programmatic, deep-merge, reset |
|
|
151
|
+
| [10](./examples/10-undo-and-oplog.mjs) | `undo()`/`redo()`, `operations.at()` time travel, `obslog()` |
|
|
152
|
+
| [11](./examples/11-file-operations.mjs) | The `file.*` namespace: historical `show()`, `annotate()`, `search()` |
|
|
153
|
+
| [12](./examples/12-browser.mjs) | Browser usage (IndexedDB fs, persistence, CORS proxy) as a reference |
|
|
154
|
+
|
|
155
|
+
See [`examples/README.md`](./examples/README.md) for details and conventions.
|
|
156
|
+
|
|
157
|
+
Three fuller example applications live under [`example/`](./example) — each
|
|
158
|
+
exercises the library from a different angle and documents the bugs it found:
|
|
159
|
+
|
|
160
|
+
- [`jj-review-tool`](./example/jj-review-tool) — a code-review CLI built on stacked changes and bookmarks.
|
|
161
|
+
- [`jj-storage-server`](./example/jj-storage-server) — a REST API using isomorphic-jj as a versioned document store.
|
|
162
|
+
- [`jj-wiki`](./example/jj-wiki) — a collaborative wiki exercising workspaces and conflict resolution.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
45
166
|
## What's New in v1.0
|
|
46
167
|
|
|
47
168
|
isomorphic-jj v1.0 achieves **complete JJ CLI semantic compatibility** while maintaining 100% backward compatibility:
|
|
@@ -76,6 +197,7 @@ isomorphic-jj v1.0 now implements virtually all commonly-used JJ CLI commands:
|
|
|
76
197
|
- **`git.remote.*`** - Complete remote management (list, add, remove, rename, setUrl)
|
|
77
198
|
- **`bookmark.rename()`** - Rename bookmarks
|
|
78
199
|
- **`bookmark.track/untrack/forget()`** - Remote bookmark management
|
|
200
|
+
- **`tag.track/untrack()`** - Remote tag management (jj v0.44)
|
|
79
201
|
- **`config.*`** - Configuration management (get, set, list)
|
|
80
202
|
- **`diff()`** - Show file differences between revisions
|
|
81
203
|
- **`next()` / `prev()`** - Navigate between parent/child revisions
|
|
@@ -85,10 +207,10 @@ isomorphic-jj v1.0 now implements virtually all commonly-used JJ CLI commands:
|
|
|
85
207
|
- **`operations.show/diff/restore()`** - Advanced operation log features
|
|
86
208
|
- **`remote.*`** - Convenience aliases for git operations (push, fetch, add)
|
|
87
209
|
|
|
88
|
-
See [
|
|
210
|
+
See [ROADMAP.md](./ROADMAP.md) for the release-by-release feature comparison and what's still planned.
|
|
89
211
|
|
|
90
212
|
### Testing & Quality
|
|
91
|
-
- **
|
|
213
|
+
- **1823 tests** passing — ~97% statement / 91% branch coverage
|
|
92
214
|
- **100% backward compatible** - All existing code continues to work
|
|
93
215
|
- **Zero breaking changes** - Deprecated features show warnings but still function
|
|
94
216
|
|
|
@@ -97,7 +219,7 @@ See [JJ_CLI_PARITY.md](./JJ_CLI_PARITY.md) for complete feature comparison.
|
|
|
97
219
|
### Installation
|
|
98
220
|
|
|
99
221
|
```bash
|
|
100
|
-
npm install isomorphic-jj isomorphic-git
|
|
222
|
+
npm install @johnhenry/isomorphic-jj isomorphic-git
|
|
101
223
|
```
|
|
102
224
|
|
|
103
225
|
For browsers, also install:
|
|
@@ -108,7 +230,7 @@ npm install @isomorphic-git/lightning-fs
|
|
|
108
230
|
### Basic Workflow
|
|
109
231
|
|
|
110
232
|
```javascript
|
|
111
|
-
import { createJJ } from 'isomorphic-jj';
|
|
233
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
112
234
|
import git from 'isomorphic-git';
|
|
113
235
|
import fs from 'fs';
|
|
114
236
|
import http from 'isomorphic-git/http/node';
|
|
@@ -147,13 +269,13 @@ console.log(log);
|
|
|
147
269
|
### Browser Usage
|
|
148
270
|
|
|
149
271
|
```javascript
|
|
150
|
-
import { createJJ } from 'isomorphic-jj';
|
|
151
|
-
import { createBrowserFS } from 'isomorphic-jj/browser';
|
|
272
|
+
import { createJJ } from '@johnhenry/isomorphic-jj';
|
|
273
|
+
import { createBrowserFS } from '@johnhenry/isomorphic-jj/browser';
|
|
152
274
|
import git from 'isomorphic-git';
|
|
153
275
|
import http from 'isomorphic-git/http/web';
|
|
154
276
|
|
|
155
277
|
// Create browser filesystem (uses IndexedDB)
|
|
156
|
-
const fs = createBrowserFS({ name: 'my-repo' });
|
|
278
|
+
const fs = await createBrowserFS({ name: 'my-repo' });
|
|
157
279
|
|
|
158
280
|
const jj = await createJJ({ fs, dir: '/repo', git, http });
|
|
159
281
|
await jj.git.init({ userName: 'User', userEmail: 'user@example.com' });
|
|
@@ -494,7 +616,7 @@ await jj.undo();
|
|
|
494
616
|
Merge drivers enable smart merging of structured files like JSON, package.json, and YAML:
|
|
495
617
|
|
|
496
618
|
```javascript
|
|
497
|
-
import { jsonDriver, packageJsonDriver, yamlDriver, markdownDriver } from 'isomorphic-jj';
|
|
619
|
+
import { jsonDriver, packageJsonDriver, yamlDriver, markdownDriver } from '@johnhenry/isomorphic-jj';
|
|
498
620
|
|
|
499
621
|
// Register merge drivers for different file types
|
|
500
622
|
jj.mergeDrivers.register({
|
|
@@ -625,23 +747,23 @@ const bookmarks = await jj.bookmark.list();
|
|
|
625
747
|
### Browser Support
|
|
626
748
|
|
|
627
749
|
```javascript
|
|
628
|
-
import { createBrowserFS, requestPersistentStorage } from 'isomorphic-jj/browser';
|
|
750
|
+
import { createBrowserFS, requestPersistentStorage } from '@johnhenry/isomorphic-jj/browser';
|
|
629
751
|
|
|
630
752
|
// Request persistent storage (prevents eviction)
|
|
631
753
|
const persistent = await requestPersistentStorage();
|
|
632
754
|
|
|
633
755
|
// Create filesystem with IndexedDB backend
|
|
634
|
-
const fs = createBrowserFS({ backend: 'idb', name: 'my-repo' });
|
|
756
|
+
const fs = await createBrowserFS({ backend: 'idb', name: 'my-repo' });
|
|
635
757
|
|
|
636
758
|
// Check browser capabilities
|
|
637
|
-
import { detectCapabilities } from 'isomorphic-jj/browser';
|
|
759
|
+
import { detectCapabilities } from '@johnhenry/isomorphic-jj/browser';
|
|
638
760
|
const caps = detectCapabilities();
|
|
639
761
|
if (caps.indexedDB && caps.serviceWorker) {
|
|
640
762
|
// Enable offline support
|
|
641
763
|
}
|
|
642
764
|
|
|
643
765
|
// Get storage quota
|
|
644
|
-
import { getStorageQuota } from 'isomorphic-jj/browser';
|
|
766
|
+
import { getStorageQuota } from '@johnhenry/isomorphic-jj/browser';
|
|
645
767
|
const quota = await getStorageQuota();
|
|
646
768
|
console.log(`Using ${quota.percentage}% of available storage`);
|
|
647
769
|
```
|
|
@@ -794,7 +916,7 @@ Full API documentation available in [TypeScript definitions](./src/types.d.ts).
|
|
|
794
916
|
### Main Interface
|
|
795
917
|
|
|
796
918
|
```typescript
|
|
797
|
-
import { createJJ, type JJ, type CreateJJOptions } from 'isomorphic-jj';
|
|
919
|
+
import { createJJ, type JJ, type CreateJJOptions } from '@johnhenry/isomorphic-jj';
|
|
798
920
|
|
|
799
921
|
const jj: JJ = await createJJ(options: CreateJJOptions);
|
|
800
922
|
```
|
|
@@ -804,8 +926,8 @@ const jj: JJ = await createJJ(options: CreateJJOptions);
|
|
|
804
926
|
- **Repository**: `init()`, `status()`, `stats()`
|
|
805
927
|
- **Files**: `write()`, `read()`, `cat()`, `move()` (deprecated for history), `remove()`, `listFiles()` | **Namespace**: `file.write()`, `file.show()`, `file.list()`, `file.move()`, `file.remove()`
|
|
806
928
|
- **Changes**: `describe()`, `new()`, `amend()`, `commit()`, `edit()`, `show()`
|
|
807
|
-
- **History**: `log()`, `obslog()`, `squash()`, `split()`, `rebase()`, `abandon()`, `unabandon()`
|
|
808
|
-
- **Operations**: `undo()`, `operations.list()`, `operations.at()`
|
|
929
|
+
- **History**: `log()`, `obslog()`, `squash()`, `split()`, `rebase()`, `abandon()`, `unabandon()`, `converge()`
|
|
930
|
+
- **Operations**: `undo()`, `redo()`, `operations.list()`, `operations.at()`, `operations.restore()`
|
|
809
931
|
- **Bookmarks**: `bookmark.list()`, `bookmark.set()`, `bookmark.move()`, `bookmark.delete()`
|
|
810
932
|
- **Git**: `git.init()`, `git.fetch()`, `git.push()`, `git.import()`, `git.export()`
|
|
811
933
|
- **Remotes**: `remote.add()`, `remote.fetch()`, `remote.push()`
|
|
@@ -898,10 +1020,10 @@ repo/
|
|
|
898
1020
|
|
|
899
1021
|
## Project Status
|
|
900
1022
|
|
|
901
|
-
**Current Version**: v1.7.0
|
|
902
|
-
**Test Coverage**:
|
|
903
|
-
**JJ CLI parity**: tracks Jujutsu through v0.
|
|
904
|
-
**Status**:
|
|
1023
|
+
**Current Version**: 0.3.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)
|
|
1024
|
+
**Test Coverage**: 1823 tests passing, ~97% statements / 91% branches
|
|
1025
|
+
**JJ CLI parity**: tracks Jujutsu through v0.45.1
|
|
1026
|
+
**Status**: Actively developed; broad JJ CLI semantic parity, used in the examples/apps in this repo and in [JJHub](#family). Not yet 1.0 — API surface can still shift; pin a version and read the CHANGELOG before upgrading.
|
|
905
1027
|
|
|
906
1028
|
**Completed:**
|
|
907
1029
|
- ✅ v0.1: Core JJ experience (stable IDs, undo, bookmarks, revsets)
|
|
@@ -911,6 +1033,9 @@ repo/
|
|
|
911
1033
|
- ✅ v0.5: Custom merge drivers, enhanced revsets (time-based, graph analytics), conflict resolution enhancements
|
|
912
1034
|
- ✅ v1.0: Full JJ CLI semantic parity, complete `file.*`/`operations.*`/`git.*` namespaces
|
|
913
1035
|
- ✅ 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).
|
|
1036
|
+
- ✅ 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).
|
|
1037
|
+
- ✅ 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).
|
|
1038
|
+
- ✅ 0.3.0: Parity refresh tracking jj through **v0.45.1** — a working `/browser` entry (async `createBrowserFS()`, bundle-safe protobuf schema loading, no stray Node-only imports), `undo()`/`operations.restore()` reverting graph rewrites with real progressive undo/redo, `squash()`/`abandon()`/`edit()`/`new()` fully syncing content instead of only metadata, `rebase()` performing a real three-way merge and recording conflicts, and the new `converge()` command (jj v0.45.0) to auto-resolve divergent commits. See [CHANGELOG.md](./CHANGELOG.md).
|
|
914
1039
|
|
|
915
1040
|
See [ROADMAP.md](./ROADMAP.md) for detailed plans, and [CHANGELOG.md](./CHANGELOG.md) for the release history.
|
|
916
1041
|
|
|
@@ -920,7 +1045,7 @@ See [ROADMAP.md](./ROADMAP.md) for detailed plans, and [CHANGELOG.md](./CHANGELO
|
|
|
920
1045
|
|
|
921
1046
|
### Requirements
|
|
922
1047
|
|
|
923
|
-
- **Node.js**:
|
|
1048
|
+
- **Node.js**: 26.0.0 or higher, matching `engines.node` and CI (uses the Web Crypto global, stabilized in Node 20 — the family floor is higher than the functional minimum)
|
|
924
1049
|
- **Browsers**: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
|
|
925
1050
|
- **Optional**: Git CLI (for JJ CLI interop testing)
|
|
926
1051
|
|
|
@@ -941,6 +1066,58 @@ See [ROADMAP.md](./ROADMAP.md) for detailed plans, and [CHANGELOG.md](./CHANGELO
|
|
|
941
1066
|
|
|
942
1067
|
---
|
|
943
1068
|
|
|
1069
|
+
## Security model
|
|
1070
|
+
|
|
1071
|
+
isomorphic-jj is a headless reimplementation of JJ's model, not a wrapper
|
|
1072
|
+
around the `jj` or `git` binaries — that architectural choice is also its
|
|
1073
|
+
main security property. It does not run a sandbox and does not vet the
|
|
1074
|
+
repositories you point it at.
|
|
1075
|
+
|
|
1076
|
+
**What isomorphic-jj guarantees:**
|
|
1077
|
+
|
|
1078
|
+
- **No shell-out, ever.** The library never spawns a child process to run
|
|
1079
|
+
`git`, `jj`, or any other binary — repository paths, branch/bookmark
|
|
1080
|
+
names, and commit messages are handled as plain JS values through
|
|
1081
|
+
`isomorphic-git`'s and `protobufjs`'s pure-JS encoders, never interpolated
|
|
1082
|
+
into a shell command string. This removes the whole class of
|
|
1083
|
+
shell-injection risk that a CLI-wrapping library (like `simple-git`) has
|
|
1084
|
+
to defend against by construction, not by escaping.
|
|
1085
|
+
- **Storage writes are atomic.** `jj-operation-store.js`, `jj-view-store.js`,
|
|
1086
|
+
and `jj-tree-state.js` route every write through a shared
|
|
1087
|
+
`atomicWriteFile()` temp+rename helper, and same-process concurrent writes
|
|
1088
|
+
to the same path are serialized through a path-keyed async mutex — a
|
|
1089
|
+
crash or a racing autosnapshot mid-write cannot leave a truncated or
|
|
1090
|
+
undecodable store file. Fixed in [PR #19](https://github.com/johnhenry/isomorphic-jj/pull/19) (0.2.0); scoped to
|
|
1091
|
+
same-process concurrency — cross-process locking is not yet implemented
|
|
1092
|
+
(see "still yours" below).
|
|
1093
|
+
|
|
1094
|
+
**What is still yours:**
|
|
1095
|
+
|
|
1096
|
+
- **Remote authentication and transport trust.** `git.push()`/`git.fetch()`
|
|
1097
|
+
delegate to `isomorphic-git`'s HTTP client (and, in the browser, whatever
|
|
1098
|
+
CORS proxy you configure) — isomorphic-jj neither stores nor validates
|
|
1099
|
+
credentials, and does not verify a remote server's identity beyond what
|
|
1100
|
+
that HTTP layer does. A misconfigured or malicious CORS proxy sees
|
|
1101
|
+
everything a normal Git remote would.
|
|
1102
|
+
- **Custom merge drivers run arbitrary code with full process access.**
|
|
1103
|
+
`mergeDrivers.register()` lets you register a function per file
|
|
1104
|
+
pattern (`'*.json'`, `'package.json'`, …); a registered driver — yours or
|
|
1105
|
+
a dependency's — executes with the same privileges as the rest of your
|
|
1106
|
+
process during every merge that touches a matching path. isomorphic-jj
|
|
1107
|
+
does not sandbox driver execution; treat a third-party merge driver the
|
|
1108
|
+
same as any other third-party code you'd `require()`.
|
|
1109
|
+
- **Cross-process locking is not implemented.** The same-process mutex
|
|
1110
|
+
above does not protect a repository opened by two separate OS processes
|
|
1111
|
+
at once (e.g. two CLI invocations, or a CLI run concurrently with a
|
|
1112
|
+
long-running server process) — don't rely on isomorphic-jj to serialize
|
|
1113
|
+
those for you.
|
|
1114
|
+
- **No allowlisting of untrusted repository content.** Cloning or importing
|
|
1115
|
+
a Git history runs its objects through the same parsing code regardless
|
|
1116
|
+
of whether you trust the source, matching `isomorphic-git`'s own model —
|
|
1117
|
+
this library adds no additional vetting layer.
|
|
1118
|
+
|
|
1119
|
+
---
|
|
1120
|
+
|
|
944
1121
|
## Contributing
|
|
945
1122
|
|
|
946
1123
|
We welcome contributions! Here's how to help:
|
|
@@ -982,7 +1159,7 @@ A: Yes! Colocated repos expose normal Git commits. Git users never see JJ metada
|
|
|
982
1159
|
A: Comparable to isomorphic-git for Git operations. JJ metadata (JSON) is fast in Node, acceptable in browsers. Large histories need pagination.
|
|
983
1160
|
|
|
984
1161
|
**Q: Does this support all JJ features?**
|
|
985
|
-
A:
|
|
1162
|
+
A: Broad semantic parity (tracks Jujutsu through v0.45.1), 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
1163
|
|
|
987
1164
|
**Q: Can I migrate my Git repo?**
|
|
988
1165
|
A: Yes! `jj.git.init()` works on existing Git repositories.
|
|
@@ -992,12 +1169,35 @@ A: JJ's model genuinely improves common workflows—stable change IDs, fearless
|
|
|
992
1169
|
|
|
993
1170
|
---
|
|
994
1171
|
|
|
995
|
-
##
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1172
|
+
## Family
|
|
1173
|
+
|
|
1174
|
+
isomorphic-jj has no other package in the `@johnhenry` npm scope yet — it
|
|
1175
|
+
consumes and is consumed by projects outside that scope. Those relationships
|
|
1176
|
+
are still concrete enough to document precisely, so they're recorded here
|
|
1177
|
+
rather than under a vaguer "see also".
|
|
1178
|
+
|
|
1179
|
+
- **[JJHub](https://jjhub.erisera.com)** — a Jujutsu-native overlay on GitHub,
|
|
1180
|
+
and the closest thing this library has to a downstream consumer: it calls
|
|
1181
|
+
`createJJ()` directly (not a wrapped or forked copy) to get stable change
|
|
1182
|
+
IDs and stacked PRs on top of existing GitHub repos, platform-wide undo,
|
|
1183
|
+
and an MCP server exposing the same fearless-undo, no-staging-area
|
|
1184
|
+
workflow to coding agents. JJHub is a private Erisera product, not a
|
|
1185
|
+
`@johnhenry/*` package — install its CLI with `npm i -g jjhub`.
|
|
1186
|
+
- **[`isomorphic-git`](https://github.com/isomorphic-git/isomorphic-git)** —
|
|
1187
|
+
a real, non-optional dependency: this library's Git backend support
|
|
1188
|
+
(`jj.git.init/clone/push/fetch`) is built directly on isomorphic-git's
|
|
1189
|
+
pure-JS Git object model, not a fork or reimplementation of it. Peer
|
|
1190
|
+
dependency, not bundled — see [Installation & Environment](#installation--environment).
|
|
1191
|
+
|
|
1192
|
+
### Prior art
|
|
1193
|
+
|
|
1194
|
+
- [Jujutsu](https://jj-vcs.github.io/jj/) — the original JJ version control
|
|
1195
|
+
system this library reimplements the semantics of (headlessly, in JS; not
|
|
1196
|
+
a wrapper around the `jj` binary).
|
|
1197
|
+
- [simple-git](https://github.com/steveukx/git-js) — a Git CLI wrapper for
|
|
1198
|
+
Node.js; unlike isomorphic-jj, it shells out to the `git` binary rather
|
|
1199
|
+
than reimplementing the object model in JS.
|
|
1200
|
+
- [Git plumbing vs porcelain](https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain) — the architecture pattern this library's own layering follows.
|
|
1001
1201
|
|
|
1002
1202
|
---
|
|
1003
1203
|
|
|
@@ -1013,5 +1213,3 @@ Built on the shoulders of:
|
|
|
1013
1213
|
- [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) by William Hilton
|
|
1014
1214
|
- [Jujutsu](https://github.com/martinvonz/jj) by Martin von Zweigbergk
|
|
1015
1215
|
- 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>]
|