@ibgib/vcs-gib 0.0.34 → 0.0.39

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 (37) hide show
  1. package/VCS_CLI_USER_GUIDE.md +23 -7
  2. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  3. package/dist/AUTO-GENERATED-version.mjs +1 -1
  4. package/dist/bootstrap/node-bootstrap.d.mts +5 -0
  5. package/dist/bootstrap/node-bootstrap.d.mts.map +1 -1
  6. package/dist/bootstrap/node-bootstrap.mjs +4 -0
  7. package/dist/bootstrap/node-bootstrap.mjs.map +1 -1
  8. package/dist/cli/commands/cli-checkout-cmd.mjs +5 -5
  9. package/dist/cli/commands/cli-checkout-cmd.mjs.map +1 -1
  10. package/dist/cli/commands/cli-init-cmd.d.mts.map +1 -1
  11. package/dist/cli/commands/cli-init-cmd.mjs +16 -1
  12. package/dist/cli/commands/cli-init-cmd.mjs.map +1 -1
  13. package/dist/cli/commands/cli-log-cmd.d.mts +2 -1
  14. package/dist/cli/commands/cli-log-cmd.d.mts.map +1 -1
  15. package/dist/cli/commands/cli-log-cmd.mjs +23 -10
  16. package/dist/cli/commands/cli-log-cmd.mjs.map +1 -1
  17. package/dist/cli/commands/cli-merge-cmd.d.mts.map +1 -1
  18. package/dist/cli/commands/cli-merge-cmd.mjs +9 -11
  19. package/dist/cli/commands/cli-merge-cmd.mjs.map +1 -1
  20. package/dist/cli/vcs-cli-main.d.mts.map +1 -1
  21. package/dist/cli/vcs-cli-main.mjs +24 -1
  22. package/dist/cli/vcs-cli-main.mjs.map +1 -1
  23. package/dist/cli/vcs-repo-helper.d.mts.map +1 -1
  24. package/dist/cli/vcs-repo-helper.mjs +6 -2
  25. package/dist/cli/vcs-repo-helper.mjs.map +1 -1
  26. package/package.json +3 -3
  27. package/src/AUTO-GENERATED-version.mts +1 -1
  28. package/src/bootstrap/node-bootstrap.mts +10 -0
  29. package/src/cli/cli-e2e.respec.mts +151 -0
  30. package/src/cli/commands/cli-checkout-cmd.mts +5 -5
  31. package/src/cli/commands/cli-init-cmd.mts +17 -1
  32. package/src/cli/commands/cli-log-cmd.mts +27 -9
  33. package/src/cli/commands/cli-merge-cmd.mts +9 -11
  34. package/src/cli/vcs-cli-main.mts +21 -1
  35. package/src/cli/vcs-repo-helper.mts +7 -2
  36. package/tsconfig.test.tsbuildinfo +1 -1
  37. package/tsconfig.tsbuildinfo +1 -1
@@ -37,6 +37,10 @@ export interface BootstrapVcsMetaspaceOpts {
37
37
  * Default space name for non-interactive headless testing. Defaults to 'vcs_space'
38
38
  */
39
39
  defaultSpaceName?: string;
40
+ /**
41
+ * If true, throws if .vcsgib is not found during upward discovery without initializing spaces.
42
+ */
43
+ mustExist?: boolean;
40
44
  }
41
45
 
