@ibgib/vcs-gib 0.0.17 → 0.0.18

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.
Files changed (143) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/bootstrap/node-bootstrap.d.mts.map +1 -1
  4. package/dist/bootstrap/node-bootstrap.mjs +3 -0
  5. package/dist/bootstrap/node-bootstrap.mjs.map +1 -1
  6. package/dist/bootstrap/{node-bootstrap.respec.node.d.mts → node-bootstrap.respec.d.mts} +1 -1
  7. package/dist/bootstrap/node-bootstrap.respec.d.mts.map +1 -0
  8. package/dist/bootstrap/{node-bootstrap.respec.node.mjs → node-bootstrap.respec.mjs} +1 -1
  9. package/dist/bootstrap/node-bootstrap.respec.mjs.map +1 -0
  10. package/dist/cli/{cli-arg-parser.respec.node.d.mts → cli-arg-parser.respec.d.mts} +1 -1
  11. package/dist/cli/cli-arg-parser.respec.d.mts.map +1 -0
  12. package/dist/cli/{cli-arg-parser.respec.node.mjs → cli-arg-parser.respec.mjs} +1 -1
  13. package/dist/cli/cli-arg-parser.respec.mjs.map +1 -0
  14. package/dist/cli/cli-constants.d.mts +1 -0
  15. package/dist/cli/cli-constants.d.mts.map +1 -1
  16. package/dist/cli/cli-constants.mjs +1 -0
  17. package/dist/cli/cli-constants.mjs.map +1 -1
  18. package/dist/cli/{cli-e2e.respec.node.d.mts → cli-e2e.respec.d.mts} +1 -1
  19. package/dist/cli/cli-e2e.respec.d.mts.map +1 -0
  20. package/dist/cli/{cli-e2e.respec.node.mjs → cli-e2e.respec.mjs} +1 -1
  21. package/dist/cli/cli-e2e.respec.mjs.map +1 -0
  22. package/dist/cli/commands/add-cmd.d.mts.map +1 -1
  23. package/dist/cli/commands/add-cmd.mjs +2 -2
  24. package/dist/cli/commands/add-cmd.mjs.map +1 -1
  25. package/dist/cli/commands/{branch-checkout-cmd.respec.node.d.mts → branch-checkout-cmd.respec.d.mts} +1 -1
  26. package/dist/cli/commands/branch-checkout-cmd.respec.d.mts.map +1 -0
  27. package/dist/cli/commands/{branch-checkout-cmd.respec.node.mjs → branch-checkout-cmd.respec.mjs} +103 -9
  28. package/dist/cli/commands/branch-checkout-cmd.respec.mjs.map +1 -0
  29. package/dist/cli/commands/branch-cmd.d.mts.map +1 -1
  30. package/dist/cli/commands/branch-cmd.mjs +2 -2
  31. package/dist/cli/commands/branch-cmd.mjs.map +1 -1
  32. package/dist/cli/commands/checkout-cmd.d.mts.map +1 -1
  33. package/dist/cli/commands/checkout-cmd.mjs +15 -7
  34. package/dist/cli/commands/checkout-cmd.mjs.map +1 -1
  35. package/dist/cli/commands/commit-cmd.d.mts.map +1 -1
  36. package/dist/cli/commands/commit-cmd.mjs +2 -2
  37. package/dist/cli/commands/commit-cmd.mjs.map +1 -1
  38. package/dist/cli/commands/{commit-log-cmd.respec.node.d.mts → commit-log-cmd.respec.d.mts} +1 -1
  39. package/dist/cli/commands/commit-log-cmd.respec.d.mts.map +1 -0
  40. package/dist/cli/commands/{commit-log-cmd.respec.node.mjs → commit-log-cmd.respec.mjs} +1 -1
  41. package/dist/cli/commands/commit-log-cmd.respec.mjs.map +1 -0
  42. package/dist/cli/commands/{init-cmd.respec.node.d.mts → init-cmd.respec.d.mts} +1 -1
  43. package/dist/cli/commands/init-cmd.respec.d.mts.map +1 -0
  44. package/dist/cli/commands/{init-cmd.respec.node.mjs → init-cmd.respec.mjs} +1 -1
  45. package/dist/cli/commands/init-cmd.respec.mjs.map +1 -0
  46. package/dist/cli/commands/merge-cmd.d.mts +23 -0
  47. package/dist/cli/commands/merge-cmd.d.mts.map +1 -0
  48. package/dist/cli/commands/merge-cmd.mjs +224 -0
  49. package/dist/cli/commands/merge-cmd.mjs.map +1 -0
  50. package/dist/cli/commands/{reset-restore-cmd.respec.node.d.mts → reset-restore-cmd.respec.d.mts} +1 -1
  51. package/dist/cli/commands/reset-restore-cmd.respec.d.mts.map +1 -0
  52. package/dist/cli/commands/{reset-restore-cmd.respec.node.mjs → reset-restore-cmd.respec.mjs} +1 -1
  53. package/dist/cli/commands/reset-restore-cmd.respec.mjs.map +1 -0
  54. package/dist/cli/commands/{status-add-cmd.respec.node.d.mts → status-add-cmd.respec.d.mts} +1 -1
  55. package/dist/cli/commands/status-add-cmd.respec.d.mts.map +1 -0
  56. package/dist/cli/commands/{status-add-cmd.respec.node.mjs → status-add-cmd.respec.mjs} +1 -1
  57. package/dist/cli/commands/status-add-cmd.respec.mjs.map +1 -0
  58. package/dist/cli/vcs-cli-main.d.mts.map +1 -1
  59. package/dist/cli/vcs-cli-main.mjs +7 -0
  60. package/dist/cli/vcs-cli-main.mjs.map +1 -1
  61. package/dist/engine/branch/{branch-space.respec.node.d.mts → branch-space.respec.d.mts} +1 -1
  62. package/dist/engine/branch/branch-space.respec.d.mts.map +1 -0
  63. package/dist/engine/branch/{branch-space.respec.node.mjs → branch-space.respec.mjs} +1 -1
  64. package/dist/engine/branch/branch-space.respec.mjs.map +1 -0
  65. package/dist/engine/diff/{line-diff-helper.respec.node.d.mts → line-diff-helper.respec.d.mts} +1 -1
  66. package/dist/engine/diff/line-diff-helper.respec.d.mts.map +1 -0
  67. package/dist/engine/diff/{line-diff-helper.respec.node.mjs → line-diff-helper.respec.mjs} +1 -1
  68. package/dist/engine/diff/line-diff-helper.respec.mjs.map +1 -0
  69. package/dist/engine/filter/{glob-helper.respec.node.d.mts → glob-helper.respec.d.mts} +1 -1
  70. package/dist/engine/filter/glob-helper.respec.d.mts.map +1 -0
  71. package/dist/engine/filter/{glob-helper.respec.node.mjs → glob-helper.respec.mjs} +1 -1
  72. package/dist/engine/filter/glob-helper.respec.mjs.map +1 -0
  73. package/dist/engine/item/{item.respec.node.d.mts → item.respec.d.mts} +1 -1
  74. package/dist/engine/item/item.respec.d.mts.map +1 -0
  75. package/dist/engine/item/{item.respec.node.mjs → item.respec.mjs} +1 -1
  76. package/dist/engine/item/item.respec.mjs.map +1 -0
  77. package/dist/engine/merge/b2tfs-3way-merge-strategy.d.mts +38 -0
  78. package/dist/engine/merge/b2tfs-3way-merge-strategy.d.mts.map +1 -0
  79. package/dist/engine/merge/b2tfs-3way-merge-strategy.mjs +593 -0
  80. package/dist/engine/merge/b2tfs-3way-merge-strategy.mjs.map +1 -0
  81. package/dist/engine/merge/b2tfs-merge-strategy.respec.d.mts +7 -0
  82. package/dist/engine/merge/b2tfs-merge-strategy.respec.d.mts.map +1 -0
  83. package/dist/engine/merge/b2tfs-merge-strategy.respec.mjs +335 -0
  84. package/dist/engine/merge/b2tfs-merge-strategy.respec.mjs.map +1 -0
  85. package/dist/respec-gib.node.mjs +1 -1
  86. package/dist/respec-gib.node.mjs.map +1 -1
  87. package/dist/{vcs-gib.respec.node.d.mts → vcs-gib.respec.d.mts} +1 -1
  88. package/dist/vcs-gib.respec.d.mts.map +1 -0
  89. package/dist/{vcs-gib.respec.node.mjs → vcs-gib.respec.mjs} +1 -1
  90. package/dist/vcs-gib.respec.mjs.map +1 -0
  91. package/package.json +3 -3
  92. package/src/AUTO-GENERATED-version.mts +1 -1
  93. package/src/bootstrap/node-bootstrap.mts +4 -0
  94. package/src/cli/cli-constants.mts +1 -0
  95. package/src/cli/commands/add-cmd.mts +5 -3
  96. package/src/cli/commands/{branch-checkout-cmd.respec.node.mts → branch-checkout-cmd.respec.mts} +112 -9
  97. package/src/cli/commands/branch-cmd.mts +3 -2
  98. package/src/cli/commands/checkout-cmd.mts +23 -9
  99. package/src/cli/commands/commit-cmd.mts +5 -3
  100. package/src/cli/commands/merge-cmd.mts +289 -0
  101. package/src/cli/vcs-cli-main.mts +7 -0
  102. package/src/engine/merge/b2tfs-3way-merge-strategy.mts +725 -0
  103. package/src/engine/merge/b2tfs-merge-strategy.respec.mts +438 -0
  104. package/src/respec-gib.node.mts +2 -2
  105. package/tsconfig.tsbuildinfo +1 -1
  106. package/dist/bootstrap/node-bootstrap.respec.node.d.mts.map +0 -1
  107. package/dist/bootstrap/node-bootstrap.respec.node.mjs.map +0 -1
  108. package/dist/cli/cli-arg-parser.respec.node.d.mts.map +0 -1
  109. package/dist/cli/cli-arg-parser.respec.node.mjs.map +0 -1
  110. package/dist/cli/cli-e2e.respec.node.d.mts.map +0 -1
  111. package/dist/cli/cli-e2e.respec.node.mjs.map +0 -1
  112. package/dist/cli/commands/branch-checkout-cmd.respec.node.d.mts.map +0 -1
  113. package/dist/cli/commands/branch-checkout-cmd.respec.node.mjs.map +0 -1
  114. package/dist/cli/commands/commit-log-cmd.respec.node.d.mts.map +0 -1
  115. package/dist/cli/commands/commit-log-cmd.respec.node.mjs.map +0 -1
  116. package/dist/cli/commands/init-cmd.respec.node.d.mts.map +0 -1
  117. package/dist/cli/commands/init-cmd.respec.node.mjs.map +0 -1
  118. package/dist/cli/commands/reset-restore-cmd.respec.node.d.mts.map +0 -1
  119. package/dist/cli/commands/reset-restore-cmd.respec.node.mjs.map +0 -1
  120. package/dist/cli/commands/status-add-cmd.respec.node.d.mts.map +0 -1
  121. package/dist/cli/commands/status-add-cmd.respec.node.mjs.map +0 -1
  122. package/dist/engine/branch/branch-space.respec.node.d.mts.map +0 -1
  123. package/dist/engine/branch/branch-space.respec.node.mjs.map +0 -1
  124. package/dist/engine/diff/line-diff-helper.respec.node.d.mts.map +0 -1
  125. package/dist/engine/diff/line-diff-helper.respec.node.mjs.map +0 -1
  126. package/dist/engine/filter/glob-helper.respec.node.d.mts.map +0 -1
  127. package/dist/engine/filter/glob-helper.respec.node.mjs.map +0 -1
  128. package/dist/engine/item/item.respec.node.d.mts.map +0 -1
  129. package/dist/engine/item/item.respec.node.mjs.map +0 -1
  130. package/dist/vcs-gib.respec.node.d.mts.map +0 -1
  131. package/dist/vcs-gib.respec.node.mjs.map +0 -1
  132. /package/src/bootstrap/{node-bootstrap.respec.node.mts → node-bootstrap.respec.mts} +0 -0
  133. /package/src/cli/{cli-arg-parser.respec.node.mts → cli-arg-parser.respec.mts} +0 -0
  134. /package/src/cli/{cli-e2e.respec.node.mts → cli-e2e.respec.mts} +0 -0
  135. /package/src/cli/commands/{commit-log-cmd.respec.node.mts → commit-log-cmd.respec.mts} +0 -0
  136. /package/src/cli/commands/{init-cmd.respec.node.mts → init-cmd.respec.mts} +0 -0
  137. /package/src/cli/commands/{reset-restore-cmd.respec.node.mts → reset-restore-cmd.respec.mts} +0 -0
  138. /package/src/cli/commands/{status-add-cmd.respec.node.mts → status-add-cmd.respec.mts} +0 -0
  139. /package/src/engine/branch/{branch-space.respec.node.mts → branch-space.respec.mts} +0 -0
  140. /package/src/engine/diff/{line-diff-helper.respec.node.mts → line-diff-helper.respec.mts} +0 -0
  141. /package/src/engine/filter/{glob-helper.respec.node.mts → glob-helper.respec.mts} +0 -0
  142. /package/src/engine/item/{item.respec.node.mts → item.respec.mts} +0 -0
  143. /package/src/{vcs-gib.respec.node.mts → vcs-gib.respec.mts} +0 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @module branch-checkout-cmd.respec
