@ibgib/vcs-gib 0.0.23 → 0.0.29
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 +22 -0
- package/VCS_CLI_USER_GUIDE.md +94 -89
- package/dist/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/cli/cli-arg-parser.d.mts +1 -0
- package/dist/cli/cli-arg-parser.d.mts.map +1 -1
- package/dist/cli/cli-arg-parser.mjs +5 -1
- package/dist/cli/cli-arg-parser.mjs.map +1 -1
- package/dist/cli/cli-constants.d.mts +6 -0
- package/dist/cli/cli-constants.d.mts.map +1 -1
- package/dist/cli/cli-constants.mjs +12 -0
- package/dist/cli/cli-constants.mjs.map +1 -1
- package/dist/cli/commands/add-cmd.d.mts.map +1 -1
- package/dist/cli/commands/add-cmd.mjs +11 -3
- package/dist/cli/commands/add-cmd.mjs.map +1 -1
- package/dist/cli/commands/commit-cmd.d.mts +5 -4
- package/dist/cli/commands/commit-cmd.d.mts.map +1 -1
- package/dist/cli/commands/commit-cmd.mjs +155 -35
- package/dist/cli/commands/commit-cmd.mjs.map +1 -1
- package/dist/cli/commands/generate-comment-cmd.d.mts +19 -0
- package/dist/cli/commands/generate-comment-cmd.d.mts.map +1 -0
- package/dist/cli/commands/generate-comment-cmd.mjs +140 -0
- package/dist/cli/commands/generate-comment-cmd.mjs.map +1 -0
- package/dist/cli/commands/log-cmd.d.mts +12 -3
- package/dist/cli/commands/log-cmd.d.mts.map +1 -1
- package/dist/cli/commands/log-cmd.mjs +242 -39
- package/dist/cli/commands/log-cmd.mjs.map +1 -1
- package/dist/cli/commands/status-cmd.d.mts +5 -6
- package/dist/cli/commands/status-cmd.d.mts.map +1 -1
- package/dist/cli/commands/status-cmd.mjs +51 -205
- package/dist/cli/commands/status-cmd.mjs.map +1 -1
- package/dist/cli/vcs-cli-main.d.mts.map +1 -1
- package/dist/cli/vcs-cli-main.mjs +25 -4
- package/dist/cli/vcs-cli-main.mjs.map +1 -1
- package/dist/engine/branch/branch-helper.d.mts +14 -0
- package/dist/engine/branch/branch-helper.d.mts.map +1 -1
- package/dist/engine/branch/branch-helper.mjs +21 -0
- package/dist/engine/branch/branch-helper.mjs.map +1 -1
- package/dist/engine/index/index-helper.d.mts.map +1 -1
- package/dist/engine/index/index-helper.mjs +3 -0
- package/dist/engine/index/index-helper.mjs.map +1 -1
- package/dist/engine/item/item-helper.d.mts.map +1 -1
- package/dist/engine/item/item-helper.mjs +5 -2
- package/dist/engine/item/item-helper.mjs.map +1 -1
- package/dist/engine/merge/b2tfs-3way-graft-strategy.d.mts +28 -0
- package/dist/engine/merge/b2tfs-3way-graft-strategy.d.mts.map +1 -1
- package/dist/engine/merge/b2tfs-3way-graft-strategy.mjs +84 -12
- package/dist/engine/merge/b2tfs-3way-graft-strategy.mjs.map +1 -1
- package/dist/engine/status/status-helper.d.mts +90 -0
- package/dist/engine/status/status-helper.d.mts.map +1 -0
- package/dist/engine/status/status-helper.mjs +286 -0
- package/dist/engine/status/status-helper.mjs.map +1 -0
- package/package.json +3 -3
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/cli/cli-arg-parser.mts +5 -0
- package/src/cli/cli-constants.mts +13 -0
- package/src/cli/commands/add-cmd.mts +11 -3
- package/src/cli/commands/branch-checkout-cmd.respec.mts +54 -0
- package/src/cli/commands/commit-cmd.mts +171 -44
- package/src/cli/commands/commit-log-cmd.respec.mts +216 -3
- package/src/cli/commands/generate-comment-cmd.mts +171 -0
- package/src/cli/commands/log-cmd.mts +294 -42
- package/src/cli/commands/status-cmd.mts +58 -256
- package/src/cli/vcs-cli-main.mts +25 -4
- package/src/engine/branch/branch-helper.mts +33 -0
- package/src/engine/index/index-helper.mts +3 -0
- package/src/engine/item/item-helper.mts +5 -2
- package/src/engine/merge/b2tfs-3way-graft-strategy.mts +89 -10
- package/src/engine/merge/b2tfs-sync-comments.respec.mts +399 -0
- package/src/engine/status/status-helper.mts +383 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module b2tfs-3way-graft-strategy
|
|
3
3
|
*
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* ⚠️ CRITICAL GRAFT STRATEGY GUIDANCE WARNING ⚠️
|
|
6
|
+
* BEFORE MAKING ANY CHANGES TO THIS FILE, YOU MUST CONSULT AND CAREFULLY READ:
|
|
7
|
+
* libs/core-gib/src/sync/GRAFT_STRATEGY_GUIDANCE.md
|
|
8
|
+
*
|
|
9
|
+
* Graft strategies must strictly fulfill the `GraftTimelinesResult` contract,
|
|
10
|
+
* ensure timeline `n` monotonicity (using Math.max over base/orphan branch frames),
|
|
11
|
+
* and include ALL newly referenced ancillary dependency nodes (comments, binary
|
|
12
|
+
* stones, DNAs) in `ancillaryIbGibsCreated` so that the destination branch
|
|
13
|
+
* space is completely self-contained.
|
|
14
|
+
* ============================================================================
|
|
15
|
+
*
|
|
4
16
|
* 3-way text line diff graft strategy for B2tFS file item timelines.
|
|
5
17
|
* Reconciles diverging file content via 3-way line diff (LCA, Base, Orphan)
|
|
6
18
|
* and inserts Git conflict markers when line edits collide.
|
|
@@ -13,14 +25,15 @@
|
|
|
13
25
|
import { extractErrorMsg, hash } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
14
26
|
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
15
27
|
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
16
|
-
import { getGib } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
|
|
28
|
+
import { getGib, isDna } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
|
|
17
29
|
import { rel8 } from '@ibgib/ts-gib/dist/V1/transforms/rel8.mjs';
|
|
18
30
|
|
|
19
31
|
import { GraftMergeStrategyBase_V1 } from '@ibgib/core-gib/dist/sync/graft-info/graft-merge-strategy-base-v1.mjs';
|
|
20
32
|
import { GraftContext, GraftStrategy, GraftTimelinesResult } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-types.mjs';
|
|
21
33
|
import { FlatIbGibGraph } from '@ibgib/core-gib/dist/common/other/graph-types.mjs';
|
|
22
34
|
import { getFromSpace, persistTransformResult, registerNewIbGib, putInSpace } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
|
|
23
|
-
import { getBinIb, getIbGibsFromCache_fallbackToSpaces } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
35
|
+
import { getBinIb, getIbGibsFromCache_fallbackToSpaces, splitPerTjpAndOrDna, getTimelinesGroupedByTjp } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
36
|
+
import { getDependencyGraph } from '@ibgib/core-gib/dist/common/other/graph-helper.mjs';
|
|
24
37
|
import { createGraftInfo } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-helpers.mjs';
|
|
25
38
|
import { GRAFT_INFO_REL8N_NAME } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-constants.mjs';
|
|
26
39
|
import { BinIbGib_V1 } from '@ibgib/core-gib/dist/common/bin/bin-types.mjs';
|
|
@@ -151,6 +164,12 @@ export function perform3WayTextLineMerge({
|
|
|
151
164
|
};
|
|
152
165
|
}
|
|
153
166
|
|
|
167
|
+
/**
|
|
168
|
+
* 3-way text line diff graft strategy for B2tFS file item timelines.
|
|
169
|
+
*
|
|
170
|
+
* ⚠️ **IMPORTANT**: Before making ANY changes to this class or its methods, consult:
|
|
171
|
+
* `libs/core-gib/src/sync/GRAFT_STRATEGY_GUIDANCE.md`.
|
|
172
|
+
*/
|
|
154
173
|
export class B2tFS3WayGraftStrategy extends GraftMergeStrategyBase_V1 implements GraftStrategy {
|
|
155
174
|
readonly strategyId: string = 'B2tFS3WayGraftStrategy';
|
|
156
175
|
private lc = '[B2tFS3WayGraftStrategy]';
|
|
@@ -211,6 +230,16 @@ export class B2tFS3WayGraftStrategy extends GraftMergeStrategyBase_V1 implements
|
|
|
211
230
|
}
|
|
212
231
|
}
|
|
213
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Executes 3-way line diff text graft across LCA, Base, and Orphan file items.
|
|
235
|
+
*
|
|
236
|
+
* ⚠️ **CRITICAL CONTRACT**: Before modifying this method, review:
|
|
237
|
+
* `libs/core-gib/src/sync/GRAFT_STRATEGY_GUIDANCE.md`
|
|
238
|
+
* Ensure:
|
|
239
|
+
* 1. Monotonic `n` counter (`highestDataN = Math.max(...)`)
|
|
240
|
+
* 2. Full dependency graph inclusion in `ancillaryIbGibsCreated` (comments, bin stones, DNAs)
|
|
241
|
+
* 3. Self-contained destination branch space
|
|
242
|
+
*/
|
|
214
243
|
async graft(context: GraftContext): Promise<GraftTimelinesResult | undefined> {
|
|
215
244
|
const lc = `${lcFile}[${this.strategyId}][${this.graft.name}]`;
|
|
216
245
|
try {
|
|
@@ -360,16 +389,67 @@ export class B2tFS3WayGraftStrategy extends GraftMergeStrategyBase_V1 implements
|
|
|
360
389
|
};
|
|
361
390
|
const baseItemDataN = fnGetN(baseItem);
|
|
362
391
|
const orphanItemDataN = fnGetN(orphanItem);
|
|
363
|
-
const
|
|
392
|
+
const highestDataN = Math.max(baseItemDataN, orphanItemDataN);
|
|
393
|
+
|
|
394
|
+
const lcaCommentAddrs = lcaItem.rel8ns?.comment || [];
|
|
395
|
+
const lcaCommentAddrsSet = new Set(lcaCommentAddrs);
|
|
396
|
+
const mergedCommentAddrsSinceLCA_base = (baseItem.rel8ns?.comment || []).filter(addr => !lcaCommentAddrsSet.has(addr));
|
|
397
|
+
const mergedCommentAddrsSinceLCA_orphan = (orphanItem.rel8ns?.comment || []).filter(addr => !lcaCommentAddrsSet.has(addr));
|
|
398
|
+
const newCommentAddrs = Array.from(new Set([...mergedCommentAddrsSinceLCA_base, ...mergedCommentAddrsSinceLCA_orphan]));
|
|
399
|
+
const allMergedCommentAddrs = Array.from(new Set([...lcaCommentAddrs, ...newCommentAddrs]));
|
|
400
|
+
|
|
401
|
+
const commentDepIbGibs: IbGib_V1[] = [];
|
|
402
|
+
if (newCommentAddrs.length > 0) {
|
|
403
|
+
for (const cAddr of newCommentAddrs) {
|
|
404
|
+
const cIb = await this.fetchIbGib({ addr: cAddr, space, readCache_graph: ibGibReadCache });
|
|
405
|
+
if (!cIb) {
|
|
406
|
+
throw new Error(`(UNEXPECTED) Comment ibgib for address '${cAddr}' not found in cache or space (E: a1b2c3d4e5f67a8b9c0d1e2f3a4b5c6d)`);
|
|
407
|
+
}
|
|
408
|
+
commentDepIbGibs.push(cIb);
|
|
409
|
+
const cDepGraph = await getDependencyGraph({
|
|
410
|
+
ibGib: cIb,
|
|
411
|
+
space,
|
|
412
|
+
ibGibs: ibGibReadCache ? Object.values(ibGibReadCache) : undefined,
|
|
413
|
+
});
|
|
414
|
+
for (const depNode of Object.values(cDepGraph)) {
|
|
415
|
+
const depAddr = getIbGibAddr({ ibGib: depNode });
|
|
416
|
+
if (!commentDepIbGibs.some(x => getIbGibAddr({ ibGib: x }) === depAddr)) {
|
|
417
|
+
commentDepIbGibs.push(depNode);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const { mapWithTjp_YesDna, mapWithoutTjps } = splitPerTjpAndOrDna({ ibGibs: commentDepIbGibs });
|
|
423
|
+
|
|
424
|
+
// Put DNA and non-timeline stones into space with isDna flag
|
|
425
|
+
for (const dnaOrStone of Object.values(mapWithoutTjps)) {
|
|
426
|
+
if (space) {
|
|
427
|
+
await putInSpace({ ibGib: dnaOrStone, space, isDna: isDna({ ibGib: dnaOrStone }) });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Register timeline frames in order
|
|
432
|
+
const timelinesByTjp = getTimelinesGroupedByTjp({ ibGibs: Object.values(mapWithTjp_YesDna) });
|
|
433
|
+
for (const tjpAddr of Object.keys(timelinesByTjp)) {
|
|
434
|
+
const timeline = timelinesByTjp[tjpAddr];
|
|
435
|
+
for (const frame of timeline) {
|
|
436
|
+
if (space) {
|
|
437
|
+
await putInSpace({ ibGib: frame, space });
|
|
438
|
+
await registerNewIbGib({ ibGib: frame, space });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
364
443
|
|
|
365
444
|
const evolveRes = await evolveB2tFSItemIbGib({
|
|
366
445
|
existingItem: baseItem,
|
|
367
446
|
dataToUpdate: {
|
|
368
447
|
fileHash,
|
|
369
|
-
n:
|
|
448
|
+
n: highestDataN + 1,
|
|
370
449
|
},
|
|
371
450
|
rel8nsToUpdate: {
|
|
372
451
|
[BINARY_REL8N_NAME]: [binAddr],
|
|
452
|
+
...(allMergedCommentAddrs.length > 0 ? { comment: allMergedCommentAddrs } : {}),
|
|
373
453
|
},
|
|
374
454
|
});
|
|
375
455
|
|
|
@@ -438,7 +518,7 @@ export class B2tFS3WayGraftStrategy extends GraftMergeStrategyBase_V1 implements
|
|
|
438
518
|
dnasReplayed: [],
|
|
439
519
|
dnasCreatedInGraft: newGraftDnaIbGibs,
|
|
440
520
|
intermediateIbGibsCreatedInGraft: newGraftedIntermediateIbGibs_ordered,
|
|
441
|
-
ancillaryIbGibsCreated: [binIbGib],
|
|
521
|
+
ancillaryIbGibsCreated: [binIbGib, ...commentDepIbGibs],
|
|
442
522
|
newGraftTip: finalGraftTip,
|
|
443
523
|
graftInfoCreated: graftInfo,
|
|
444
524
|
};
|
|
@@ -462,17 +542,16 @@ export class B2tFS3WayGraftStrategy extends GraftMergeStrategyBase_V1 implements
|
|
|
462
542
|
readCache_graph?: FlatIbGibGraph;
|
|
463
543
|
}): Promise<IbGib_V1 | undefined> {
|
|
464
544
|
if (!addr) { return undefined; }
|
|
465
|
-
const cleanAddr = addr.includes('^') ? addr.split('^')[0] : addr;
|
|
466
545
|
if (readCache_graph) {
|
|
467
|
-
if (readCache_graph[cleanAddr]) { return readCache_graph[cleanAddr]; }
|
|
468
546
|
if (readCache_graph[addr]) { return readCache_graph[addr]; }
|
|
469
|
-
const
|
|
547
|
+
const gib = addr.includes('^') ? addr.substring(addr.lastIndexOf('^') + 1) : addr;
|
|
548
|
+
const matchingKey = Object.keys(readCache_graph).find(k => k === addr || (gib && k.endsWith('^' + gib)));
|
|
470
549
|
if (matchingKey) { return readCache_graph[matchingKey]; }
|
|
471
550
|
}
|
|
472
551
|
try {
|
|
473
552
|
const res = await getFromSpace({ space, addrs: [addr] });
|
|
474
|
-
if (res
|
|
475
|
-
const found = res.ibGibs.find((x: any) => !!x);
|
|
553
|
+
if (res) {
|
|
554
|
+
const found = res.ibGibs?.find((x: any) => !!x) || (res.rawResultIbGib as any)?.ibGibs?.find((x: any) => !!x);
|
|
476
555
|
if (found) { return found; }
|
|
477
556
|
}
|
|
478
557
|
} catch {
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module b2tfs-sync-comments.respec.node
|
|
3
|
+
*
|
|
4
|
+
* Direct in-memory sync & dependency graph verification for B2tFS items and comments.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
respecfully, ifWe, ifWeMight, iReckon, firstOfEach
|
|
12
|
+
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
13
|
+
|
|
14
|
+
import { clone, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
15
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
16
|
+
import { getGib } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
|
|
17
|
+
import { SyncSagaCoordinator } from '@ibgib/core-gib/dist/sync/sync-saga-coordinator.mjs';
|
|
18
|
+
import { SyncPeerInnerspace_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
|
|
19
|
+
import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
|
|
20
|
+
import { SyncConflictStrategy } from '@ibgib/core-gib/dist/sync/sync-constants.mjs';
|
|
21
|
+
import { GraftPreference } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-types.mjs';
|
|
22
|
+
import { GraftStrategyService } from '@ibgib/core-gib/dist/sync/graft-info/graft-strategy-service.mjs';
|
|
23
|
+
import { getDependencyGraph } from '@ibgib/core-gib/dist/common/other/graph-helper.mjs';
|
|
24
|
+
import { createCommentIbGib } from '@ibgib/core-gib/dist/common/comment/comment-helper.mjs';
|
|
25
|
+
import { getBinIb } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
26
|
+
import { BinIbGib_V1 } from '@ibgib/core-gib/dist/common/bin/bin-types.mjs';
|
|
27
|
+
import { putInSpace, persistTransformResult, registerNewIbGib, getFromSpace, getLatestAddrs } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
|
|
28
|
+
|
|
29
|
+
import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
|
|
30
|
+
import { getOrCreateBranchSpace } from '../branch/branch-space-helper.mjs';
|
|
31
|
+
import { createB2tFSItemIbGib, evolveB2tFSItemIbGib } from '../item/item-helper.mjs';
|
|
32
|
+
import { B2tFSItemIbGib_V1, BINARY_REL8N_NAME } from '../item/item-types.mjs';
|
|
33
|
+
import { B2tFS3WayGraftStrategy } from './b2tfs-3way-graft-strategy.mjs';
|
|
34
|
+
|
|
35
|
+
const lc = `[apps/vcs-gib/src/engine/merge/b2tfs-sync-comments.respec.node.mts]`;
|
|
36
|
+
const graftStrategySvc = GraftStrategyService.instance;
|
|
37
|
+
|
|
38
|
+
async function createFileWithBinAndComment({
|
|
39
|
+
name,
|
|
40
|
+
content,
|
|
41
|
+
commentText,
|
|
42
|
+
space,
|
|
43
|
+
}: {
|
|
44
|
+
name: string;
|
|
45
|
+
content: string;
|
|
46
|
+
commentText?: string;
|
|
47
|
+
space: any;
|
|
48
|
+
}): Promise<{ fileItem: B2tFSItemIbGib_V1; commentAddr?: string }> {
|
|
49
|
+
const fileHash = await hash({ s: content });
|
|
50
|
+
const fileExt = name.includes('.') ? name.split('.').pop() || 'txt' : 'txt';
|
|
51
|
+
const binIb = getBinIb({ binHash: fileHash, binExt: fileExt, binEncoding: 'utf8' });
|
|
52
|
+
const binIbGib: BinIbGib_V1 = {
|
|
53
|
+
ib: binIb,
|
|
54
|
+
data: Buffer.from(content, 'utf8'),
|
|
55
|
+
rel8ns: {},
|
|
56
|
+
};
|
|
57
|
+
binIbGib.gib = await getGib({ ibGib: binIbGib, hasTjp: false });
|
|
58
|
+
await putInSpace({ space, ibGib: binIbGib });
|
|
59
|
+
await registerNewIbGib({ space, ibGib: binIbGib });
|
|
60
|
+
const binAddr = getIbGibAddr({ ibGib: binIbGib });
|
|
61
|
+
|
|
62
|
+
let commentAddr: string | undefined;
|
|
63
|
+
const rel8nsToAdd: any = {
|
|
64
|
+
[BINARY_REL8N_NAME]: [binAddr],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (commentText) {
|
|
68
|
+
const commentRes = await createCommentIbGib({
|
|
69
|
+
text: commentText,
|
|
70
|
+
});
|
|
71
|
+
await persistTransformResult({ resTransform: commentRes, space });
|
|
72
|
+
await registerNewIbGib({ ibGib: commentRes.newIbGib, space });
|
|
73
|
+
commentAddr = getIbGibAddr({ ibGib: commentRes.newIbGib });
|
|
74
|
+
rel8nsToAdd.comment = [commentAddr];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const itemRes = await createB2tFSItemIbGib({
|
|
78
|
+
fsType: 'file',
|
|
79
|
+
name,
|
|
80
|
+
rel8ns: rel8nsToAdd,
|
|
81
|
+
});
|
|
82
|
+
await persistTransformResult({ resTransform: itemRes, space });
|
|
83
|
+
await registerNewIbGib({ ibGib: itemRes.newIbGib, space });
|
|
84
|
+
|
|
85
|
+
return { fileItem: itemRes.newIbGib as B2tFSItemIbGib_V1, commentAddr };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function evolveFileWithBinAndComment({
|
|
89
|
+
existingItem,
|
|
90
|
+
newContent,
|
|
91
|
+
commentText,
|
|
92
|
+
space,
|
|
93
|
+
}: {
|
|
94
|
+
existingItem: B2tFSItemIbGib_V1;
|
|
95
|
+
newContent: string;
|
|
96
|
+
commentText?: string;
|
|
97
|
+
space: any;
|
|
98
|
+
}): Promise<{ evolvedItem: B2tFSItemIbGib_V1; commentAddr?: string }> {
|
|
99
|
+
const fileHash = await hash({ s: newContent });
|
|
100
|
+
const fileExt = existingItem.data?.fileExt || (existingItem.data?.name?.includes('.') ? existingItem.data.name.split('.').pop() || 'txt' : 'txt');
|
|
101
|
+
const binIb = getBinIb({ binHash: fileHash, binExt: fileExt, binEncoding: 'utf8' });
|
|
102
|
+
const binIbGib: BinIbGib_V1 = {
|
|
103
|
+
ib: binIb,
|
|
104
|
+
data: Buffer.from(newContent, 'utf8'),
|
|
105
|
+
rel8ns: {},
|
|
106
|
+
};
|
|
107
|
+
binIbGib.gib = await getGib({ ibGib: binIbGib, hasTjp: false });
|
|
108
|
+
await putInSpace({ space, ibGib: binIbGib });
|
|
109
|
+
await registerNewIbGib({ space, ibGib: binIbGib });
|
|
110
|
+
const binAddr = getIbGibAddr({ ibGib: binIbGib });
|
|
111
|
+
|
|
112
|
+
let commentAddr: string | undefined;
|
|
113
|
+
const rel8nsToAdd: any = {
|
|
114
|
+
[BINARY_REL8N_NAME]: [binAddr],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (commentText) {
|
|
118
|
+
const commentRes = await createCommentIbGib({
|
|
119
|
+
text: commentText,
|
|
120
|
+
});
|
|
121
|
+
await persistTransformResult({ resTransform: commentRes, space });
|
|
122
|
+
await registerNewIbGib({ ibGib: commentRes.newIbGib, space });
|
|
123
|
+
commentAddr = getIbGibAddr({ ibGib: commentRes.newIbGib });
|
|
124
|
+
rel8nsToAdd.comment = [commentAddr];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const evolvedRes = await evolveB2tFSItemIbGib({
|
|
128
|
+
existingItem,
|
|
129
|
+
rel8nsToUpdate: rel8nsToAdd,
|
|
130
|
+
});
|
|
131
|
+
await persistTransformResult({ resTransform: evolvedRes, space });
|
|
132
|
+
await registerNewIbGib({ ibGib: evolvedRes.newIbGib, space });
|
|
133
|
+
|
|
134
|
+
return { evolvedItem: evolvedRes.newIbGib as B2tFSItemIbGib_V1, commentAddr };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await respecfully(lc, `B2tFS Item & Comment Sync Graph Integrity`, async () => {
|
|
138
|
+
|
|
139
|
+
firstOfEach(lc, async () => {
|
|
140
|
+
graftStrategySvc.reset();
|
|
141
|
+
graftStrategySvc.registerStrategy(new B2tFS3WayGraftStrategy());
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await ifWe(lc, `transfers single b2tfs item and its attached comment from Space A to Space B`, async () => {
|
|
145
|
+
const testDir = path.join(process.cwd(), '.test_sync_comments_1');
|
|
146
|
+
const vcsFolderName = '.vcsgib_test_1';
|
|
147
|
+
try {
|
|
148
|
+
const { metaspace, vcsPath } = await bootstrapVcsMetaspace({
|
|
149
|
+
repoPath: testDir,
|
|
150
|
+
vcsFolderName,
|
|
151
|
+
});
|
|
152
|
+
const { space: spaceA } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_a' });
|
|
153
|
+
const { space: spaceB } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_b' });
|
|
154
|
+
|
|
155
|
+
// 1. Create file item with comment in Space A
|
|
156
|
+
const { fileItem } = await createFileWithBinAndComment({
|
|
157
|
+
name: 'app.ts',
|
|
158
|
+
content: 'const x = 1;',
|
|
159
|
+
commentText: 'Initial commit comment in Space A',
|
|
160
|
+
space: spaceA,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 2. Sync A -> B
|
|
164
|
+
const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
|
|
165
|
+
await peer.initialized;
|
|
166
|
+
await peer.initializeOpts({
|
|
167
|
+
localMetaspace: metaspace,
|
|
168
|
+
localSpace: spaceA,
|
|
169
|
+
receiverSpace: spaceB,
|
|
170
|
+
receiverCoordinator: new SyncSagaCoordinator(),
|
|
171
|
+
receiverMetaspace: metaspace,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const senderCoordinator = new SyncSagaCoordinator();
|
|
175
|
+
const syncSaga = await senderCoordinator.sync({
|
|
176
|
+
peer,
|
|
177
|
+
localSpace: spaceA,
|
|
178
|
+
metaspace,
|
|
179
|
+
domainIbGibs: [fileItem],
|
|
180
|
+
conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
|
|
181
|
+
});
|
|
182
|
+
await syncSaga.done;
|
|
183
|
+
|
|
184
|
+
// 3. Verify getDependencyGraph in Space B alone without metaspace
|
|
185
|
+
const fileAddr = getIbGibAddr({ ibGib: fileItem });
|
|
186
|
+
const resB = await getFromSpace({ addr: fileAddr, space: spaceB });
|
|
187
|
+
const fileInB = resB.ibGibs?.at(0);
|
|
188
|
+
iReckon(lc, fileInB).isGonnaBeTruthy();
|
|
189
|
+
|
|
190
|
+
const depGraphInB = await getDependencyGraph({
|
|
191
|
+
ibGib: fileInB,
|
|
192
|
+
space: spaceB,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const graphValues = Object.values(depGraphInB);
|
|
196
|
+
const commentInGraph = graphValues.find(x => (x.data as any)?.text === 'Initial commit comment in Space A');
|
|
197
|
+
iReckon(lc, commentInGraph).isGonnaBeTruthy();
|
|
198
|
+
} finally {
|
|
199
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await ifWe(lc, `syncs accreted comments bidirectionally between Space A and Space B`, async () => {
|
|
204
|
+
const testDir = path.join(process.cwd(), '.test_sync_comments_2');
|
|
205
|
+
const vcsFolderName = '.vcsgib_test_2';
|
|
206
|
+
try {
|
|
207
|
+
const { metaspace, vcsPath } = await bootstrapVcsMetaspace({
|
|
208
|
+
repoPath: testDir,
|
|
209
|
+
vcsFolderName,
|
|
210
|
+
});
|
|
211
|
+
const { space: spaceA } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_a' });
|
|
212
|
+
const { space: spaceB } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_b' });
|
|
213
|
+
|
|
214
|
+
// 1. Create file with comment 1 in Space A
|
|
215
|
+
const { fileItem: fileItemA1 } = await createFileWithBinAndComment({
|
|
216
|
+
name: 'app.ts',
|
|
217
|
+
content: 'const x = 1;',
|
|
218
|
+
commentText: 'Comment 1 from Space A',
|
|
219
|
+
space: spaceA,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// 2. Sync A -> B
|
|
223
|
+
const peerA2B = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
|
|
224
|
+
await peerA2B.initialized;
|
|
225
|
+
await peerA2B.initializeOpts({
|
|
226
|
+
localMetaspace: metaspace,
|
|
227
|
+
localSpace: spaceA,
|
|
228
|
+
receiverSpace: spaceB,
|
|
229
|
+
receiverCoordinator: new SyncSagaCoordinator(),
|
|
230
|
+
receiverMetaspace: metaspace,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const senderA = new SyncSagaCoordinator();
|
|
234
|
+
const sagaA = await senderA.sync({
|
|
235
|
+
peer: peerA2B,
|
|
236
|
+
localSpace: spaceA,
|
|
237
|
+
metaspace,
|
|
238
|
+
domainIbGibs: [fileItemA1],
|
|
239
|
+
conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
|
|
240
|
+
});
|
|
241
|
+
await sagaA.done;
|
|
242
|
+
|
|
243
|
+
// 3. Evolve file in Space B with comment 2
|
|
244
|
+
const fileAddrA1 = getIbGibAddr({ ibGib: fileItemA1 });
|
|
245
|
+
const fileInB1 = (await getFromSpace({ addr: fileAddrA1, space: spaceB })).ibGibs?.at(0) as B2tFSItemIbGib_V1;
|
|
246
|
+
const { evolvedItem: fileInB2 } = await evolveFileWithBinAndComment({
|
|
247
|
+
existingItem: fileInB1,
|
|
248
|
+
newContent: 'const x = 2;',
|
|
249
|
+
commentText: 'Comment 2 from Space B',
|
|
250
|
+
space: spaceB,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// 4. Sync B -> A
|
|
254
|
+
const peerB2A = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
|
|
255
|
+
await peerB2A.initialized;
|
|
256
|
+
await peerB2A.initializeOpts({
|
|
257
|
+
localMetaspace: metaspace,
|
|
258
|
+
localSpace: spaceB,
|
|
259
|
+
receiverSpace: spaceA,
|
|
260
|
+
receiverCoordinator: new SyncSagaCoordinator(),
|
|
261
|
+
receiverMetaspace: metaspace,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const senderB = new SyncSagaCoordinator();
|
|
265
|
+
const sagaB = await senderB.sync({
|
|
266
|
+
peer: peerB2A,
|
|
267
|
+
localSpace: spaceB,
|
|
268
|
+
metaspace,
|
|
269
|
+
domainIbGibs: [fileInB2],
|
|
270
|
+
conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
|
|
271
|
+
});
|
|
272
|
+
await sagaB.done;
|
|
273
|
+
|
|
274
|
+
// 5. Verify Space A has full graph containing both comments
|
|
275
|
+
const fileAddrB2 = getIbGibAddr({ ibGib: fileInB2 });
|
|
276
|
+
const fileInA2 = (await getFromSpace({ addr: fileAddrB2, space: spaceA })).ibGibs?.at(0);
|
|
277
|
+
iReckon(lc, fileInA2).isGonnaBeTruthy();
|
|
278
|
+
|
|
279
|
+
const depGraphInA = await getDependencyGraph({
|
|
280
|
+
ibGib: fileInA2,
|
|
281
|
+
space: spaceA,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const graphValuesA = Object.values(depGraphInA);
|
|
285
|
+
const comment1 = graphValuesA.find(x => (x.data as any)?.text === 'Comment 1 from Space A');
|
|
286
|
+
const comment2 = graphValuesA.find(x => (x.data as any)?.text === 'Comment 2 from Space B');
|
|
287
|
+
iReckon(lc, comment1).isGonnaBeTruthy();
|
|
288
|
+
iReckon(lc, comment2).isGonnaBeTruthy();
|
|
289
|
+
} finally {
|
|
290
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
await ifWe(lc, `preserves and transfers all comments in full 3-way graft merge`, async () => {
|
|
295
|
+
const testDir = path.join(process.cwd(), '.test_sync_comments_3');
|
|
296
|
+
const vcsFolderName = '.vcsgib_test_3';
|
|
297
|
+
try {
|
|
298
|
+
const { metaspace, vcsPath } = await bootstrapVcsMetaspace({
|
|
299
|
+
repoPath: testDir,
|
|
300
|
+
vcsFolderName,
|
|
301
|
+
});
|
|
302
|
+
const { space: spaceA } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_a' });
|
|
303
|
+
const { space: spaceB } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_b' });
|
|
304
|
+
|
|
305
|
+
// 1. Initial base file in Space A
|
|
306
|
+
const { fileItem: lcaFile } = await createFileWithBinAndComment({
|
|
307
|
+
name: 'app.ts',
|
|
308
|
+
content: 'const a = 1;\nconst b = 2;\n',
|
|
309
|
+
commentText: 'Base commit comment',
|
|
310
|
+
space: spaceA,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// 2. Sync base file from A -> B so both spaces share the LCA
|
|
314
|
+
const peerA2B = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
|
|
315
|
+
await peerA2B.initialized;
|
|
316
|
+
await peerA2B.initializeOpts({
|
|
317
|
+
localMetaspace: metaspace,
|
|
318
|
+
localSpace: spaceA,
|
|
319
|
+
receiverSpace: spaceB,
|
|
320
|
+
receiverCoordinator: new SyncSagaCoordinator(),
|
|
321
|
+
receiverMetaspace: metaspace,
|
|
322
|
+
});
|
|
323
|
+
const senderA = new SyncSagaCoordinator();
|
|
324
|
+
const initSaga = await senderA.sync({
|
|
325
|
+
peer: peerA2B,
|
|
326
|
+
localSpace: spaceA,
|
|
327
|
+
metaspace,
|
|
328
|
+
domainIbGibs: [lcaFile],
|
|
329
|
+
conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
|
|
330
|
+
});
|
|
331
|
+
await initSaga.done;
|
|
332
|
+
|
|
333
|
+
// 3. Branch A: evolve file in Space A with comment A
|
|
334
|
+
const { evolvedItem: fileA } = await evolveFileWithBinAndComment({
|
|
335
|
+
existingItem: lcaFile,
|
|
336
|
+
newContent: 'const a = 100;\nconst b = 2;\n',
|
|
337
|
+
commentText: 'Branch A comment (updated a to 100)',
|
|
338
|
+
space: spaceA,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// 4. Branch B: evolve file in Space B with comment B
|
|
342
|
+
const lcaInB = (await getFromSpace({ addr: getIbGibAddr({ ibGib: lcaFile }), space: spaceB })).ibGibs?.at(0) as B2tFSItemIbGib_V1;
|
|
343
|
+
const { evolvedItem: fileB } = await evolveFileWithBinAndComment({
|
|
344
|
+
existingItem: lcaInB,
|
|
345
|
+
newContent: 'const a = 1;\nconst b = 200;\n',
|
|
346
|
+
commentText: 'Branch B comment (updated b to 200)',
|
|
347
|
+
space: spaceB,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// 5. Merge Branch A into Branch B (Space A -> Space B) with 3-way graft
|
|
351
|
+
const peerMerge = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
|
|
352
|
+
await peerMerge.initialized;
|
|
353
|
+
await peerMerge.initializeOpts({
|
|
354
|
+
localMetaspace: metaspace,
|
|
355
|
+
localSpace: spaceA,
|
|
356
|
+
receiverSpace: spaceB,
|
|
357
|
+
receiverCoordinator: new SyncSagaCoordinator(),
|
|
358
|
+
receiverMetaspace: metaspace,
|
|
359
|
+
});
|
|
360
|
+
const mergeCoordinator = new SyncSagaCoordinator();
|
|
361
|
+
const mergeSaga = await mergeCoordinator.sync({
|
|
362
|
+
peer: peerMerge,
|
|
363
|
+
localSpace: spaceA,
|
|
364
|
+
metaspace,
|
|
365
|
+
domainIbGibs: [fileA],
|
|
366
|
+
conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
|
|
367
|
+
graftPreference: GraftPreference.receiver,
|
|
368
|
+
});
|
|
369
|
+
await mergeSaga.done;
|
|
370
|
+
|
|
371
|
+
// 6. Inspect grafted tip in Space B
|
|
372
|
+
const tjpAddr = fileB.rel8ns?.tjp?.at(0) || getIbGibAddr({ ibGib: fileB });
|
|
373
|
+
const resLatestInB = await getLatestAddrs({ addrs: [tjpAddr], space: spaceB });
|
|
374
|
+
const latestAddrInB = (resLatestInB?.data as any)?.latestAddrsMap?.[tjpAddr];
|
|
375
|
+
iReckon(lc, latestAddrInB).isGonnaBeTruthy();
|
|
376
|
+
|
|
377
|
+
const resGraftedInB = await getFromSpace({ addr: latestAddrInB, space: spaceB });
|
|
378
|
+
const graftedTipInB = resGraftedInB.ibGibs?.at(0);
|
|
379
|
+
iReckon(lc, graftedTipInB).isGonnaBeTruthy();
|
|
380
|
+
|
|
381
|
+
// 7. Verify getDependencyGraph strictly inside spaceB
|
|
382
|
+
const depGraphB = await getDependencyGraph({
|
|
383
|
+
ibGib: graftedTipInB,
|
|
384
|
+
space: spaceB,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const graphValuesB = Object.values(depGraphB);
|
|
388
|
+
const commentBase = graphValuesB.find(x => (x.data as any)?.text === 'Base commit comment');
|
|
389
|
+
const commentA = graphValuesB.find(x => (x.data as any)?.text === 'Branch A comment (updated a to 100)');
|
|
390
|
+
const commentB = graphValuesB.find(x => (x.data as any)?.text === 'Branch B comment (updated b to 200)');
|
|
391
|
+
|
|
392
|
+
iReckon(lc, commentBase).isGonnaBeTruthy();
|
|
393
|
+
iReckon(lc, commentA).isGonnaBeTruthy();
|
|
394
|
+
iReckon(lc, commentB).isGonnaBeTruthy();
|
|
395
|
+
} finally {
|
|
396
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
});
|