amalgm 0.1.218 → 0.1.220
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
|
@@ -176,6 +176,46 @@ test('a checkpoint base replaces covered history and keeps only newer mutations'
|
|
|
176
176
|
assert.equal(reduced.checkpointRevision, revisionOf(oid, patchAB, ''));
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
+
test('an unfinished checkpoint base cannot replace the last complete base', () => {
|
|
180
|
+
const oid = 'a'.repeat(40);
|
|
181
|
+
const complete = card('/complete', oid, 1);
|
|
182
|
+
complete.checkpointBase = {
|
|
183
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
184
|
+
baseOid: oid,
|
|
185
|
+
branch: 'main',
|
|
186
|
+
through: 4,
|
|
187
|
+
value: value(oid, 'complete-patch'),
|
|
188
|
+
};
|
|
189
|
+
complete.checkpoints = [
|
|
190
|
+
transition('machine-a', 5, oid, 'complete-patch', 'later-patch'),
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
const unfinished = card('/unfinished', oid, 1);
|
|
194
|
+
unfinished.checkpointBase = {
|
|
195
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
196
|
+
baseOid: oid,
|
|
197
|
+
branch: 'main',
|
|
198
|
+
through: 6,
|
|
199
|
+
value: {
|
|
200
|
+
revision: 'b'.repeat(64),
|
|
201
|
+
payload: {
|
|
202
|
+
kind: 'pending',
|
|
203
|
+
contract: 'opaque-chunks@1',
|
|
204
|
+
checksum: 'c'.repeat(64),
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const plan = reduceWall([
|
|
210
|
+
{ machine: 'machine-a', cards: [complete] },
|
|
211
|
+
{ machine: 'machine-b', cards: [unfinished] },
|
|
212
|
+
]).get('root:test');
|
|
213
|
+
|
|
214
|
+
assert.equal(plan.checkpointBase.through, 4);
|
|
215
|
+
assert.equal(plan.checkpointBaseConflict, false);
|
|
216
|
+
assert.deepEqual(plan.transitions.map((entry) => entry.version), [5]);
|
|
217
|
+
});
|
|
218
|
+
|
|
179
219
|
test('different snapshots claiming the same rail position stop instead of choosing a winner', () => {
|
|
180
220
|
const oid = 'a'.repeat(40);
|
|
181
221
|
const first = card('/first', oid, 1);
|
|
@@ -685,6 +725,51 @@ test('a local commit made after the verified card snapshot is never parked or ov
|
|
|
685
725
|
);
|
|
686
726
|
});
|
|
687
727
|
|
|
728
|
+
test('a pending descendant commit catches up without becoming a losing commit', async () => {
|
|
729
|
+
const { repo, oid: baseOid } = await makeRepo('pending-descendant-card');
|
|
730
|
+
const firstPatch = await patchFor(repo, { 'a.txt': 'before commit\n' });
|
|
731
|
+
const secondPatch = await patchFor(repo, {
|
|
732
|
+
'a.txt': 'before commit\n',
|
|
733
|
+
'b.txt': 'after commit\n',
|
|
734
|
+
});
|
|
735
|
+
const winningCard = card(repo, baseOid, 1);
|
|
736
|
+
const before = {
|
|
737
|
+
repoKey: 'root:test',
|
|
738
|
+
card: winningCard,
|
|
739
|
+
transitions: [transition('machine-peer', 2, baseOid, '', firstPatch)],
|
|
740
|
+
parked: [],
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
await materializeRepo(winningCard, before, 2);
|
|
744
|
+
await git(repo, 'add', '.');
|
|
745
|
+
await git(repo, 'commit', '-m', 'local pending card');
|
|
746
|
+
const pendingOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
|
|
747
|
+
|
|
748
|
+
const pendingCard = card(repo, pendingOid, null);
|
|
749
|
+
const caughtUp = await materializeRepo(pendingCard, {
|
|
750
|
+
...before,
|
|
751
|
+
transitions: [
|
|
752
|
+
...before.transitions,
|
|
753
|
+
transition('machine-peer', 3, baseOid, firstPatch, secondPatch),
|
|
754
|
+
],
|
|
755
|
+
}, 3);
|
|
756
|
+
|
|
757
|
+
assert.equal(caughtUp.state, 'in-sync');
|
|
758
|
+
assert.equal((await git(repo, 'rev-parse', 'HEAD')).trim(), pendingOid);
|
|
759
|
+
assert.equal(fs.readFileSync(path.join(repo, 'a.txt'), 'utf8'), 'before commit\n');
|
|
760
|
+
assert.equal(fs.readFileSync(path.join(repo, 'b.txt'), 'utf8'), 'after commit\n');
|
|
761
|
+
const staged = (await git(repo, 'diff', '--cached', '--name-only')).trim();
|
|
762
|
+
const status = (await git(repo, 'status', '--short')).trim();
|
|
763
|
+
assert.equal(staged, 'b.txt', status);
|
|
764
|
+
await assert.rejects(
|
|
765
|
+
git(repo, 'rev-parse', `refs/amalgm/parked/${pendingOid}`),
|
|
766
|
+
);
|
|
767
|
+
const state = readRepoState('root:test');
|
|
768
|
+
assert.equal(state.baseOid, pendingOid);
|
|
769
|
+
assert.equal(state.appliedThrough, 3);
|
|
770
|
+
assert.equal(state.cardVersion, null);
|
|
771
|
+
});
|
|
772
|
+
|
|
688
773
|
test('an empty index advance cannot overwrite a commit that just finished', async () => {
|
|
689
774
|
const { repo } = await makeRepo('index-commit-race');
|
|
690
775
|
const observedIndex = (await git(repo, 'write-tree')).trim();
|
|
@@ -148,6 +148,15 @@ test('only a value different from both the materialized baseline and the rail is
|
|
|
148
148
|
branches: [{ name: 'main', oid: oidC }],
|
|
149
149
|
};
|
|
150
150
|
assert.equal(hasLocalContribution(local, remoteAdvance, baseline), false);
|
|
151
|
+
assert.equal(hasLocalContribution({
|
|
152
|
+
...local,
|
|
153
|
+
head: { branch: 'main', oid: oidB, detached: false },
|
|
154
|
+
refs: [{ name: 'main', oid: oidB }],
|
|
155
|
+
}, remoteAdvance, {
|
|
156
|
+
...baseline,
|
|
157
|
+
baseOid: oidB,
|
|
158
|
+
pendingCard: true,
|
|
159
|
+
}), true);
|
|
151
160
|
assert.equal(hasLocalContribution(local, remoteAdvance, baseline, {
|
|
152
161
|
checkpoint: 'repo-checkpoint@8',
|
|
153
162
|
machine: 'machine-a',
|
|
@@ -444,6 +453,20 @@ test('a checkpoint base replaces history already covered by the applied rail', a
|
|
|
444
453
|
assert.deepEqual(settleProject(), [3]);
|
|
445
454
|
});
|
|
446
455
|
|
|
456
|
+
test('an uploading checkpoint base is not announced as complete', () => {
|
|
457
|
+
const card = readWall(repoKey)[0].cards[0];
|
|
458
|
+
const [stamped] = stampCards([card], [], { card, branches: [], transitions: [] }, {
|
|
459
|
+
repoKey,
|
|
460
|
+
baseOid: card.head.oid,
|
|
461
|
+
branch: card.head.branch,
|
|
462
|
+
appliedThrough: 3,
|
|
463
|
+
indexDiff: 'x'.repeat(600 * 1024),
|
|
464
|
+
worktreeDiff: '',
|
|
465
|
+
}, 'mut_project_pending_base', project);
|
|
466
|
+
|
|
467
|
+
assert.equal(stamped.checkpointBase, undefined);
|
|
468
|
+
});
|
|
469
|
+
|
|
447
470
|
test('one failed project publisher cannot block the next repo', () => {
|
|
448
471
|
const selected = [{ repoKey: 'root:first' }, { repoKey: 'root:second' }];
|
|
449
472
|
const calls = [];
|
|
@@ -153,6 +153,7 @@ function reduceWall(entries) {
|
|
|
153
153
|
.filter((entry) => (
|
|
154
154
|
entry.baseOid === repo.card.head?.oid
|
|
155
155
|
&& entry.branch === repo.card.head?.branch
|
|
156
|
+
&& checkpointValueReady(entry.value)
|
|
156
157
|
))
|
|
157
158
|
.sort((left, right) => right.through - left.through);
|
|
158
159
|
const newestThrough = matchingBases[0]?.through ?? null;
|
|
@@ -288,6 +289,14 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
288
289
|
&& current.head.branch === targetCard.head?.branch
|
|
289
290
|
) return { state: 'ready', moved: false };
|
|
290
291
|
|
|
292
|
+
const pendingCard = current.head.oid
|
|
293
|
+
&& targetCard.head?.oid
|
|
294
|
+
&& current.head.branch === targetCard.head?.branch
|
|
295
|
+
&& await isAncestor(localCard.path, targetCard.head.oid, current.head.oid);
|
|
296
|
+
if (pendingCard) {
|
|
297
|
+
return { state: 'ready', moved: false, pendingCard: true, conflicts: [] };
|
|
298
|
+
}
|
|
299
|
+
|
|
291
300
|
const objects = await obtainCardObjects(localCard, targetCard);
|
|
292
301
|
if (objects.state !== 'ready') return { state: 'waiting', reason: objects.state };
|
|
293
302
|
|
|
@@ -594,38 +603,87 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
594
603
|
const reduced = await reduceCheckpoint(localCard, plan);
|
|
595
604
|
if (reduced.state !== 'ready') return reduced;
|
|
596
605
|
|
|
597
|
-
const
|
|
598
|
-
const
|
|
599
|
-
const represented = captureIsOnRail(plan, captured);
|
|
600
|
-
const capturedIndex = represented
|
|
601
|
-
? await merge.treeOfPatch(localCard.path, plan.card.head.oid, captured.indexDiff)
|
|
602
|
-
: null;
|
|
603
|
-
const capturedWorktree = represented
|
|
604
|
-
? await merge.treeOfPatch(localCard.path, capturedIndex, captured.worktreeDiff)
|
|
605
|
-
: null;
|
|
606
|
-
const heldWorktree = capturedWorktree || (
|
|
606
|
+
const sharedBaseTree = await merge.treeOf(localCard.path, plan.card.head.oid);
|
|
607
|
+
const previousHeldWorktree = (
|
|
607
608
|
previous?.baseOid === plan.card.head.oid
|
|
608
609
|
&& previous.heldTree
|
|
609
610
|
&& await merge.hasTree(localCard.path, previous.heldTree)
|
|
610
611
|
? previous.heldTree
|
|
611
|
-
:
|
|
612
|
+
: sharedBaseTree
|
|
612
613
|
);
|
|
613
|
-
const
|
|
614
|
+
const previousHeldIndex = (
|
|
614
615
|
previous?.baseOid === plan.card.head.oid
|
|
615
616
|
&& previous.heldIndexTree
|
|
616
617
|
&& await merge.hasTree(localCard.path, previous.heldIndexTree)
|
|
617
618
|
? previous.heldIndexTree
|
|
618
|
-
:
|
|
619
|
+
: sharedBaseTree
|
|
619
620
|
);
|
|
621
|
+
let stateBaseOid = plan.card.head.oid;
|
|
622
|
+
let stateBranch = plan.card.head.branch;
|
|
623
|
+
let stateCardVersion = plan.card.positionVersion;
|
|
624
|
+
let stateCardMutationId = plan.card.positionMutationId;
|
|
625
|
+
let stateIndexDiff = reduced.indexDiff;
|
|
626
|
+
let stateWorktreeDiff = reduced.worktreeDiff;
|
|
627
|
+
let heldWorktree;
|
|
628
|
+
let heldIndex;
|
|
629
|
+
let targetWorktree;
|
|
630
|
+
let targetIndex;
|
|
631
|
+
const pendingConflicts = [];
|
|
632
|
+
|
|
633
|
+
if (cardResult.pendingCard) {
|
|
634
|
+
const pendingBaseTree = await merge.treeOf(localCard.path, localCard.head.oid);
|
|
635
|
+
const rebasedIndex = await merge.reduceTree(
|
|
636
|
+
localCard.path,
|
|
637
|
+
pendingBaseTree,
|
|
638
|
+
previousHeldIndex,
|
|
639
|
+
reduced.indexTree,
|
|
640
|
+
{ label: `pending-card-index-${appliedThrough ?? 'local'}` },
|
|
641
|
+
);
|
|
642
|
+
const rebasedWorktree = await merge.reduceTree(
|
|
643
|
+
localCard.path,
|
|
644
|
+
pendingBaseTree,
|
|
645
|
+
previousHeldWorktree,
|
|
646
|
+
reduced.worktreeTree,
|
|
647
|
+
{ label: `pending-card-worktree-${appliedThrough ?? 'local'}` },
|
|
648
|
+
);
|
|
649
|
+
heldIndex = pendingBaseTree;
|
|
650
|
+
heldWorktree = pendingBaseTree;
|
|
651
|
+
targetIndex = rebasedIndex.tree;
|
|
652
|
+
targetWorktree = rebasedWorktree.tree;
|
|
653
|
+
stateBaseOid = localCard.head.oid;
|
|
654
|
+
stateBranch = localCard.head.branch;
|
|
655
|
+
stateCardVersion = null;
|
|
656
|
+
stateCardMutationId = null;
|
|
657
|
+
stateIndexDiff = await merge.diffOfTree(localCard.path, stateBaseOid, targetIndex);
|
|
658
|
+
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
|
+
} else {
|
|
664
|
+
const captured = await captureCheckpoint(localCard.path);
|
|
665
|
+
const represented = captureIsOnRail(plan, captured);
|
|
666
|
+
const capturedIndex = represented
|
|
667
|
+
? await merge.treeOfPatch(localCard.path, plan.card.head.oid, captured.indexDiff)
|
|
668
|
+
: null;
|
|
669
|
+
const capturedWorktree = represented
|
|
670
|
+
? await merge.treeOfPatch(localCard.path, capturedIndex, captured.worktreeDiff)
|
|
671
|
+
: null;
|
|
672
|
+
heldWorktree = capturedWorktree || previousHeldWorktree;
|
|
673
|
+
heldIndex = capturedIndex || previousHeldIndex;
|
|
674
|
+
targetWorktree = reduced.worktreeTree;
|
|
675
|
+
targetIndex = reduced.indexTree;
|
|
676
|
+
}
|
|
677
|
+
|
|
620
678
|
const currentIndex = await merge.indexTree(localCard.path);
|
|
621
679
|
const reducedLocalIndex = await merge.reduceTree(
|
|
622
680
|
localCard.path,
|
|
623
681
|
currentIndex,
|
|
624
682
|
heldIndex,
|
|
625
|
-
|
|
683
|
+
targetIndex,
|
|
626
684
|
{ label: `index-${reduced.checkpointRevision || 'clean'}` },
|
|
627
685
|
);
|
|
628
|
-
const applied = await merge.advance(localCard.path, heldWorktree,
|
|
686
|
+
const applied = await merge.advance(localCard.path, heldWorktree, targetWorktree, {
|
|
629
687
|
label: `checkpoint-${reduced.checkpointRevision || 'clean'}`,
|
|
630
688
|
});
|
|
631
689
|
const indexResult = await advanceIndex(localCard.path, currentIndex, reducedLocalIndex.tree);
|
|
@@ -633,28 +691,30 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
633
691
|
const state = writeRepoState(plan.repoKey, {
|
|
634
692
|
path: localCard.path,
|
|
635
693
|
replica: localCard.replica,
|
|
636
|
-
branch:
|
|
637
|
-
baseOid:
|
|
638
|
-
cardVersion:
|
|
639
|
-
cardMutationId:
|
|
694
|
+
branch: stateBranch,
|
|
695
|
+
baseOid: stateBaseOid,
|
|
696
|
+
cardVersion: stateCardVersion,
|
|
697
|
+
cardMutationId: stateCardMutationId,
|
|
698
|
+
pendingCard: Boolean(cardResult.pendingCard),
|
|
640
699
|
appliedThrough,
|
|
641
|
-
checkpointRevision:
|
|
642
|
-
indexDiff:
|
|
643
|
-
worktreeDiff:
|
|
700
|
+
checkpointRevision: revisionOf(stateBaseOid, stateIndexDiff, stateWorktreeDiff),
|
|
701
|
+
indexDiff: stateIndexDiff,
|
|
702
|
+
worktreeDiff: stateWorktreeDiff,
|
|
644
703
|
checkpointVersions: plan.transitions.map((entry) => entry.version),
|
|
645
704
|
branches: plan.branches || [],
|
|
646
705
|
heldTree: applied.heldTree,
|
|
647
|
-
heldIndexTree:
|
|
706
|
+
heldIndexTree: targetIndex,
|
|
648
707
|
parked,
|
|
649
708
|
parkedWaiting: [...parkedWaiting, ...branchResult.waiting],
|
|
650
709
|
conflicts: [
|
|
651
710
|
...(cardResult.conflicts || []),
|
|
652
711
|
...reduced.conflicts,
|
|
712
|
+
...pendingConflicts,
|
|
653
713
|
...reducedLocalIndex.conflicts.map((item) => ({ ...item, layer: 'index-materialize' })),
|
|
654
714
|
...applied.conflicts,
|
|
655
715
|
],
|
|
656
|
-
materialized: applied.heldTree ===
|
|
657
|
-
&& reducedLocalIndex.tree ===
|
|
716
|
+
materialized: applied.heldTree === targetWorktree
|
|
717
|
+
&& reducedLocalIndex.tree === targetIndex,
|
|
658
718
|
});
|
|
659
719
|
appendStateEvent({
|
|
660
720
|
resource: 'workspace-reducer',
|
|
@@ -20,6 +20,7 @@ const { listRepoCards } = require('./cards');
|
|
|
20
20
|
const {
|
|
21
21
|
CHECKPOINT_CONTRACT,
|
|
22
22
|
checkpointValue,
|
|
23
|
+
checkpointValueReady,
|
|
23
24
|
readCheckpoint,
|
|
24
25
|
repoKeyOfCard,
|
|
25
26
|
sameTransition,
|
|
@@ -252,7 +253,8 @@ function hasLocalContribution(card, plan, baseline, checkpoint = null, previousC
|
|
|
252
253
|
const onRail = samePosition(card, plan.card);
|
|
253
254
|
const atBaseline = baseline
|
|
254
255
|
&& card.head?.branch === baseline.branch
|
|
255
|
-
&& card.head?.oid === baseline.baseOid
|
|
256
|
+
&& card.head?.oid === baseline.baseOid
|
|
257
|
+
&& !baseline.pendingCard;
|
|
256
258
|
const alreadyPublished = validVersion(previousCard?.positionVersion)
|
|
257
259
|
&& samePosition(card, previousCard);
|
|
258
260
|
const reversesPublishedSharedPosition = previousCard
|
|
@@ -330,6 +332,7 @@ function checkpointBaseFor(card, baseline, project, shared = null) {
|
|
|
330
332
|
baseline.indexDiff || '',
|
|
331
333
|
baseline.worktreeDiff || '',
|
|
332
334
|
);
|
|
335
|
+
if (!checkpointValueReady(value)) return null;
|
|
333
336
|
const reusableThrough = shared
|
|
334
337
|
&& shared.baseOid === card.head.oid
|
|
335
338
|
&& shared.branch === card.head.branch
|