@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
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module status-helper
|
|
3
|
+
*
|
|
4
|
+
* Pure status inspection helpers and pending `.ib.*.md` comment metadata detection.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import { Dirent } from 'node:fs';
|
|
10
|
+
import crypto from 'node:crypto';
|
|
11
|
+
|
|
12
|
+
import { B2tFSItemIbGib_V1, B2TFS_CHILD_ITEM_REL8N_NAME } from '../item/item-types.mjs';
|
|
13
|
+
import { getIbGibWithMetaspaceFallback } from '../item/item-helper.mjs';
|
|
14
|
+
import { B2tFSBranchIbGib_V1 } from '../branch/branch-types.mjs';
|
|
15
|
+
import { isPathFiltered, parseFilterFileContent } from '../filter/glob-helper.mjs';
|
|
16
|
+
import { getB2tFSIndexIbGib } from '../index/index-helper.mjs';
|
|
17
|
+
import { B2tFSStagedItemRecord } from '../index/index-types.mjs';
|
|
18
|
+
import { getOrCreateBranchSpace } from '../branch/branch-space-helper.mjs';
|
|
19
|
+
|
|
20
|
+
export interface VcsStatusItem {
|
|
21
|
+
path: string;
|
|
22
|
+
fsType: 'file' | 'folder';
|
|
23
|
+
status: 'untracked' | 'modified' | 'deleted' | 'staged';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PendingCommentInfo {
|
|
27
|
+
/** Relative path of the companion file, e.g. "src/.ib.index.mts.md" or ".ib.architecture.md" */
|
|
28
|
+
commentRelPath: string;
|
|
29
|
+
/** Full disk path of the companion file */
|
|
30
|
+
commentFullPath: string;
|
|
31
|
+
/** Target relative path this comment attaches to, e.g. "src/index.mts" or "src" (folder) or "" (root) */
|
|
32
|
+
targetRelPath: string;
|
|
33
|
+
/** 'file' if .ib.<filename>.md matches a file, or 'folder' */
|
|
34
|
+
targetType: 'file' | 'folder';
|
|
35
|
+
/** The topic name (the * in .ib.*.md) or filename */
|
|
36
|
+
topicName: string;
|
|
37
|
+
/** Raw text content of the markdown comment file */
|
|
38
|
+
content: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RepoStatusResult {
|
|
42
|
+
branchName: string;
|
|
43
|
+
activeWorktreeDir: string;
|
|
44
|
+
staged: VcsStatusItem[];
|
|
45
|
+
modified: VcsStatusItem[];
|
|
46
|
+
deleted: VcsStatusItem[];
|
|
47
|
+
untracked: VcsStatusItem[];
|
|
48
|
+
pendingComments: PendingCommentInfo[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns true if filename is a `.ib.*.md` comment metadata file.
|
|
53
|
+
*/
|
|
54
|
+
export function isCommentMetadataFilename(filename: string): boolean {
|
|
55
|
+
return filename.startsWith('.ib.') && filename.endsWith('.md') && filename.length > '.ib..md'.length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extracts the inner topic / filename from `.ib.<topic>.md`.
|
|
60
|
+
*/
|
|
61
|
+
export function extractCommentTopicName(filename: string): string {
|
|
62
|
+
if (!isCommentMetadataFilename(filename)) { return ''; }
|
|
63
|
+
return filename.slice(4, -3); // strips leading '.ib.' and trailing '.md'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Pure helper to walk the branch graph items.
|
|
68
|
+
*/
|
|
69
|
+
export async function walkBranchGraph({
|
|
70
|
+
item,
|
|
71
|
+
space,
|
|
72
|
+
metaspace,
|
|
73
|
+
currentRelPath,
|
|
74
|
+
outMap,
|
|
75
|
+
}: {
|
|
76
|
+
item: B2tFSItemIbGib_V1;
|
|
77
|
+
space: any;
|
|
78
|
+
metaspace?: any;
|
|
79
|
+
currentRelPath: string;
|
|
80
|
+
outMap: Map<string, { relPath: string; fileHash?: string; fsType: 'file' | 'folder' }>;
|
|
81
|
+
}): Promise<void> {
|
|
82
|
+
if (!item || !item.data) { return; }
|
|
83
|
+
|
|
84
|
+
const { fsType, fileHash } = item.data;
|
|
85
|
+
if (currentRelPath) {
|
|
86
|
+
outMap.set(currentRelPath, { relPath: currentRelPath, fileHash, fsType });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (fsType === 'folder') {
|
|
90
|
+
const childAddrs = item.rel8ns?.[B2TFS_CHILD_ITEM_REL8N_NAME] || [];
|
|
91
|
+
for (const childAddr of childAddrs) {
|
|
92
|
+
const childItem = await getIbGibWithMetaspaceFallback({
|
|
93
|
+
addr: childAddr,
|
|
94
|
+
space,
|
|
95
|
+
metaspace,
|
|
96
|
+
}) as B2tFSItemIbGib_V1;
|
|
97
|
+
if (childItem && childItem.data) {
|
|
98
|
+
const childRelPath = currentRelPath
|
|
99
|
+
? `${currentRelPath}/${childItem.data.name}`
|
|
100
|
+
: childItem.data.name;
|
|
101
|
+
await walkBranchGraph({
|
|
102
|
+
item: childItem,
|
|
103
|
+
space,
|
|
104
|
+
metaspace,
|
|
105
|
+
currentRelPath: childRelPath,
|
|
106
|
+
outMap,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Pure helper to scan physical workspace, separating source files from `.ib.*.md` comment files.
|
|
115
|
+
*/
|
|
116
|
+
export async function scanPhysicalWorkspace({
|
|
117
|
+
dirPath,
|
|
118
|
+
baseDir,
|
|
119
|
+
vcsFolderName,
|
|
120
|
+
filterPatterns,
|
|
121
|
+
outSourceMap,
|
|
122
|
+
outCommentFiles,
|
|
123
|
+
}: {
|
|
124
|
+
dirPath: string;
|
|
125
|
+
baseDir: string;
|
|
126
|
+
vcsFolderName: string;
|
|
127
|
+
filterPatterns: string[];
|
|
128
|
+
outSourceMap: Map<string, { relPath: string; fullPath: string; isDirectory: boolean; fileHash?: string }>;
|
|
129
|
+
outCommentFiles: Array<{ relPath: string; fullPath: string; parentRelPath: string; filename: string }>;
|
|
130
|
+
}): Promise<void> {
|
|
131
|
+
let entries: Dirent[] = [];
|
|
132
|
+
try {
|
|
133
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
134
|
+
} catch {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const fullEntryPath = path.join(dirPath, entry.name);
|
|
140
|
+
const relPath = path.relative(baseDir, fullEntryPath).replace(/\\/g, '/');
|
|
141
|
+
|
|
142
|
+
// Ignore vcs storage folder, .git, and worktrees folder b
|
|
143
|
+
if (
|
|
144
|
+
relPath === vcsFolderName || relPath.startsWith(vcsFolderName + '/') ||
|
|
145
|
+
relPath === '.git' || relPath.startsWith('.git/') ||
|
|
146
|
+
relPath === 'b' || relPath.startsWith('b/')
|
|
147
|
+
) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (isPathFiltered({ relativePath: relPath, isDirectory: entry.isDirectory(), filterPatterns })) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (entry.isDirectory()) {
|
|
156
|
+
outSourceMap.set(relPath, { relPath, fullPath: fullEntryPath, isDirectory: true });
|
|
157
|
+
await scanPhysicalWorkspace({
|
|
158
|
+
dirPath: fullEntryPath,
|
|
159
|
+
baseDir,
|
|
160
|
+
vcsFolderName,
|
|
161
|
+
filterPatterns,
|
|
162
|
+
outSourceMap,
|
|
163
|
+
outCommentFiles,
|
|
164
|
+
});
|
|
165
|
+
} else if (entry.isFile()) {
|
|
166
|
+
if (isCommentMetadataFilename(entry.name)) {
|
|
167
|
+
const parentRelPath = path.relative(baseDir, dirPath).replace(/\\/g, '/');
|
|
168
|
+
outCommentFiles.push({
|
|
169
|
+
relPath,
|
|
170
|
+
fullPath: fullEntryPath,
|
|
171
|
+
parentRelPath: parentRelPath === '.' ? '' : parentRelPath,
|
|
172
|
+
filename: entry.name,
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
let fileHash: string | undefined;
|
|
176
|
+
try {
|
|
177
|
+
const fileData = await fs.readFile(fullEntryPath);
|
|
178
|
+
fileHash = crypto.createHash('sha256').update(fileData).digest('hex');
|
|
179
|
+
} catch {
|
|
180
|
+
// File unreadable
|
|
181
|
+
}
|
|
182
|
+
outSourceMap.set(relPath, { relPath, fullPath: fullEntryPath, isDirectory: false, fileHash });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Computes complete repository status, including staged, modified, deleted, untracked,
|
|
190
|
+
* and mapped pending `.ib.*.md` comment files.
|
|
191
|
+
*/
|
|
192
|
+
export async function getRepoStatus({
|
|
193
|
+
metaspace,
|
|
194
|
+
localSpace,
|
|
195
|
+
vcsPath,
|
|
196
|
+
repoRoot,
|
|
197
|
+
vcsFolderName = '.vcsgib',
|
|
198
|
+
}: {
|
|
199
|
+
metaspace: any;
|
|
200
|
+
localSpace: any;
|
|
201
|
+
vcsPath: string;
|
|
202
|
+
repoRoot: string;
|
|
203
|
+
vcsFolderName?: string;
|
|
204
|
+
}): Promise<RepoStatusResult> {
|
|
205
|
+
const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, vcsPath });
|
|
206
|
+
const branchName = indexIbGib?.data?.activeBranch || 'main';
|
|
207
|
+
const activeWorktreeDir = branchName === 'main' ? repoRoot : path.join(repoRoot, 'b', branchName);
|
|
208
|
+
|
|
209
|
+
const { space: activeBranchSpace } = await getOrCreateBranchSpace({
|
|
210
|
+
metaspace,
|
|
211
|
+
vcsPath,
|
|
212
|
+
branchName,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const stagedMap = new Map<string, B2tFSStagedItemRecord>();
|
|
216
|
+
const indexStagedPaths = indexIbGib?.data?.stagedPaths || {};
|
|
217
|
+
for (const [relPath, itemRec] of Object.entries(indexStagedPaths)) {
|
|
218
|
+
stagedMap.set(relPath, itemRec);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let rootItemAddr: string | undefined;
|
|
222
|
+
const branchAddrs = [
|
|
223
|
+
...(indexIbGib?.rel8ns?.branch || []),
|
|
224
|
+
...(indexIbGib?.rel8ns?.branch_tjp || []),
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
if (branchAddrs.length > 0) {
|
|
228
|
+
let branchIbGibs: B2tFSBranchIbGib_V1[] = [];
|
|
229
|
+
for (const bAddr of branchAddrs) {
|
|
230
|
+
const bIbGib = await getIbGibWithMetaspaceFallback({
|
|
231
|
+
addr: bAddr,
|
|
232
|
+
space: activeBranchSpace,
|
|
233
|
+
metaspace,
|
|
234
|
+
});
|
|
235
|
+
if (bIbGib) {
|
|
236
|
+
branchIbGibs.push(bIbGib as B2tFSBranchIbGib_V1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const activeBranchIbGib = branchIbGibs.find(
|
|
241
|
+
(b) => (b as B2tFSBranchIbGib_V1)?.data?.name === branchName,
|
|
242
|
+
) as B2tFSBranchIbGib_V1 || (branchIbGibs.at(0) as B2tFSBranchIbGib_V1);
|
|
243
|
+
|
|
244
|
+
rootItemAddr = activeBranchIbGib?.rel8ns?.b2tFS_item?.at(0) || activeBranchIbGib?.rel8ns?.b2tfs_item?.at(0);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let filterPatterns: string[] = [];
|
|
248
|
+
const ignorePath = path.join(activeWorktreeDir, '.ibgibignore');
|
|
249
|
+
try {
|
|
250
|
+
const ignoreContent = await fs.readFile(ignorePath, 'utf8');
|
|
251
|
+
filterPatterns = parseFilterFileContent(ignoreContent);
|
|
252
|
+
} catch {
|
|
253
|
+
// Ignore file missing is fine
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const branchItemsMap = new Map<string, { relPath: string; fileHash?: string; fsType: 'file' | 'folder' }>();
|
|
257
|
+
if (rootItemAddr) {
|
|
258
|
+
const rootItem = await getIbGibWithMetaspaceFallback({
|
|
259
|
+
addr: rootItemAddr,
|
|
260
|
+
space: activeBranchSpace,
|
|
261
|
+
metaspace,
|
|
262
|
+
}) as B2tFSItemIbGib_V1;
|
|
263
|
+
|
|
264
|
+
if (rootItem) {
|
|
265
|
+
await walkBranchGraph({
|
|
266
|
+
item: rootItem,
|
|
267
|
+
space: activeBranchSpace,
|
|
268
|
+
metaspace,
|
|
269
|
+
currentRelPath: '',
|
|
270
|
+
outMap: branchItemsMap,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const physicalItemsMap = new Map<string, { relPath: string; fullPath: string; isDirectory: boolean; fileHash?: string }>();
|
|
276
|
+
const rawCommentFiles: Array<{ relPath: string; fullPath: string; parentRelPath: string; filename: string }> = [];
|
|
277
|
+
|
|
278
|
+
await scanPhysicalWorkspace({
|
|
279
|
+
dirPath: activeWorktreeDir,
|
|
280
|
+
baseDir: activeWorktreeDir,
|
|
281
|
+
vcsFolderName,
|
|
282
|
+
filterPatterns,
|
|
283
|
+
outSourceMap: physicalItemsMap,
|
|
284
|
+
outCommentFiles: rawCommentFiles,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const staged: VcsStatusItem[] = [];
|
|
288
|
+
const modified: VcsStatusItem[] = [];
|
|
289
|
+
const deleted: VcsStatusItem[] = [];
|
|
290
|
+
const untracked: VcsStatusItem[] = [];
|
|
291
|
+
|
|
292
|
+
for (const [relPath, stagedRec] of stagedMap.entries()) {
|
|
293
|
+
staged.push({
|
|
294
|
+
path: relPath,
|
|
295
|
+
fsType: stagedRec.fsType || 'file',
|
|
296
|
+
status: 'staged',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for (const [relPath, physItem] of physicalItemsMap.entries()) {
|
|
301
|
+
if (stagedMap.has(relPath)) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const branchItem = branchItemsMap.get(relPath);
|
|
306
|
+
if (!branchItem) {
|
|
307
|
+
untracked.push({
|
|
308
|
+
path: relPath,
|
|
309
|
+
fsType: physItem.isDirectory ? 'folder' : 'file',
|
|
310
|
+
status: 'untracked',
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
if (!physItem.isDirectory && physItem.fileHash && branchItem.fileHash && physItem.fileHash !== branchItem.fileHash) {
|
|
314
|
+
modified.push({
|
|
315
|
+
path: relPath,
|
|
316
|
+
fsType: 'file',
|
|
317
|
+
status: 'modified',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
for (const [relPath, branchItem] of branchItemsMap.entries()) {
|
|
324
|
+
if (!relPath || relPath === '.' || relPath === '') { continue; }
|
|
325
|
+
if (!physicalItemsMap.has(relPath) && !stagedMap.has(relPath)) {
|
|
326
|
+
deleted.push({
|
|
327
|
+
path: relPath,
|
|
328
|
+
fsType: branchItem.fsType,
|
|
329
|
+
status: 'deleted',
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Map raw comment files to targets (file or folder)
|
|
335
|
+
const pendingComments: PendingCommentInfo[] = [];
|
|
336
|
+
for (const raw of rawCommentFiles) {
|
|
337
|
+
const topicName = extractCommentTopicName(raw.filename);
|
|
338
|
+
const candidateFileRelPath = raw.parentRelPath ? `${raw.parentRelPath}/${topicName}` : topicName;
|
|
339
|
+
|
|
340
|
+
let targetType: 'file' | 'folder' = 'folder';
|
|
341
|
+
let targetRelPath = raw.parentRelPath;
|
|
342
|
+
|
|
343
|
+
if (physicalItemsMap.has(candidateFileRelPath) && !physicalItemsMap.get(candidateFileRelPath)?.isDirectory) {
|
|
344
|
+
targetType = 'file';
|
|
345
|
+
targetRelPath = candidateFileRelPath;
|
|
346
|
+
} else if (branchItemsMap.has(candidateFileRelPath) && branchItemsMap.get(candidateFileRelPath)?.fsType === 'file') {
|
|
347
|
+
targetType = 'file';
|
|
348
|
+
targetRelPath = candidateFileRelPath;
|
|
349
|
+
} else if (stagedMap.has(candidateFileRelPath)) {
|
|
350
|
+
targetType = 'file';
|
|
351
|
+
targetRelPath = candidateFileRelPath;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let content = '';
|
|
355
|
+
try {
|
|
356
|
+
content = await fs.readFile(raw.fullPath, 'utf8');
|
|
357
|
+
} catch {
|
|
358
|
+
// Unreadable comment file
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pendingComments.push({
|
|
362
|
+
commentRelPath: raw.relPath,
|
|
363
|
+
commentFullPath: raw.fullPath,
|
|
364
|
+
targetRelPath,
|
|
365
|
+
targetType,
|
|
366
|
+
topicName,
|
|
367
|
+
content,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
branchName,
|
|
373
|
+
activeWorktreeDir,
|
|
374
|
+
staged,
|
|
375
|
+
modified,
|
|
376
|
+
deleted,
|
|
377
|
+
untracked,
|
|
378
|
+
pendingComments,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module test-helpers
|
|
3
|
+
*
|
|
4
|
+
* Pure-function diagnostic helpers for logging and inspecting b2tfs ibgib
|
|
5
|
+
* state during tests. These are NOT production code — they are imported only
|
|
6
|
+
* by test files (*.respec.mts) and the handful of vcs-gib commands that
|
|
7
|
+
* temporarily wire in diagnostic logging.
|
|
8
|
+
*
|
|
9
|
+
* Usage pattern:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { logBranchState, logItemState } from '../../test/test-helpers.mjs';
|
|
13
|
+
* await logBranchState({ label: 'after commit', branchIbGib, space });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
|
|
18
|
+
import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
|
|
19
|
+
import { IbGibAddr } from '@ibgib/ts-gib/dist/types.mjs';
|
|
20
|
+
import { getLatestAddrs } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
|
|
21
|
+
import { IbGibSpaceAny } from '@ibgib/core-gib/dist/witness/space/space-base-v1.mjs';
|
|
22
|
+
|
|
23
|
+
import { B2tFSBranchIbGib_V1 } from '../engine/branch/branch-types.mjs';
|
|
24
|
+
import { B2tFSItemIbGib_V1 } from '../engine/item/item-types.mjs';
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Internal helpers
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function shortAddr(addr: IbGibAddr | undefined): string {
|
|
32
|
+
if (!addr) { return '(none)'; }
|
|
33
|
+
// show the first 12 chars of the gib portion for easy visual matching
|
|
34
|
+
const caretIdx = addr.lastIndexOf('^');
|
|
35
|
+
if (caretIdx === -1) { return addr.slice(0, 40); }
|
|
36
|
+
const ib = addr.slice(0, caretIdx);
|
|
37
|
+
const gib = addr.slice(caretIdx + 1);
|
|
38
|
+
// gib may be punctiliarHash.tjpGib — show first 8 of each
|
|
39
|
+
const dotIdx = gib.indexOf('.');
|
|
40
|
+
const gibShort = dotIdx === -1
|
|
41
|
+
? gib.slice(0, 8)
|
|
42
|
+
: `${gib.slice(0, 8)}.${gib.slice(dotIdx + 1, dotIdx + 9)}`;
|
|
43
|
+
return `${ib}^${gibShort}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shortAddrs(addrs: IbGibAddr[] | undefined): string {
|
|
47
|
+
if (!addrs || addrs.length === 0) { return '[]'; }
|
|
48
|
+
return `[\n ${addrs.map(shortAddr).join('\n ')}\n ]`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
// Exported diagnostic helpers
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Logs a compact snapshot of a b2tfs branch ibgib and the latest addr for its
|
|
58
|
+
* b2tfs_item timeline as known by the given space.
|
|
59
|
+
*/
|
|
60
|
+
export async function logBranchState({
|
|
61
|
+
label,
|
|
62
|
+
branchIbGib,
|
|
63
|
+
space,
|
|
64
|
+
}: {
|
|
65
|
+
label: string;
|
|
66
|
+
branchIbGib: B2tFSBranchIbGib_V1 | IbGib_V1 | undefined;
|
|
67
|
+
space?: IbGibSpaceAny;
|
|
68
|
+
}): Promise<void> {
|
|
69
|
+
const lc = `[logBranchState][${label}]`;
|
|
70
|
+
|
|
71
|
+
if (!branchIbGib) {
|
|
72
|
+
console.log(`${lc} branchIbGib: (UNDEFINED)`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const b = branchIbGib as B2tFSBranchIbGib_V1;
|
|
77
|
+
const addr = getIbGibAddr({ ibGib: b });
|
|
78
|
+
const n = (b.data as any)?.n ?? '?';
|
|
79
|
+
const name = b.data?.name ?? '?';
|
|
80
|
+
const tjpAddr = b.rel8ns?.tjp?.at(0);
|
|
81
|
+
const itemAddrs = b.rel8ns?.b2tfs_item ?? [];
|
|
82
|
+
|
|
83
|
+
console.log(`${lc}
|
|
84
|
+
addr : ${shortAddr(addr)}
|
|
85
|
+
name : ${name}
|
|
86
|
+
n : ${n}
|
|
87
|
+
tjp : ${shortAddr(tjpAddr)}
|
|
88
|
+
b2tfs_item: ${shortAddrs(itemAddrs)} (${itemAddrs.length} total, tip: ${shortAddr(itemAddrs.at(-1))})`);
|
|
89
|
+
|
|
90
|
+
if (space && tjpAddr) {
|
|
91
|
+
try {
|
|
92
|
+
const res = await getLatestAddrs({ addrs: [tjpAddr], space });
|
|
93
|
+
const latest = (res?.data as any)?.latestAddrs?.[0];
|
|
94
|
+
console.log(` [space ${(space as any).ib ?? space.constructor.name}] latestAddr for branch TJP: ${shortAddr(latest)}`);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.log(` [space] getLatestAddrs for branch TJP THREW: ${e}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Logs a compact snapshot of a b2tfs item ibgib and optionally the latest addr
|
|
103
|
+
* for its timeline as known by the given space.
|
|
104
|
+
*/
|
|
105
|
+
export async function logItemState({
|
|
106
|
+
label,
|
|
107
|
+
itemIbGib,
|
|
108
|
+
space,
|
|
109
|
+
}: {
|
|
110
|
+
label: string;
|
|
111
|
+
itemIbGib: B2tFSItemIbGib_V1 | IbGib_V1 | undefined;
|
|
112
|
+
space?: IbGibSpaceAny;
|
|
113
|
+
}): Promise<void> {
|
|
114
|
+
const lc = `[logItemState][${label}]`;
|
|
115
|
+
|
|
116
|
+
if (!itemIbGib) {
|
|
117
|
+
console.log(`${lc} itemIbGib: (UNDEFINED)`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const item = itemIbGib as B2tFSItemIbGib_V1;
|
|
122
|
+
const addr = getIbGibAddr({ ibGib: item });
|
|
123
|
+
const n = (item.data as any)?.n ?? '?';
|
|
124
|
+
const fsType = item.data?.fsType ?? '?';
|
|
125
|
+
const filename = item.data?.filename ?? item.data?.name ?? '?';
|
|
126
|
+
const tjpAddr = item.rel8ns?.tjp?.at(0);
|
|
127
|
+
const textContent = (item.data as any)?.textContent as string | undefined;
|
|
128
|
+
const binRel8n = (item.rel8ns as any)?.bin ?? (item.rel8ns as any)?.binary;
|
|
129
|
+
|
|
130
|
+
console.log(`${lc}
|
|
131
|
+
addr : ${shortAddr(addr)}
|
|
132
|
+
filename : ${filename} fsType: ${fsType}
|
|
133
|
+
n : ${n}
|
|
134
|
+
tjp : ${shortAddr(tjpAddr)}
|
|
135
|
+
text : ${textContent !== undefined ? JSON.stringify(textContent.slice(0, 120)) : binRel8n ? `(binary, bin rel8n: ${shortAddrs(binRel8n)})` : '(none)'}`);
|
|
136
|
+
|
|
137
|
+
if (space && tjpAddr) {
|
|
138
|
+
try {
|
|
139
|
+
const res = await getLatestAddrs({ addrs: [tjpAddr], space });
|
|
140
|
+
const latest = (res?.data as any)?.latestAddrs?.[0];
|
|
141
|
+
console.log(` [space ${(space as any).ib ?? space.constructor.name}] latestAddr for item TJP: ${shortAddr(latest)}`);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.log(` [space] getLatestAddrs for item TJP THREW: ${e}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Logs a one-line separator to make log sections visually scannable.
|
|
150
|
+
*/
|
|
151
|
+
export function logCheckpoint(label: string): void {
|
|
152
|
+
const bar = '─'.repeat(60);
|
|
153
|
+
console.log(`\n${bar}\n CHECKPOINT: ${label}\n${bar}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Logs the full addr (no shortening) for debugging addr comparison issues.
|
|
158
|
+
*/
|
|
159
|
+
export function logAddr(label: string, addr: IbGibAddr | undefined): void {
|
|
160
|
+
console.log(`[ADDR][${label}]: ${addr ?? '(undefined)'}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Logs a set of addrs with short formatting.
|
|
165
|
+
*/
|
|
166
|
+
export function logAddrs(label: string, addrs: IbGibAddr[] | undefined): void {
|
|
167
|
+
console.log(`[ADDRS][${label}]: ${shortAddrs(addrs)}`);
|
|
168
|
+
}
|