@ibgib/vcs-gib 0.0.21 → 0.0.26
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 +19 -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/bootstrap/node-bootstrap.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 +5 -2
- 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 +149 -33
- 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 +236 -38
- package/dist/cli/commands/log-cmd.mjs.map +1 -1
- package/dist/cli/commands/merge-cmd.d.mts +21 -8
- package/dist/cli/commands/merge-cmd.d.mts.map +1 -1
- package/dist/cli/commands/merge-cmd.mjs +138 -70
- package/dist/cli/commands/merge-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 +50 -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 +27 -6
- package/dist/cli/vcs-cli-main.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 +31 -1
- package/dist/engine/item/item-helper.d.mts.map +1 -1
- package/dist/engine/item/item-helper.mjs +44 -2
- package/dist/engine/item/item-helper.mjs.map +1 -1
- package/dist/engine/merge/b2tfs-3way-graft-strategy.d.mts +74 -0
- package/dist/engine/merge/b2tfs-3way-graft-strategy.d.mts.map +1 -0
- package/dist/engine/merge/{b2tfs-3way-merge-strategy.mjs → b2tfs-3way-graft-strategy.mjs} +133 -304
- package/dist/engine/merge/b2tfs-3way-graft-strategy.mjs.map +1 -0
- package/dist/engine/merge/{b2tfs-3way-merge-types.d.mts → b2tfs-3way-graft-types.d.mts} +2 -9
- package/dist/engine/merge/b2tfs-3way-graft-types.d.mts.map +1 -0
- package/dist/engine/merge/b2tfs-3way-graft-types.mjs +2 -0
- package/dist/engine/merge/b2tfs-3way-graft-types.mjs.map +1 -0
- package/dist/engine/status/status-helper.d.mts +89 -0
- package/dist/engine/status/status-helper.d.mts.map +1 -0
- package/dist/engine/status/status-helper.mjs +285 -0
- package/dist/engine/status/status-helper.mjs.map +1 -0
- package/dist/test/test-helpers.d.mts +51 -0
- package/dist/test/test-helpers.d.mts.map +1 -0
- package/dist/test/test-helpers.mjs +135 -0
- package/dist/test/test-helpers.mjs.map +1 -0
- package/package.json +4 -4
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/bootstrap/node-bootstrap.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 +5 -2
- package/src/cli/commands/branch-checkout-cmd.respec.log +8 -101
- package/src/cli/commands/branch-checkout-cmd.respec.mts +11 -10
- package/src/cli/commands/commit-cmd.mts +165 -42
- 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 +288 -41
- package/src/cli/commands/merge-cmd.mts +154 -76
- package/src/cli/commands/status-cmd.mts +57 -256
- package/src/cli/vcs-cli-main.mts +28 -7
- package/src/engine/index/index-helper.mts +3 -0
- package/src/engine/item/item-helper.mts +76 -3
- package/src/engine/merge/{b2tfs-3way-merge-strategy.mts → b2tfs-3way-graft-strategy.mts} +152 -374
- package/src/engine/merge/{b2tfs-3way-merge-strategy.respec.mts → b2tfs-3way-graft-strategy.respec.mts} +27 -122
- package/src/engine/merge/{b2tfs-3way-merge-types.mts → b2tfs-3way-graft-types.mts} +34 -42
- package/src/engine/merge/b2tfs-sync-comments.respec.mts +399 -0
- package/src/engine/status/status-helper.mts +380 -0
- package/src/test/test-helpers.mts +168 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/engine/merge/b2tfs-3way-merge-strategy.d.mts +0 -48
- package/dist/engine/merge/b2tfs-3way-merge-strategy.d.mts.map +0 -1
- package/dist/engine/merge/b2tfs-3way-merge-strategy.mjs.map +0 -1
- package/dist/engine/merge/b2tfs-3way-merge-types.d.mts.map +0 -1
- package/dist/engine/merge/b2tfs-3way-merge-types.mjs +0 -2
- package/dist/engine/merge/b2tfs-3way-merge-types.mjs.map +0 -1
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* @module commit-cmd
|
|
3
3
|
*
|
|
4
4
|
* Command handler for `vcs commit -m "message"`.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Ingests staged items and co-located .ib.*.md comment metadata files,
|
|
6
|
+
* attaches comments to corresponding file and folder b2tfs_items,
|
|
7
|
+
* attaches overall commit message to root b2tfs_item, evolves root item,
|
|
8
|
+
* advances B2tFSBranchIbGib_V1 with committedPaths, and clears index staging area.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
import fs from 'node:fs/promises';
|
|
13
|
+
import { Dirent } from 'node:fs';
|
|
12
14
|
import crypto from 'node:crypto';
|
|
13
15
|
|
|
14
16
|
import { getUUID, extractErrorMsg, delay } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
@@ -16,19 +18,18 @@ import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
|
|
|
16
18
|
import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs';
|
|
17
19
|
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
18
20
|
import { putInSpace, persistTransformResult } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
|
|
19
|
-
import { getIbGibsFromCache_fallbackToSpaces } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
20
21
|
import { createCommentIbGib } from '@ibgib/core-gib/dist/common/comment/comment-helper.mjs';
|
|
21
22
|
import { CommentIbGib_V1 } from '@ibgib/core-gib/dist/common/comment/comment-types.mjs';
|
|
22
23
|
|
|
23
24
|
import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
|
|
24
25
|
import { getOrCreateBranchSpace } from '../../engine/branch/branch-space-helper.mjs';
|
|
25
|
-
import { getB2tFSItemIb } from '../../engine/item/item-atom-helper.mjs';
|
|
26
26
|
import { B2tFSItemIbGib_V1, B2TFS_CHILD_ITEM_REL8N_NAME } from '../../engine/item/item-types.mjs';
|
|
27
27
|
import { getIbGibWithMetaspaceFallback, createB2tFSItemIbGib, evolveB2tFSItemIbGib } from '../../engine/item/item-helper.mjs';
|
|
28
28
|
import { getB2tFSBranchSpaceName } from '../../engine/branch/branch-helper.mjs';
|
|
29
29
|
import { B2tFSBranchIbGib_V1 } from '../../engine/branch/branch-types.mjs';
|
|
30
30
|
import { getB2tFSIndexIbGib, updateB2tFSIndexIbGib, getSpaceQualifiedIbGibAddr } from '../../engine/index/index-helper.mjs';
|
|
31
31
|
import { B2tFSStagedItemRecord } from '../../engine/index/index-types.mjs';
|
|
32
|
+
import { isCommentMetadataFilename, extractCommentTopicName } from '../../engine/status/status-helper.mjs';
|
|
32
33
|
import { TransformResult } from '@ibgib/ts-gib/dist/types.mjs';
|
|
33
34
|
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
34
35
|
import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
|
|
@@ -45,7 +46,7 @@ export interface CommitCmdOpts {
|
|
|
45
46
|
|
|
46
47
|
export interface CommitCmdResult {
|
|
47
48
|
commitAddr: string;
|
|
48
|
-
commentAddr
|
|
49
|
+
commentAddr?: string;
|
|
49
50
|
branchName: string;
|
|
50
51
|
committedPaths: string[];
|
|
51
52
|
rootItemAddr: string;
|
|
@@ -78,11 +79,6 @@ export async function executeCommitCmd({
|
|
|
78
79
|
throw new Error(`Not an ibgib repository (directory ${vcsPath} does not exist) (E: 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d)`);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
// Validate message
|
|
82
|
-
if (!message || message.trim().length === 0) {
|
|
83
|
-
throw new Error(`Commit message is required (E: 2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e)`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
82
|
// Fetch authoritative repository index from localSpace
|
|
87
83
|
const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, vcsPath });
|
|
88
84
|
if (!indexIbGib) {
|
|
@@ -90,6 +86,7 @@ export async function executeCommitCmd({
|
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
const branchName = indexIbGib.data?.activeBranch || 'main';
|
|
89
|
+
const activeWorktreeDir = branchName === 'main' ? repoRoot : path.join(repoRoot, 'b', branchName);
|
|
93
90
|
|
|
94
91
|
// Target active branch space b2s_<activeBranch>
|
|
95
92
|
const { space: activeSpace } = await getOrCreateBranchSpace({
|
|
@@ -98,24 +95,22 @@ export async function executeCommitCmd({
|
|
|
98
95
|
branchName,
|
|
99
96
|
});
|
|
100
97
|
|
|
101
|
-
//
|
|
102
|
-
const stagedPathsMap = indexIbGib.data?.stagedPaths || {};
|
|
103
|
-
const stagedAddrs = indexIbGib.data?.staged_item || [];
|
|
98
|
+
// Verify there are staged items
|
|
99
|
+
const stagedPathsMap: { [relPath: string]: B2tFSStagedItemRecord } = { ...(indexIbGib.data?.stagedPaths || {}) };
|
|
104
100
|
const committedPaths = Object.keys(stagedPathsMap);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
throw new Error(`Nothing to commit (no staged items) (E: 4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a)`);
|
|
101
|
+
if (committedPaths.length === 0) {
|
|
102
|
+
throw new Error(`Nothing to commit (no staged items found in index) (E: 4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a)`);
|
|
108
103
|
}
|
|
109
104
|
|
|
110
|
-
//
|
|
105
|
+
// Find active branch node
|
|
106
|
+
let currentBranchIbGib: B2tFSBranchIbGib_V1 | undefined;
|
|
107
|
+
let currentBranchAddr: string | undefined;
|
|
108
|
+
|
|
111
109
|
const branchAddrs = [
|
|
112
110
|
...(indexIbGib.rel8ns?.branch || []),
|
|
113
111
|
...(indexIbGib.rel8ns?.branch_tjp || []),
|
|
114
112
|
];
|
|
115
113
|
|
|
116
|
-
let currentBranchIbGib: B2tFSBranchIbGib_V1 | undefined;
|
|
117
|
-
let currentBranchAddr: string | undefined;
|
|
118
|
-
|
|
119
114
|
if (branchAddrs.length > 0) {
|
|
120
115
|
let branchIbGibs: B2tFSBranchIbGib_V1[] = [];
|
|
121
116
|
for (const bAddr of branchAddrs) {
|
|
@@ -133,35 +128,54 @@ export async function executeCommitCmd({
|
|
|
133
128
|
(b) => (b as B2tFSBranchIbGib_V1)?.data?.name === branchName,
|
|
134
129
|
) as B2tFSBranchIbGib_V1 || (branchIbGibs.at(-1) as B2tFSBranchIbGib_V1);
|
|
135
130
|
|
|
136
|
-
|
|
137
131
|
if (currentBranchIbGib) {
|
|
138
132
|
currentBranchAddr = getIbGibAddr({ ibGib: currentBranchIbGib });
|
|
139
133
|
}
|
|
140
134
|
}
|
|
141
135
|
|
|
142
|
-
const currentRootAddr = currentBranchIbGib?.rel8ns?.b2tfs_item?.at(-1);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// 1. Create CommentIbGib_V1 commit comment node in activeSpace
|
|
146
|
-
const commentRes = await createCommentIbGib({
|
|
147
|
-
text: message.trim(),
|
|
148
|
-
saveInSpace: true,
|
|
149
|
-
space: activeSpace,
|
|
150
|
-
});
|
|
151
|
-
const commentIbGib = commentRes.newIbGib as CommentIbGib_V1;
|
|
152
|
-
const commentAddr = getIbGibAddr({ ibGib: commentIbGib });
|
|
153
|
-
await metaspace.registerNewIbGib({ ibGib: commentIbGib, space: activeSpace });
|
|
136
|
+
const currentRootAddr = currentBranchIbGib?.rel8ns?.b2tFS_item?.at(-1) || currentBranchIbGib?.rel8ns?.b2tfs_item?.at(-1);
|
|
154
137
|
|
|
155
|
-
//
|
|
138
|
+
// 1. Evolve root item graph with staged items and ingest co-located .ib.*.md comment files
|
|
156
139
|
const repoName = path.basename(repoRoot) || 'root';
|
|
157
|
-
|
|
140
|
+
let { newRootItem, newRootAddr, ingestedCommentCount } = await commitCmd_buildEvolvedRootItem({
|
|
158
141
|
currentRootAddr,
|
|
159
142
|
stagedPathsMap,
|
|
160
143
|
space: activeSpace,
|
|
161
144
|
metaspace,
|
|
162
145
|
repoName,
|
|
146
|
+
worktreeDir: activeWorktreeDir,
|
|
163
147
|
});
|
|
164
148
|
|
|
149
|
+
if ((!message || message.trim().length === 0) && ingestedCommentCount === 0) {
|
|
150
|
+
throw new Error(`Commit message is required (-m "message" or companion comment files) (E: 2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e)`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 2. If CLI message is provided, attach CommentIbGib to the root b2tfs item
|
|
154
|
+
let commentAddr: string | undefined;
|
|
155
|
+
if (message && message.trim().length > 0) {
|
|
156
|
+
const commentRes = await createCommentIbGib({
|
|
157
|
+
text: message.trim(),
|
|
158
|
+
saveInSpace: true,
|
|
159
|
+
space: activeSpace,
|
|
160
|
+
});
|
|
161
|
+
const commentIbGib = commentRes.newIbGib as CommentIbGib_V1;
|
|
162
|
+
commentAddr = getIbGibAddr({ ibGib: commentIbGib });
|
|
163
|
+
await metaspace.registerNewIbGib({ ibGib: commentIbGib, space: activeSpace });
|
|
164
|
+
|
|
165
|
+
const existingComments = newRootItem.rel8ns?.comment || [];
|
|
166
|
+
const evolveRootRes = await evolveB2tFSItemIbGib({
|
|
167
|
+
existingItem: newRootItem,
|
|
168
|
+
rel8nsToUpdate: {
|
|
169
|
+
comment: [...existingComments, commentAddr],
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
await persistTransformResult({ resTransform: evolveRootRes, space: activeSpace });
|
|
173
|
+
await metaspace.registerNewIbGib({ ibGib: evolveRootRes.newIbGib, space: activeSpace });
|
|
174
|
+
|
|
175
|
+
newRootItem = evolveRootRes.newIbGib as B2tFSItemIbGib_V1;
|
|
176
|
+
newRootAddr = getIbGibAddr({ ibGib: newRootItem });
|
|
177
|
+
}
|
|
178
|
+
|
|
165
179
|
// 3. Advance B2tFSBranchIbGib_V1 node inside activeSpace
|
|
166
180
|
const branchUuid = currentBranchIbGib?.data?.uuid || (await getUUID());
|
|
167
181
|
const newBranchRes = await Factory_V1.firstGen({
|
|
@@ -173,10 +187,11 @@ export async function executeCommitCmd({
|
|
|
173
187
|
uuid: branchUuid,
|
|
174
188
|
classname: 'B2tFSBranchIbGib_V1',
|
|
175
189
|
committedAt: new Date().toISOString(),
|
|
190
|
+
committedPaths,
|
|
176
191
|
},
|
|
177
192
|
rel8ns: {
|
|
178
193
|
b2tfs_item: [newRootAddr],
|
|
179
|
-
comment: [commentAddr],
|
|
194
|
+
...(commentAddr ? { comment: [commentAddr] } : {}),
|
|
180
195
|
},
|
|
181
196
|
});
|
|
182
197
|
|
|
@@ -212,7 +227,8 @@ export async function executeCommitCmd({
|
|
|
212
227
|
|
|
213
228
|
if (!quiet) {
|
|
214
229
|
const shortGib = newBranchIbGib.gib ? newBranchIbGib.gib.slice(0, 7) : 'commit';
|
|
215
|
-
|
|
230
|
+
const logMsg = message ? message.trim() : '(committed)';
|
|
231
|
+
console.log(`[${branchName} ${shortGib}] ${logMsg} (I: 5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b)`);
|
|
216
232
|
console.log(` ${committedPaths.length} file(s) changed`);
|
|
217
233
|
}
|
|
218
234
|
|
|
@@ -241,13 +257,16 @@ async function commitCmd_buildEvolvedRootItem({
|
|
|
241
257
|
space,
|
|
242
258
|
metaspace,
|
|
243
259
|
repoName,
|
|
260
|
+
worktreeDir,
|
|
244
261
|
}: {
|
|
245
262
|
currentRootAddr?: string;
|
|
246
263
|
stagedPathsMap: { [relPath: string]: B2tFSStagedItemRecord };
|
|
247
264
|
space: any;
|
|
248
265
|
metaspace: any;
|
|
249
266
|
repoName: string;
|
|
250
|
-
|
|
267
|
+
worktreeDir: string;
|
|
268
|
+
}): Promise<{ newRootItem: B2tFSItemIbGib_V1; newRootAddr: string; ingestedCommentCount: number }> {
|
|
269
|
+
let ingestedCommentCount = 0;
|
|
251
270
|
const itemsByRelPath = new Map<string, { itemAddr: string; fsType: 'file' | 'folder'; item?: B2tFSItemIbGib_V1 }>();
|
|
252
271
|
|
|
253
272
|
if (currentRootAddr) {
|
|
@@ -276,6 +295,63 @@ async function commitCmd_buildEvolvedRootItem({
|
|
|
276
295
|
});
|
|
277
296
|
}
|
|
278
297
|
|
|
298
|
+
// 1. Ingest co-located .ib.<filename>.md comment files for staged files
|
|
299
|
+
for (const [relPath, itemRec] of itemsByRelPath.entries()) {
|
|
300
|
+
if (itemRec.fsType !== 'file') { continue; }
|
|
301
|
+
|
|
302
|
+
const dir = path.dirname(relPath);
|
|
303
|
+
const base = path.basename(relPath);
|
|
304
|
+
const commentDir = dir === '.' ? worktreeDir : path.join(worktreeDir, dir);
|
|
305
|
+
const commentFullPath = path.join(commentDir, `.ib.${base}.md`);
|
|
306
|
+
|
|
307
|
+
let commentText = '';
|
|
308
|
+
try {
|
|
309
|
+
commentText = await fs.readFile(commentFullPath, 'utf8');
|
|
310
|
+
} catch {
|
|
311
|
+
// No comment file for this file
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (commentText.trim().length > 0) {
|
|
316
|
+
const commentRes = await createCommentIbGib({
|
|
317
|
+
text: commentText.trim(),
|
|
318
|
+
saveInSpace: true,
|
|
319
|
+
space,
|
|
320
|
+
});
|
|
321
|
+
const commentIbGib = commentRes.newIbGib as CommentIbGib_V1;
|
|
322
|
+
const commentAddr = getIbGibAddr({ ibGib: commentIbGib });
|
|
323
|
+
await metaspace.registerNewIbGib({ ibGib: commentIbGib, space });
|
|
324
|
+
ingestedCommentCount++;
|
|
325
|
+
|
|
326
|
+
const currentItem = itemRec.item || (await getIbGibWithMetaspaceFallback({
|
|
327
|
+
addr: itemRec.itemAddr,
|
|
328
|
+
space,
|
|
329
|
+
metaspace,
|
|
330
|
+
}) as B2tFSItemIbGib_V1);
|
|
331
|
+
|
|
332
|
+
if (currentItem) {
|
|
333
|
+
const existingComments = currentItem.rel8ns?.comment || [];
|
|
334
|
+
const evolvedItemRes = await evolveB2tFSItemIbGib({
|
|
335
|
+
existingItem: currentItem,
|
|
336
|
+
rel8nsToUpdate: {
|
|
337
|
+
comment: [...existingComments, commentAddr],
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
await persistTransformResult({ resTransform: evolvedItemRes, space });
|
|
341
|
+
await metaspace.registerNewIbGib({ ibGib: evolvedItemRes.newIbGib, space });
|
|
342
|
+
|
|
343
|
+
itemRec.itemAddr = getIbGibAddr({ ibGib: evolvedItemRes.newIbGib });
|
|
344
|
+
itemRec.item = evolvedItemRes.newIbGib as B2tFSItemIbGib_V1;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
await fs.unlink(commentFullPath);
|
|
349
|
+
} catch {
|
|
350
|
+
// Delete failed / ignore
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
279
355
|
// Ensure parent folder entries exist for all items
|
|
280
356
|
const allRelPaths = Array.from(itemsByRelPath.keys());
|
|
281
357
|
for (const relPath of allRelPaths) {
|
|
@@ -331,6 +407,46 @@ async function commitCmd_buildEvolvedRootItem({
|
|
|
331
407
|
}
|
|
332
408
|
}
|
|
333
409
|
|
|
410
|
+
// Check for folder-level .ib.<topic>.md files in this folder
|
|
411
|
+
const folderDiskPath = folderRelPath ? path.join(worktreeDir, folderRelPath) : worktreeDir;
|
|
412
|
+
const folderCommentAddrs: string[] = [];
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
const dirents = await fs.readdir(folderDiskPath, { withFileTypes: true });
|
|
416
|
+
for (const dirent of dirents) {
|
|
417
|
+
if (dirent.isFile() && isCommentMetadataFilename(dirent.name)) {
|
|
418
|
+
const topicName = extractCommentTopicName(dirent.name);
|
|
419
|
+
const isCoLocatedFileComment = itemsByRelPath.has(folderRelPath ? `${folderRelPath}/${topicName}` : topicName);
|
|
420
|
+
|
|
421
|
+
if (!isCoLocatedFileComment) {
|
|
422
|
+
const fullCommentPath = path.join(folderDiskPath, dirent.name);
|
|
423
|
+
const commentContent = await fs.readFile(fullCommentPath, 'utf8');
|
|
424
|
+
|
|
425
|
+
if (commentContent.trim().length > 0) {
|
|
426
|
+
const commentRes = await createCommentIbGib({
|
|
427
|
+
text: commentContent.trim(),
|
|
428
|
+
saveInSpace: true,
|
|
429
|
+
space,
|
|
430
|
+
});
|
|
431
|
+
const commentIbGib = commentRes.newIbGib as CommentIbGib_V1;
|
|
432
|
+
const cAddr = getIbGibAddr({ ibGib: commentIbGib });
|
|
433
|
+
await metaspace.registerNewIbGib({ ibGib: commentIbGib, space });
|
|
434
|
+
folderCommentAddrs.push(cAddr);
|
|
435
|
+
ingestedCommentCount++;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
await fs.unlink(fullCommentPath);
|
|
440
|
+
} catch {
|
|
441
|
+
// ignore delete error
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
// Folder may not exist on disk
|
|
448
|
+
}
|
|
449
|
+
|
|
334
450
|
const folderName = folderRelPath ? path.basename(folderRelPath) : repoName;
|
|
335
451
|
const nameHash = crypto.createHash('sha256').update(folderName).digest('hex');
|
|
336
452
|
|
|
@@ -341,13 +457,17 @@ async function commitCmd_buildEvolvedRootItem({
|
|
|
341
457
|
if (existingFolderRec && existingFolderRec.item) {
|
|
342
458
|
const oldChildAddrs = existingFolderRec.item.rel8ns?.[B2TFS_CHILD_ITEM_REL8N_NAME] || [];
|
|
343
459
|
const toRemoveChildAddrs = oldChildAddrs.filter(a => !childAddrs.includes(a));
|
|
460
|
+
const existingComments = existingFolderRec.item.rel8ns?.comment || [];
|
|
344
461
|
|
|
345
462
|
folderRes = await evolveB2tFSItemIbGib({
|
|
346
463
|
existingItem: existingFolderRec.item,
|
|
347
464
|
dataToUpdate: {
|
|
348
465
|
nameHash,
|
|
349
466
|
},
|
|
350
|
-
rel8nsToUpdate:
|
|
467
|
+
rel8nsToUpdate: {
|
|
468
|
+
...(childAddrs.length > 0 ? { [B2TFS_CHILD_ITEM_REL8N_NAME]: childAddrs } : {}),
|
|
469
|
+
...(folderCommentAddrs.length > 0 ? { comment: [...existingComments, ...folderCommentAddrs] } : {}),
|
|
470
|
+
},
|
|
351
471
|
rel8nsToRemove: toRemoveChildAddrs.length > 0 ? { [B2TFS_CHILD_ITEM_REL8N_NAME]: toRemoveChildAddrs } : undefined,
|
|
352
472
|
});
|
|
353
473
|
} else {
|
|
@@ -357,7 +477,10 @@ async function commitCmd_buildEvolvedRootItem({
|
|
|
357
477
|
data: {
|
|
358
478
|
nameHash,
|
|
359
479
|
},
|
|
360
|
-
rel8ns:
|
|
480
|
+
rel8ns: {
|
|
481
|
+
...(childAddrs.length > 0 ? { [B2TFS_CHILD_ITEM_REL8N_NAME]: childAddrs } : {}),
|
|
482
|
+
...(folderCommentAddrs.length > 0 ? { comment: folderCommentAddrs } : {}),
|
|
483
|
+
},
|
|
361
484
|
});
|
|
362
485
|
}
|
|
363
486
|
|
|
@@ -374,7 +497,7 @@ async function commitCmd_buildEvolvedRootItem({
|
|
|
374
497
|
}
|
|
375
498
|
}
|
|
376
499
|
|
|
377
|
-
return { newRootItem: rootFolderItem!, newRootAddr: rootFolderAddr };
|
|
500
|
+
return { newRootItem: rootFolderItem!, newRootAddr: rootFolderAddr, ingestedCommentCount };
|
|
378
501
|
}
|
|
379
502
|
|
|
380
503
|
function commitCmd_getParentRelPath(relPath: string): string {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module commit-log-cmd.respec
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Unit tests for `vcs commit` and `vcs log` command handlers.
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -8,9 +8,11 @@ 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, ifWeMight, iReckon
|
|
12
12
|
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
13
13
|
|
|
14
|
+
import { getDependencyGraph } from '@ibgib/core-gib/dist/common/other/graph-helper.mjs';
|
|
15
|
+
import { getFromSpace } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
|
|
14
16
|
import { executeInitCmd } from './init-cmd.mjs';
|
|
15
17
|
import { executeAddCmd } from './add-cmd.mjs';
|
|
16
18
|
import { executeStatusCmd } from './status-cmd.mjs';
|
|
@@ -18,8 +20,12 @@ import { executeCommitCmd } from './commit-cmd.mjs';
|
|
|
18
20
|
import { executeLogCmd } from './log-cmd.mjs';
|
|
19
21
|
import { executeBranchCmd } from './branch-cmd.mjs';
|
|
20
22
|
import { executeCheckoutCmd } from './checkout-cmd.mjs';
|
|
23
|
+
import { executeMergeCmd } from './merge-cmd.mjs';
|
|
24
|
+
import { executeGenerateCommentCmd } from './generate-comment-cmd.mjs';
|
|
21
25
|
import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
|
|
22
26
|
import { getB2tFSIndexIbGib } from '../../engine/index/index-helper.mjs';
|
|
27
|
+
import { getOrCreateBranchSpace } from '../../engine/branch/branch-space-helper.mjs';
|
|
28
|
+
import { getIbGibWithMetaspaceFallback } from '../../engine/item/item-helper.mjs';
|
|
23
29
|
|
|
24
30
|
const lc = `[apps/vcs-gib/src/cli/commands/commit-log-cmd.respec.node.mts]`;
|
|
25
31
|
|
|
@@ -267,8 +273,215 @@ await respecfully(lc, `vcs commit and vcs log Command Handlers`, async () => {
|
|
|
267
273
|
});
|
|
268
274
|
|
|
269
275
|
iReckon(lc, logRes.branchName).willEqual('feature-log');
|
|
270
|
-
iReckon(lc, logRes.commits.length).willEqual(
|
|
276
|
+
iReckon(lc, logRes.commits.length).willEqual(2);
|
|
271
277
|
iReckon(lc, logRes.commits[0].message).willEqual('Feature branch commit');
|
|
278
|
+
iReckon(lc, logRes.commits[1].message).willEqual('Main branch commit');
|
|
279
|
+
} finally {
|
|
280
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await ifWe(lc, `generates companion comment files, displays in status, ingests on commit, and projects in file log`, async () => {
|
|
285
|
+
const testDir = path.join(process.cwd(), '.test_commit_log_companion');
|
|
286
|
+
const vcsFolderName = '.vcsgib_test_commit_companion';
|
|
287
|
+
try {
|
|
288
|
+
await executeInitCmd({
|
|
289
|
+
targetDir: testDir,
|
|
290
|
+
vcsFolderName,
|
|
291
|
+
quiet: true,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const filePath = path.join(testDir, 'app.ts');
|
|
295
|
+
await fs.writeFile(filePath, 'console.log("hello world");', 'utf8');
|
|
296
|
+
|
|
297
|
+
// 1. Test generate-comment
|
|
298
|
+
const genRes = await executeGenerateCommentCmd({
|
|
299
|
+
targetDir: testDir,
|
|
300
|
+
vcsFolderName,
|
|
301
|
+
paths: ['app.ts'],
|
|
302
|
+
quiet: true,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
iReckon(lc, genRes.createdFiles.length).willEqual(1);
|
|
306
|
+
iReckon(lc, genRes.createdFiles[0]).includes('.ib.app.ts.md');
|
|
307
|
+
|
|
308
|
+
const companionPath = path.join(testDir, '.ib.app.ts.md');
|
|
309
|
+
let companionExists = false;
|
|
310
|
+
try {
|
|
311
|
+
await fs.access(companionPath);
|
|
312
|
+
companionExists = true;
|
|
313
|
+
} catch { }
|
|
314
|
+
iReckon(lc, companionExists).isGonnaBeTrue();
|
|
315
|
+
|
|
316
|
+
// Write custom notes into companion file
|
|
317
|
+
await fs.writeFile(companionPath, 'feat: initial implementation of app.ts\n* added console log', 'utf8');
|
|
318
|
+
|
|
319
|
+
// Write folder-level notes
|
|
320
|
+
const folderCompanionPath = path.join(testDir, '.ib.architecture.md');
|
|
321
|
+
await fs.writeFile(folderCompanionPath, 'Arch overview: client-side entrypoint', 'utf8');
|
|
322
|
+
|
|
323
|
+
// Stage app.ts
|
|
324
|
+
await executeAddCmd({
|
|
325
|
+
paths: ['app.ts'],
|
|
326
|
+
targetDir: testDir,
|
|
327
|
+
vcsFolderName,
|
|
328
|
+
quiet: true,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// 2. Test status with comments
|
|
332
|
+
const statusRes = await executeStatusCmd({
|
|
333
|
+
targetDir: testDir,
|
|
334
|
+
vcsFolderName,
|
|
335
|
+
comments: true,
|
|
336
|
+
quiet: true,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
iReckon(lc, statusRes.pendingComments.length).willEqual(2);
|
|
340
|
+
const fileComment = statusRes.pendingComments.find(c => c.targetType === 'file');
|
|
341
|
+
iReckon(lc, Boolean(fileComment)).isGonnaBeTrue();
|
|
342
|
+
iReckon(lc, fileComment?.content).includes('initial implementation of app.ts');
|
|
343
|
+
|
|
344
|
+
// 3. Commit staged files and notes
|
|
345
|
+
const commitRes = await executeCommitCmd({
|
|
346
|
+
message: 'Initial root commit',
|
|
347
|
+
targetDir: testDir,
|
|
348
|
+
vcsFolderName,
|
|
349
|
+
quiet: true,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
iReckon(lc, commitRes.committedPaths).includes('app.ts');
|
|
353
|
+
|
|
354
|
+
// Verify companion files were consumed and removed
|
|
355
|
+
let companionStillExists = false;
|
|
356
|
+
try {
|
|
357
|
+
await fs.access(companionPath);
|
|
358
|
+
companionStillExists = true;
|
|
359
|
+
} catch { }
|
|
360
|
+
iReckon(lc, companionStillExists).isGonnaBeFalse();
|
|
361
|
+
|
|
362
|
+
let folderCompanionStillExists = false;
|
|
363
|
+
try {
|
|
364
|
+
await fs.access(folderCompanionPath);
|
|
365
|
+
folderCompanionStillExists = true;
|
|
366
|
+
} catch { }
|
|
367
|
+
iReckon(lc, folderCompanionStillExists).isGonnaBeFalse();
|
|
368
|
+
|
|
369
|
+
// 4. Test file-specific log (vcs log app.ts)
|
|
370
|
+
const fileLogRes = await executeLogCmd({
|
|
371
|
+
targetDir: testDir,
|
|
372
|
+
vcsFolderName,
|
|
373
|
+
filePath: 'app.ts',
|
|
374
|
+
quiet: true,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
iReckon(lc, fileLogRes.commits.length >= 1).isGonnaBeTrue();
|
|
378
|
+
iReckon(lc, fileLogRes.commits[0].message).includes('initial implementation of app.ts');
|
|
379
|
+
|
|
380
|
+
// 5. Test root log with allComments
|
|
381
|
+
const rootLogRes = await executeLogCmd({
|
|
382
|
+
targetDir: testDir,
|
|
383
|
+
vcsFolderName,
|
|
384
|
+
allComments: true,
|
|
385
|
+
quiet: true,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
iReckon(lc, rootLogRes.commits.length).willEqual(1);
|
|
389
|
+
iReckon(lc, rootLogRes.commits[0].message).willEqual('Initial root commit');
|
|
390
|
+
iReckon(lc, rootLogRes.commits[0].committedPaths).includes('app.ts');
|
|
391
|
+
} finally {
|
|
392
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
await ifWe(lc, `preserves and unions file comments across 3-way branch merges`, async () => {
|
|
397
|
+
const testDir = path.join(process.cwd(), '.test_commit_log_merge_comments');
|
|
398
|
+
const vcsFolderName = '.vcsgib_test_commit_merge_comments';
|
|
399
|
+
try {
|
|
400
|
+
await executeInitCmd({
|
|
401
|
+
targetDir: testDir,
|
|
402
|
+
vcsFolderName,
|
|
403
|
+
quiet: true,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Base commit on main
|
|
407
|
+
const appFile = path.join(testDir, 'app.ts');
|
|
408
|
+
await fs.writeFile(appFile, 'const a = 1;\nconst b = 2;\n', 'utf8');
|
|
409
|
+
await executeAddCmd({ paths: ['app.ts'], targetDir: testDir, vcsFolderName, quiet: true });
|
|
410
|
+
await executeCommitCmd({ message: 'Base commit on main', targetDir: testDir, vcsFolderName, quiet: true });
|
|
411
|
+
|
|
412
|
+
// Create feature branch
|
|
413
|
+
await executeBranchCmd({ branchName: 'feature-comments', targetDir: testDir, vcsFolderName, quiet: true });
|
|
414
|
+
const checkoutRes = await executeCheckoutCmd({ branchName: 'feature-comments', targetDir: testDir, vcsFolderName, quiet: true });
|
|
415
|
+
|
|
416
|
+
// On feature branch: modify app.ts and attach companion note
|
|
417
|
+
const featureAppFile = path.join(checkoutRes.worktreePath, 'app.ts');
|
|
418
|
+
await fs.writeFile(featureAppFile, 'const a = 1;\nconst b = 200;\n', 'utf8');
|
|
419
|
+
const featureNoteFile = path.join(checkoutRes.worktreePath, '.ib.app.ts.md');
|
|
420
|
+
await fs.writeFile(featureNoteFile, 'feat(feature-comments): updated b to 200', 'utf8');
|
|
421
|
+
|
|
422
|
+
await executeAddCmd({ paths: ['app.ts'], targetDir: checkoutRes.worktreePath, vcsFolderName, quiet: true });
|
|
423
|
+
await executeCommitCmd({ message: 'Feature branch commit', targetDir: checkoutRes.worktreePath, vcsFolderName, quiet: true });
|
|
424
|
+
|
|
425
|
+
// Switch back to main branch
|
|
426
|
+
await executeCheckoutCmd({ branchName: 'main', targetDir: testDir, vcsFolderName, quiet: true });
|
|
427
|
+
|
|
428
|
+
// On main branch: modify app.ts and attach companion note
|
|
429
|
+
await fs.writeFile(appFile, 'const a = 100;\nconst b = 2;\n', 'utf8');
|
|
430
|
+
const mainNoteFile = path.join(testDir, '.ib.app.ts.md');
|
|
431
|
+
await fs.writeFile(mainNoteFile, 'feat(main): updated a to 100', 'utf8');
|
|
432
|
+
|
|
433
|
+
await executeAddCmd({ paths: ['app.ts'], targetDir: testDir, vcsFolderName, quiet: true });
|
|
434
|
+
await executeCommitCmd({ message: 'Main branch update', targetDir: testDir, vcsFolderName, quiet: true });
|
|
435
|
+
|
|
436
|
+
// Merge feature-comments into main
|
|
437
|
+
await executeMergeCmd({ incomingBranchName: 'feature-comments', targetDir: testDir, vcsFolderName, quiet: true });
|
|
438
|
+
|
|
439
|
+
// Check file log on main: both comments should be present!
|
|
440
|
+
const mergedFileLog = await executeLogCmd({
|
|
441
|
+
targetDir: testDir,
|
|
442
|
+
vcsFolderName,
|
|
443
|
+
filePath: 'app.ts',
|
|
444
|
+
quiet: true,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const allMessages = mergedFileLog.commits.map(c => c.message).join(' -- ');
|
|
448
|
+
iReckon(lc, allMessages).includes('updated b to 200');
|
|
449
|
+
iReckon(lc, allMessages).includes('updated a to 100');
|
|
450
|
+
|
|
451
|
+
// Verify getDependencyGraph works 100% within main branch space alone (no metaspace fallback)
|
|
452
|
+
const { metaspace, vcsPath } = await bootstrapVcsMetaspace({
|
|
453
|
+
repoPath: testDir,
|
|
454
|
+
vcsFolderName,
|
|
455
|
+
});
|
|
456
|
+
const { space: mainSpace } = await getOrCreateBranchSpace({
|
|
457
|
+
metaspace,
|
|
458
|
+
vcsPath,
|
|
459
|
+
branchName: 'main',
|
|
460
|
+
});
|
|
461
|
+
const indexIbGib = await getB2tFSIndexIbGib({ space: metaspace.zeroSpace, vcsPath });
|
|
462
|
+
const mainBranchAddr = indexIbGib?.rel8ns?.branch?.at(-1) || indexIbGib?.rel8ns?.branch_tjp?.at(-1);
|
|
463
|
+
const mainBranch = await getIbGibWithMetaspaceFallback({ addr: mainBranchAddr!, space: mainSpace, metaspace });
|
|
464
|
+
const mainRootAddr = mainBranch?.rel8ns?.b2tfs_item?.at(-1);
|
|
465
|
+
const mainRoot = await getIbGibWithMetaspaceFallback({ addr: mainRootAddr!, space: mainSpace, metaspace });
|
|
466
|
+
let appItem: any;
|
|
467
|
+
for (const childAddr of mainRoot?.rel8ns?.b2tfs_child || []) {
|
|
468
|
+
const child = await getIbGibWithMetaspaceFallback({ addr: childAddr, space: mainSpace, metaspace });
|
|
469
|
+
if (child?.data?.name === 'app.ts') {
|
|
470
|
+
appItem = child;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
iReckon(lc, appItem).isGonnaBeTruthy();
|
|
475
|
+
|
|
476
|
+
// Ensure getDependencyGraph runs strictly within mainSpace with NO metaspace fallback
|
|
477
|
+
const depGraph = await getDependencyGraph({
|
|
478
|
+
ibGib: appItem,
|
|
479
|
+
space: mainSpace,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const graphValues = Object.values(depGraph);
|
|
483
|
+
const foundCommentB = graphValues.some(x => (x.data as any)?.text === 'feat(feature-comments): updated b to 200');
|
|
484
|
+
iReckon(lc, foundCommentB).isGonnaBeTruthy();
|
|
272
485
|
} finally {
|
|
273
486
|
await fs.rm(testDir, { recursive: true, force: true });
|
|
274
487
|
}
|