isomorphic-jj 1.4.3 → 1.7.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 ADDED
@@ -0,0 +1,226 @@
1
+ # Changelog
2
+
3
+ All notable changes to isomorphic-jj are documented here. The project tracks the
4
+ [Jujutsu (jj)](https://github.com/jj-vcs/jj) CLI; each release notes the upstream
5
+ jj version whose semantics it targets.
6
+
7
+ ## [1.7.0] — Real revset parser, testable/UX-improved CLI, background-ops fix
8
+
9
+ Closes out the remaining known-issue backlog.
10
+
11
+ ### Changed — revset engine rewritten around a real tokenizer + parser + AST
12
+
13
+ `RevsetEngine.evaluate()` previously matched the whole trimmed expression
14
+ against one long chain of regexes, one per construct. This had two real bugs:
15
+
16
+ - **Nested function-call arguments silently mis-parsed.** A non-greedy regex
17
+ like `/^roots\((.+?)\)$/` truncates at the *first* `)` it sees, so
18
+ `roots(ancestors(x))` never worked — the previous "fix" for this was a
19
+ one-off hand-rolled paren-counter used only by `reachable()`.
20
+ - **No real operator precedence.** Mixed `&`/`|`/`~` expressions were
21
+ resolved by checking `.includes(' & ')` before `' | '` before `' ~ '`
22
+ regardless of the expression's actual structure.
23
+
24
+ The expression is now tokenized and parsed into a small AST via a real
25
+ recursive-descent / precedence-climbing parser (`&`/`~` at one precedence
26
+ tier, `|` lower, both left-associative), which is then evaluated by walking
27
+ the tree. This is purely a parsing-layer replacement — every per-function
28
+ filter/traversal method (`filterByAuthor`, `getAncestors`, `findForkPoint`,
29
+ etc.) is untouched, and **all 183 pre-existing revset tests pass unchanged**.
30
+
31
+ New capabilities (all strictly additive — nothing that worked before changed
32
+ behavior):
33
+ - Function-call arguments can nest arbitrarily deep: `roots(ancestors(x))`,
34
+ `heads(roots(ancestors(x)))`, etc.
35
+ - Parenthesized grouping: `(a | b) & c`.
36
+ - Quote-aware argument splitting: `description("a, b, and c")` no longer
37
+ mis-splits on the comma inside the quoted string.
38
+ - Whitespace around `&`/`|`/`~` is now optional (`a|b` works, not just `a | b`).
39
+
40
+ ### Fixed — background-ops (td-488842)
41
+
42
+ `enableAutoSnapshot()`'s timer callback awaited only `queue()`'s immediate
43
+ `{ id, promise }` handle, not the operation's own settling `promise`. Since
44
+ `queue()` resolves as soon as the operation is *enqueued* (not once it
45
+ finishes), a later `describe()` rejection landed on the unobserved promise
46
+ and became an unhandled promise rejection instead of reaching the
47
+ surrounding `catch`. Now awaits the returned `promise` too.
48
+
49
+ ### Changed — CLI (`bin/isojj.js`) rewritten to be testable, with real bugs fixed
50
+
51
+ The CLI had zero tests (calling `process.exit()` at import time made it
52
+ untestable) and several real bugs:
53
+ - **Made testable**: `parseArgs`/`formatOutput`/`findRepoRoot`/
54
+ `loadGitBackend`/`run` are now exported; `run()` returns an exit code
55
+ instead of calling `process.exit()` directly, guarded behind an
56
+ is-this-the-entrypoint check.
57
+ - **Fixed a parsing bug**: boolean flags (`dryRun`, `json`, `force`,
58
+ `interactive`, etc.) used to swallow the next token as a fake "value",
59
+ silently dropping a following positional — `describe --dryRun "fix typo"`
60
+ lost the commit message. Boolean flags never consume the next token now;
61
+ `--flag=true`/`--flag=false` is coerced to a real boolean.
62
+ - **Fixed a latent bug**: the CLI's own error message promised searching
63
+ "any parent up to mount point /" for a `.jj` repo but never actually did
64
+ so. Added `findRepoRoot()` to walk up parent directories like git/jj, so
65
+ the CLI now works from any subdirectory of a repo.
66
+ - **Wired up the real Git backend**: the CLI never passed `git`/`http` to
67
+ `createJJ()`, so `isojj init` silently ran in storage-only "mock" mode
68
+ with no real `.git` directory. It now dynamically imports isomorphic-git
69
+ (an optional peer dependency) when present, falling back gracefully when
70
+ it isn't.
71
+ - Added a full test suite (`tests/integration/cli.test.js`) and included
72
+ `bin/**/*.js` in coverage collection.
73
+
74
+ ### Testing
75
+
76
+ 1701 tests passing (was 1671); lint, format:check, typecheck, coverage
77
+ (91.2% branches), and build all green.
78
+
79
+ ## [1.6.1] — Bugfix batch
80
+
81
+ Fixes nine real, pre-existing defects that the v1.5/v1.6 coverage push
82
+ surfaced and documented (each had a test asserting the buggy behavior with a
83
+ `// BUG:` note — those tests now assert the corrected behavior instead).
84
+
85
+ ### Fixed
86
+
87
+ - **`bookmarks([pattern])` revset returned `[undefined]`.** `filterBookmarks()`
88
+ read `bookmark.target`, but `BookmarkStore.list()` returns objects keyed
89
+ `changeId`. It now reads `bookmark.changeId`.
90
+ - **`show()` never attributed bookmarks to a change**, for the same
91
+ `.target` vs. `.changeId` mismatch. Fixed the same way.
92
+ - **`bookmark.delete()`'s not-found guard was dead code.** It called
93
+ `bookmarks.get(name)` without `await`, so the "truthy Promise" always
94
+ passed the guard; the store's own `BOOKMARK_NOT_FOUND` was reached
95
+ instead of the friendlier method-level `NOT_FOUND`. Added the missing
96
+ `await`.
97
+ - **`operations.revert()` on a bookmark-move operation always threw
98
+ `BOOKMARK_EXISTS`.** Its "moved bookmark" branch called `bookmarks.set()`
99
+ (create-only) to move the bookmark back, but the bookmark still exists
100
+ during a move-revert. Changed to `bookmarks.move()`.
101
+ - **`ChangeGraph.getAncestors()` returned duplicate ancestors on
102
+ diamond-shaped history** (e.g. `a<-b`, `a<-c`, `b,c<-d`). A node was only
103
+ marked visited when dequeued, so a shared ancestor reachable via two
104
+ parents could be enqueued twice. Nodes are now marked visited the moment
105
+ they're enqueued.
106
+ - **`matchesPattern()` (merge driver registry) broke `**` glob patterns.**
107
+ Literal dots were escaped *after* `**` had already been converted to
108
+ `.*`, corrupting it into `\.*` (zero-or-more literal dots) instead of "any
109
+ characters". Dots are now escaped before the glob substitutions.
110
+ - **`IsomorphicGitBackend.getCurrentTree()` always threw
111
+ `TREE_READ_FAILED`.** It called `git.writeTree({ fs, dir })` without the
112
+ library's required `tree` argument (`git.writeTree` writes a single,
113
+ already-built tree — it doesn't build one from the working directory).
114
+ Reimplemented to walk the git index (`STAGE`) and build the nested tree
115
+ bottom-up, verified to produce the same oid as an equivalent real commit.
116
+ - **`ConflictModel._tryMergeDriver()`'s "no custom driver" guard never
117
+ matched.** It compared `driver === this.mergeDriverRegistry.defaultDriver`,
118
+ but `MergeDriverRegistry` never set a `defaultDriver` property (always
119
+ `undefined`), so every file was routed through the generic three-way
120
+ merge driver instead of falling back to the richer path-based conflict-type
121
+ detection. `MergeDriverRegistry` now exposes `defaultDriver`.
122
+ - **`IsomorphicGitBackend.stageAll()` double-wrapped per-file errors.** A
123
+ specific `STAGE_FILE_FAILED` thrown for one file was immediately re-wrapped
124
+ by the surrounding `catch` as the generic `STAGE_FAILED`, hiding the more
125
+ useful code. The outer catch now re-throws an already-categorized
126
+ `JJError` as-is.
127
+
128
+ ## [1.6.0] — Automatic working-copy snapshotting
129
+
130
+ Closes a long-standing fidelity gap: the library now **walks the working
131
+ directory on disk** and reconciles tracked state before read/commit operations,
132
+ so files created, modified, or deleted **out-of-band** (an editor, the shell,
133
+ `git checkout`) are picked up — matching jj's "snapshot before every command".
134
+ Previously only files written through `jj.write()` were ever tracked, so
135
+ `status`/`describe`/`diff`/`file.search`/`read` couldn't see anything else.
136
+
137
+ ### Added
138
+
139
+ - `WorkingCopy.walk()` — recursively lists working-directory files (excluding
140
+ `.git`, `.jj`, `node_modules`), robust across Node fs and in-memory fses.
141
+ - `WorkingCopy.snapshot()` — reconciles tracked file state with disk, returning
142
+ `{ added, modified, deleted }`; honors sparse patterns.
143
+ - `jj.snapshot()` — public method to trigger a snapshot explicitly.
144
+ - `createJJ({ autoSnapshot })` option (default `true`) to opt out of the
145
+ automatic behavior.
146
+
147
+ ### Changed
148
+
149
+ - `describe()`, `status()`, `diff()`, `read()`, `file.list()`, and
150
+ `file.search()` now auto-snapshot the working copy first (when operating on
151
+ `@`), so they reflect on-disk reality. `status()` now returns real `added` and
152
+ `removed` lists (previously always empty).
153
+ - A tracked file deleted on disk is now gracefully untracked on the next
154
+ snapshot instead of making `describe()` throw `SNAPSHOT_FILE_FAILED` (that
155
+ path still applies under `{ autoSnapshot: false }`).
156
+
157
+ ## [1.5.0] — Parity refresh (tracks jj through v0.43)
158
+
159
+ This release brings isomorphic-jj up to date with Jujutsu releases v0.31–v0.43,
160
+ adds a batch of revset functions and commands, and cleans up the toolchain.
161
+
162
+ ### Added — revset functions (jj v0.31–v0.43)
163
+
164
+ - `change_id(prefix)` / `commit_id(prefix)` — resolve a change by a hex prefix (jj v0.31).
165
+ - `subject(pattern)` — match only the first line of a description (jj v0.26).
166
+ - `author_name(x)`, `author_email(x)`, `committer(x)`, `committer_name(x)`,
167
+ `committer_email(x)` — fine-grained signature filters (jj v0.26).
168
+ - `signed()` — cryptographically signed changes (jj v0.29).
169
+ - `divergent()` — changes flagged as divergent (jj v0.38).
170
+ - `merges()` — canonical alias of the existing `merge()`.
171
+ - `forks()` — changes with more than one child.
172
+ - `first_parent(x)` / `first_ancestors(x)` — first-parent navigation (jj v0.32).
173
+ - `fork_point(x)` — youngest common ancestor of a set (jj v0.32).
174
+ - `merge_point(x)` — youngest common descendant of a set.
175
+ - `exactly(x, n)` — the set, but errors unless it has exactly `n` elements (jj v0.34).
176
+ - `present(x)` — evaluate without erroring on unknown symbols.
177
+ - `coalesce(a, b, …)` — the first argument that resolves to a non-empty set.
178
+ - `remote_tags([pattern])` — remote (slash-qualified) tag targets (jj v0.38).
179
+ - `ancestors(x, depth)` — generalized to accept any nested revset plus an
180
+ optional depth limit (previously accepted only a bare 32-hex change ID).
181
+
182
+ ### Added — commands
183
+
184
+ - `revert({ revision })` — canonical replacement for `backout()` (jj renamed
185
+ `backout` → `revert` and removed `backout` in v0.35). `backout()` remains as a
186
+ deprecated alias.
187
+ - `redo()` — progressively re-applies operations reverted by `undo()` (jj v0.33).
188
+ - `sign()` / `unsign()` — record and clear signature metadata on a change (jj
189
+ v0.27). Previously hard `UNSUPPORTED_OPERATION` stubs; now implemented as
190
+ metadata operations that light up the `signed()` revset. (A pure-JS library
191
+ cannot verify GPG/SSH keys, so cryptographic verification is still delegated to
192
+ the Git layer.)
193
+ - `file.search({ pattern })` — search tracked file contents, regex by default
194
+ with `{ kind: 'substring' }` for literal matches (jj v0.37 / v0.41).
195
+ - `bookmark.advance({ name, to })` — move a bookmark forward only; refuses
196
+ non-descendant targets (jj v0.39).
197
+ - `tag.set({ name, changeId })` — create-or-move (upsert) a tag (jj v0.35).
198
+
199
+ ### Fixed
200
+
201
+ - **`tags()` revset now works.** It was a stub returning `[]` even though a
202
+ `TagStore` existed; the `RevsetEngine` is now wired to the tag store and
203
+ `tags()` / `tags(pattern)` return real results.
204
+ - **Duplicate `conflicts` key in the API object.** The raw `ConflictModel` was
205
+ exposed under the same key as the public `conflicts` namespace and silently
206
+ shadowed. The raw model is now available as `jj.conflictModel`.
207
+ - **`npm run lint` was completely broken** — the ESLint config was authored as an
208
+ ES module in a `"type": "module"` package. Renamed to `.eslintrc.cjs` and
209
+ fixed; the source tree now lints clean (0 errors).
210
+ - Latent `fail()` calls in tests (removed from jest-circus) replaced with `throw`.
211
+ - Removed an unnecessary try/catch, an empty catch block, an unused import, and
212
+ several unused variables flagged by the newly-working linter.
213
+
214
+ ### Changed
215
+
216
+ - `tsconfig.json` now includes the DOM/WebWorker libs and Node types so the
217
+ type-check reflects the library's real isomorphic runtime.
218
+ - Expanded the `isojj` CLI: `--version`, a far more complete `help`, positional
219
+ argument handling for `log`/`describe`/`file.*`, and generic pass-through of
220
+ all `--flags` so every API method is reachable from the CLI.
221
+
222
+ ## [1.4.3] and earlier
223
+
224
+ See the git history and [ROADMAP.md](./ROADMAP.md) for the v0.1–v1.4 development
225
+ record (core model, history editing, Git backend, first-class conflicts, revset
226
+ query language, workspaces, browser support, events, and full v1.0 JJ CLI parity).
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # isomorphic-jj
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/isomorphic-jj.svg)](https://www.npmjs.com/package/isomorphic-jj)
4
- [![test coverage](https://img.shields.io/badge/tests-460%20passing-brightgreen.svg)](https://github.com/johnhenry/isomorphic-jj)
4
+ [![test coverage](https://img.shields.io/badge/tests-1701%20passing-brightgreen.svg)](https://github.com/johnhenry/isomorphic-jj)
5
5
  [![license](https://img.shields.io/npm/l/isomorphic-jj.svg)](LICENSE)
6
6
 
7
7
  > **Jujutsu version control for JavaScript**—stable change IDs, fearless undo, and no staging area. Works in Node.js and browsers.
@@ -88,7 +88,7 @@ isomorphic-jj v1.0 now implements virtually all commonly-used JJ CLI commands:
88
88
  See [JJ_CLI_PARITY.md](./JJ_CLI_PARITY.md) for complete feature comparison.
89
89
 
90
90
  ### Testing & Quality
91
- - **460 tests** passing - Complete test coverage for all v1.0 features
91
+ - **1701 tests** passing — ~97% statement / 91% branch coverage (v1.0–v1.7)
92
92
  - **100% backward compatible** - All existing code continues to work
93
93
  - **Zero breaking changes** - Deprecated features show warnings but still function
94
94
 
@@ -898,8 +898,9 @@ repo/
898
898
 
899
899
  ## Project Status
900
900
 
901
- **Current Version**: v0.5.0
902
- **Test Coverage**: 351 tests, 100% passing
901
+ **Current Version**: v1.7.0
902
+ **Test Coverage**: 1701 tests, ~97% statements / 91% branches
903
+ **JJ CLI parity**: tracks Jujutsu through v0.43
903
904
  **Status**: Ready for experimentation and prototyping
904
905
 
905
906
  **Completed:**
@@ -908,13 +909,10 @@ repo/
908
909
  - ✅ v0.3: Git backend, conflicts, workspaces, browser support
909
910
  - ✅ v0.4: Shallow clones, advanced revsets, event system
910
911
  - ✅ v0.5: Custom merge drivers, enhanced revsets (time-based, graph analytics), conflict resolution enhancements
912
+ - ✅ v1.0: Full JJ CLI semantic parity, complete `file.*`/`operations.*`/`git.*` namespaces
913
+ - ✅ 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).
911
914
 
912
- **Coming Next (v0.6):**
913
- - Repository analytics and debugging tools
914
- - Interactive workflows
915
- - Performance optimizations
916
-
917
- See [ROADMAP.md](./ROADMAP.md) for detailed plans through v1.0.
915
+ See [ROADMAP.md](./ROADMAP.md) for detailed plans, and [CHANGELOG.md](./CHANGELOG.md) for the release history.
918
916
 
919
917
  ---
920
918
 
@@ -922,7 +920,7 @@ See [ROADMAP.md](./ROADMAP.md) for detailed plans through v1.0.
922
920
 
923
921
  ### Requirements
924
922
 
925
- - **Node.js**: 18.0.0 or higher
923
+ - **Node.js**: 20.0.0 or higher (uses the Web Crypto global, stabilized in Node 20; Node 18 is EOL)
926
924
  - **Browsers**: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
927
925
  - **Optional**: Git CLI (for JJ CLI interop testing)
928
926