amalgm 0.1.180 → 0.1.181

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.180",
3
+ "version": "0.1.181",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -23,6 +23,7 @@ fs.writeFileSync(
23
23
  const { closeLocalDb } = require('../state/db');
24
24
  const { revisionOf } = require('../workspace/checkpoint');
25
25
  const {
26
+ captureIsOnRail,
26
27
  hydrateRepo,
27
28
  materializeRepo,
28
29
  readRepoState,
@@ -296,6 +297,39 @@ test('one materializer converges to the reduced checkpoint and later removes a r
296
297
  assert.equal(fs.readFileSync(path.join(repo, 'b.txt'), 'utf8'), 'b1\n');
297
298
  });
298
299
 
300
+ test('once local work is on the rail, a checkpoint conflict materializes exactly without echoing', async () => {
301
+ const { repo, oid } = await makeRepo('materialize-conflict');
302
+ const localPatch = await patchFor(repo, { 'a.txt': 'local answer\n' });
303
+ const remotePatch = await patchFor(repo, { 'a.txt': 'remote answer\n' });
304
+ const localTransition = transition('machine-local', 2, oid, '', localPatch);
305
+ const plan = {
306
+ repoKey: 'root:test',
307
+ card: card(repo, oid, 1),
308
+ transitions: [
309
+ localTransition,
310
+ transition('machine-remote', 3, oid, '', remotePatch),
311
+ ],
312
+ };
313
+
314
+ fs.writeFileSync(path.join(repo, 'a.txt'), 'local answer\n');
315
+ await git(repo, 'add', 'a.txt');
316
+ assert.equal(captureIsOnRail(plan, {
317
+ baseOid: oid,
318
+ indexDiff: localPatch,
319
+ worktreeDiff: '',
320
+ }), true);
321
+
322
+ const outcome = await materializeRepo(plan.card, plan);
323
+ assert.equal(outcome.state, 'conflict');
324
+ const first = fs.readFileSync(path.join(repo, 'a.txt'), 'utf8');
325
+ assert.match(first, /local answer/);
326
+ assert.match(first, /remote answer/);
327
+ assert.match(first, /<<<<<<< current/);
328
+
329
+ await materializeRepo(plan.card, plan);
330
+ assert.equal(fs.readFileSync(path.join(repo, 'a.txt'), 'utf8'), first);
331
+ });
332
+
299
333
  test('a writer publishing again cannot erase its earlier uncommitted work', async () => {
300
334
  const { repo, oid } = await makeRepo('checkpoint-history');
301
335
  const patchA = await patchFor(repo, { 'a.txt': 'a1\n' });
@@ -32,6 +32,7 @@ const {
32
32
  repoKeyOfCard,
33
33
  revisionOf,
34
34
  } = require('./checkpoint');
35
+ const { machineId } = require('./identity');
35
36
  const merge = require('./merge');
36
37
  const transition = require('./transition');
37
38
 
@@ -416,6 +417,25 @@ async function reduceCheckpoint(localCard, plan) {
416
417
  };
417
418
  }
418
419
 
420
+ function captureIsOnRail(plan, captured, localMachine = machineId()) {
421
+ if (
422
+ !captured
423
+ || captured.indexDiff === null
424
+ || captured.baseOid !== plan.card.head.oid
425
+ ) return false;
426
+ const latest = [...plan.transitions]
427
+ .reverse()
428
+ .find((operation) => operation.machine === localMachine);
429
+ return Boolean(
430
+ latest
431
+ && latest.to?.revision === revisionOf(
432
+ captured.baseOid,
433
+ captured.indexDiff,
434
+ captured.worktreeDiff,
435
+ ),
436
+ );
437
+ }
438
+
419
439
  async function materializeRepo(localCardInput, plan) {
420
440
  const refreshed = await refreshRepoCard(localCardInput.replica) || localCardInput;
421
441
  const cardResult = await moveToWinningCard(refreshed, plan.card);
@@ -436,16 +456,28 @@ async function materializeRepo(localCardInput, plan) {
436
456
  if (reduced.state !== 'ready') return reduced;
437
457
 
438
458
  const baseTree = await merge.treeOf(localCard.path, plan.card.head.oid);
439
- const heldWorktree = previous?.baseOid === plan.card.head.oid
440
- && previous.heldTree
441
- && await merge.hasTree(localCard.path, previous.heldTree)
442
- ? previous.heldTree
443
- : baseTree;
444
- const heldIndex = previous?.baseOid === plan.card.head.oid
445
- && previous.heldIndexTree
446
- && await merge.hasTree(localCard.path, previous.heldIndexTree)
447
- ? previous.heldIndexTree
448
- : baseTree;
459
+ const captured = await captureCheckpoint(localCard.path);
460
+ const represented = captureIsOnRail(plan, captured);
461
+ const capturedIndex = represented
462
+ ? await merge.treeOfPatch(localCard.path, plan.card.head.oid, captured.indexDiff)
463
+ : null;
464
+ const capturedWorktree = represented
465
+ ? await merge.treeOfPatch(localCard.path, capturedIndex, captured.worktreeDiff)
466
+ : null;
467
+ const heldWorktree = capturedWorktree || (
468
+ previous?.baseOid === plan.card.head.oid
469
+ && previous.heldTree
470
+ && await merge.hasTree(localCard.path, previous.heldTree)
471
+ ? previous.heldTree
472
+ : baseTree
473
+ );
474
+ const heldIndex = capturedIndex || (
475
+ previous?.baseOid === plan.card.head.oid
476
+ && previous.heldIndexTree
477
+ && await merge.hasTree(localCard.path, previous.heldIndexTree)
478
+ ? previous.heldIndexTree
479
+ : baseTree
480
+ );
449
481
  const currentIndex = await merge.indexTree(localCard.path);
450
482
  const reducedLocalIndex = await merge.reduceTree(
451
483
  localCard.path,
@@ -642,6 +674,7 @@ function reducerStats() {
642
674
  }
643
675
 
644
676
  module.exports = {
677
+ captureIsOnRail,
645
678
  hydrateRepo,
646
679
  materializeRepo,
647
680
  readRepoState,