@c956180462/awbs 0.0.1
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/AWBS_CORE_DESIGN.md +983 -0
- package/AWBS_CURRENT_FEATURES.md +463 -0
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/TASK_001_VIEW_AUTHORITY.md +446 -0
- package/TASK_003_AUTHORITY_LEDGER_AND_DB_AUDIT.md +268 -0
- package/TASK_004_TRUSTED_AUTHORITY_LAYER.md +547 -0
- package/TASK_005_AUTHORITY_SESSION.md +218 -0
- package/TASK_006_TRUST_BOUNDARY_HARDENING.md +381 -0
- package/TASK_007_TRUSTED_OPERATION_ENTRY.md +129 -0
- package/bin/awbs.js +2 -0
- package/docs/DEVELOPMENT_LEARNING.md +319 -0
- package/docs/FULL_CHAIN.md +295 -0
- package/docs/PRODUCT.md +188 -0
- package/docs/USAGE.md +294 -0
- package/package.json +45 -0
- package/src/adapters/file-summary-store.ts +88 -0
- package/src/adapters/git-cli.ts +107 -0
- package/src/adapters/local-authority-session.ts +606 -0
- package/src/adapters/local-file-database.ts +199 -0
- package/src/adapters/sealed-authority.ts +725 -0
- package/src/adapters/session-authority-client.ts +176 -0
- package/src/adapters/sqlite-index-store.ts +176 -0
- package/src/cli.ts +491 -0
- package/src/domain/authority-types.ts +194 -0
- package/src/domain/constants.ts +11 -0
- package/src/domain/errors.ts +6 -0
- package/src/domain/hash.ts +27 -0
- package/src/domain/path-policy.ts +36 -0
- package/src/domain/paths.ts +65 -0
- package/src/domain/session-proof.ts +140 -0
- package/src/domain/session-types.ts +101 -0
- package/src/domain/types.ts +94 -0
- package/src/ports/authority-session.ts +8 -0
- package/src/ports/authority.ts +26 -0
- package/src/ports/file-database.ts +18 -0
- package/src/ports/git.ts +23 -0
- package/src/ports/index-store.ts +7 -0
- package/src/ports/summary-store.ts +16 -0
- package/src/runtime.ts +56 -0
- package/src/session-entry.ts +1 -0
- package/src/usecases/authority.ts +53 -0
- package/src/usecases/changeset.ts +437 -0
- package/src/usecases/db.ts +192 -0
- package/src/usecases/index.ts +136 -0
- package/src/usecases/init.ts +48 -0
- package/src/usecases/ledger.ts +146 -0
- package/src/usecases/session.ts +48 -0
- package/src/usecases/trusted-chain.ts +56 -0
- package/src/usecases/view.ts +166 -0
package/docs/USAGE.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# AWBS 使用文档
|
|
2
|
+
|
|
3
|
+
本文档面向想直接试用 AWBS CLI 的用户。
|
|
4
|
+
|
|
5
|
+
## 1. 环境要求
|
|
6
|
+
|
|
7
|
+
需要:
|
|
8
|
+
|
|
9
|
+
- Node.js `>=24.0.0`
|
|
10
|
+
- Git
|
|
11
|
+
- 一个可以运行 shell 命令的终端
|
|
12
|
+
|
|
13
|
+
查看版本:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
node --version
|
|
17
|
+
git --version
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 2. 安装
|
|
21
|
+
|
|
22
|
+
从 npm 安装:
|
|
23
|
+
|
|
24
|
+
```powershell
|
|
25
|
+
npm install -g @c956180462/awbs
|
|
26
|
+
awbs --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
从本地 checkout 安装:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
npm install -g .
|
|
33
|
+
awbs --help
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
开发时直接运行:
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
node src\cli.ts --help
|
|
40
|
+
npm run awbs -- --help
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 3. 初始化数据库
|
|
44
|
+
|
|
45
|
+
进入一个项目目录:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
mkdir my-awbs-db
|
|
49
|
+
cd my-awbs-db
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
初始化 AWBS:
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
awbs init
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
创建一些业务目录:
|
|
59
|
+
|
|
60
|
+
```powershell
|
|
61
|
+
mkdir A
|
|
62
|
+
mkdir B
|
|
63
|
+
"read only context" | Set-Content A\context.md
|
|
64
|
+
"first draft" | Set-Content B\draft.md
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
创建初始 Git commit:
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
git add .
|
|
71
|
+
git commit -m "initialize database"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
如果 Git 还没有用户名和邮箱,需要先配置:
|
|
75
|
+
|
|
76
|
+
```powershell
|
|
77
|
+
git config user.name "AWBS User"
|
|
78
|
+
git config user.email "awbs@example.test"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 4. 启动 Authority Session
|
|
82
|
+
|
|
83
|
+
开发试用时可以用简单字符串。正式应用中,`recoverySecret` 和 `controllerToken` 应由上层应用的非 AI 控制层管理。
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
'{"recoverySecret":"dev-recovery","controllerToken":"dev-controller"}' |
|
|
87
|
+
awbs authority session start --control-stdin
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
检查状态:
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
awbs authority session status
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 5. 启动 Trusted Chain
|
|
97
|
+
|
|
98
|
+
```powershell
|
|
99
|
+
'dev-controller' | awbs ledger bootstrap --control-token-stdin
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
检查 ledger:
|
|
103
|
+
|
|
104
|
+
```powershell
|
|
105
|
+
awbs ledger inspect
|
|
106
|
+
awbs ledger verify
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 6. 建立索引
|
|
110
|
+
|
|
111
|
+
```powershell
|
|
112
|
+
awbs index rebuild
|
|
113
|
+
awbs index query
|
|
114
|
+
awbs index query draft
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
输出来自 `.awbs/index/files.sqlite`。索引可删除重建,不是事实源。
|
|
118
|
+
|
|
119
|
+
## 7. 写入外部摘要
|
|
120
|
+
|
|
121
|
+
AWBS 不生成 AI 摘要。摘要由上层业务写入。
|
|
122
|
+
|
|
123
|
+
```powershell
|
|
124
|
+
awbs summary set B/draft.md --text "A draft document owned by the business layer."
|
|
125
|
+
awbs summary get B/draft.md
|
|
126
|
+
awbs summary list
|
|
127
|
+
awbs index rebuild
|
|
128
|
+
awbs index query business
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 8. 创建工作空间视图
|
|
132
|
+
|
|
133
|
+
创建一个 workspace:
|
|
134
|
+
|
|
135
|
+
```powershell
|
|
136
|
+
'dev-controller' |
|
|
137
|
+
awbs view create --out ..\my-awbs-workspace --read A --write B --control-token-stdin
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
含义:
|
|
141
|
+
|
|
142
|
+
- `A` 被投影进 workspace,但只读。
|
|
143
|
+
- `B` 被投影进 workspace,可写。
|
|
144
|
+
- workspace 保持原目录结构。
|
|
145
|
+
|
|
146
|
+
查看 view:
|
|
147
|
+
|
|
148
|
+
```powershell
|
|
149
|
+
awbs view inspect <viewId>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 9. 在 workspace 中工作
|
|
153
|
+
|
|
154
|
+
修改可写目录:
|
|
155
|
+
|
|
156
|
+
```powershell
|
|
157
|
+
"second draft" | Set-Content ..\my-awbs-workspace\B\draft.md
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
如果修改只读目录,例如:
|
|
161
|
+
|
|
162
|
+
```powershell
|
|
163
|
+
"changed context" | Set-Content ..\my-awbs-workspace\A\context.md
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
AWBS 不会阻止你在 workspace 中这么做,但 collect 后 changeset 会是 invalid,apply 会拒绝。
|
|
167
|
+
|
|
168
|
+
## 10. 收集 Changeset
|
|
169
|
+
|
|
170
|
+
```powershell
|
|
171
|
+
awbs changeset collect --workspace ..\my-awbs-workspace
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
输出类似:
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
Changeset collected: changeset_...
|
|
178
|
+
Status: valid
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
查看:
|
|
182
|
+
|
|
183
|
+
```powershell
|
|
184
|
+
awbs changeset inspect <changesetId>
|
|
185
|
+
awbs changeset inspect <changesetId> --json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 11. 应用 Changeset
|
|
189
|
+
|
|
190
|
+
```powershell
|
|
191
|
+
'dev-controller' |
|
|
192
|
+
awbs changeset apply <changesetId> --control-token-stdin
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
成功后:
|
|
196
|
+
|
|
197
|
+
- 文件写回 AWBS 数据库。
|
|
198
|
+
- Git 创建 commit。
|
|
199
|
+
- trusted chain 前进。
|
|
200
|
+
- `refs/awbs/trusted` 更新。
|
|
201
|
+
|
|
202
|
+
## 12. 撤销 View
|
|
203
|
+
|
|
204
|
+
```powershell
|
|
205
|
+
'dev-controller' |
|
|
206
|
+
awbs view revoke <viewId> --control-token-stdin
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
撤销只影响未来 collect/apply,不删除旧 workspace,不删除已提交数据,不改 Git 历史。
|
|
210
|
+
|
|
211
|
+
## 13. 审计数据库
|
|
212
|
+
|
|
213
|
+
```powershell
|
|
214
|
+
awbs db audit
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
它会报告:
|
|
218
|
+
|
|
219
|
+
- 当前 HEAD。
|
|
220
|
+
- trusted commit。
|
|
221
|
+
- 工作树是否 dirty。
|
|
222
|
+
- 是否存在外部 commit。
|
|
223
|
+
- authority / ledger 是否可验证。
|
|
224
|
+
|
|
225
|
+
## 14. 从 Trusted Chain 重建干净数据库
|
|
226
|
+
|
|
227
|
+
如果普通工作区被污染,可以执行:
|
|
228
|
+
|
|
229
|
+
```powershell
|
|
230
|
+
awbs db clean-rebuild
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
注意:
|
|
234
|
+
|
|
235
|
+
- 运行前需要停止 authority session。
|
|
236
|
+
- 原目录会被整体改名为 backup。
|
|
237
|
+
- 干净目录从 trusted commit 重建。
|
|
238
|
+
- backup 不会自动删除。
|
|
239
|
+
|
|
240
|
+
停止 session:
|
|
241
|
+
|
|
242
|
+
```powershell
|
|
243
|
+
'dev-controller' | awbs authority session stop --control-token-stdin
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 15. Session 崩溃恢复
|
|
247
|
+
|
|
248
|
+
如果 session daemon 崩溃,`local.json` 不存在,但 `recovery.seal.json` 仍在:
|
|
249
|
+
|
|
250
|
+
```powershell
|
|
251
|
+
'dev-recovery' | awbs authority session recover --recovery-secret-stdin
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
错误 recovery secret 会失败,不会写出伪成功文件。
|
|
255
|
+
|
|
256
|
+
## 16. 常用命令总览
|
|
257
|
+
|
|
258
|
+
```text
|
|
259
|
+
awbs init
|
|
260
|
+
awbs index rebuild
|
|
261
|
+
awbs index query [term] [--status active|removed|all] [--json]
|
|
262
|
+
awbs summary set <path> (--text <summary> | --file <file>)
|
|
263
|
+
awbs summary get <path> [--json]
|
|
264
|
+
awbs summary list [--json]
|
|
265
|
+
awbs view create --out <workspace> [--read A] [--write B] --control-token-stdin
|
|
266
|
+
awbs view inspect <viewId> [--json]
|
|
267
|
+
awbs view revoke <viewId> --control-token-stdin
|
|
268
|
+
awbs changeset collect --workspace <workspace>
|
|
269
|
+
awbs changeset inspect <changesetDir|id> [--json]
|
|
270
|
+
awbs changeset apply <changesetDir|id> --control-token-stdin
|
|
271
|
+
awbs ledger bootstrap [--json] --control-token-stdin
|
|
272
|
+
awbs ledger inspect [--json]
|
|
273
|
+
awbs ledger verify [--json]
|
|
274
|
+
awbs db audit [--json]
|
|
275
|
+
awbs db clean-rebuild [--json]
|
|
276
|
+
awbs db backups list [--json]
|
|
277
|
+
awbs authority session start --control-stdin [--json]
|
|
278
|
+
awbs authority session status [--json]
|
|
279
|
+
awbs authority session stop --control-token-stdin [--json]
|
|
280
|
+
awbs authority session recover --recovery-secret-stdin [--json]
|
|
281
|
+
awbs authority verify [--json]
|
|
282
|
+
awbs authority repair-mirrors --control-token-stdin [--json]
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## 17. 发布前检查
|
|
286
|
+
|
|
287
|
+
开发者发布前建议运行:
|
|
288
|
+
|
|
289
|
+
```powershell
|
|
290
|
+
npm test
|
|
291
|
+
node src\cli.ts --help
|
|
292
|
+
npm pack --dry-run
|
|
293
|
+
git diff --check
|
|
294
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c956180462/awbs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Agent Work Base Space CLI prototype for file-system database workspaces.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"awbs": "bin/awbs.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/a956180462/AWBS.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/a956180462/AWBS/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/a956180462/AWBS#readme",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"agent",
|
|
20
|
+
"workspace",
|
|
21
|
+
"filesystem",
|
|
22
|
+
"git",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"bin",
|
|
27
|
+
"src",
|
|
28
|
+
"docs",
|
|
29
|
+
"AWBS_CORE_DESIGN.md",
|
|
30
|
+
"AWBS_CURRENT_FEATURES.md",
|
|
31
|
+
"TASK_001_VIEW_AUTHORITY.md",
|
|
32
|
+
"TASK_003_AUTHORITY_LEDGER_AND_DB_AUDIT.md",
|
|
33
|
+
"TASK_004_TRUSTED_AUTHORITY_LAYER.md",
|
|
34
|
+
"TASK_005_AUTHORITY_SESSION.md",
|
|
35
|
+
"TASK_006_TRUST_BOUNDARY_HARDENING.md",
|
|
36
|
+
"TASK_007_TRUSTED_OPERATION_ENTRY.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"awbs": "node src/cli.ts",
|
|
40
|
+
"test": "node --test --test-concurrency=1 tests/*.test.ts"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=24.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import type { IndexKind, SummaryEntry } from "../domain/types.ts";
|
|
3
|
+
import type { FileDatabasePort } from "../ports/file-database.ts";
|
|
4
|
+
import type { SummaryStorePort, SummaryWriteInput } from "../ports/summary-store.ts";
|
|
5
|
+
|
|
6
|
+
export class FileSummaryStoreAdapter implements SummaryStorePort {
|
|
7
|
+
private readonly files: FileDatabasePort;
|
|
8
|
+
|
|
9
|
+
constructor(files: FileDatabasePort) {
|
|
10
|
+
this.files = files;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
readSummaries(summaryFile: string): SummaryEntry[] {
|
|
14
|
+
if (!this.files.pathExists(summaryFile)) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const content = this.files.readText(summaryFile).trim();
|
|
18
|
+
if (!content) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
return content.split(/\r?\n/).map((line) => JSON.parse(line) as SummaryEntry);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
writeSummary(summaryFile: string, input: SummaryWriteInput): SummaryEntry {
|
|
25
|
+
const summaries = this.readSummaries(summaryFile);
|
|
26
|
+
const nextEntry: SummaryEntry = {
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
path: input.path,
|
|
29
|
+
kind: input.kind,
|
|
30
|
+
sha256: input.sha256,
|
|
31
|
+
commit: input.commit,
|
|
32
|
+
summary: input.summary,
|
|
33
|
+
source: "external",
|
|
34
|
+
updatedAt: new Date().toISOString(),
|
|
35
|
+
ext: {}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const next = summaries.filter((entry) => !(entry.path === nextEntry.path && entry.sha256 === nextEntry.sha256));
|
|
39
|
+
next.push(nextEntry);
|
|
40
|
+
next.sort((a, b) => a.path.localeCompare(b.path) || (a.sha256 ?? "").localeCompare(b.sha256 ?? ""));
|
|
41
|
+
this.files.writeText(summaryFile, next.map((entry) => `${JSON.stringify(entry)}\n`).join(""));
|
|
42
|
+
return nextEntry;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
findSummary(summaryFile: string, path: string, sha256: string | null): SummaryEntry | null {
|
|
46
|
+
const summaries = this.readSummaries(summaryFile).filter((entry) => entry.path === path);
|
|
47
|
+
const exact = summaries.find((entry) => entry.sha256 === sha256);
|
|
48
|
+
if (exact) {
|
|
49
|
+
return exact;
|
|
50
|
+
}
|
|
51
|
+
const pathLevel = summaries.find((entry) => entry.sha256 === null);
|
|
52
|
+
return pathLevel ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fallbackSummary(absPath: string, relPath: string, kind: IndexKind): string {
|
|
56
|
+
if (kind === "directory") {
|
|
57
|
+
const count = readdirSync(absPath).length;
|
|
58
|
+
return `Directory ${relPath} with ${count} item${count === 1 ? "" : "s"}.`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const buffer = readFileSync(absPath);
|
|
62
|
+
if (!looksText(buffer)) {
|
|
63
|
+
return `Binary file ${relPath} (${buffer.length} bytes).`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (buffer.length === 0) {
|
|
67
|
+
return `Empty text file ${relPath}.`;
|
|
68
|
+
}
|
|
69
|
+
return `Text file ${relPath} (${buffer.length} bytes).`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function looksText(buffer: Buffer): boolean {
|
|
74
|
+
if (buffer.length === 0) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
const sample = buffer.subarray(0, Math.min(buffer.length, 4096));
|
|
78
|
+
let suspicious = 0;
|
|
79
|
+
for (const byte of sample) {
|
|
80
|
+
if (byte === 0) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (byte < 7 || (byte > 14 && byte < 32)) {
|
|
84
|
+
suspicious += 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return suspicious / sample.length < 0.05;
|
|
88
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { AwbsError } from "../domain/errors.ts";
|
|
3
|
+
import type { GitCommandResult, GitPort } from "../ports/git.ts";
|
|
4
|
+
|
|
5
|
+
export class GitCliAdapter implements GitPort {
|
|
6
|
+
isRepository(root: string): boolean {
|
|
7
|
+
const result = this.runResult(["rev-parse", "--is-inside-work-tree"], root);
|
|
8
|
+
return result.status === 0 && result.stdout.trim() === "true";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
init(root: string): void {
|
|
12
|
+
this.run(["init"], root);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
headCommit(root: string): string | null {
|
|
16
|
+
const result = this.runResult(["rev-parse", "HEAD"], root);
|
|
17
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
requireHeadCommit(root: string): string {
|
|
21
|
+
const commit = this.headCommit(root);
|
|
22
|
+
if (!commit) {
|
|
23
|
+
throw new AwbsError("Git HEAD is not available. Create an initial commit before running this command.");
|
|
24
|
+
}
|
|
25
|
+
return commit;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
refCommit(root: string, ref: string): string | null {
|
|
29
|
+
const result = this.runResult(["rev-parse", "--verify", ref], root);
|
|
30
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
updateRef(root: string, ref: string, commit: string): void {
|
|
34
|
+
this.run(["update-ref", ref, commit], root);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isAncestor(root: string, ancestor: string, descendant: string): boolean {
|
|
38
|
+
const result = this.runResult(["merge-base", "--is-ancestor", ancestor, descendant], root);
|
|
39
|
+
return result.status === 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
revList(root: string, range: string): string[] {
|
|
43
|
+
const output = this.run(["rev-list", "--reverse", range], root).trim();
|
|
44
|
+
return output ? output.split(/\r?\n/) : [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
statusPorcelain(root: string): string {
|
|
48
|
+
return this.run(["status", "--porcelain", "-uall"], root);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
addAll(root: string, paths: string[]): void {
|
|
52
|
+
this.run(["add", "-A", "--", ...paths], root);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
commit(root: string, message: string): void {
|
|
56
|
+
this.run(["commit", "-m", message], root);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
createDetachedWorktree(root: string, path: string, commit: string): void {
|
|
60
|
+
this.run(["worktree", "add", "--detach", path, commit], root);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
removeWorktree(root: string, path: string): void {
|
|
64
|
+
this.run(["worktree", "remove", "--force", path], root);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cloneAtCommit(sourceRoot: string, destination: string, commit: string): void {
|
|
68
|
+
this.run(["clone", "--no-checkout", "--no-hardlinks", sourceRoot, destination], sourceRoot);
|
|
69
|
+
const sourceRemote = this.runResult(["remote", "get-url", "origin"], sourceRoot);
|
|
70
|
+
if (sourceRemote.status === 0 && sourceRemote.stdout.trim()) {
|
|
71
|
+
this.run(["remote", "set-url", "origin", sourceRemote.stdout.trim()], destination);
|
|
72
|
+
}
|
|
73
|
+
this.run(["checkout", "--detach", commit], destination);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
diffNoIndex(baselineRoot: string, workspacePath: string): string {
|
|
77
|
+
const result = spawnSync("git", ["diff", "--no-index", "--binary", "--no-color", "--", baselineRoot, workspacePath], {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
80
|
+
});
|
|
81
|
+
if (result.status === 0 || result.status === 1) {
|
|
82
|
+
return result.stdout ?? "";
|
|
83
|
+
}
|
|
84
|
+
return `git diff --no-index failed:\n${result.stderr ?? ""}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private run(args: string[], cwd: string): string {
|
|
88
|
+
const result = this.runResult(args, cwd);
|
|
89
|
+
if (result.status !== 0) {
|
|
90
|
+
throw new AwbsError(`git ${args.join(" ")} failed:\n${result.stderr || result.stdout}`);
|
|
91
|
+
}
|
|
92
|
+
return result.stdout;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private runResult(args: string[], cwd: string): GitCommandResult {
|
|
96
|
+
const result = spawnSync("git", args, {
|
|
97
|
+
cwd,
|
|
98
|
+
encoding: "utf8",
|
|
99
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
stdout: result.stdout ?? "",
|
|
103
|
+
stderr: result.stderr ?? "",
|
|
104
|
+
status: result.status
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|