3
- *
3
+ *
4
4
  * Unit tests for `vcs branch` and `vcs checkout` command handlers & worktree projections.
5
5
  */
6
6
 
@@ -8,7 +8,8 @@ import path from 'node:path';
8
8
  import fs from 'node:fs/promises';
9
9
 
10
10
  import {
11
- respecfully, ifWe, iReckon
11
+ respecfully, ifWe, iReckon,
12
+ ifWeMight
12
13
  } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
13
14
 
14
15
  import { executeInitCmd } from './init-cmd.mjs';
@@ -16,13 +17,14 @@ import { executeAddCmd } from './add-cmd.mjs';
16
17
  import { executeCommitCmd } from './commit-cmd.mjs';
17
18
  import { executeBranchCmd } from './branch-cmd.mjs';
18
19
  import { executeCheckoutCmd } from './checkout-cmd.mjs';
20
+ import { executeMergeCmd } from './merge-cmd.mjs';
19
21
  import { executeStatusCmd } from './status-cmd.mjs';
20
22
 
21
23
  const lc = `[apps/vcs-gib/src/cli/commands/branch-checkout-cmd.respec.node.mts]`;
22
24
 
23
- await respecfully(lc, `vcs branch and vcs checkout Command Handlers`, async () => {
25
+ await respecfully(lc, `vcs branch, checkout, and merge Command Handlers`, async () => {
24
26
 
25
- await ifWe(lc, `lists default branch main for new repository`, async () => {
27
+ await ifWeMight(lc, `lists default branch main for new repository`, async () => {
26
28
  const testDir = path.join(process.cwd(), '.test_branch_checkout_1');
27
29
  const vcsFolderName = '.vcsgib_test_bc_1';
28
30
  try {
@@ -46,7 +48,7 @@ await respecfully(lc, `vcs branch and vcs checkout Command Handlers`, async () =
46
48
  }
47
49
  });
48
50
 
49
- await ifWe(lc, `creates new branch from active branch root item and rejects duplicates`, async () => {
51
+ await ifWeMight(lc, `creates new branch from active branch root item and rejects duplicates`, async () => {
50
52
  const testDir = path.join(process.cwd(), '.test_branch_checkout_2');
51
53
  const vcsFolderName = '.vcsgib_test_bc_2';
52
54
  try {
@@ -110,7 +112,7 @@ await respecfully(lc, `vcs branch and vcs checkout Command Handlers`, async () =
110
112
  }
111
113
  });
112
114
 
113
- await ifWe(lc, `switches active branch with checkout and projects branch snapshot onto worktree subfolder`, async () => {
115
+ await ifWeMight(lc, `switches active branch with checkout and projects branch snapshot onto worktree subfolder`, async () => {
114
116
  const testDir = path.join(process.cwd(), '.test_branch_checkout_3');
115
117
  const vcsFolderName = '.vcsgib_test_bc_3';
116
118
  try {
@@ -236,7 +238,7 @@ await respecfully(lc, `vcs branch and vcs checkout Command Handlers`, async () =
236
238
  }
237
239
  });
238
240
 
239
- await ifWe(lc, `rejects checkout when branch does not exist or branchName missing`, async () => {
241
+ await ifWeMight(lc, `rejects checkout when branch does not exist or branchName missing`, async () => {
240
242
  const testDir = path.join(process.cwd(), '.test_branch_checkout_4');
241
243
  const vcsFolderName = '.vcsgib_test_bc_4';
242
244
  try {
@@ -278,7 +280,7 @@ await respecfully(lc, `vcs branch and vcs checkout Command Handlers`, async () =
278
280
  }
279
281
  });
280
282
 
281
- await ifWe(lc, `lists branches silently on secondary branch without error noise`, async () => {
283
+ await ifWeMight(lc, `lists branches silently on secondary branch without error noise`, async () => {
282
284
  const testDir = path.join(process.cwd(), '.test_branch_checkout_secondary');
283
285
  const vcsFolderName = '.vcsgib_test_bc_sec';
284
286
  try {
@@ -320,7 +322,7 @@ await respecfully(lc, `vcs branch and vcs checkout Command Handlers`, async () =
320
322
  }
321
323
  });
322
324
 
323
- await ifWe(lc, `branches single file-root item and projects into file-branch worktree on checkout`, async () => {
325
+ await ifWeMight(lc, `branches single file-root item and projects into file-branch worktree on checkout`, async () => {
324
326
  const testDir = path.join(process.cwd(), '.test_branch_checkout_file_root');
325
327
  const vcsFolderName = '.vcsgib_test_bc_file';
326
328
  try {
@@ -398,4 +400,105 @@ await respecfully(lc, `vcs branch and vcs checkout Command Handlers`, async () =
398
400
  await fs.rm(testDir, { recursive: true, force: true });
399
401
  }
400
402
  });
403
+
404
+ await ifWeMight(lc, `performs vcs merge end-to-end between main and secondary branch`, async () => {
405
+ const testDir = path.join(process.cwd(), '.test_vcs_merge_e2e');
406
+ const vcsFolderName = '.vcsgib_test_merge_e2e';
407
+ try {
408
+ await executeInitCmd({
409
+ targetDir: testDir,
410
+ vcsFolderName,
411
+ quiet: true,
412
+ });
413
+
414
+ // 1. Initial commit on main
415
+ const initialContent = "line 1\nline 2\nline 3";
416
+ await fs.writeFile(path.join(testDir, 'app.ts'), initialContent, 'utf8');
417
+ await executeAddCmd({
418
+ paths: [path.join(testDir, 'app.ts')],
419
+ targetDir: testDir,
420
+ vcsFolderName,
421
+ quiet: true,
422
+ });
423
+ await executeCommitCmd({
424
+ message: 'Initial commit on main',
425
+ targetDir: testDir,
426
+ vcsFolderName,
427
+ quiet: true,
428
+ });
429
+
430
+ // 2. Create secondary branch feature-merge
431
+ await executeBranchCmd({
432
+ branchName: 'feature-merge',
433
+ targetDir: testDir,
434
+ vcsFolderName,
435
+ quiet: true,
436
+ });
437
+
438
+ // 3. Checkout feature-merge branch and make a commit (modify line 3)
439
+ await executeCheckoutCmd({
440
+ branchName: 'feature-merge',
441
+ targetDir: testDir,
442
+ vcsFolderName,
443
+ quiet: true,
444
+ });
445
+ const featureWorktree = path.join(testDir, 'b', 'feature-merge');
446
+ const featureContent = "line 1\nline 2\nline 3 modified by secondary";
447
+ await fs.writeFile(path.join(featureWorktree, 'app.ts'), featureContent, 'utf8');
448
+ await executeAddCmd({
449
+ paths: [path.join(featureWorktree, 'app.ts')],
450
+ targetDir: testDir,
451
+ vcsFolderName,
452
+ quiet: true,
453
+ });
454
+ await executeCommitCmd({
455
+ message: 'Update app.ts on feature-merge',
456
+ targetDir: testDir,
457
+ vcsFolderName,
458
+ quiet: true,
459
+ });
460
+
461
+ // 4. Checkout main branch and make a commit (modify line 1)
462
+ await executeCheckoutCmd({
463
+ branchName: 'main',
464
+ targetDir: testDir,
465
+ vcsFolderName,
466
+ quiet: true,
467
+ });
468
+ const mainContent = "line 1 modified by main\nline 2\nline 3";
469
+ await fs.writeFile(path.join(testDir, 'app.ts'), mainContent, 'utf8');
470
+ await executeAddCmd({
471
+ paths: [path.join(testDir, 'app.ts')],
472
+ targetDir: testDir,
473
+ vcsFolderName,
474
+ quiet: true,
475
+ });
476
+ await executeCommitCmd({
477
+ message: 'Update app.ts on main',
478
+ targetDir: testDir,
479
+ vcsFolderName,
480
+ quiet: true,
481
+ });
482
+
483
+ // 5. Execute vcs merge feature-merge into main
484
+ const mergeRes = await executeMergeCmd({
485
+ targetBranchName: 'feature-merge',
486
+ targetDir: testDir,
487
+ vcsFolderName,
488
+ quiet: true,
489
+ });
490
+
491
+ iReckon(lc, mergeRes.activeBranch).willEqual('main');
492
+ iReckon(lc, mergeRes.targetBranch).willEqual('feature-merge');
493
+
494
+ // 6. Verify projected file on physical worktree disk contains merged lines
495
+ const mergedAppTs = await fs.readFile(path.join(testDir, 'app.ts'), 'utf8');
496
+ iReckon(lc, mergedAppTs).includes('line 1 modified by main');
497
+ iReckon(lc, mergedAppTs).includes('line 2');
498
+ iReckon(lc, mergedAppTs).includes('line 3 modified by secondary');
499
+ } finally {
500
+ await fs.rm(testDir, { recursive: true, force: true });
501
+ }
502
+ });
401
503
  });
504
+
@@ -124,7 +124,7 @@ export async function executeBranchCmd({
124
124
  const branches: BranchInfo[] = [];
125
125
  for (const [name, bNode] of branchMap.entries()) {
126
126
  const addr = getIbGibAddr({ ibGib: bNode });
127
- const rootItemAddr = bNode.rel8ns?.b2tfs_item?.at(0);
127
+ const rootItemAddr = bNode.rel8ns?.b2tfs_item?.at(-1);
128
128
  const isActive = name === activeBranchName;
129
129
  branches.push({
130
130
  name,
@@ -142,6 +142,7 @@ export async function executeBranchCmd({
142
142
  }
143
143
 
144
144
  return { branches };
145
+
145
146
  }
146
147
 
147
148
  // CREATE BRANCH
@@ -151,7 +152,7 @@ export async function executeBranchCmd({
151
152
 
152
153
  // Find active branch root item to fork from
153
154
  const activeBranchIbGib = branchMap.get(activeBranchName) || branchIbGibs.at(0);
154
- const rootItemAddr = activeBranchIbGib?.rel8ns?.b2tfs_item?.at(0);
155
+ const rootItemAddr = activeBranchIbGib?.rel8ns?.b2tfs_item?.at(-1);
155
156
 
156
157
  if (!rootItemAddr) {
157
158
  throw new Error(`Cannot find active branch root item to fork branch from (E: 8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c)`);
@@ -11,7 +11,8 @@ import fs from 'node:fs/promises';
11
11
  import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
12
12
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
13
13
  import { getIbGibsFromCache_fallbackToSpaces } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
14
- import { putInSpace } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
14
+ import { putInSpace, getLatestAddrs } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
15
+
15
16
 
16
17
  import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
17
18
  import { B2tFSBranchIbGib_V1 } from '../../engine/branch/branch-types.mjs';
@@ -100,21 +101,15 @@ export async function executeCheckoutCmd({
100
101
  }
101
102
 
102
103
  // Find target branch node
103
- const targetBranchIbGib = branchIbGibs.find(
104
+ const targetBranchIbGib = branchIbGibs.findLast(
104
105
  (b) => b?.data?.name === trimmedBranchName,
105
106
  );
106
107
 
108
+
107
109
  if (!targetBranchIbGib) {
108
110
  throw new Error(`Branch '${trimmedBranchName}' does not exist (E: 7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a)`);
109
111
  }
110
112
 
111
- const targetBranchAddr = getIbGibAddr({ ibGib: targetBranchIbGib });
112
- const rootItemAddr = targetBranchIbGib.rel8ns?.b2tfs_item?.at(0);
113
-
114
- if (!rootItemAddr) {
115
- throw new Error(`Branch '${trimmedBranchName}' does not have a valid root item address (E: 8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b)`);
116
- }
117
-
118
113
  // 3. Resolve target branch space b2s_<branchName>
119
114
  const targetBranchSpaceRes = await getOrCreateBranchSpace({
120
115
  metaspace,
@@ -123,6 +118,25 @@ export async function executeCheckoutCmd({
123
118
  });
124
119
  const targetBranchSpace = targetBranchSpaceRes.space;
125
120
 
121
+ const targetBranchAddr = getIbGibAddr({ ibGib: targetBranchIbGib });
122
+ const targetBranchTjpAddr = targetBranchIbGib.rel8ns?.tjp?.at(0) || targetBranchAddr;
123
+ const latestTargetBranchRes = await getLatestAddrs({ space: targetBranchSpace, addrs: [targetBranchTjpAddr] });
124
+ const latestTargetBranchAddr = (latestTargetBranchRes?.data as any)?.latestAddrs?.[0] || targetBranchAddr;
125
+
126
+
127
+ const latestTargetBranchIbGib = (await getIbGibWithMetaspaceFallback({
128
+ addr: latestTargetBranchAddr,
129
+ space: targetBranchSpace,
130
+ metaspace,
131
+ })) as B2tFSBranchIbGib_V1 || targetBranchIbGib;
132
+
133
+ const rootItemAddr = latestTargetBranchIbGib.rel8ns?.b2tfs_item?.at(-1);
134
+
135
+ if (!rootItemAddr) {
136
+ throw new Error(`Branch '${trimmedBranchName}' does not have a valid root item address (E: 8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b)`);
137
+ }
138
+
139
+
126
140
  await putInSpace({ space: targetBranchSpace, ibGib: targetBranchIbGib });
127
141
 
128
142
  // Update activeBranch in B2tFSIndexIbGib_V1 in targetBranchSpace and localSpace
@@ -129,16 +129,18 @@ export async function executeCommitCmd({
129
129
  }
130
130
  }
131
131
 
132
- currentBranchIbGib = branchIbGibs.find(
132
+ currentBranchIbGib = branchIbGibs.findLast(
133
133
  (b) => (b as B2tFSBranchIbGib_V1)?.data?.name === branchName,
134
- ) as B2tFSBranchIbGib_V1 || (branchIbGibs.at(0) as B2tFSBranchIbGib_V1);
134
+ ) as B2tFSBranchIbGib_V1 || (branchIbGibs.at(-1) as B2tFSBranchIbGib_V1);
135
+
135
136
 
136
137
  if (currentBranchIbGib) {
137
138
  currentBranchAddr = getIbGibAddr({ ibGib: currentBranchIbGib });
138
139
  }
139
140
  }
140
141
 
141
- const currentRootAddr = currentBranchIbGib?.rel8ns?.b2tfs_item?.at(0);
142
+ const currentRootAddr = currentBranchIbGib?.rel8ns?.b2tfs_item?.at(-1);
143
+
142
144
 
143
145
  // 1. Create CommentIbGib_V1 commit comment node in activeSpace
144
146
  const commentRes = await createCommentIbGib({
@@ -0,0 +1,289 @@
1
+ /**
2
+ * @module merge-cmd
3
+ *
4
+ * Command handler for `vcs merge <branchName>`.
5
+ * Merges target branch into active branch using SyncSagaCoordinator sync and 3-way graft merge strategy.
6
+ * Projects merged branch snapshot onto active branch physical worktree path.
7
+ */
8
+
9
+ import path from 'node:path';
10
+ import fs from 'node:fs/promises';
11
+
12
+ import { clone, extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
13
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
14
+ import { rel8 } from '@ibgib/ts-gib/dist/V1/transforms/rel8.mjs';
15
+ import { putInSpace, getLatestAddrs, getFromSpace, registerNewIbGib } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
16
+ import { SyncSagaCoordinator } from '@ibgib/core-gib/dist/sync/sync-saga-coordinator.mjs';
17
+ import { SyncPeerInnerspace_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
18
+ import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
19
+ import { SyncConflictStrategy } from '@ibgib/core-gib/dist/sync/sync-constants.mjs';
20
+ import { GraftPreference } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-types.mjs';
21
+
22
+
23
+ import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
24
+ import { B2tFSBranchIbGib_V1 } from '../../engine/branch/branch-types.mjs';
25
+ import { getOrCreateBranchSpace } from '../../engine/branch/branch-space-helper.mjs';
26
+ import { B2tFSItemIbGib_V1, B2TFS_CHILD_ITEM_REL8N_NAME } from '../../engine/item/item-types.mjs';
27
+ import { writeB2tFSItemToFS, getIbGibWithMetaspaceFallback } from '../../engine/item/item-helper.mjs';
28
+ import { getB2tFSIndexIbGib, updateB2tFSIndexIbGib } from '../../engine/index/index-helper.mjs';
29
+ import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
30
+
31
+ const lcFile = `[merge-cmd]`;
32
+ const logalot = GLOBAL_LOG_A_LOT;
33
+
34
+ export interface MergeCmdOpts {
35
+ targetBranchName?: string;
36
+ targetDir?: string;
37
+ vcsFolderName?: string;
38
+ quiet?: boolean;
39
+ }
40
+
41
+ export interface MergeCmdResult {
42
+ activeBranch: string;
43
+ targetBranch: string;
44
+ activeSpaceId: string;
45
+ targetSpaceId: string;
46
+ mergedBranchAddr?: string;
47
+ worktreePath: string;
48
+ }
49
+
50
+ export async function executeMergeCmd({
51
+ targetBranchName,
52
+ targetDir = process.cwd(),
53
+ vcsFolderName = '.vcsgib',
54
+ quiet = false,
55
+ }: MergeCmdOpts = {}): Promise<MergeCmdResult> {
56
+ const lc = `${lcFile}[${executeMergeCmd.name}]`;
57
+ try {
58
+ if (logalot) {
59
+ console.log(`${lc} starting... (I: 4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f)`);
60
+ }
61
+
62
+ const trimmedTargetBranch = targetBranchName?.trim();
63
+ if (!trimmedTargetBranch) {
64
+ throw new Error(`Branch name is required for merge (E: 5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a)`);
65
+ }
66
+
67
+ const absTargetDir = path.resolve(targetDir);
68
+
69
+ const { metaspace, localSpace, repoRoot, vcsPath } = await bootstrapVcsMetaspace({
70
+ repoPath: absTargetDir,
71
+ vcsFolderName,
72
+ });
73
+
74
+ // Verify repo exists
75
+ try {
76
+ await fs.access(vcsPath);
77
+ } catch {
78
+ throw new Error(`Not an ibgib repository (directory ${vcsPath} does not exist) (E: 6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b)`);
79
+ }
80
+
81
+ // 1. Fetch authoritative repository index from localSpace
82
+ const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, vcsPath });
83
+ if (!indexIbGib) {
84
+ throw new Error(`Repository index B2tFSIndexIbGib_V1 not found in localSpace (E: 7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c)`);
85
+ }
86
+
87
+ const activeBranchName = indexIbGib.data?.activeBranch || 'main';
88
+
89
+ if (activeBranchName === trimmedTargetBranch) {
90
+ throw new Error(`Cannot merge branch '${trimmedTargetBranch}' into itself (E: 8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d)`);
91
+ }
92
+
93
+ // 2. Fetch all registered branch ibgibs
94
+ const branchAddrs = [
95
+ ...(indexIbGib.rel8ns?.branch || []),
96
+ ...(indexIbGib.rel8ns?.branch_tjp || []),
97
+ ];
98
+
99
+ let branchIbGibs: B2tFSBranchIbGib_V1[] = [];
100
+ if (branchAddrs.length > 0) {
101
+ for (const bAddr of branchAddrs) {
102
+ const bIbGib = await getIbGibWithMetaspaceFallback({
103
+ addr: bAddr,
104
+ space: localSpace,
105
+ metaspace,
106
+ });
107
+ if (bIbGib) {
108
+ branchIbGibs.push(bIbGib as B2tFSBranchIbGib_V1);
109
+ }
110
+ }
111
+ }
112
+
113
+ // Resolve active branch node & target branch node
114
+ const activeBranchIbGib = branchIbGibs.findLast(
115
+ (b) => b?.data?.name === activeBranchName,
116
+ );
117
+ if (!activeBranchIbGib) {
118
+ throw new Error(`Active branch '${activeBranchName}' not found in repository (E: 9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e)`);
119
+ }
120
+
121
+ const targetBranchIbGib = branchIbGibs.findLast(
122
+ (b) => b?.data?.name === trimmedTargetBranch,
123
+ );
124
+
125
+ if (!targetBranchIbGib) {
126
+ throw new Error(`Branch '${trimmedTargetBranch}' does not exist (E: 0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f)`);
127
+ }
128
+
129
+ // 3. Resolve active branch space & target branch space
130
+ const activeSpaceRes = await getOrCreateBranchSpace({
131
+ metaspace,
132
+ vcsPath,
133
+ branchName: activeBranchName,
134
+ });
135
+ const activeSpace = activeSpaceRes.space;
136
+
137
+ const targetSpaceRes = await getOrCreateBranchSpace({
138
+ metaspace,
139
+ vcsPath,
140
+ branchName: trimmedTargetBranch,
141
+ });
142
+ const targetSpace = targetSpaceRes.space;
143
+
144
+ await putInSpace({ space: activeSpace, ibGib: activeBranchIbGib });
145
+ await putInSpace({ space: targetSpace, ibGib: targetBranchIbGib });
146
+
147
+ // Resolve latest branch tip inside targetSpace (in case commits occurred on target branch)
148
+ const targetBranchTjpAddr = targetBranchIbGib.rel8ns?.tjp?.at(0) || getIbGibAddr({ ibGib: targetBranchIbGib });
149
+ const latestTargetBranchRes = await getLatestAddrs({ space: targetSpace, addrs: [targetBranchTjpAddr] });
150
+ const latestTargetBranchAddr = (latestTargetBranchRes?.data as any)?.latestAddrs?.[0] || getIbGibAddr({ ibGib: targetBranchIbGib });
151
+
152
+ const latestTargetBranchIbGib = (await getIbGibWithMetaspaceFallback({
153
+ addr: latestTargetBranchAddr,
154
+ space: targetSpace,
155
+ metaspace,
156
+ })) as B2tFSBranchIbGib_V1 || targetBranchIbGib;
157
+
158
+ const targetRootAddr = latestTargetBranchIbGib.rel8ns?.b2tfs_item?.at(-1);
159
+
160
+ if (!targetRootAddr) {
161
+ throw new Error(`Target branch '${trimmedTargetBranch}' has no valid root item address (E: 3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e)`);
162
+ }
163
+ const targetRootItem = await getIbGibWithMetaspaceFallback({
164
+ addr: targetRootAddr,
165
+ space: targetSpace,
166
+ metaspace,
167
+ });
168
+ if (!targetRootItem) {
169
+ throw new Error(`Target root item '${targetRootAddr}' not found in target space (E: 4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f)`);
170
+ }
171
+
172
+ // 4. Setup SyncPeerInnerspace and SyncSagaCoordinator syncing target branch space into active branch space
173
+ const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
174
+ await peer.initialized;
175
+ await peer.initializeOpts({
176
+ localMetaspace: metaspace,
177
+ localSpace: targetSpace,
178
+ receiverSpace: activeSpace,
179
+ receiverCoordinator: new SyncSagaCoordinator(),
180
+ receiverMetaspace: metaspace,
181
+ });
182
+
183
+ const senderCoordinator = new SyncSagaCoordinator();
184
+ const syncSaga = await senderCoordinator.sync({
185
+ peer,
186
+ localSpace: targetSpace,
187
+ metaspace,
188
+ domainIbGibs: [targetRootItem],
189
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
190
+ graftPreference: GraftPreference.receiver,
191
+ });
192
+
193
+
194
+
195
+
196
+ await syncSaga.done;
197
+
198
+ // 5. Query activeSpace for updated active branch node & root item evolved natively by sync graft strategy
199
+ const activeBranchTjpAddr = activeBranchIbGib.rel8ns?.tjp?.at(0) || getIbGibAddr({ ibGib: activeBranchIbGib });
200
+ const latestBranchRes = await getLatestAddrs({ space: activeSpace, addrs: [activeBranchTjpAddr] });
201
+ const mergedBranchAddr = (latestBranchRes?.data as any)?.latestAddrs?.[0] || getIbGibAddr({ ibGib: activeBranchIbGib });
202
+
203
+ const mergedBranchIbGib = (await getIbGibWithMetaspaceFallback({
204
+ addr: mergedBranchAddr,
205
+ space: activeSpace,
206
+ metaspace,
207
+ })) as B2tFSBranchIbGib_V1 || activeBranchIbGib;
208
+
209
+ const mergedRootItemAddr = mergedBranchIbGib.rel8ns?.b2tfs_item?.at(-1);
210
+
211
+ // Update indexIbGib to reference new merged branch node
212
+ await updateB2tFSIndexIbGib({
213
+ space: activeSpace,
214
+ metaspace,
215
+ indexIbGib,
216
+ rel8nsToUpdate: {
217
+ branch: [mergedBranchAddr],
218
+ },
219
+ vcsPath,
220
+ });
221
+
222
+
223
+ // 6. Project merged branch snapshot files onto physical worktree disk
224
+ const isDefaultBranch = activeBranchName === 'main';
225
+ const worktreePath = isDefaultBranch
226
+ ? repoRoot
227
+ : path.join(repoRoot, 'b', activeBranchName);
228
+
229
+ await fs.mkdir(worktreePath, { recursive: true });
230
+
231
+ if (mergedRootItemAddr) {
232
+ const mergedRootItem = await getIbGibWithMetaspaceFallback({
233
+ addr: mergedRootItemAddr,
234
+ space: activeSpace,
235
+ metaspace,
236
+ }) as B2tFSItemIbGib_V1;
237
+
238
+ if (mergedRootItem) {
239
+ if (mergedRootItem.data?.fsType === 'file') {
240
+ await writeB2tFSItemToFS({
241
+ item: mergedRootItem,
242
+ destPath: worktreePath,
243
+ space: activeSpace,
244
+ metaspace,
245
+ });
246
+ } else {
247
+ const childAddrs = mergedRootItem.rel8ns?.[B2TFS_CHILD_ITEM_REL8N_NAME] || [];
248
+ for (const childAddr of childAddrs) {
249
+ const childItem = await getIbGibWithMetaspaceFallback({
250
+ addr: childAddr,
251
+ space: activeSpace,
252
+ metaspace,
253
+ }) as B2tFSItemIbGib_V1;
254
+
255
+ if (childItem) {
256
+ await writeB2tFSItemToFS({
257
+ item: childItem,
258
+ destPath: worktreePath,
259
+ space: activeSpace,
260
+ metaspace,
261
+ });
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ if (!quiet) {
269
+ console.log(`Successfully merged branch '${trimmedTargetBranch}' into '${activeBranchName}' (I: 1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a)`);
270
+ console.log(` Worktree projection: ${worktreePath}`);
271
+ }
272
+
273
+ return {
274
+ activeBranch: activeBranchName,
275
+ targetBranch: trimmedTargetBranch,
276
+ activeSpaceId: activeSpaceRes.spaceId,
277
+ targetSpaceId: targetSpaceRes.spaceId,
278
+ mergedBranchAddr: getIbGibAddr({ ibGib: mergedBranchIbGib }),
279
+ worktreePath,
280
+ };
281
+ } catch (error) {
282
+ console.error(`${lc} ${extractErrorMsg(error)} (E: 2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b)`);
283
+ throw error;
284
+ } finally {
285
+ if (logalot) {
286
+ console.log(`${lc} complete. (I: 3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c)`);
287
+ }
288
+ }
289
+ }
@@ -17,6 +17,7 @@ import { executeCommitCmd } from './commands/commit-cmd.mjs';
17
17
  import { executeLogCmd } from './commands/log-cmd.mjs';
18
18
  import { executeBranchCmd } from './commands/branch-cmd.mjs';
19
19
  import { executeCheckoutCmd } from './commands/checkout-cmd.mjs';
20
+ import { executeMergeCmd } from './commands/merge-cmd.mjs';
20
21
  import { executeResetCmd } from './commands/reset-cmd.mjs';
21
22
  import { executeRestoreCmd } from './commands/restore-cmd.mjs';
22
23
  import { AUTO_GENERATED_VERSION } from '../AUTO-GENERATED-version.mjs';
@@ -44,6 +45,7 @@ Commands:
44
45
  log Show commit logs
45
46
  branch [name] List or create branches
46
47
  checkout <branch> Switch to a specified branch and project worktree
48
+ merge <branch> Merge specified branch into active branch
47
49
  reset [path...] Unstage specified files from index (-a, --all for all)
48
50
  restore [path...] Restore working tree files or unstage (--staged)
49
51
 
@@ -120,6 +122,11 @@ export async function main({
120
122
  await executeCheckoutCmd({ branchName });
121
123
  break;
122
124
  }
125
+ case VCS_SUBCOMMANDS.MERGE: {
126
+ const targetBranchName = parsed.positionals[0] || parsed.flags.branchName;
127
+ await executeMergeCmd({ targetBranchName });
128
+ break;
129
+ }
123
130
  case VCS_SUBCOMMANDS.RESET: {
124
131
  await executeResetCmd({
125
132
  paths: parsed.positionals,