amalgm 0.1.220 → 0.1.234
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/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/state/attachments.js +7 -1
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +39 -2
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +180 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +47 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +28 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +152 -12
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +6 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +105 -67
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +214 -23
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +99 -206
- package/runtime/scripts/amalgm-mcp/workspace/card.js +30 -9
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +3 -2
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +194 -20
- package/runtime/scripts/amalgm-mcp/workspace/git.js +23 -5
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +24 -186
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +13 -1
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +323 -0
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +127 -104
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +7 -7
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +106 -137
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The reducer reads the single rail-ordered wall and derives exactly:
|
|
7
7
|
* - one winning card (highest official card version), and
|
|
8
|
-
* - one checkpoint tree (checkpoint transitions
|
|
8
|
+
* - one checkpoint tree (checkpoint transitions applied in official order).
|
|
9
9
|
*
|
|
10
10
|
* The materializer has no peer branches. It obtains the winning commit,
|
|
11
11
|
* moves to that exact card when safe, advances the one held checkpoint tree
|
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
const {
|
|
31
31
|
CHECKPOINT_CONTRACT,
|
|
32
32
|
checkpointValueReady,
|
|
33
|
+
readCheckpoint,
|
|
33
34
|
readCheckpointValue,
|
|
34
35
|
repoKeyOfCard,
|
|
35
36
|
revisionOf,
|
|
@@ -80,20 +81,40 @@ function readRepoState(repoKey) {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
function
|
|
84
|
+
function repoStateIdentity(state) {
|
|
85
|
+
if (!state) return null;
|
|
86
|
+
const { indexDiff, worktreeDiff, updatedAt, ...identity } = state;
|
|
87
|
+
return JSON.stringify(identity);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function persistRepoState(repoKey, state) {
|
|
84
91
|
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
85
92
|
const next = { repoKey, ...state, updatedAt: new Date().toISOString() };
|
|
93
|
+
const previous = readRepoState(repoKey);
|
|
94
|
+
if (repoStateIdentity(previous) === repoStateIdentity(next)) {
|
|
95
|
+
return { state: previous, changed: false };
|
|
96
|
+
}
|
|
86
97
|
fs.writeFileSync(stateFile(repoKey), `${JSON.stringify(next, null, 2)}\n`);
|
|
87
|
-
return next;
|
|
98
|
+
return { state: next, changed: true };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function repoStateEventValue(state) {
|
|
102
|
+
const { indexDiff, worktreeDiff, ...value } = state;
|
|
103
|
+
return value;
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
function validVersion(value) {
|
|
91
107
|
return Number.isInteger(value) && value > 0;
|
|
92
108
|
}
|
|
93
109
|
|
|
110
|
+
function preferBundleObjects(held, candidate) {
|
|
111
|
+
if (held?.objects?.kind === 'bundle' || candidate?.objects?.kind !== 'bundle') return held;
|
|
112
|
+
return { ...held, objects: candidate.objects };
|
|
113
|
+
}
|
|
114
|
+
|
|
94
115
|
/**
|
|
95
116
|
* Pure wall reduction. Machine identity never chooses a winner. Checkpoint
|
|
96
|
-
* transitions remain in the wall until the next card
|
|
117
|
+
* transitions remain in the wall until the next card includes them in a
|
|
97
118
|
* commit, so reducing current state never forgets an earlier edit.
|
|
98
119
|
*/
|
|
99
120
|
function reduceWall(entries) {
|
|
@@ -121,11 +142,25 @@ function reduceWall(entries) {
|
|
|
121
142
|
}
|
|
122
143
|
if (!repo.card || card.positionVersion > repo.card.positionVersion) {
|
|
123
144
|
repo.card = { ...card, writer: wallEntry.machine };
|
|
145
|
+
} else if (
|
|
146
|
+
card.positionVersion === repo.card.positionVersion
|
|
147
|
+
&& card.positionMutationId === repo.card.positionMutationId
|
|
148
|
+
&& card.head?.oid === repo.card.head?.oid
|
|
149
|
+
) {
|
|
150
|
+
repo.card = preferBundleObjects(repo.card, card);
|
|
124
151
|
}
|
|
125
152
|
for (const ref of card.branches || []) {
|
|
126
153
|
if (!ref?.name || !validVersion(ref.version)) continue;
|
|
127
154
|
const held = repo.branchMap.get(ref.name);
|
|
128
|
-
if (!held || ref.version > held.version)
|
|
155
|
+
if (!held || ref.version > held.version) {
|
|
156
|
+
repo.branchMap.set(ref.name, ref);
|
|
157
|
+
} else if (
|
|
158
|
+
ref.version === held.version
|
|
159
|
+
&& ref.mutationId === held.mutationId
|
|
160
|
+
&& ref.oid === held.oid
|
|
161
|
+
) {
|
|
162
|
+
repo.branchMap.set(ref.name, preferBundleObjects(held, ref));
|
|
163
|
+
}
|
|
129
164
|
}
|
|
130
165
|
for (const checkpoint of card.checkpoints || []) {
|
|
131
166
|
if (
|
|
@@ -293,9 +328,7 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
293
328
|
&& targetCard.head?.oid
|
|
294
329
|
&& current.head.branch === targetCard.head?.branch
|
|
295
330
|
&& await isAncestor(localCard.path, targetCard.head.oid, current.head.oid);
|
|
296
|
-
if (pendingCard) {
|
|
297
|
-
return { state: 'ready', moved: false, pendingCard: true, conflicts: [] };
|
|
298
|
-
}
|
|
331
|
+
if (pendingCard) return { state: 'ready', moved: false, pendingCard: true };
|
|
299
332
|
|
|
300
333
|
const objects = await obtainCardObjects(localCard, targetCard);
|
|
301
334
|
if (objects.state !== 'ready') return { state: 'waiting', reason: objects.state };
|
|
@@ -340,25 +373,14 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
340
373
|
});
|
|
341
374
|
}
|
|
342
375
|
|
|
343
|
-
const commonTree = await merge.treeOf(localCard.path, commonOid);
|
|
344
376
|
const winningTree = await merge.treeOf(localCard.path, targetCard.head.oid);
|
|
345
377
|
const oldTree = await merge.treeOf(localCard.path, current.head.oid);
|
|
346
|
-
const committed = forward || current.head.oid === targetCard.head.oid
|
|
347
|
-
? { tree: winningTree, conflicts: [] }
|
|
348
|
-
: await merge.reduceTree(
|
|
349
|
-
localCard.path,
|
|
350
|
-
winningTree,
|
|
351
|
-
commonTree,
|
|
352
|
-
oldTree,
|
|
353
|
-
{ label: `parked-${current.head.oid.slice(0, 12)}` },
|
|
354
|
-
);
|
|
355
378
|
const oldIndexTree = await merge.treeOfPatch(localCard.path, current.head.oid, movingIndexDiff);
|
|
356
379
|
const staged = await merge.reduceTree(
|
|
357
380
|
localCard.path,
|
|
358
|
-
|
|
381
|
+
winningTree,
|
|
359
382
|
oldTree,
|
|
360
383
|
oldIndexTree,
|
|
361
|
-
{ label: `staged-${current.head.oid.slice(0, 12)}` },
|
|
362
384
|
);
|
|
363
385
|
const oldWorktree = await merge.treeOfPatch(localCard.path, oldIndexTree, movingWorktreeDiff);
|
|
364
386
|
const working = await merge.reduceTree(
|
|
@@ -366,13 +388,7 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
366
388
|
staged.tree,
|
|
367
389
|
oldIndexTree,
|
|
368
390
|
oldWorktree,
|
|
369
|
-
{ label: `working-${current.head.oid.slice(0, 12)}` },
|
|
370
391
|
);
|
|
371
|
-
const conflicts = [
|
|
372
|
-
...committed.conflicts,
|
|
373
|
-
...staged.conflicts,
|
|
374
|
-
...working.conflicts,
|
|
375
|
-
];
|
|
376
392
|
|
|
377
393
|
const rollbackDir = path.join(STATE_DIR, 'card-moves');
|
|
378
394
|
fs.mkdirSync(rollbackDir, { recursive: true });
|
|
@@ -398,7 +414,6 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
398
414
|
state: 'ready',
|
|
399
415
|
moved: true,
|
|
400
416
|
parked,
|
|
401
|
-
conflicts: [...conflicts, ...applied.conflicts],
|
|
402
417
|
};
|
|
403
418
|
}
|
|
404
419
|
|
|
@@ -427,9 +442,11 @@ async function materializeParked(localCard, entries) {
|
|
|
427
442
|
return waiting;
|
|
428
443
|
}
|
|
429
444
|
|
|
430
|
-
async function materializeBranches(localCard, plan) {
|
|
445
|
+
async function materializeBranches(localCard, plan, heldBranches = []) {
|
|
431
446
|
const current = new Map((await localBranches(localCard.path)).map((ref) => [ref.name, ref.oid]));
|
|
447
|
+
const held = new Map(heldBranches.map((ref) => [ref.name, ref.oid]));
|
|
432
448
|
const waiting = [];
|
|
449
|
+
const pending = [];
|
|
433
450
|
let moved = false;
|
|
434
451
|
for (const ref of plan.branches || []) {
|
|
435
452
|
if (ref.name === plan.card.head?.branch) {
|
|
@@ -438,6 +455,13 @@ async function materializeBranches(localCard, plan) {
|
|
|
438
455
|
}
|
|
439
456
|
continue;
|
|
440
457
|
}
|
|
458
|
+
const currentOid = current.get(ref.name) || null;
|
|
459
|
+
const heldOid = held.get(ref.name) || null;
|
|
460
|
+
if (currentOid !== heldOid && currentOid !== ref.oid) {
|
|
461
|
+
pending.push({ name: ref.name, oid: currentOid });
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (currentOid === ref.oid) continue;
|
|
441
465
|
if (ref.oid === null) {
|
|
442
466
|
if (current.has(ref.name)) {
|
|
443
467
|
await transition.deleteBranchRef(localCard.path, ref.name);
|
|
@@ -468,7 +492,7 @@ async function materializeBranches(localCard, plan) {
|
|
|
468
492
|
moved = true;
|
|
469
493
|
}
|
|
470
494
|
}
|
|
471
|
-
return { moved, waiting };
|
|
495
|
+
return { moved, waiting, pending };
|
|
472
496
|
}
|
|
473
497
|
|
|
474
498
|
async function reduceCheckpoint(localCard, plan) {
|
|
@@ -476,7 +500,6 @@ async function reduceCheckpoint(localCard, plan) {
|
|
|
476
500
|
const baseTree = await merge.treeOf(localCard.path, baseOid);
|
|
477
501
|
let indexTree = baseTree;
|
|
478
502
|
let worktreeTree = baseTree;
|
|
479
|
-
const conflicts = [];
|
|
480
503
|
if (plan.checkpointBaseConflict) {
|
|
481
504
|
return { state: 'waiting', reason: 'checkpoint-base-disagrees' };
|
|
482
505
|
}
|
|
@@ -493,32 +516,6 @@ async function reduceCheckpoint(localCard, plan) {
|
|
|
493
516
|
);
|
|
494
517
|
}
|
|
495
518
|
|
|
496
|
-
for (const entry of plan.parked || []) {
|
|
497
|
-
const commonOid = await mergeBase(localCard.path, baseOid, entry.oid);
|
|
498
|
-
if (!commonOid) return { state: 'waiting', reason: `parked-unrelated-${entry.oid}` };
|
|
499
|
-
const commonTree = await merge.treeOf(localCard.path, commonOid);
|
|
500
|
-
const parkedTree = await merge.treeOf(localCard.path, entry.oid);
|
|
501
|
-
const label = `parked-${entry.version || entry.oid.slice(0, 12)}`;
|
|
502
|
-
const reducedIndex = await merge.reduceTree(
|
|
503
|
-
localCard.path,
|
|
504
|
-
indexTree,
|
|
505
|
-
commonTree,
|
|
506
|
-
parkedTree,
|
|
507
|
-
{ label },
|
|
508
|
-
);
|
|
509
|
-
indexTree = reducedIndex.tree;
|
|
510
|
-
conflicts.push(...reducedIndex.conflicts.map((item) => ({ ...item, layer: 'index' })));
|
|
511
|
-
const reducedWorktree = await merge.reduceTree(
|
|
512
|
-
localCard.path,
|
|
513
|
-
worktreeTree,
|
|
514
|
-
commonTree,
|
|
515
|
-
parkedTree,
|
|
516
|
-
{ label },
|
|
517
|
-
);
|
|
518
|
-
worktreeTree = reducedWorktree.tree;
|
|
519
|
-
conflicts.push(...reducedWorktree.conflicts.map((item) => ({ ...item, layer: 'worktree' })));
|
|
520
|
-
}
|
|
521
|
-
|
|
522
519
|
for (const operation of plan.transitions) {
|
|
523
520
|
const from = readCheckpointValue(baseOid, operation.from);
|
|
524
521
|
const to = readCheckpointValue(baseOid, operation.to);
|
|
@@ -528,11 +525,13 @@ async function reduceCheckpoint(localCard, plan) {
|
|
|
528
525
|
}
|
|
529
526
|
const fromIndexTree = await merge.treeOfPatch(localCard.path, baseOid, from.indexDiff);
|
|
530
527
|
const toIndexTree = await merge.treeOfPatch(localCard.path, baseOid, to.indexDiff);
|
|
531
|
-
const reducedIndex = await merge.reduceTree(
|
|
532
|
-
|
|
533
|
-
|
|
528
|
+
const reducedIndex = await merge.reduceTree(
|
|
529
|
+
localCard.path,
|
|
530
|
+
indexTree,
|
|
531
|
+
fromIndexTree,
|
|
532
|
+
toIndexTree,
|
|
533
|
+
);
|
|
534
534
|
indexTree = reducedIndex.tree;
|
|
535
|
-
conflicts.push(...reducedIndex.conflicts.map((item) => ({ ...item, layer: 'index' })));
|
|
536
535
|
|
|
537
536
|
const fromWorktreeTree = await merge.treeOfPatch(localCard.path, fromIndexTree, from.worktreeDiff);
|
|
538
537
|
const toWorktreeTree = await merge.treeOfPatch(localCard.path, toIndexTree, to.worktreeDiff);
|
|
@@ -541,10 +540,8 @@ async function reduceCheckpoint(localCard, plan) {
|
|
|
541
540
|
worktreeTree,
|
|
542
541
|
fromWorktreeTree,
|
|
543
542
|
toWorktreeTree,
|
|
544
|
-
{ label: String(operation.version) },
|
|
545
543
|
);
|
|
546
544
|
worktreeTree = reducedWorktree.tree;
|
|
547
|
-
conflicts.push(...reducedWorktree.conflicts.map((item) => ({ ...item, layer: 'worktree' })));
|
|
548
545
|
}
|
|
549
546
|
|
|
550
547
|
const indexDiff = await merge.diffOfTree(localCard.path, baseOid, indexTree);
|
|
@@ -556,26 +553,32 @@ async function reduceCheckpoint(localCard, plan) {
|
|
|
556
553
|
indexDiff,
|
|
557
554
|
worktreeDiff,
|
|
558
555
|
checkpointRevision: revisionOf(baseOid, indexDiff, worktreeDiff),
|
|
559
|
-
conflicts,
|
|
560
556
|
};
|
|
561
557
|
}
|
|
562
558
|
|
|
563
|
-
function captureIsOnRail(plan, captured, localMachine = machineId()) {
|
|
559
|
+
function captureIsOnRail(plan, captured, localMachine = machineId(), acknowledged = null) {
|
|
564
560
|
if (
|
|
565
561
|
!captured
|
|
566
562
|
|| captured.indexDiff === null
|
|
567
563
|
|| captured.baseOid !== plan.card.head.oid
|
|
568
564
|
) return false;
|
|
565
|
+
const revision = revisionOf(
|
|
566
|
+
captured.baseOid,
|
|
567
|
+
captured.indexDiff,
|
|
568
|
+
captured.worktreeDiff,
|
|
569
|
+
);
|
|
569
570
|
const latest = [...plan.transitions]
|
|
570
571
|
.reverse()
|
|
571
572
|
.find((operation) => operation.machine === localMachine);
|
|
572
573
|
return Boolean(
|
|
573
|
-
latest
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
captured.
|
|
578
|
-
|
|
574
|
+
(latest && latest.to?.revision === revision)
|
|
575
|
+
|| (
|
|
576
|
+
Number.isInteger(acknowledged?.version)
|
|
577
|
+
&& acknowledged.version > 0
|
|
578
|
+
&& acknowledged.baseOid === captured.baseOid
|
|
579
|
+
&& acknowledged.branch === plan.card.head.branch
|
|
580
|
+
&& acknowledged.to?.revision === revision
|
|
581
|
+
)
|
|
579
582
|
);
|
|
580
583
|
}
|
|
581
584
|
|
|
@@ -597,9 +600,31 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
597
600
|
const planParked = new Set((plan.parked || []).map((entry) => entry.oid));
|
|
598
601
|
const requiredParkedWaiting = parkedWaiting.filter((entry) => planParked.has(entry.oid));
|
|
599
602
|
if (requiredParkedWaiting.length > 0) {
|
|
603
|
+
// Parking is a local write. Remember it before waiting on an inbound
|
|
604
|
+
// bundle, so the next pass cannot replace unnumbered local work.
|
|
605
|
+
const persisted = persistRepoState(plan.repoKey, {
|
|
606
|
+
...(previous || {}),
|
|
607
|
+
path: localCard.path,
|
|
608
|
+
replica: localCard.replica,
|
|
609
|
+
parked,
|
|
610
|
+
parkedWaiting,
|
|
611
|
+
materialized: false,
|
|
612
|
+
});
|
|
613
|
+
if (persisted.changed) {
|
|
614
|
+
appendStateEvent({
|
|
615
|
+
resource: 'workspace-reducer',
|
|
616
|
+
op: previous ? 'update' : 'insert',
|
|
617
|
+
id: localCard.replica,
|
|
618
|
+
value: repoStateEventValue(persisted.state),
|
|
619
|
+
source: 'workspace-reducer',
|
|
620
|
+
});
|
|
621
|
+
}
|
|
600
622
|
return { state: 'waiting', reason: `parked-${requiredParkedWaiting[0].state}` };
|
|
601
623
|
}
|
|
602
|
-
const branchResult = await materializeBranches(localCard, plan);
|
|
624
|
+
const branchResult = await materializeBranches(localCard, plan, previous?.branches);
|
|
625
|
+
if (branchResult.waiting.length > 0) {
|
|
626
|
+
return { state: 'waiting', reason: `branch-${branchResult.waiting[0].state}` };
|
|
627
|
+
}
|
|
603
628
|
const reduced = await reduceCheckpoint(localCard, plan);
|
|
604
629
|
if (reduced.state !== 'ready') return reduced;
|
|
605
630
|
|
|
@@ -628,23 +653,19 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
628
653
|
let heldIndex;
|
|
629
654
|
let targetWorktree;
|
|
630
655
|
let targetIndex;
|
|
631
|
-
const pendingConflicts = [];
|
|
632
|
-
|
|
633
656
|
if (cardResult.pendingCard) {
|
|
634
657
|
const pendingBaseTree = await merge.treeOf(localCard.path, localCard.head.oid);
|
|
635
658
|
const rebasedIndex = await merge.reduceTree(
|
|
636
659
|
localCard.path,
|
|
637
|
-
pendingBaseTree,
|
|
638
|
-
previousHeldIndex,
|
|
639
660
|
reduced.indexTree,
|
|
640
|
-
|
|
661
|
+
previousHeldIndex,
|
|
662
|
+
pendingBaseTree,
|
|
641
663
|
);
|
|
642
664
|
const rebasedWorktree = await merge.reduceTree(
|
|
643
665
|
localCard.path,
|
|
644
|
-
pendingBaseTree,
|
|
645
|
-
previousHeldWorktree,
|
|
646
666
|
reduced.worktreeTree,
|
|
647
|
-
|
|
667
|
+
previousHeldWorktree,
|
|
668
|
+
pendingBaseTree,
|
|
648
669
|
);
|
|
649
670
|
heldIndex = pendingBaseTree;
|
|
650
671
|
heldWorktree = pendingBaseTree;
|
|
@@ -656,13 +677,20 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
656
677
|
stateCardMutationId = null;
|
|
657
678
|
stateIndexDiff = await merge.diffOfTree(localCard.path, stateBaseOid, targetIndex);
|
|
658
679
|
stateWorktreeDiff = await merge.diffOfTree(localCard.path, targetIndex, targetWorktree);
|
|
659
|
-
pendingConflicts.push(
|
|
660
|
-
...rebasedIndex.conflicts.map((item) => ({ ...item, layer: 'pending-card-index' })),
|
|
661
|
-
...rebasedWorktree.conflicts.map((item) => ({ ...item, layer: 'pending-card-worktree' })),
|
|
662
|
-
);
|
|
663
680
|
} else {
|
|
664
681
|
const captured = await captureCheckpoint(localCard.path);
|
|
665
|
-
const
|
|
682
|
+
const wall = require('./wall');
|
|
683
|
+
const localCheckpoint = readCheckpoint(machineId(), plan.repoKey);
|
|
684
|
+
const project = wall.projectForRepo(plan.repoKey);
|
|
685
|
+
const acknowledged = localCheckpoint && {
|
|
686
|
+
...localCheckpoint,
|
|
687
|
+
version: wall.mutationVersion(
|
|
688
|
+
project?.resourceId,
|
|
689
|
+
localCheckpoint.mutationId,
|
|
690
|
+
plan.repoKey,
|
|
691
|
+
),
|
|
692
|
+
};
|
|
693
|
+
const represented = captureIsOnRail(plan, captured, machineId(), acknowledged);
|
|
666
694
|
const capturedIndex = represented
|
|
667
695
|
? await merge.treeOfPatch(localCard.path, plan.card.head.oid, captured.indexDiff)
|
|
668
696
|
: null;
|
|
@@ -678,17 +706,16 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
678
706
|
const currentIndex = await merge.indexTree(localCard.path);
|
|
679
707
|
const reducedLocalIndex = await merge.reduceTree(
|
|
680
708
|
localCard.path,
|
|
681
|
-
currentIndex,
|
|
682
|
-
heldIndex,
|
|
683
709
|
targetIndex,
|
|
684
|
-
|
|
710
|
+
heldIndex,
|
|
711
|
+
currentIndex,
|
|
685
712
|
);
|
|
686
713
|
const applied = await merge.advance(localCard.path, heldWorktree, targetWorktree, {
|
|
687
714
|
label: `checkpoint-${reduced.checkpointRevision || 'clean'}`,
|
|
688
715
|
});
|
|
689
716
|
const indexResult = await advanceIndex(localCard.path, currentIndex, reducedLocalIndex.tree);
|
|
690
717
|
if (indexResult.state !== 'ready') return indexResult;
|
|
691
|
-
const
|
|
718
|
+
const persisted = persistRepoState(plan.repoKey, {
|
|
692
719
|
path: localCard.path,
|
|
693
720
|
replica: localCard.replica,
|
|
694
721
|
branch: stateBranch,
|
|
@@ -702,30 +729,26 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
702
729
|
worktreeDiff: stateWorktreeDiff,
|
|
703
730
|
checkpointVersions: plan.transitions.map((entry) => entry.version),
|
|
704
731
|
branches: plan.branches || [],
|
|
732
|
+
pendingRefs: branchResult.pending,
|
|
705
733
|
heldTree: applied.heldTree,
|
|
706
734
|
heldIndexTree: targetIndex,
|
|
707
735
|
parked,
|
|
708
736
|
parkedWaiting: [...parkedWaiting, ...branchResult.waiting],
|
|
709
|
-
conflicts: [
|
|
710
|
-
...(cardResult.conflicts || []),
|
|
711
|
-
...reduced.conflicts,
|
|
712
|
-
...pendingConflicts,
|
|
713
|
-
...reducedLocalIndex.conflicts.map((item) => ({ ...item, layer: 'index-materialize' })),
|
|
714
|
-
...applied.conflicts,
|
|
715
|
-
],
|
|
716
737
|
materialized: applied.heldTree === targetWorktree
|
|
717
738
|
&& reducedLocalIndex.tree === targetIndex,
|
|
718
739
|
});
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
740
|
+
if (persisted.changed) {
|
|
741
|
+
appendStateEvent({
|
|
742
|
+
resource: 'workspace-reducer',
|
|
743
|
+
op: previous ? 'update' : 'insert',
|
|
744
|
+
id: localCard.replica,
|
|
745
|
+
value: repoStateEventValue(persisted.state),
|
|
746
|
+
source: 'workspace-reducer',
|
|
747
|
+
});
|
|
748
|
+
}
|
|
726
749
|
return {
|
|
727
|
-
state:
|
|
728
|
-
reason:
|
|
750
|
+
state: 'in-sync',
|
|
751
|
+
reason: null,
|
|
729
752
|
moved: cardResult.moved || branchResult.moved || applied.applied.length > 0,
|
|
730
753
|
};
|
|
731
754
|
}
|
|
@@ -337,13 +337,13 @@ function setIndexTree(worktreeRoot, treeOid) {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
340
|
+
* Apply one ordered file write set to a working tree. Each entry is
|
|
341
|
+
* { path, content, mode } (content a
|
|
342
342
|
* Buffer, mode '100644' | '100755' | '120000') or { path, content: null }
|
|
343
343
|
* for a deletion. Paths are confined to the worktree; the whole set lands
|
|
344
344
|
* as one journal line.
|
|
345
345
|
*/
|
|
346
|
-
function
|
|
346
|
+
function applyFiles(worktreeRoot, writes, label) {
|
|
347
347
|
const root = path.resolve(worktreeRoot);
|
|
348
348
|
const written = [];
|
|
349
349
|
const removed = [];
|
|
@@ -368,20 +368,20 @@ function applyMergedFiles(worktreeRoot, writes, label) {
|
|
|
368
368
|
}
|
|
369
369
|
written.push(write.path);
|
|
370
370
|
}
|
|
371
|
-
journal({ action: '
|
|
371
|
+
journal({ action: 'apply-files', worktree: root, label, written, removed, ok: true });
|
|
372
372
|
} catch (error) {
|
|
373
373
|
journal({
|
|
374
|
-
action: '
|
|
374
|
+
action: 'apply-files', worktree: root, label, written, removed, ok: false,
|
|
375
375
|
error: String(error.message).slice(0, 500),
|
|
376
376
|
});
|
|
377
|
-
throw new TransitionError('
|
|
377
|
+
throw new TransitionError('apply-files', root, String(error.message));
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
module.exports = {
|
|
382
382
|
TransitionError,
|
|
383
383
|
applyIndexPatch,
|
|
384
|
-
|
|
384
|
+
applyFiles,
|
|
385
385
|
applyPatch,
|
|
386
386
|
checkoutInitialCard,
|
|
387
387
|
createBundle,
|