42
46
  export interface BootstrapVcsMetaspaceResult {
@@ -44,6 +48,7 @@ export interface BootstrapVcsMetaspaceResult {
44
48
  localSpace: IbGibSpaceAny;
45
49
  vcsPath: string;
46
50
  repoRoot: string;
51
+ foundVcsPath?: string;
47
52
  }
48
53
 
49
54
  /**
@@ -94,6 +99,10 @@ export async function bootstrapVcsMetaspace(
94
99
  currentDir = parentDir;
95
100
  }
96
101
 
102
+ if (opts.mustExist && !foundVcsPath) {
103
+ throw new Error(`fatal: not an ibgib repository (or any of the parent directories): ${vcsFolderName}`);
104
+ }
105
+
97
106
  const vcsPath = foundVcsPath || path.resolve(repoPath, vcsFolderName);
98
107
  if (!foundVcsPath) {
99
108
  repoRoot = path.resolve(repoPath);
@@ -181,6 +190,7 @@ export async function bootstrapVcsMetaspace(
181
190
  localSpace,
182
191
  vcsPath,
183
192
  repoRoot,
193
+ foundVcsPath,
184
194
  };
185
195
  } catch (error: any) {
186
196
  console.error(`${lc} ${error?.message || error} (E: genuuid)`);
@@ -21,6 +21,7 @@ import { executeCommitCmd } from './commands/cli-commit-cmd.mjs';
21
21
  import { executeLogCmd } from './commands/cli-log-cmd.mjs';
22
22
  import { executeBranchCmd } from './commands/cli-branch-cmd.mjs';
23
23
  import { executeCheckoutCmd } from './commands/cli-checkout-cmd.mjs';
24
+ import { executeMergeCmd } from './commands/cli-merge-cmd.mjs';
24
25
  import { bootstrapVcsMetaspace } from '../bootstrap/node-bootstrap.mjs';
25
26
  import { getB2tFSIndexIbGib } from '@ibgib/core-gib/dist/vcs/index/index-helper.mjs';
26
27
 
@@ -316,4 +317,154 @@ await respecfully(lc, `Full E2E CLI Integration Spec`, async () => {
316
317
  }
317
318
  });
318
319
 
320
+ await ifWe(lc, `fails gracefully with fatal error message on uninitialized repository`, async () => {
321
+ const uninitDir = path.join(process.cwd(), `.test_cli_e2e_uninit_${Date.now()}`);
322
+ await fs.mkdir(uninitDir, { recursive: true });
323
+
324
+ const origCwd = process.cwd();
325
+ const origError = console.error;
326
+ let capturedErrors: string[] = [];
327
+
328
+ console.error = (...args: any[]) => {
329
+ capturedErrors.push(args.join(' '));
330
+ };
331
+
332
+ try {
333
+ process.chdir(uninitDir);
334
+
335
+ // Execute status, commit, log, branch, checkout in uninitialized directory
336
+ const statusExitCode = await main({ argv: ['status'] });
337
+ iReckon(lc, statusExitCode).willEqual(1);
338
+
339
+ const commitExitCode = await main({ argv: ['commit', '-m', 'test'] });
340
+ iReckon(lc, commitExitCode).willEqual(1);
341
+
342
+ const logExitCode = await main({ argv: ['log'] });
343
+ iReckon(lc, logExitCode).willEqual(1);
344
+
345
+ const branchExitCode = await main({ argv: ['branch'] });
346
+ iReckon(lc, branchExitCode).willEqual(1);
347
+
348
+ const checkoutExitCode = await main({ argv: ['checkout', 'main'] });
349
+ iReckon(lc, checkoutExitCode).willEqual(1);
350
+
351
+ // Verify clean error messages without storage stack traces
352
+ const hasFatalMsg = capturedErrors.some(e => e.includes('fatal: not an ibgib repository'));
353
+ iReckon(lc, hasFatalMsg).isGonnaBeTrue();
354
+
355
+ const hasStorageError = capturedErrors.some(e => e.includes('couldn\'t get special'));
356
+ iReckon(lc, hasStorageError).isGonnaBeFalse();
357
+ } finally {
358
+ process.chdir(origCwd);
359
+ console.error = origError;
360
+ await fs.rm(uninitDir, { recursive: true, force: true });
361
+ }
362
+ });
363
+
364
+ await ifWe(lc, `preserves worktree state when checkout is executed from within secondary worktree`, async () => {
365
+ const testDir = path.join(process.cwd(), `.test_cli_e2e_worktree_${Date.now()}`);
366
+ const vcsFolderName = '.vcsgib';
367
+
368
+ const origCwd = process.cwd();
369
+
370
+ try {
371
+ process.chdir(testDir);
372
+ } catch {
373
+ await fs.mkdir(testDir, { recursive: true });
374
+ process.chdir(testDir);
375
+ }
376
+
377
+ try {
378
+ // 1. Init main repo
379
+ await executeInitCmd({
380
+ targetDir: testDir,
381
+ vcsFolderName,
382
+ quiet: true,
383
+ });
384
+
385
+ await fs.writeFile(path.join(testDir, 'main.txt'), 'content on main\n', 'utf8');
386
+ await executeAddCmd({ targetDir: testDir, vcsFolderName, all: true, quiet: true });
387
+ await executeCommitCmd({ targetDir: testDir, vcsFolderName, message: 'Commit on main', quiet: true });
388
+
389
+ // 2. Create feature branch
390
+ await executeBranchCmd({
391
+ targetDir: testDir,
392
+ vcsFolderName,
393
+ branchName: 'feature-strings',
394
+ quiet: true,
395
+ });
396
+
397
+ const worktreeDir = path.join(testDir, 'b', 'feature-strings');
398
+ await fs.mkdir(worktreeDir, { recursive: true });
399
+ const worktreeFile = path.join(worktreeDir, 'feature.txt');
400
+ await fs.writeFile(worktreeFile, 'content on feature branch\n', 'utf8');
401
+
402
+ // 3. Commit within secondary worktree
403
+ await executeAddCmd({ targetDir: worktreeDir, vcsFolderName, all: true, quiet: true });
404
+ await executeCommitCmd({ targetDir: worktreeDir, vcsFolderName, message: 'Commit on feature', quiet: true });
405
+
406
+ // 4. Run checkout main from within the secondary worktree directory
407
+ process.chdir(worktreeDir);
408
+ const checkoutRes = await executeCheckoutCmd({
409
+ targetDir: worktreeDir,
410
+ vcsFolderName,
411
+ branchName: 'main',
412
+ quiet: true,
413
+ });
414
+
415
+ iReckon(lc, checkoutRes.currentBranch).willEqual('main');
416
+
417
+ // 5. Verify feature.txt still exists untouched in b/feature-strings
418
+ const featureFileExists = await fs.stat(worktreeFile).then(s => s.isFile()).catch(() => false);
419
+ iReckon(lc, featureFileExists).isGonnaBeTrue();
420
+
421
+ const featureContent = await fs.readFile(worktreeFile, 'utf8');
422
+ iReckon(lc, featureContent).willEqual('content on feature branch\n');
423
+
424
+ // 6. Verify log from inside worktree succeeds and reflects feature branch history
425
+ const logRes = await executeLogCmd({
426
+ targetDir: worktreeDir,
427
+ vcsFolderName,
428
+ quiet: true,
429
+ });
430
+ iReckon(lc, logRes.branchName).willEqual('feature-strings');
431
+ iReckon(lc, logRes.commits.length).willEqual(2);
432
+ iReckon(lc, logRes.commits[0].message).willEqual('Commit on feature');
433
+ iReckon(lc, logRes.commits[1].message).willEqual('Commit on main');
434
+
435
+ // 7. Verify explicit -b flag logs feature-strings from repo root
436
+ const rootLogExplicitBranchRes = await executeLogCmd({
437
+ targetDir: testDir,
438
+ vcsFolderName,
439
+ branchName: 'feature-strings',
440
+ quiet: true,
441
+ });
442
+ iReckon(lc, rootLogExplicitBranchRes.branchName).willEqual('feature-strings');
443
+ iReckon(lc, rootLogExplicitBranchRes.commits.length).willEqual(2);
444
+ iReckon(lc, rootLogExplicitBranchRes.commits[0].message).willEqual('Commit on feature');
445
+ iReckon(lc, rootLogExplicitBranchRes.commits[1].message).willEqual('Commit on main');
446
+
447
+ // 8. Merge feature-strings into main and verify log on main
448
+ process.chdir(testDir);
449
+ await executeMergeCmd({
450
+ targetDir: testDir,
451
+ vcsFolderName,
452
+ incomingBranchName: 'feature-strings',
453
+ });
454
+
455
+ const mainLogAfterMergeRes = await executeLogCmd({
456
+ targetDir: testDir,
457
+ vcsFolderName,
458
+ quiet: true,
459
+ });
460
+ iReckon(lc, mainLogAfterMergeRes.branchName).willEqual('main');
461
+ iReckon(lc, mainLogAfterMergeRes.commits.length >= 2).isGonnaBeTrue();
462
+ iReckon(lc, mainLogAfterMergeRes.commits[0].message).willEqual('Commit on feature');
463
+ iReckon(lc, mainLogAfterMergeRes.commits[1].message).willEqual('Commit on main');
464
+ } finally {
465
+ process.chdir(origCwd);
466
+ await fs.rm(testDir, { recursive: true, force: true });
467
+ }
468
+ });
469
+
319
470
  });
@@ -37,11 +37,11 @@ export async function executeCheckoutCmd({
37
37
  }: CheckoutCmdOpts = {}): Promise<CheckoutCmdResult> {
38
38
  const lc = `${lcFile}[${executeCheckoutCmd.name}]`;
39
39
  try {
40
- if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
40
+ if (logalot) { console.log(`${lc} starting... (I: 1cd9d2565ff16c18e82d7802ea033226)`); }
41
41
 
42
42
  const trimmedBranchName = branchName?.trim();
43
43
  if (!trimmedBranchName) {
44
- throw new Error(`Branch name is required for checkout (E: genuuid)`);
44
+ throw new Error(`Branch name is required for checkout (E: 16edcde284b7fdfb05547c829fda5d26)`);
45
45
  }
46
46
 
47
47
  const absTargetDir = path.resolve(targetDir);
@@ -54,7 +54,7 @@ export async function executeCheckoutCmd({
54
54
  });
55
55
 
56
56
  if (!quiet) {
57
- console.log(`Switched to branch '${checkoutRes.currentBranch}' (I: genuuid)`);
57
+ console.log(`Switched to branch '${checkoutRes.currentBranch}' (I: f73f287d3271149058e5ee9851392726)`);
58
58
  console.log(`Worktree path: ${checkoutRes.worktreePath}`);
59
59
  }
60
60
 
@@ -64,9 +64,9 @@ export async function executeCheckoutCmd({
64
64
  worktreePath: checkoutRes.worktreePath,
65
65
  };
66
66
  } catch (error) {
67
- console.error(`${lc} ${extractErrorMsg(error)} (E: genuuid)`);
67
+ console.error(`${lc} ${extractErrorMsg(error)} (E: 008fbfd6f9f8eb496854b4edc55ea726)`);
68
68
  throw error;
69
69
  } finally {
70
- if (logalot) { console.log(`${lc} complete. (I: genuuid)`); }
70
+ if (logalot) { console.log(`${lc} complete. (I: d515f834dd18286f2fe1871605888c26)`); }
71
71
  }
72
72
  }
@@ -14,6 +14,7 @@ import { B2TFS_BRANCH_DEFAULT_NAME } from '@ibgib/core-gib/dist/vcs/branch/branc
14
14
 
15
15
  import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
16
16
  import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
17
+ import { getB2tFSIndexIbGib } from '@ibgib/core-gib/dist/vcs/index/index-helper.mjs';
17
18
  import { writeVcsIndexProjectionToDisk } from '../vcs-projection-helper.mjs';
18
19
 
19
20
  const lcFile = `[init-cmd]`;
@@ -46,12 +47,27 @@ export async function executeInitCmd({
46
47
  await fs.mkdir(absTargetDir, { recursive: true });
47
48
 
48
49
  // 2. Bootstrap Metaspace and localSpace (creates .vcsgib on disk)
49
- const { metaspace, localSpace } = await bootstrapVcsMetaspace({
50
+ const { metaspace, localSpace, foundVcsPath } = await bootstrapVcsMetaspace({
50
51
  repoPath: absTargetDir,
51
52
  vcsFolderName,
52
53
  defaultSpaceName: `vcs_${path.basename(absTargetDir).replace(/[^a-zA-Z0-9_\-]/g, '_')}`,
53
54
  });
54
55
 
56
+ if (foundVcsPath) {
57
+ const existingIndex = await getB2tFSIndexIbGib({ space: localSpace, metaspace });
58
+ if (existingIndex) {
59
+ if (!quiet) {
60
+ console.log(`Reinitialized existing ibgib repository in ${vcsPath}`);
61
+ }
62
+ return {
63
+ vcsPath,
64
+ spaceId: existingIndex.data.activeSpaceId,
65
+ branchName: existingIndex.data.activeBranchName || defaultBranchName,
66
+ rootItemIb: '',
67
+ };
68
+ }
69
+ }
70
+
55
71
  // 3. Facade instantiation and domain init
56
72
  const repo = new VcsRepository({
57
73
  metaspace,
@@ -9,7 +9,7 @@ import path from 'node:path';
9
9
  import fs from 'node:fs/promises';
10
10
 
11
11
  import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
12
- // import { getSpaceIdFromIb } from '@ibgib/core-gib/dist/witness/space/metaspace/metaspace-innerspace/metaspace-innerspace-helper.mjs';
12
+ import { getAllBranchInfosFromIndex } from '@ibgib/core-gib/dist/vcs/branch/branch-helper.mjs';
13
13
 
14
14
  import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
15
15
  import { openVcsRepository } from '../vcs-repo-helper.mjs';
@@ -33,6 +33,7 @@ export interface LogCmdOpts {
33
33
  targetDir?: string;
34
34
  vcsFolderName?: string;
35
35
  filePath?: string;
36
+ branchName?: string;
36
37
  allComments?: boolean;
37
38
  quiet?: boolean;
38
39
  }
@@ -47,6 +48,7 @@ export async function executeLogCmd({
47
48
  targetDir = process.cwd(),
48
49
  vcsFolderName = '.vcsgib',
49
50
  filePath,
51
+ branchName,
50
52
  allComments = false,
51
53
  quiet = false,
52
54
  }: LogCmdOpts = {}): Promise<LogCmdResult> {
@@ -54,19 +56,35 @@ export async function executeLogCmd({
54
56
 
55
57
  try {
56
58
  if (logalot) {
57
- console.log(`${lc} starting executeLogCmd (I: genuuid)`);
59
+ console.log(`${lc} starting executeLogCmd (I: c6920dc083bff49d8b4dd148a36de826)`);
58
60
  }
59
61
 
60
62
  const absTargetDir = path.resolve(targetDir);
61
63
  const { repo } = await openVcsRepository({ targetDir: absTargetDir, vcsFolderName });
62
64
  const indexIbGib = await repo.getIndexIbGib();
63
- const activeBranchSpace = await repo.getActiveBranchSpace(indexIbGib);
65
+
66
+ let resolvedBranchName = branchName;
67
+ let resolvedFilePath = filePath;
68
+
69
+ if (!resolvedBranchName && resolvedFilePath) {
70
+ const branchInfos = await getAllBranchInfosFromIndex({ indexIbGib });
71
+ if (branchInfos.some(b => b.branchName === resolvedFilePath)) {
72
+ resolvedBranchName = resolvedFilePath;
73
+ resolvedFilePath = undefined;
74
+ }
75
+ }
76
+
77
+ const activeBranchSpace = await repo.getActiveBranchSpace({
78
+ indexIbGib,
79
+ targetDir: absTargetDir,
80
+ branchName: resolvedBranchName,
81
+ });
64
82
 
65
83
  const logRes = await repo.log({
66
84
  activeBranchSpace,
67
85
  indexIbGib,
68
- // repoRoot, // need this? we need jsdocs on wth repoRoot is
69
- filePath,
86
+ branchName: resolvedBranchName,
87
+ filePath: resolvedFilePath,
70
88
  allComments,
71
89
  targetDir: absTargetDir,
72
90
  });
@@ -75,7 +93,7 @@ export async function executeLogCmd({
75
93
 
76
94
  if (!quiet) {
77
95
  if (logRes.targetPath) {
78
- console.log(`File: ${logRes.targetPath} (branch: ${logRes.branchName}) (I: genuuid)`);
96
+ console.log(`File: ${logRes.targetPath} (branch: ${logRes.branchName}) (I: 5b56b87941a81ed7e9036f382ca80326)`);
79
97
  if (commits.length === 0) {
80
98
  console.log('No comments found for this file.');
81
99
  } else {
@@ -86,7 +104,7 @@ export async function executeLogCmd({
86
104
  }
87
105
  }
88
106
  } else {
89
- console.log(`Branch: ${logRes.branchName} (I: genuuid)`);
107
+ console.log(`Branch: ${logRes.branchName} (I: da4a28fe715c302be839ad08d9f0ca26)`);
90
108
  if (commits.length === 0) {
91
109
  console.log('No commits yet.');
92
110
  } else {
@@ -116,11 +134,11 @@ export async function executeLogCmd({
116
134
  commits,
117
135
  };
118
136
  } catch (error) {
119
- console.error(`${lc} ${extractErrorMsg(error)} (E: genuuid)`);
137
+ console.error(`${lc} ${extractErrorMsg(error)} (E: f279d589ced70f7e78050948f2191e26)`);
120
138
  throw error;
121
139
  } finally {
122
140
  if (logalot) {
123
- console.log(`${lc} complete. (I: genuuid)`);
141
+ console.log(`${lc} complete. (I: 081e084ef158f7a608fd16a801bedf26)`);
124
142
  }
125
143
  }
126
144
  }
@@ -42,40 +42,38 @@ export async function executeMergeCmd({
42
42
  const lc = `${lcFile}[${executeMergeCmd.name}]`;
43
43
  try {
44
44
  if (logalot) {
45
- console.log(`${lc} starting... (I: genuuid)`);
45
+ console.log(`${lc} starting... (I: d14fb7ac187875d165abcbf8fe51ce26)`);
46
46
  }
47
47
 
48
48
  const trimmedIncomingBranchName = incomingBranchName?.trim();
49
49
  if (!trimmedIncomingBranchName) {
50
- throw new Error(`Branch name is required for merge (E: genuuid)`);
50
+ throw new Error(`Branch name is required for merge (E: f47fd65e49a8518da859217ae1bef826)`);
51
51
  }
52
52
 
53
53
  const absTargetDir = path.resolve(targetDir);
54
54
  const { repo, vcsPath, activeWorktreeDir } =
55
55
  await openVcsRepository({ targetDir: absTargetDir, vcsFolderName });
56
56
 
57
- // todo: get baseBranchName based off of targetDir?
58
- const baseBranchName = await repo.getActiveBranchName();
57
+ const indexIbGib = await repo.getIndexIbGib();
58
+ const baseBranchName = await repo.getActiveBranchName({ indexIbGib, targetDir: absTargetDir });
59
59
 
60
60
  const incomingSpaceRes = await getOrCreateBranchSpace({
61
61
  metaspace: repo.metaspace,
62
62
  branchName: trimmedIncomingBranchName,
63
63
  });
64
64
 
65
- const activeBranchSpace = await repo.getActiveBranchSpace();
65
+ const activeBranchSpace = await repo.getActiveBranchSpace({ indexIbGib, targetDir: absTargetDir });
66
66
  const activeBranchSpaceId = getSpaceIdFromIb(activeBranchSpace.ib);
67
67
 
68
68
  const mergeRes = await repo.merge({
69
- baseBranchSpace: activeBranchSpace, // what should this be? always the active one? Or does the working dir determine it? What is absTargetDir?
70
- // message,
71
- // repoRoot,
69
+ baseBranchSpace: activeBranchSpace,
72
70
  incomingBranchName: trimmedIncomingBranchName,
73
71
  incomingBranchSpace: incomingSpaceRes.space,
74
72
  targetDir: absTargetDir,
75
73
  });
76
74
 
77
75
  if (!quiet) {
78
- console.log(`Successfully merged branch '${trimmedIncomingBranchName}' into '${baseBranchName}' (I: genuuid)`);
76
+ console.log(`Successfully merged branch '${trimmedIncomingBranchName}' into '${baseBranchName}' (I: 943c67d40bb8a3177d470da667571726)`);
79
77
  console.log(` Worktree projection: ${activeWorktreeDir}`);
80
78
  }
81
79
 
@@ -89,11 +87,11 @@ export async function executeMergeCmd({
89
87
  worktreePath: activeWorktreeDir,
90
88
  };
91
89
  } catch (error) {
92
- console.error(`${lc} ${extractErrorMsg(error)} (E: genuuid)`);
90
+ console.error(`${lc} ${extractErrorMsg(error)} (E: acfb283f49f8e533586e0578ac9d4426)`);
93
91
  throw error;
94
92
  } finally {
95
93
  if (logalot) {
96
- console.log(`${lc} complete. (I: genuuid)`);
94
+ console.log(`${lc} complete. (I: 67210ef7c05454c19cdb6b08149b7326)`);
97
95
  }
98
96
  }
99
97
  }
@@ -126,9 +126,11 @@ export async function main({
126
126
  case VCS_SUBCOMMANDS.LOG: {
127
127
  const targetDir = process.cwd();
128
128
  const filePath = parsed.positionals[0];
129
+ const branchName = parsed.flags.branchName;
129
130
  await executeLogCmd({
130
131
  targetDir,
131
132
  filePath,
133
+ branchName,
132
134
  allComments: parsed.flags.all,
133
135
  });
134
136
  break;
@@ -183,7 +185,25 @@ export async function main({
183
185
 
184
186
  return 0;
185
187
  } catch (error) {
186
- console.error(`Error: ${extractErrorMsg(error)} (E: genuuid)`);
188
+ let rawMsg = extractErrorMsg(error);
189
+ // Strip trailing internal UUID / error markers like (E: ...)
190
+ const cleanMsg = rawMsg.replace(/\s*\(E:\s*[a-f0-9-]+\)\s*$/i, '').trim();
191
+
192
+ if (cleanMsg.startsWith('fatal:') || cleanMsg.startsWith('error:')) {
193
+ console.error(cleanMsg);
194
+ } else if (cleanMsg.toLowerCase().includes('nothing to commit')) {
195
+ console.error('nothing to commit, working tree clean');
196
+ } else if (cleanMsg.toLowerCase().includes('already exists')) {
197
+ const branchMatch = cleanMsg.match(/["']([^"']+)["']/);
198
+ const name = branchMatch ? branchMatch[1] : '';
199
+ console.error(name ? `fatal: a branch named '${name}' already exists.` : `fatal: ${cleanMsg}`);
200
+ } else if (cleanMsg.toLowerCase().includes('not found in indexibgib') || cleanMsg.toLowerCase().includes('not found on branch')) {
201
+ const branchMatch = cleanMsg.match(/\(([^)]+)\)/) || cleanMsg.match(/['"]([^'"]+)['"]/);
202
+ const name = branchMatch ? branchMatch[1] : '';
203
+ console.error(name ? `error: pathspec '${name}' did not match any file(s) known to ibgib` : `error: ${cleanMsg}`);
204
+ } else {
205
+ console.error(`fatal: ${cleanMsg}`);
206
+ }
187
207
  return 1;
188
208
  } finally {
189
209
  if (logalot) {
@@ -45,15 +45,20 @@ export async function openVcsRepository({
45
45
  const absTargetDir = path.resolve(targetDir);
46
46
 
47
47
  // 1. Locate repository root and bootstrap Node localSpace
48
- const { metaspace, localSpace, vcsPath, repoRoot } = await bootstrapVcsMetaspace({
48
+ const { metaspace, localSpace, vcsPath, repoRoot, foundVcsPath } = await bootstrapVcsMetaspace({
49
49
  repoPath: absTargetDir,
50
50
  vcsFolderName,
51
+ mustExist: true,
51
52
  });
52
53
 
54
+ if (!foundVcsPath) {
55
+ throw new Error(`fatal: not an ibgib repository (or any of the parent directories): ${vcsFolderName}`);
56
+ }
57
+
53
58
  // 2. Fetch authoritative in-band index from localSpace
54
59
  const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, metaspace });
55
60
  if (!indexIbGib) {
56
- throw new Error(`Not an ibgib repository (index not found in ${vcsPath})`);
61
+ throw new Error(`fatal: not an ibgib repository (index not found in ${vcsPath})`);
57
62
  }
58
63
 
59
64
  // 3. Determine active branch and worktree directory