@ibgib/vcs-gib 0.0.6
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/README.md +94 -0
- package/VCS_CLI_USER_GUIDE.md +198 -0
- package/dist/AUTO-GENERATED-version.d.mts +6 -0
- package/dist/AUTO-GENERATED-version.d.mts.map +1 -0
- package/dist/AUTO-GENERATED-version.mjs +6 -0
- package/dist/AUTO-GENERATED-version.mjs.map +1 -0
- package/dist/assumptions.respec.d.mts +2 -0
- package/dist/assumptions.respec.d.mts.map +1 -0
- package/dist/assumptions.respec.mjs +41 -0
- package/dist/assumptions.respec.mjs.map +1 -0
- package/dist/bootstrap/node-bootstrap.d.mts +35 -0
- package/dist/bootstrap/node-bootstrap.d.mts.map +1 -0
- package/dist/bootstrap/node-bootstrap.mjs +165 -0
- package/dist/bootstrap/node-bootstrap.mjs.map +1 -0
- package/dist/bootstrap/node-bootstrap.respec.node.d.mts +7 -0
- package/dist/bootstrap/node-bootstrap.respec.node.d.mts.map +1 -0
- package/dist/bootstrap/node-bootstrap.respec.node.mjs +28 -0
- package/dist/bootstrap/node-bootstrap.respec.node.mjs.map +1 -0
- package/dist/cli/cli-arg-parser.d.mts +27 -0
- package/dist/cli/cli-arg-parser.d.mts.map +1 -0
- package/dist/cli/cli-arg-parser.mjs +96 -0
- package/dist/cli/cli-arg-parser.mjs.map +1 -0
- package/dist/cli/cli-arg-parser.respec.node.d.mts +7 -0
- package/dist/cli/cli-arg-parser.respec.node.d.mts.map +1 -0
- package/dist/cli/cli-arg-parser.respec.node.mjs +35 -0
- package/dist/cli/cli-arg-parser.respec.node.mjs.map +1 -0
- package/dist/cli/cli-constants.d.mts +26 -0
- package/dist/cli/cli-constants.d.mts.map +1 -0
- package/dist/cli/cli-constants.mjs +67 -0
- package/dist/cli/cli-constants.mjs.map +1 -0
- package/dist/cli/cli-e2e.respec.node.d.mts +8 -0
- package/dist/cli/cli-e2e.respec.node.d.mts.map +1 -0
- package/dist/cli/cli-e2e.respec.node.mjs +263 -0
- package/dist/cli/cli-e2e.respec.node.mjs.map +1 -0
- package/dist/cli/commands/add-cmd.d.mts +20 -0
- package/dist/cli/commands/add-cmd.d.mts.map +1 -0
- package/dist/cli/commands/add-cmd.mjs +333 -0
- package/dist/cli/commands/add-cmd.mjs.map +1 -0
- package/dist/cli/commands/branch-checkout-cmd.respec.node.d.mts +7 -0
- package/dist/cli/commands/branch-checkout-cmd.respec.node.d.mts.map +1 -0
- package/dist/cli/commands/branch-checkout-cmd.respec.node.mjs +288 -0
- package/dist/cli/commands/branch-checkout-cmd.respec.node.mjs.map +1 -0
- package/dist/cli/commands/branch-cmd.d.mts +25 -0
- package/dist/cli/commands/branch-cmd.d.mts.map +1 -0
- package/dist/cli/commands/branch-cmd.mjs +187 -0
- package/dist/cli/commands/branch-cmd.mjs.map +1 -0
- package/dist/cli/commands/checkout-cmd.d.mts +21 -0
- package/dist/cli/commands/checkout-cmd.d.mts.map +1 -0
- package/dist/cli/commands/checkout-cmd.mjs +154 -0
- package/dist/cli/commands/checkout-cmd.mjs.map +1 -0
- package/dist/cli/commands/commit-cmd.d.mts +23 -0
- package/dist/cli/commands/commit-cmd.d.mts.map +1 -0
- package/dist/cli/commands/commit-cmd.mjs +326 -0
- package/dist/cli/commands/commit-cmd.mjs.map +1 -0
- package/dist/cli/commands/commit-log-cmd.respec.node.d.mts +7 -0
- package/dist/cli/commands/commit-log-cmd.respec.node.d.mts.map +1 -0
- package/dist/cli/commands/commit-log-cmd.respec.node.mjs +251 -0
- package/dist/cli/commands/commit-log-cmd.respec.node.mjs.map +1 -0
- package/dist/cli/commands/init-cmd.d.mts +20 -0
- package/dist/cli/commands/init-cmd.d.mts.map +1 -0
- package/dist/cli/commands/init-cmd.mjs +119 -0
- package/dist/cli/commands/init-cmd.mjs.map +1 -0
- package/dist/cli/commands/init-cmd.respec.node.d.mts +7 -0
- package/dist/cli/commands/init-cmd.respec.node.d.mts.map +1 -0
- package/dist/cli/commands/init-cmd.respec.node.mjs +40 -0
- package/dist/cli/commands/init-cmd.respec.node.mjs.map +1 -0
- package/dist/cli/commands/log-cmd.d.mts +27 -0
- package/dist/cli/commands/log-cmd.d.mts.map +1 -0
- package/dist/cli/commands/log-cmd.mjs +146 -0
- package/dist/cli/commands/log-cmd.mjs.map +1 -0
- package/dist/cli/commands/status-add-cmd.respec.node.d.mts +7 -0
- package/dist/cli/commands/status-add-cmd.respec.node.d.mts.map +1 -0
- package/dist/cli/commands/status-add-cmd.respec.node.mjs +215 -0
- package/dist/cli/commands/status-add-cmd.respec.node.mjs.map +1 -0
- package/dist/cli/commands/status-cmd.d.mts +25 -0
- package/dist/cli/commands/status-cmd.d.mts.map +1 -0
- package/dist/cli/commands/status-cmd.mjs +286 -0
- package/dist/cli/commands/status-cmd.mjs.map +1 -0
- package/dist/cli/vcs-cli-main.d.mts +14 -0
- package/dist/cli/vcs-cli-main.d.mts.map +1 -0
- package/dist/cli/vcs-cli-main.mjs +136 -0
- package/dist/cli/vcs-cli-main.mjs.map +1 -0
- package/dist/engine/branch/branch-graph-helper.d.mts +19 -0
- package/dist/engine/branch/branch-graph-helper.d.mts.map +1 -0
- package/dist/engine/branch/branch-graph-helper.mjs +49 -0
- package/dist/engine/branch/branch-graph-helper.mjs.map +1 -0
- package/dist/engine/branch/branch-helper.d.mts +26 -0
- package/dist/engine/branch/branch-helper.d.mts.map +1 -0
- package/dist/engine/branch/branch-helper.mjs +57 -0
- package/dist/engine/branch/branch-helper.mjs.map +1 -0
- package/dist/engine/branch/branch-space-helper.d.mts +22 -0
- package/dist/engine/branch/branch-space-helper.d.mts.map +1 -0
- package/dist/engine/branch/branch-space-helper.mjs +77 -0
- package/dist/engine/branch/branch-space-helper.mjs.map +1 -0
- package/dist/engine/branch/branch-space.respec.node.d.mts +8 -0
- package/dist/engine/branch/branch-space.respec.node.d.mts.map +1 -0
- package/dist/engine/branch/branch-space.respec.node.mjs +138 -0
- package/dist/engine/branch/branch-space.respec.node.mjs.map +1 -0
- package/dist/engine/branch/branch-types.d.mts +26 -0
- package/dist/engine/branch/branch-types.d.mts.map +1 -0
- package/dist/engine/branch/branch-types.mjs +7 -0
- package/dist/engine/branch/branch-types.mjs.map +1 -0
- package/dist/engine/diff/line-diff-helper.d.mts +76 -0
- package/dist/engine/diff/line-diff-helper.d.mts.map +1 -0
- package/dist/engine/diff/line-diff-helper.mjs +144 -0
- package/dist/engine/diff/line-diff-helper.mjs.map +1 -0
- package/dist/engine/diff/line-diff-helper.respec.node.d.mts +7 -0
- package/dist/engine/diff/line-diff-helper.respec.node.d.mts.map +1 -0
- package/dist/engine/diff/line-diff-helper.respec.node.mjs +66 -0
- package/dist/engine/diff/line-diff-helper.respec.node.mjs.map +1 -0
- package/dist/engine/filter/glob-helper.d.mts +51 -0
- package/dist/engine/filter/glob-helper.d.mts.map +1 -0
- package/dist/engine/filter/glob-helper.mjs +132 -0
- package/dist/engine/filter/glob-helper.mjs.map +1 -0
- package/dist/engine/filter/glob-helper.respec.node.d.mts +7 -0
- package/dist/engine/filter/glob-helper.respec.node.d.mts.map +1 -0
- package/dist/engine/filter/glob-helper.respec.node.mjs +78 -0
- package/dist/engine/filter/glob-helper.respec.node.mjs.map +1 -0
- package/dist/engine/index/index-helper.d.mts +78 -0
- package/dist/engine/index/index-helper.d.mts.map +1 -0
- package/dist/engine/index/index-helper.mjs +289 -0
- package/dist/engine/index/index-helper.mjs.map +1 -0
- package/dist/engine/index/index-types.d.mts +63 -0
- package/dist/engine/index/index-types.d.mts.map +1 -0
- package/dist/engine/index/index-types.mjs +7 -0
- package/dist/engine/index/index-types.mjs.map +1 -0
- package/dist/engine/item/item-atom-helper.d.mts +20 -0
- package/dist/engine/item/item-atom-helper.d.mts.map +1 -0
- package/dist/engine/item/item-atom-helper.mjs +30 -0
- package/dist/engine/item/item-atom-helper.mjs.map +1 -0
- package/dist/engine/item/item-helper.d.mts +37 -0
- package/dist/engine/item/item-helper.d.mts.map +1 -0
- package/dist/engine/item/item-helper.mjs +194 -0
- package/dist/engine/item/item-helper.mjs.map +1 -0
- package/dist/engine/item/item-types.d.mts +127 -0
- package/dist/engine/item/item-types.d.mts.map +1 -0
- package/dist/engine/item/item-types.mjs +16 -0
- package/dist/engine/item/item-types.mjs.map +1 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +7 -0
- package/dist/index.mjs.map +1 -0
- package/dist/respec-gib.node.d.mts +7 -0
- package/dist/respec-gib.node.d.mts.map +1 -0
- package/dist/respec-gib.node.mjs +11 -0
- package/dist/respec-gib.node.mjs.map +1 -0
- package/dist/vcs-gib-constants.d.mts +7 -0
- package/dist/vcs-gib-constants.d.mts.map +1 -0
- package/dist/vcs-gib-constants.mjs +7 -0
- package/dist/vcs-gib-constants.mjs.map +1 -0
- package/dist/vcs-gib.respec.node.d.mts +8 -0
- package/dist/vcs-gib.respec.node.d.mts.map +1 -0
- package/dist/vcs-gib.respec.node.mjs +82 -0
- package/dist/vcs-gib.respec.node.mjs.map +1 -0
- package/generate-version-file.js +20 -0
- package/package.json +28 -0
- package/src/AUTO-GENERATED-version.mts +5 -0
- package/src/assumptions.respec.mts +50 -0
- package/src/bootstrap/node-bootstrap.mts +198 -0
- package/src/bootstrap/node-bootstrap.respec.node.mts +35 -0
- package/src/cli/cli-arg-parser.mts +119 -0
- package/src/cli/cli-arg-parser.respec.node.mts +45 -0
- package/src/cli/cli-constants.mts +77 -0
- package/src/cli/cli-e2e.respec.node.mts +318 -0
- package/src/cli/commands/add-cmd.mts +415 -0
- package/src/cli/commands/branch-checkout-cmd.respec.node.mts +322 -0
- package/src/cli/commands/branch-cmd.mts +243 -0
- package/src/cli/commands/checkout-cmd.mts +202 -0
- package/src/cli/commands/commit-cmd.mts +421 -0
- package/src/cli/commands/commit-log-cmd.respec.node.mts +276 -0
- package/src/cli/commands/init-cmd.mts +152 -0
- package/src/cli/commands/init-cmd.respec.node.mts +49 -0
- package/src/cli/commands/log-cmd.mts +197 -0
- package/src/cli/commands/status-add-cmd.respec.node.mts +246 -0
- package/src/cli/commands/status-cmd.mts +366 -0
- package/src/cli/vcs-cli-main.mts +153 -0
- package/src/engine/branch/branch-graph-helper.mts +69 -0
- package/src/engine/branch/branch-helper.mts +79 -0
- package/src/engine/branch/branch-space-helper.mts +104 -0
- package/src/engine/branch/branch-space.respec.node.mts +158 -0
- package/src/engine/branch/branch-types.mts +29 -0
- package/src/engine/diff/line-diff-helper.mts +210 -0
- package/src/engine/diff/line-diff-helper.respec.node.mts +84 -0
- package/src/engine/filter/glob-helper.mts +165 -0
- package/src/engine/filter/glob-helper.respec.node.mts +94 -0
- package/src/engine/index/index-helper.mts +378 -0
- package/src/engine/index/index-types.mts +66 -0
- package/src/engine/item/item-atom-helper.mts +30 -0
- package/src/engine/item/item-helper.mts +239 -0
- package/src/engine/item/item-types.mts +136 -0
- package/src/index.mts +7 -0
- package/src/respec-gib.node.mts +12 -0
- package/src/vcs-gib-constants.mts +7 -0
- package/src/vcs-gib.respec.node.mts +96 -0
- package/test_runs/001-hello-node/20260728_090100_0001-flow-core-main.md +39 -0
- package/test_runs/001-hello-node/20260728_090200_0003-flow-branching-worktree.md +54 -0
- package/test_runs/001-hello-node/20260728_090300_0002-flow-selective-add.md +45 -0
- package/test_runs/001-hello-node/20260728_142100_0007-flow-edge-cases.md +44 -0
- package/test_runs/001-hello-node/20260728_142400_0005-flow-hardcoded-ignores.md +36 -0
- package/tools/pack-for-test.mjs +73 -0
- package/tsconfig.json +18 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module status-add-cmd.respec
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for `vcs status` and `vcs add` command handlers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
respecfully, ifWe, iReckon
|
|
12
|
+
} from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
|
|
13
|
+
|
|
14
|
+
import { executeInitCmd } from './init-cmd.mjs';
|
|
15
|
+
import { executeStatusCmd } from './status-cmd.mjs';
|
|
16
|
+
import { executeAddCmd } from './add-cmd.mjs';
|
|
17
|
+
import { executeCommitCmd } from './commit-cmd.mjs';
|
|
18
|
+
import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
|
|
19
|
+
import { getB2tFSIndexIbGib } from '../../engine/index/index-helper.mjs';
|
|
20
|
+
|
|
21
|
+
const lc = `[apps/vcs-gib/src/cli/commands/status-add-cmd.respec.node.mts]`;
|
|
22
|
+
|
|
23
|
+
await respecfully(lc, `vcs status and vcs add Command Handlers`, async () => {
|
|
24
|
+
|
|
25
|
+
await ifWe(lc, `detects untracked files in fresh repository`, async () => {
|
|
26
|
+
const testDir = path.join(process.cwd(), '.test_status_add_1');
|
|
27
|
+
const vcsFolderName = '.vcsgib_test_1';
|
|
28
|
+
try {
|
|
29
|
+
await executeInitCmd({
|
|
30
|
+
targetDir: testDir,
|
|
31
|
+
vcsFolderName,
|
|
32
|
+
quiet: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await fs.writeFile(path.join(testDir, 'hello.txt'), 'Hello ibgib!', 'utf8');
|
|
36
|
+
|
|
37
|
+
const statusRes = await executeStatusCmd({
|
|
38
|
+
targetDir: testDir,
|
|
39
|
+
vcsFolderName,
|
|
40
|
+
quiet: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
iReckon(lc, statusRes.branchName).willEqual('main');
|
|
44
|
+
iReckon(lc, statusRes.untracked.map(x => x.path)).includes('hello.txt');
|
|
45
|
+
iReckon(lc, statusRes.staged.length).willEqual(0);
|
|
46
|
+
} finally {
|
|
47
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await ifWe(lc, `discovers .vcsgib via upward discovery when executed from a subfolder`, async () => {
|
|
52
|
+
const testDir = path.join(process.cwd(), '.test_status_add_subfolder');
|
|
53
|
+
const vcsFolderName = '.vcsgib_test_subfolder';
|
|
54
|
+
try {
|
|
55
|
+
await executeInitCmd({
|
|
56
|
+
targetDir: testDir,
|
|
57
|
+
vcsFolderName,
|
|
58
|
+
quiet: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const subDir = path.join(testDir, 'src', 'components');
|
|
62
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
63
|
+
await fs.writeFile(path.join(subDir, 'Button.tsx'), 'export const Button = () => null;', 'utf8');
|
|
64
|
+
|
|
65
|
+
const statusRes = await executeStatusCmd({
|
|
66
|
+
targetDir: subDir,
|
|
67
|
+
vcsFolderName,
|
|
68
|
+
quiet: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
iReckon(lc, statusRes.branchName).willEqual('main');
|
|
72
|
+
iReckon(lc, statusRes.untracked.map(x => x.path)).includes('src/components/Button.tsx');
|
|
73
|
+
} finally {
|
|
74
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await ifWe(lc, `skips re-staging unmodified committed files during executeAddCmd with all flag`, async () => {
|
|
79
|
+
const testDir = path.join(process.cwd(), '.test_status_add_unmodified');
|
|
80
|
+
const vcsFolderName = '.vcsgib_test_unmodified';
|
|
81
|
+
try {
|
|
82
|
+
await executeInitCmd({
|
|
83
|
+
targetDir: testDir,
|
|
84
|
+
vcsFolderName,
|
|
85
|
+
quiet: true,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await fs.writeFile(path.join(testDir, 'file.txt'), 'Initial Content', 'utf8');
|
|
89
|
+
|
|
90
|
+
await executeAddCmd({
|
|
91
|
+
paths: ['file.txt'],
|
|
92
|
+
targetDir: testDir,
|
|
93
|
+
vcsFolderName,
|
|
94
|
+
quiet: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await executeCommitCmd({
|
|
98
|
+
message: 'Initial commit',
|
|
99
|
+
targetDir: testDir,
|
|
100
|
+
vcsFolderName,
|
|
101
|
+
quiet: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Run add -a again when file is unmodified
|
|
105
|
+
const addRes = await executeAddCmd({
|
|
106
|
+
all: true,
|
|
107
|
+
targetDir: testDir,
|
|
108
|
+
vcsFolderName,
|
|
109
|
+
quiet: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
iReckon(lc, addRes.stagedCount).willEqual(0);
|
|
113
|
+
iReckon(lc, addRes.stagedPaths.length).willEqual(0);
|
|
114
|
+
} finally {
|
|
115
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await ifWe(lc, `stages untracked file via executeAddCmd`, async () => {
|
|
120
|
+
const testDir = path.join(process.cwd(), '.test_status_add_2');
|
|
121
|
+
const vcsFolderName = '.vcsgib_test_2';
|
|
122
|
+
try {
|
|
123
|
+
await executeInitCmd({
|
|
124
|
+
targetDir: testDir,
|
|
125
|
+
vcsFolderName,
|
|
126
|
+
quiet: true,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await fs.writeFile(path.join(testDir, 'file1.txt'), 'Content 1', 'utf8');
|
|
130
|
+
await fs.writeFile(path.join(testDir, 'file2.txt'), 'Content 2', 'utf8');
|
|
131
|
+
|
|
132
|
+
const addRes = await executeAddCmd({
|
|
133
|
+
paths: ['file1.txt'],
|
|
134
|
+
targetDir: testDir,
|
|
135
|
+
vcsFolderName,
|
|
136
|
+
quiet: true,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
iReckon(lc, addRes.stagedPaths).includes('file1.txt');
|
|
140
|
+
|
|
141
|
+
const { localSpace, vcsPath } = await bootstrapVcsMetaspace({
|
|
142
|
+
repoPath: testDir,
|
|
143
|
+
vcsFolderName,
|
|
144
|
+
});
|
|
145
|
+
const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, vcsPath });
|
|
146
|
+
iReckon(lc, Boolean(indexIbGib?.data?.staged_item && indexIbGib.data.staged_item.length > 0)).isGonnaBeTrue();
|
|
147
|
+
iReckon(lc, indexIbGib?.rel8ns?.staged_item).isGonnaBeFalsy();
|
|
148
|
+
|
|
149
|
+
const statusRes = await executeStatusCmd({
|
|
150
|
+
targetDir: testDir,
|
|
151
|
+
vcsFolderName,
|
|
152
|
+
quiet: true,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
iReckon(lc, statusRes.staged.map(x => x.path)).includes('file1.txt');
|
|
156
|
+
iReckon(lc, statusRes.untracked.map(x => x.path)).includes('file2.txt');
|
|
157
|
+
iReckon(lc, statusRes.untracked.map(x => x.path)).not.includes('file1.txt');
|
|
158
|
+
} finally {
|
|
159
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await ifWe(lc, `respects .ibgibignore patterns during status and add`, async () => {
|
|
164
|
+
const testDir = path.join(process.cwd(), '.test_status_add_3');
|
|
165
|
+
const vcsFolderName = '.vcsgib_test_3';
|
|
166
|
+
try {
|
|
167
|
+
await executeInitCmd({
|
|
168
|
+
targetDir: testDir,
|
|
169
|
+
vcsFolderName,
|
|
170
|
+
quiet: true,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await fs.writeFile(path.join(testDir, '.ibgibignore'), 'ignored.txt\ntemp/\n', 'utf8');
|
|
174
|
+
await fs.writeFile(path.join(testDir, 'ignored.txt'), 'Secrets', 'utf8');
|
|
175
|
+
await fs.writeFile(path.join(testDir, 'tracked.txt'), 'Public', 'utf8');
|
|
176
|
+
await fs.mkdir(path.join(testDir, 'temp'), { recursive: true });
|
|
177
|
+
await fs.writeFile(path.join(testDir, 'temp/cache.dat'), 'Cache', 'utf8');
|
|
178
|
+
|
|
179
|
+
const statusRes = await executeStatusCmd({
|
|
180
|
+
targetDir: testDir,
|
|
181
|
+
vcsFolderName,
|
|
182
|
+
quiet: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
iReckon(lc, statusRes.untracked.map(x => x.path)).includes('tracked.txt');
|
|
186
|
+
iReckon(lc, statusRes.untracked.map(x => x.path)).not.includes('ignored.txt');
|
|
187
|
+
iReckon(lc, statusRes.untracked.map(x => x.path)).not.includes('temp/cache.dat');
|
|
188
|
+
|
|
189
|
+
const addRes = await executeAddCmd({
|
|
190
|
+
all: true,
|
|
191
|
+
targetDir: testDir,
|
|
192
|
+
vcsFolderName,
|
|
193
|
+
quiet: true,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
iReckon(lc, addRes.stagedPaths).includes('tracked.txt');
|
|
197
|
+
iReckon(lc, addRes.stagedPaths).not.includes('ignored.txt');
|
|
198
|
+
iReckon(lc, addRes.stagedPaths).not.includes('temp/cache.dat');
|
|
199
|
+
} finally {
|
|
200
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await ifWe(lc, `stages files matching glob pattern with executeAddCmd`, async () => {
|
|
205
|
+
const testDir = path.join(process.cwd(), '.test_status_add_glob');
|
|
206
|
+
const vcsFolderName = '.vcsgib_test_glob';
|
|
207
|
+
try {
|
|
208
|
+
await executeInitCmd({
|
|
209
|
+
targetDir: testDir,
|
|
210
|
+
vcsFolderName,
|
|
211
|
+
quiet: true,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await fs.writeFile(path.join(testDir, 'app.js'), 'console.log("app");', 'utf8');
|
|
215
|
+
await fs.writeFile(path.join(testDir, 'utils.js'), 'console.log("utils");', 'utf8');
|
|
216
|
+
await fs.writeFile(path.join(testDir, 'index.html'), '<html></html>', 'utf8');
|
|
217
|
+
|
|
218
|
+
const addRes = await executeAddCmd({
|
|
219
|
+
paths: ['*.js'],
|
|
220
|
+
targetDir: testDir,
|
|
221
|
+
vcsFolderName,
|
|
222
|
+
quiet: true,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
iReckon(lc, addRes.stagedPaths).includes('app.js');
|
|
226
|
+
iReckon(lc, addRes.stagedPaths).includes('utils.js');
|
|
227
|
+
iReckon(lc, addRes.stagedPaths).not.includes('index.html');
|
|
228
|
+
|
|
229
|
+
let errorThrown = false;
|
|
230
|
+
try {
|
|
231
|
+
await executeAddCmd({
|
|
232
|
+
paths: ['*.xyz'],
|
|
233
|
+
targetDir: testDir,
|
|
234
|
+
vcsFolderName,
|
|
235
|
+
quiet: true,
|
|
236
|
+
});
|
|
237
|
+
} catch (err: any) {
|
|
238
|
+
errorThrown = true;
|
|
239
|
+
iReckon(lc, err.message).includes('No files matched glob pattern: *.xyz');
|
|
240
|
+
}
|
|
241
|
+
iReckon(lc, errorThrown).isGonnaBeTrue();
|
|
242
|
+
} finally {
|
|
243
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module status-cmd
|
|
3
|
+
*
|
|
4
|
+
* Command handler for `vcs status`.
|
|
5
|
+
* Compares physical workspace state against active branch snapshot tip and in-band index staging area.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import { Dirent } from 'node:fs';
|
|
11
|
+
import crypto from 'node:crypto';
|
|
12
|
+
|
|
13
|
+
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
|
|
14
|
+
import { getIbGibsFromCache_fallbackToSpaces } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
|
|
15
|
+
|
|
16
|
+
import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
|
|
17
|
+
import { getOrCreateBranchSpace } from '../../engine/branch/branch-space-helper.mjs';
|
|
18
|
+
import { B2tFSItemIbGib_V1, B2TFS_CHILD_ITEM_REL8N_NAME } from '../../engine/item/item-types.mjs';
|
|
19
|
+
import { getIbGibWithMetaspaceFallback } from '../../engine/item/item-helper.mjs';
|
|
20
|
+
import { B2tFSBranchIbGib_V1 } from '../../engine/branch/branch-types.mjs';
|
|
21
|
+
import { isPathFiltered, parseFilterFileContent } from '../../engine/filter/glob-helper.mjs';
|
|
22
|
+
import { getB2tFSIndexIbGib } from '../../engine/index/index-helper.mjs';
|
|
23
|
+
import { B2tFSStagedItemRecord } from '../../engine/index/index-types.mjs';
|
|
24
|
+
import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
|
|
25
|
+
|
|
26
|
+
const lcFile = `[status-cmd]`;
|
|
27
|
+
const logalot = GLOBAL_LOG_A_LOT;
|
|
28
|
+
|
|
29
|
+
export interface VcsStatusItem {
|
|
30
|
+
path: string;
|
|
31
|
+
fsType: 'file' | 'folder';
|
|
32
|
+
status: 'untracked' | 'modified' | 'deleted' | 'staged';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StatusCmdResult {
|
|
36
|
+
branchName: string;
|
|
37
|
+
staged: VcsStatusItem[];
|
|
38
|
+
modified: VcsStatusItem[];
|
|
39
|
+
deleted: VcsStatusItem[];
|
|
40
|
+
untracked: VcsStatusItem[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface StatusCmdOpts {
|
|
44
|
+
targetDir?: string;
|
|
45
|
+
vcsFolderName?: string;
|
|
46
|
+
quiet?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function executeStatusCmd({
|
|
50
|
+
targetDir = process.cwd(),
|
|
51
|
+
vcsFolderName = '.vcsgib',
|
|
52
|
+
quiet = false,
|
|
53
|
+
}: StatusCmdOpts = {}): Promise<StatusCmdResult> {
|
|
54
|
+
const lc = `${lcFile}[${executeStatusCmd.name}]`;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
if (logalot) {
|
|
58
|
+
console.log(`${lc} starting executeStatusCmd (I: 7a8b9c10d11e12f13a14b15c16d17e18)`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const absTargetDir = path.resolve(targetDir);
|
|
62
|
+
|
|
63
|
+
const { metaspace, localSpace, vcsPath, repoRoot } = await bootstrapVcsMetaspace({
|
|
64
|
+
repoPath: absTargetDir,
|
|
65
|
+
vcsFolderName,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Verify repo exists
|
|
69
|
+
try {
|
|
70
|
+
await fs.access(vcsPath);
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error(`Not an ibgib repository (directory ${vcsPath} does not exist) (E: 4d5e6f7a8b9c10d11e12f13a14b15c6d)`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 1. Fetch authoritative B2tFSIndexIbGib_V1 directly from localSpace
|
|
76
|
+
const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, vcsPath });
|
|
77
|
+
const branchName = indexIbGib?.data?.activeBranch || 'main';
|
|
78
|
+
|
|
79
|
+
const activeWorktreeDir = branchName === 'main' ? repoRoot : path.join(repoRoot, 'b', branchName);
|
|
80
|
+
|
|
81
|
+
// Target active branch space b2s_<activeBranch>
|
|
82
|
+
const { space: activeBranchSpace } = await getOrCreateBranchSpace({
|
|
83
|
+
metaspace,
|
|
84
|
+
vcsPath,
|
|
85
|
+
branchName,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// 2. Extract staged items directly from in-band B2tFSIndexIbGib_V1 data
|
|
89
|
+
const stagedMap = new Map<string, B2tFSStagedItemRecord>();
|
|
90
|
+
const indexStagedPaths = indexIbGib?.data?.stagedPaths || {};
|
|
91
|
+
for (const [relPath, itemRec] of Object.entries(indexStagedPaths)) {
|
|
92
|
+
stagedMap.set(relPath, itemRec);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 3. Find active branch root item address from in-band branch graph node
|
|
96
|
+
let rootItemAddr: string | undefined;
|
|
97
|
+
const branchAddrs = [
|
|
98
|
+
...(indexIbGib?.rel8ns?.branch || []),
|
|
99
|
+
...(indexIbGib?.rel8ns?.branch_tjp || []),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
if (branchAddrs.length > 0) {
|
|
103
|
+
let branchIbGibs: B2tFSBranchIbGib_V1[] = [];
|
|
104
|
+
for (const bAddr of branchAddrs) {
|
|
105
|
+
const bIbGib = await getIbGibWithMetaspaceFallback({
|
|
106
|
+
addr: bAddr,
|
|
107
|
+
space: activeBranchSpace,
|
|
108
|
+
metaspace,
|
|
109
|
+
});
|
|
110
|
+
if (bIbGib) {
|
|
111
|
+
branchIbGibs.push(bIbGib as B2tFSBranchIbGib_V1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const activeBranchIbGib = branchIbGibs.find(
|
|
116
|
+
(b) => (b as B2tFSBranchIbGib_V1)?.data?.name === branchName,
|
|
117
|
+
) as B2tFSBranchIbGib_V1 || (branchIbGibs.at(0) as B2tFSBranchIbGib_V1);
|
|
118
|
+
|
|
119
|
+
rootItemAddr = activeBranchIbGib?.rel8ns?.b2tfs_item?.at(0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Read .ibgibignore patterns
|
|
123
|
+
let filterPatterns: string[] = [];
|
|
124
|
+
const ignorePath = path.join(activeWorktreeDir, '.ibgibignore');
|
|
125
|
+
try {
|
|
126
|
+
const ignoreContent = await fs.readFile(ignorePath, 'utf8');
|
|
127
|
+
filterPatterns = parseFilterFileContent(ignoreContent);
|
|
128
|
+
} catch {
|
|
129
|
+
// Ignore file missing is fine
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 4. Walk active branch graph items
|
|
133
|
+
const branchItemsMap = new Map<string, { relPath: string; fileHash?: string; fsType: 'file' | 'folder' }>();
|
|
134
|
+
|
|
135
|
+
if (rootItemAddr) {
|
|
136
|
+
const rootItem = await getIbGibWithMetaspaceFallback({
|
|
137
|
+
addr: rootItemAddr,
|
|
138
|
+
space: activeBranchSpace,
|
|
139
|
+
metaspace,
|
|
140
|
+
}) as B2tFSItemIbGib_V1;
|
|
141
|
+
|
|
142
|
+
if (rootItem) {
|
|
143
|
+
await statusCmd_walkBranchGraph({
|
|
144
|
+
item: rootItem,
|
|
145
|
+
space: activeBranchSpace,
|
|
146
|
+
metaspace,
|
|
147
|
+
currentRelPath: '',
|
|
148
|
+
outMap: branchItemsMap,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 5. Scan physical workspace
|
|
154
|
+
const physicalItemsMap = new Map<string, { relPath: string; fullPath: string; isDirectory: boolean; fileHash?: string }>();
|
|
155
|
+
await statusCmd_scanPhysicalWorkspace({
|
|
156
|
+
dirPath: activeWorktreeDir,
|
|
157
|
+
baseDir: activeWorktreeDir,
|
|
158
|
+
vcsFolderName,
|
|
159
|
+
filterPatterns,
|
|
160
|
+
outMap: physicalItemsMap,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 6. Categorize items into staged, modified, deleted, untracked
|
|
164
|
+
const staged: VcsStatusItem[] = [];
|
|
165
|
+
const modified: VcsStatusItem[] = [];
|
|
166
|
+
const deleted: VcsStatusItem[] = [];
|
|
167
|
+
const untracked: VcsStatusItem[] = [];
|
|
168
|
+
|
|
169
|
+
// Add staged items
|
|
170
|
+
for (const [relPath, stagedRec] of stagedMap.entries()) {
|
|
171
|
+
staged.push({
|
|
172
|
+
path: relPath,
|
|
173
|
+
fsType: stagedRec.fsType || 'file',
|
|
174
|
+
status: 'staged',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Compare physical items against branch items and staged
|
|
179
|
+
for (const [relPath, physItem] of physicalItemsMap.entries()) {
|
|
180
|
+
if (stagedMap.has(relPath)) {
|
|
181
|
+
continue; // Staged item already reported
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const branchItem = branchItemsMap.get(relPath);
|
|
185
|
+
if (!branchItem) {
|
|
186
|
+
// Not in active branch snapshot and not staged => untracked
|
|
187
|
+
untracked.push({
|
|
188
|
+
path: relPath,
|
|
189
|
+
fsType: physItem.isDirectory ? 'folder' : 'file',
|
|
190
|
+
status: 'untracked',
|
|
191
|
+
});
|
|
192
|
+
} else {
|
|
193
|
+
// Tracked in branch -> check if modified
|
|
194
|
+
if (!physItem.isDirectory && physItem.fileHash && branchItem.fileHash && physItem.fileHash !== branchItem.fileHash) {
|
|
195
|
+
modified.push({
|
|
196
|
+
path: relPath,
|
|
197
|
+
fsType: 'file',
|
|
198
|
+
status: 'modified',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check for deleted items (in branch snapshot, but not in physical workspace and not staged)
|
|
205
|
+
for (const [relPath, branchItem] of branchItemsMap.entries()) {
|
|
206
|
+
if (!relPath || relPath === '.' || relPath === '') { continue; }
|
|
207
|
+
if (!physicalItemsMap.has(relPath) && !stagedMap.has(relPath)) {
|
|
208
|
+
deleted.push({
|
|
209
|
+
path: relPath,
|
|
210
|
+
fsType: branchItem.fsType,
|
|
211
|
+
status: 'deleted',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!quiet) {
|
|
217
|
+
console.log(`On branch ${branchName} (I: 9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b)`);
|
|
218
|
+
if (staged.length > 0) {
|
|
219
|
+
console.log('\nChanges to be committed:');
|
|
220
|
+
for (const item of staged) {
|
|
221
|
+
console.log(` staged: ${item.path}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (modified.length > 0 || deleted.length > 0) {
|
|
225
|
+
console.log('\nChanges not staged for commit:');
|
|
226
|
+
for (const item of modified) {
|
|
227
|
+
console.log(` modified: ${item.path}`);
|
|
228
|
+
}
|
|
229
|
+
for (const item of deleted) {
|
|
230
|
+
console.log(` deleted: ${item.path}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (untracked.length > 0) {
|
|
234
|
+
console.log('\nUntracked files:');
|
|
235
|
+
for (const item of untracked) {
|
|
236
|
+
console.log(` untracked:${item.path}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (staged.length === 0 && modified.length === 0 && deleted.length === 0 && untracked.length === 0) {
|
|
240
|
+
console.log('nothing to commit, working tree clean');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
branchName,
|
|
246
|
+
staged,
|
|
247
|
+
modified,
|
|
248
|
+
deleted,
|
|
249
|
+
untracked,
|
|
250
|
+
};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error(`${lc} ${extractErrorMsg(error)} (E: 5e6f7a8b9c10d11e12f13a14b15c6d7e)`);
|
|
253
|
+
throw error;
|
|
254
|
+
} finally {
|
|
255
|
+
if (logalot) {
|
|
256
|
+
console.log(`${lc} complete. (I: 6f7a8b9c10d11e12f13a14b15c6d7e8f)`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// #region statusCmd_walkBranchGraph
|
|
262
|
+
async function statusCmd_walkBranchGraph({
|
|
263
|
+
item,
|
|
264
|
+
space,
|
|
265
|
+
metaspace,
|
|
266
|
+
currentRelPath,
|
|
267
|
+
outMap,
|
|
268
|
+
}: {
|
|
269
|
+
item: B2tFSItemIbGib_V1;
|
|
270
|
+
space: any;
|
|
271
|
+
metaspace?: any;
|
|
272
|
+
currentRelPath: string;
|
|
273
|
+
outMap: Map<string, { relPath: string; fileHash?: string; fsType: 'file' | 'folder' }>;
|
|
274
|
+
}): Promise<void> {
|
|
275
|
+
if (!item || !item.data) { return; }
|
|
276
|
+
|
|
277
|
+
const { fsType, fileHash } = item.data;
|
|
278
|
+
if (currentRelPath) {
|
|
279
|
+
outMap.set(currentRelPath, { relPath: currentRelPath, fileHash, fsType });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (fsType === 'folder') {
|
|
283
|
+
const childAddrs = item.rel8ns?.[B2TFS_CHILD_ITEM_REL8N_NAME] || [];
|
|
284
|
+
for (const childAddr of childAddrs) {
|
|
285
|
+
const childItem = await getIbGibWithMetaspaceFallback({
|
|
286
|
+
addr: childAddr,
|
|
287
|
+
space,
|
|
288
|
+
metaspace,
|
|
289
|
+
}) as B2tFSItemIbGib_V1;
|
|
290
|
+
if (childItem && childItem.data) {
|
|
291
|
+
const childRelPath = currentRelPath
|
|
292
|
+
? `${currentRelPath}/${childItem.data.name}`
|
|
293
|
+
: childItem.data.name;
|
|
294
|
+
await statusCmd_walkBranchGraph({
|
|
295
|
+
item: childItem,
|
|
296
|
+
space,
|
|
297
|
+
metaspace,
|
|
298
|
+
currentRelPath: childRelPath,
|
|
299
|
+
outMap,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// #endregion statusCmd_walkBranchGraph
|
|
306
|
+
|
|
307
|
+
// #region statusCmd_scanPhysicalWorkspace
|
|
308
|
+
async function statusCmd_scanPhysicalWorkspace({
|
|
309
|
+
dirPath,
|
|
310
|
+
baseDir,
|
|
311
|
+
vcsFolderName,
|
|
312
|
+
filterPatterns,
|
|
313
|
+
outMap,
|
|
314
|
+
}: {
|
|
315
|
+
dirPath: string;
|
|
316
|
+
baseDir: string;
|
|
317
|
+
vcsFolderName: string;
|
|
318
|
+
filterPatterns: string[];
|
|
319
|
+
outMap: Map<string, { relPath: string; fullPath: string; isDirectory: boolean; fileHash?: string }>;
|
|
320
|
+
}): Promise<void> {
|
|
321
|
+
let entries: Dirent[] = [];
|
|
322
|
+
try {
|
|
323
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
324
|
+
} catch {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
for (const entry of entries) {
|
|
329
|
+
const fullEntryPath = path.join(dirPath, entry.name);
|
|
330
|
+
const relPath = path.relative(baseDir, fullEntryPath).replace(/\\/g, '/');
|
|
331
|
+
|
|
332
|
+
// Ignore vcs storage folder, .git, and worktrees folder b
|
|
333
|
+
if (
|
|
334
|
+
relPath === vcsFolderName || relPath.startsWith(vcsFolderName + '/') ||
|
|
335
|
+
relPath === '.git' || relPath.startsWith('.git/') ||
|
|
336
|
+
relPath === 'b' || relPath.startsWith('b/')
|
|
337
|
+
) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (isPathFiltered({ relativePath: relPath, isDirectory: entry.isDirectory(), filterPatterns })) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (entry.isDirectory()) {
|
|
346
|
+
outMap.set(relPath, { relPath, fullPath: fullEntryPath, isDirectory: true });
|
|
347
|
+
await statusCmd_scanPhysicalWorkspace({
|
|
348
|
+
dirPath: fullEntryPath,
|
|
349
|
+
baseDir,
|
|
350
|
+
vcsFolderName,
|
|
351
|
+
filterPatterns,
|
|
352
|
+
outMap,
|
|
353
|
+
});
|
|
354
|
+
} else if (entry.isFile()) {
|
|
355
|
+
let fileHash: string | undefined;
|
|
356
|
+
try {
|
|
357
|
+
const fileData = await fs.readFile(fullEntryPath);
|
|
358
|
+
fileHash = crypto.createHash('sha256').update(fileData).digest('hex');
|
|
359
|
+
} catch {
|
|
360
|
+
// File unreadable
|
|
361
|
+
}
|
|
362
|
+
outMap.set(relPath, { relPath, fullPath: fullEntryPath, isDirectory: false, fileHash });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// #endregion statusCmd_scanPhysicalWorkspace
|