isomorphic-jj 1.12.1 → 1.13.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 +63 -0
- package/README.md +3 -2
- package/dist/index.cjs +40 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +40 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/api/repository.js +40 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.13.0 — 2026-10-05 — Write-path fixes: converge on a fresh instance, new files lost on edit(), rebase content
|
|
4
|
+
|
|
5
|
+
Fixed in [PR #52](https://github.com/johnhenry/isomorphic-jj/pull/52).
|
|
6
|
+
|
|
7
|
+
Three bugs found while wiring a consumer (JJHub's isomorphic jj engine)
|
|
8
|
+
to write through this library, constructing one fresh `createJJ()`
|
|
9
|
+
instance per call so no read is ever served from a stale cache. Two of
|
|
10
|
+
them change observable behavior, hence a minor bump.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`converge()` threw `WORKING_COPY_NOT_LOADED` on a fresh instance.**
|
|
15
|
+
Both outcomes record the working copy in their operation, but
|
|
16
|
+
`converge()` loaded only the graph and user config — so it only worked
|
|
17
|
+
after some earlier call on the same instance had loaded the working
|
|
18
|
+
copy. It now loads it itself. Regression tests: "on a fresh instance"
|
|
19
|
+
in `tests/integration/converge.test.js`.
|
|
20
|
+
- **A new file `write()` created was deleted by the next `edit()` away.**
|
|
21
|
+
`write()` tracks a file without recording it in the working-copy
|
|
22
|
+
change (the next command commits it, as in jj), but `edit()` first
|
|
23
|
+
deleted every tracked file missing from the change being left, as
|
|
24
|
+
cross-branch "pollution" from an earlier checkout. Since issue #30 every
|
|
25
|
+
switch fully syncs the working directory to its target, so nothing
|
|
26
|
+
leaks that way any more; what the cleanup actually removed was every new
|
|
27
|
+
file — top-level or nested — written into that change. Edits to files
|
|
28
|
+
already recorded survived, which made it look path-dependent. `edit()`
|
|
29
|
+
now commits every tracked file into the change it leaves, like
|
|
30
|
+
`new()`/`describe()`. **Behavior change:** a tracked file on disk is
|
|
31
|
+
now always the checked-out change's content when `edit()` leaves it;
|
|
32
|
+
code that removed a file from a change by editing the graph alone (not
|
|
33
|
+
`remove()`) must now remove it from disk too —
|
|
34
|
+
`tests/integration/issue-43-restore-reliability.test.js`'s
|
|
35
|
+
descendant-deletion test did exactly that and now uses `remove()`.
|
|
36
|
+
Regression tests: "write() then edit() away" in
|
|
37
|
+
`tests/integration/file-operations.test.js`.
|
|
38
|
+
- **`moveChange()`/`rebase()` kept the change's own tree verbatim.** The
|
|
39
|
+
three-way conflict check from issue #31 ran, but the rebased content
|
|
40
|
+
was never derived from it: the change kept every file only its OLD
|
|
41
|
+
parent had and never picked up what only the NEW parent changed. Its
|
|
42
|
+
content is now jj's rebase, path by path — the new parent's value
|
|
43
|
+
wherever the change didn't touch a path relative to its old parent,
|
|
44
|
+
the change's own value wherever it did (a path both sides changed
|
|
45
|
+
differently is the conflict #31 already reports, with markers). When
|
|
46
|
+
the moved change is checked out, its files on disk are synced to the
|
|
47
|
+
result. **Behavior change:** a rebased change's `fileSnapshot` now
|
|
48
|
+
differs from before for any path its old and new parents disagree on.
|
|
49
|
+
Regression tests: "content of the rebased change" in
|
|
50
|
+
`tests/integration/rebase-conflict-detection.test.js`.
|
|
51
|
+
|
|
52
|
+
### Not changed
|
|
53
|
+
|
|
54
|
+
- `moveChange()` still re-derives only the moved change's own content,
|
|
55
|
+
not that of its descendants (they follow it structurally, by parent
|
|
56
|
+
change id, but keep their trees) — see the README's status section.
|
|
57
|
+
- Conflict detection still compares whole files, not lines: two sides
|
|
58
|
+
changing different lines of one file is a conflict.
|
|
59
|
+
|
|
60
|
+
### Testing
|
|
61
|
+
|
|
62
|
+
1882 tests passing, 0 skipped; lint, format:check, typecheck, and all 12
|
|
63
|
+
`examples/*.mjs` green; coverage 97.39% statements / 90.13% branches
|
|
64
|
+
(gate 90%). Each new regression test fails against 1.12.1's source.
|
|
65
|
+
|
|
3
66
|
## 1.12.1 — 2026-09-27 — Conflict type shape + README status refresh
|
|
4
67
|
|
|
5
68
|
A cross-library documentation/typing audit flagged four things to verify
|
package/README.md
CHANGED
|
@@ -1020,8 +1020,8 @@ repo/
|
|
|
1020
1020
|
|
|
1021
1021
|
## Project Status
|
|
1022
1022
|
|
|
1023
|
-
**Current Version**: 1.
|
|
1024
|
-
**Test Coverage**:
|
|
1023
|
+
**Current Version**: 1.13.0 (`@johnhenry/isomorphic-jj`; same library/API lineage as the old `isomorphic-jj`, which reached v1.8.0 before the rename — see the provenance note at the top of this file, and the CHANGELOG's `1.9.0` entry for how the scoped/unscoped version lineages were merged back together)
|
|
1024
|
+
**Test Coverage**: 1882 tests passing, ~97% statements / 90% branches
|
|
1025
1025
|
**JJ CLI parity**: tracks Jujutsu through v0.45.1
|
|
1026
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 in the API-stability sense (the version number is past 1.0 only because merging the scoped/unscoped lineages forced it above the unscoped side's prior ceiling, not because the API is frozen) — API surface can still shift; pin a version and read the CHANGELOG before upgrading.
|
|
1027
1027
|
|
|
@@ -1038,6 +1038,7 @@ repo/
|
|
|
1038
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).
|
|
1039
1039
|
- ✅ 0.4.0: `converge()` generalized from pairwise to N-way — any number of divergent copies of one change id resolve in a single call, matching how jj's own underlying engine already treats divergence internally (issue #32). See [CHANGELOG.md](./CHANGELOG.md).
|
|
1040
1040
|
- ✅ 1.9.0: Scoped (`@johnhenry/isomorphic-jj`) and unscoped (`isomorphic-jj`) version lineages merged into one shared version going forward, with both names dual-published on every release.
|
|
1041
|
+
- ✅ 1.13.0: Write-path fixes — `converge()` works on a fresh instance, `edit()` no longer deletes new files `write()` created in the change it leaves, and `moveChange()`/`rebase()` derives the rebased change's content (new parent + the change's own edits) instead of keeping its tree verbatim. Not done: a rebase re-derives only the moved change's content, not its descendants', and conflicts are detected per whole file, not per line. See [CHANGELOG.md](./CHANGELOG.md).
|
|
1041
1042
|
- ✅ 1.9.1 – 1.12.0: Operation-log reliability pass — `undo()`/`operations.restore()` correctness across write/snapshot/squash/rebase/abandon/edit, `log()` visibility fixes, and recording gaps in descendant rebases and conflict-resolution ops (issues #37–#39, #41, #43, #45, #48). See [CHANGELOG.md](./CHANGELOG.md) for the full per-fix breakdown.
|
|
1042
1043
|
|
|
1043
1044
|
See [ROADMAP.md](./ROADMAP.md) for detailed plans, and [CHANGELOG.md](./CHANGELOG.md) for the release history.
|
package/dist/index.cjs
CHANGED
|
@@ -9330,6 +9330,27 @@ async function createJJ(options) {
|
|
|
9330
9330
|
await conflicts.addConflict(conflict);
|
|
9331
9331
|
}
|
|
9332
9332
|
|
|
9333
|
+
// The rebased change's content, path by path: (new base) + (what the
|
|
9334
|
+
// change itself did relative to its old base), as jj's rebase. It
|
|
9335
|
+
// used to keep its own full tree verbatim — keeping every file only
|
|
9336
|
+
// the OLD base had and never picking up what only the NEW base
|
|
9337
|
+
// changed. A path both sides changed differently keeps the change's
|
|
9338
|
+
// own value here; if that's a real conflict, detectConflicts() above
|
|
9339
|
+
// reported it and the marker text below replaces it.
|
|
9340
|
+
const contentChanged = Boolean(oldParentChange);
|
|
9341
|
+
if (oldParentChange) {
|
|
9342
|
+
/** @type {Record<string, string>} */
|
|
9343
|
+
const rebased = {};
|
|
9344
|
+
const allPaths = new Set([...baseFiles.keys(), ...leftFiles.keys(), ...rightFiles.keys()]);
|
|
9345
|
+
for (const filePath of allPaths) {
|
|
9346
|
+
const baseValue = baseFiles.get(filePath);
|
|
9347
|
+
const ownValue = leftFiles.get(filePath);
|
|
9348
|
+
const value = ownValue === baseValue ? rightFiles.get(filePath) : ownValue;
|
|
9349
|
+
if (value !== undefined) rebased[filePath] = value;
|
|
9350
|
+
}
|
|
9351
|
+
change.fileSnapshot = rebased;
|
|
9352
|
+
}
|
|
9353
|
+
|
|
9333
9354
|
// Materialize real conflict-marker text into the moved change's file
|
|
9334
9355
|
// content — issue #43(3): rebase()/moveChange() used to record the
|
|
9335
9356
|
// conflict correctly as DATA (via conflicts.addConflict() above) but
|
|
@@ -9371,6 +9392,13 @@ async function createJJ(options) {
|
|
|
9371
9392
|
}
|
|
9372
9393
|
}
|
|
9373
9394
|
|
|
9395
|
+
// A checked-out change's files on disk are its content: bring them in
|
|
9396
|
+
// line with the rebased snapshot (markers included), tracked, so the
|
|
9397
|
+
// next command's auto-snapshot finds nothing to re-record.
|
|
9398
|
+
if (contentChanged && changeId === workingCopy.getCurrentChangeId()) {
|
|
9399
|
+
await syncWorkingCopyFiles(change.fileSnapshot || {});
|
|
9400
|
+
}
|
|
9401
|
+
|
|
9374
9402
|
// Update parent
|
|
9375
9403
|
change.parents = [newParent];
|
|
9376
9404
|
await graph.updateChange(change);
|
|
@@ -10354,29 +10382,18 @@ async function createJJ(options) {
|
|
|
10354
10382
|
// see issue #29.
|
|
10355
10383
|
const fileSnapshotBeforeSwitch = await snapshotFilesystem();
|
|
10356
10384
|
|
|
10357
|
-
//
|
|
10358
|
-
//
|
|
10385
|
+
// Commit the change being left: every tracked file on disk is its
|
|
10386
|
+
// content, as in new()/describe(). This used to first DELETE every
|
|
10387
|
+
// tracked file missing from the change's recorded fileSnapshot, as
|
|
10388
|
+
// "pollution" from an earlier checkout — but a switch now fully syncs
|
|
10389
|
+
// the working directory to its target (issue #30, below), so nothing
|
|
10390
|
+
// leaks that way any more, and what that cleanup actually deleted was
|
|
10391
|
+
// every new file write() had created in this change: write() tracks a
|
|
10392
|
+
// file without recording it in the change, so it is only committed
|
|
10393
|
+
// here, by the next command.
|
|
10359
10394
|
if (previousChangeId !== args.changeId) {
|
|
10360
10395
|
const previousChange = await graph.getChange(previousChangeId);
|
|
10361
10396
|
if (previousChange) {
|
|
10362
|
-
const previousSnapshot = previousChange.fileSnapshot || {};
|
|
10363
|
-
const workingDirFiles = await workingCopy.listFiles();
|
|
10364
|
-
|
|
10365
|
-
// Delete files that exist in working dir but NOT in previous changeId's snapshot
|
|
10366
|
-
// This removes pollution from other branches that were previously checked out
|
|
10367
|
-
for (const filePath of workingDirFiles) {
|
|
10368
|
-
if (!previousSnapshot[filePath]) {
|
|
10369
|
-
try {
|
|
10370
|
-
const fullPath = join(dir, filePath);
|
|
10371
|
-
await fs.promises.unlink(fullPath);
|
|
10372
|
-
await workingCopy.untrackFile(filePath);
|
|
10373
|
-
} catch (error) {
|
|
10374
|
-
// Ignore errors - file might already be deleted or inaccessible
|
|
10375
|
-
}
|
|
10376
|
-
}
|
|
10377
|
-
}
|
|
10378
|
-
|
|
10379
|
-
// Now snapshot the CLEAN working directory (no pollution!)
|
|
10380
10397
|
const currentSnapshot = await snapshotFilesystem();
|
|
10381
10398
|
if (currentSnapshot && Object.keys(currentSnapshot).length > 0) {
|
|
10382
10399
|
changeSnapshot[previousChangeId] = structuredClone(previousChange);
|
|
@@ -11681,6 +11698,9 @@ async function createJJ(options) {
|
|
|
11681
11698
|
}
|
|
11682
11699
|
|
|
11683
11700
|
await graph.load();
|
|
11701
|
+
// Both outcomes below record the working copy in their operation —
|
|
11702
|
+
// a fresh instance hasn't loaded it yet.
|
|
11703
|
+
await workingCopy.load();
|
|
11684
11704
|
await userConfig.load();
|
|
11685
11705
|
const siblings = graph.getDivergentSiblings(changeId);
|
|
11686
11706
|
|