@zqagent/core 0.1.30 → 0.2.5
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/CONTRIBUTING.md +65 -0
- package/LICENSE +190 -0
- package/README.md +102 -146
- package/README.oss.en.md +161 -0
- package/README.oss.md +165 -0
- package/README.oss.zh-TW.md +165 -0
- package/SECURITY.md +51 -0
- package/dist/{ToolRegistry-BmvPxgMr.d.cts → ToolRegistry-C3pTFiaJ.d.cts} +13 -4
- package/dist/{ToolRegistry-BmvPxgMr.d.ts → ToolRegistry-C3pTFiaJ.d.ts} +13 -4
- package/dist/cli/index.cjs +17799 -2062
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +16729 -985
- package/dist/cli/index.js.map +1 -1
- package/dist/{explore-OI6htHN1.d.cts → explore-D4lCKXbA.d.ts} +160 -19
- package/dist/{explore-Pmhyk4gv.d.ts → explore-Dj1j_GyN.d.cts} +160 -19
- package/dist/index.cjs +3626 -453
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -27
- package/dist/index.d.ts +337 -27
- package/dist/index.js +3617 -452
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +14029 -1482
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +14036 -1482
- package/dist/server/index.js.map +1 -1
- package/dist/tools/ask.cjs +14 -0
- package/dist/tools/ask.cjs.map +1 -1
- package/dist/tools/ask.d.cts +8 -2
- package/dist/tools/ask.d.ts +8 -2
- package/dist/tools/ask.js +13 -0
- package/dist/tools/ask.js.map +1 -1
- package/dist/tools/diff.cjs +59 -30
- package/dist/tools/diff.cjs.map +1 -1
- package/dist/tools/diff.d.cts +1 -1
- package/dist/tools/diff.d.ts +1 -1
- package/dist/tools/diff.js +59 -30
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/directories.d.cts +1 -1
- package/dist/tools/directories.d.ts +1 -1
- package/dist/tools/explore.cjs +1050 -309
- package/dist/tools/explore.cjs.map +1 -1
- package/dist/tools/explore.d.cts +2 -2
- package/dist/tools/explore.d.ts +2 -2
- package/dist/tools/explore.js +1057 -309
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/files.cjs +58 -43
- package/dist/tools/files.cjs.map +1 -1
- package/dist/tools/files.d.cts +10 -4
- package/dist/tools/files.d.ts +10 -4
- package/dist/tools/files.js +56 -42
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/git.cjs +816 -63
- package/dist/tools/git.cjs.map +1 -1
- package/dist/tools/git.d.cts +200 -2
- package/dist/tools/git.d.ts +200 -2
- package/dist/tools/git.js +788 -62
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/media.cjs +1191 -0
- package/dist/tools/media.cjs.map +1 -0
- package/dist/tools/media.d.cts +31 -0
- package/dist/tools/media.d.ts +31 -0
- package/dist/tools/media.js +1159 -0
- package/dist/tools/media.js.map +1 -0
- package/dist/tools/plan.d.cts +1 -1
- package/dist/tools/plan.d.ts +1 -1
- package/dist/tools/search.cjs +513 -33
- package/dist/tools/search.cjs.map +1 -1
- package/dist/tools/search.d.cts +12 -1
- package/dist/tools/search.d.ts +12 -1
- package/dist/tools/search.js +508 -33
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.cjs +631 -41
- package/dist/tools/shell.cjs.map +1 -1
- package/dist/tools/shell.d.cts +37 -4
- package/dist/tools/shell.d.ts +37 -4
- package/dist/tools/shell.js +626 -40
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/todo.d.cts +1 -1
- package/dist/tools/todo.d.ts +1 -1
- package/dist/tools/web.cjs +51 -18
- package/dist/tools/web.cjs.map +1 -1
- package/dist/tools/web.d.cts +1 -1
- package/dist/tools/web.d.ts +1 -1
- package/dist/tools/web.js +51 -18
- package/dist/tools/web.js.map +1 -1
- package/package.json +43 -8
package/dist/tools/git.js
CHANGED
|
@@ -1,88 +1,441 @@
|
|
|
1
1
|
// src/tools/git.ts
|
|
2
|
-
import
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
// src/tools/execAsync.ts
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
|
|
8
|
+
// src/tools/killTree.ts
|
|
9
|
+
import { spawnSync } from "child_process";
|
|
10
|
+
function killTree(pid) {
|
|
11
|
+
if (!pid) return;
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
try {
|
|
14
|
+
spawnSync("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
process.kill(-pid, "SIGKILL");
|
|
21
|
+
} catch {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, "SIGKILL");
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/tools/execAsync.ts
|
|
30
|
+
var liveChildren = /* @__PURE__ */ new Set();
|
|
31
|
+
var exitCleanupInstalled = false;
|
|
32
|
+
function trackChild(pid) {
|
|
33
|
+
if (!pid) return;
|
|
34
|
+
liveChildren.add(pid);
|
|
35
|
+
if (exitCleanupInstalled) return;
|
|
36
|
+
exitCleanupInstalled = true;
|
|
37
|
+
process.once("exit", () => {
|
|
38
|
+
for (const p of liveChildren) killTree(p);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
var DEFAULT_MAX_BUFFER = 1024 * 1024 * 5;
|
|
42
|
+
var KILL_GRACE_MS = 1e3;
|
|
43
|
+
function makeSink(maxBuffer) {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
let len = 0;
|
|
46
|
+
let over = false;
|
|
47
|
+
return {
|
|
48
|
+
push(chunk) {
|
|
49
|
+
if (len >= maxBuffer) {
|
|
50
|
+
over = true;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const room = maxBuffer - len;
|
|
54
|
+
if (chunk.length > room) {
|
|
55
|
+
chunks.push(chunk.subarray(0, room));
|
|
56
|
+
len = maxBuffer;
|
|
57
|
+
over = true;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
chunks.push(chunk);
|
|
61
|
+
len += chunk.length;
|
|
62
|
+
},
|
|
63
|
+
done: () => Buffer.concat(chunks),
|
|
64
|
+
over: () => over
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function execAsync(file, argv, opts = {}) {
|
|
68
|
+
return new Promise((resolve2) => {
|
|
69
|
+
const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
70
|
+
const out = makeSink(maxBuffer);
|
|
71
|
+
const err = makeSink(maxBuffer);
|
|
72
|
+
if (opts.signal?.aborted) {
|
|
73
|
+
resolve2({
|
|
74
|
+
status: null,
|
|
75
|
+
stdout: Buffer.alloc(0),
|
|
76
|
+
stderr: Buffer.alloc(0),
|
|
77
|
+
timedOut: false,
|
|
78
|
+
truncated: false,
|
|
79
|
+
aborted: true
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
let child;
|
|
84
|
+
try {
|
|
85
|
+
child = spawn(file, argv, {
|
|
86
|
+
...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
|
|
87
|
+
...opts.env !== void 0 ? { env: opts.env } : {},
|
|
88
|
+
// 自成进程组(POSIX):超时/中断时用 killTree 一次收掉整棵进程树
|
|
89
|
+
detached: process.platform !== "win32",
|
|
90
|
+
stdio: [opts.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
|
|
91
|
+
windowsHide: true
|
|
92
|
+
});
|
|
93
|
+
} catch (e) {
|
|
94
|
+
resolve2({
|
|
95
|
+
status: null,
|
|
96
|
+
stdout: Buffer.alloc(0),
|
|
97
|
+
stderr: Buffer.alloc(0),
|
|
98
|
+
error: e,
|
|
99
|
+
timedOut: false,
|
|
100
|
+
truncated: false
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
trackChild(child.pid);
|
|
105
|
+
let settled = false;
|
|
106
|
+
let timedOut = false;
|
|
107
|
+
let aborted = false;
|
|
108
|
+
let spawnError;
|
|
109
|
+
let timer = null;
|
|
110
|
+
let graceTimer = null;
|
|
111
|
+
const onAbort = () => {
|
|
112
|
+
aborted = true;
|
|
113
|
+
killAndSettle();
|
|
114
|
+
};
|
|
115
|
+
const finish = (status) => {
|
|
116
|
+
if (settled) return;
|
|
117
|
+
settled = true;
|
|
118
|
+
if (timer) clearTimeout(timer);
|
|
119
|
+
if (graceTimer) clearTimeout(graceTimer);
|
|
120
|
+
if (child.pid !== void 0) liveChildren.delete(child.pid);
|
|
121
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
122
|
+
const result = {
|
|
123
|
+
status,
|
|
124
|
+
stdout: out.done(),
|
|
125
|
+
stderr: err.done(),
|
|
126
|
+
timedOut,
|
|
127
|
+
truncated: out.over() || err.over()
|
|
128
|
+
};
|
|
129
|
+
if (aborted) result.aborted = true;
|
|
130
|
+
if (spawnError) result.error = spawnError;
|
|
131
|
+
resolve2(result);
|
|
132
|
+
};
|
|
133
|
+
const killAndSettle = () => {
|
|
134
|
+
killTree(child.pid);
|
|
135
|
+
if (settled || graceTimer) return;
|
|
136
|
+
graceTimer = setTimeout(() => {
|
|
137
|
+
child.stdout?.destroy();
|
|
138
|
+
child.stderr?.destroy();
|
|
139
|
+
finish(null);
|
|
140
|
+
}, KILL_GRACE_MS);
|
|
141
|
+
};
|
|
142
|
+
if (opts.timeoutMs !== void 0 && opts.timeoutMs > 0) {
|
|
143
|
+
timer = setTimeout(() => {
|
|
144
|
+
timedOut = true;
|
|
145
|
+
killAndSettle();
|
|
146
|
+
}, opts.timeoutMs);
|
|
147
|
+
}
|
|
148
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
149
|
+
child.stdout?.on("data", (c) => out.push(c));
|
|
150
|
+
child.stderr?.on("data", (c) => err.push(c));
|
|
151
|
+
child.on("error", (e) => {
|
|
152
|
+
spawnError = e;
|
|
153
|
+
if (child.pid === void 0) finish(null);
|
|
154
|
+
});
|
|
155
|
+
if (opts.input !== void 0 && child.stdin) {
|
|
156
|
+
child.stdin.on("error", () => {
|
|
157
|
+
});
|
|
158
|
+
child.stdin.end(opts.input);
|
|
159
|
+
}
|
|
160
|
+
child.on("close", (code) => finish(code));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/tools/shellBackground.ts
|
|
165
|
+
import { spawn as spawn2 } from "child_process";
|
|
166
|
+
var DEFAULT_BUFFER_LIMIT = 64 * 1024;
|
|
167
|
+
var RingBuffer = class {
|
|
168
|
+
constructor(limit = DEFAULT_BUFFER_LIMIT) {
|
|
169
|
+
this.limit = limit;
|
|
170
|
+
}
|
|
171
|
+
limit;
|
|
172
|
+
chunks = [];
|
|
173
|
+
size = 0;
|
|
174
|
+
add(chunk) {
|
|
175
|
+
if (!chunk) return;
|
|
176
|
+
this.chunks.push(chunk);
|
|
177
|
+
this.size += chunk.length;
|
|
178
|
+
while (this.size > this.limit && this.chunks.length > 1) {
|
|
179
|
+
this.size -= this.chunks.shift().length;
|
|
180
|
+
}
|
|
181
|
+
if (this.size > this.limit) {
|
|
182
|
+
const joined = this.chunks.join("");
|
|
183
|
+
this.chunks = [joined.slice(-this.limit)];
|
|
184
|
+
this.size = this.chunks[0].length;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
get() {
|
|
188
|
+
const joined = this.chunks.join("");
|
|
189
|
+
return joined.length > this.limit ? joined.slice(-this.limit) : joined;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var ShellTaskManager = class {
|
|
193
|
+
tasks = /* @__PURE__ */ new Map();
|
|
194
|
+
seq = 0;
|
|
195
|
+
cleanupInstalled = false;
|
|
196
|
+
/** 启动一个后台任务,立即返回快照(不等退出) */
|
|
197
|
+
start(opts) {
|
|
198
|
+
this.installCleanup();
|
|
199
|
+
const id = `shell-${++this.seq}-${Date.now().toString(36)}`;
|
|
200
|
+
const child = spawn2(opts.file, opts.argv, {
|
|
201
|
+
cwd: opts.cwd,
|
|
202
|
+
env: opts.env,
|
|
203
|
+
detached: process.platform !== "win32",
|
|
204
|
+
windowsHide: true,
|
|
205
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
206
|
+
});
|
|
207
|
+
const task = {
|
|
208
|
+
id,
|
|
209
|
+
command: opts.command,
|
|
210
|
+
scope: opts.scope ?? null,
|
|
211
|
+
child,
|
|
212
|
+
stdout: new RingBuffer(),
|
|
213
|
+
stderr: new RingBuffer(),
|
|
214
|
+
status: "running",
|
|
215
|
+
exitCode: null,
|
|
216
|
+
signal: null,
|
|
217
|
+
startedAt: Date.now(),
|
|
218
|
+
endedAt: null
|
|
219
|
+
};
|
|
220
|
+
this.tasks.set(id, task);
|
|
221
|
+
child.stdout?.on("data", (d) => task.stdout.add(d.toString("utf-8")));
|
|
222
|
+
child.stderr?.on("data", (d) => task.stderr.add(d.toString("utf-8")));
|
|
223
|
+
child.on("error", (err) => task.stderr.add(`[spawn error] ${err.message}
|
|
224
|
+
`));
|
|
225
|
+
child.on("exit", (code, signal) => {
|
|
226
|
+
task.exitCode = code;
|
|
227
|
+
task.signal = signal;
|
|
228
|
+
task.endedAt = Date.now();
|
|
229
|
+
if (task.status !== "killed") task.status = "exited";
|
|
230
|
+
});
|
|
231
|
+
child.unref();
|
|
232
|
+
return this.snapshot(task);
|
|
233
|
+
}
|
|
234
|
+
/** 读取任务输出与状态 */
|
|
235
|
+
output(taskId) {
|
|
236
|
+
const task = this.tasks.get(taskId);
|
|
237
|
+
if (!task) return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
|
|
238
|
+
return this.snapshot(task);
|
|
239
|
+
}
|
|
240
|
+
/** 终止任务(进程树)并返回快照;传 scope 时只允许终止该归属下的任务 */
|
|
241
|
+
kill(taskId, scope) {
|
|
242
|
+
const task = this.tasks.get(taskId);
|
|
243
|
+
if (!task) return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
|
|
244
|
+
if (scope !== void 0 && task.scope !== scope) {
|
|
245
|
+
return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
|
|
246
|
+
}
|
|
247
|
+
if (task.status === "running") {
|
|
248
|
+
task.status = "killed";
|
|
249
|
+
task.endedAt = Date.now();
|
|
250
|
+
killTree(task.child.pid);
|
|
251
|
+
}
|
|
252
|
+
return this.snapshot(task);
|
|
253
|
+
}
|
|
254
|
+
/** 列出任务快照(调试/展示用);传 scope 时只返回该归属下的任务 */
|
|
255
|
+
list(scope) {
|
|
256
|
+
const all = [...this.tasks.values()];
|
|
257
|
+
return (scope === void 0 ? all : all.filter((t) => t.scope === scope)).map(
|
|
258
|
+
(t) => this.snapshot(t)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
/** 终止全部任务(进程退出时调用) */
|
|
262
|
+
disposeAll() {
|
|
263
|
+
for (const task of this.tasks.values()) {
|
|
264
|
+
if (task.status === "running") {
|
|
265
|
+
task.status = "killed";
|
|
266
|
+
killTree(task.child.pid);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
snapshot(task) {
|
|
271
|
+
return {
|
|
272
|
+
task_id: task.id,
|
|
273
|
+
command: task.command,
|
|
274
|
+
status: task.status,
|
|
275
|
+
running: task.status === "running",
|
|
276
|
+
exit_code: task.exitCode,
|
|
277
|
+
signal: task.signal,
|
|
278
|
+
stdout: task.stdout.get(),
|
|
279
|
+
stderr: task.stderr.get(),
|
|
280
|
+
duration_ms: (task.endedAt ?? Date.now()) - task.startedAt
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
installCleanup() {
|
|
284
|
+
if (this.cleanupInstalled) return;
|
|
285
|
+
this.cleanupInstalled = true;
|
|
286
|
+
process.once("exit", () => this.disposeAll());
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// src/tools/shell.ts
|
|
291
|
+
var MAX_BUFFER = 1024 * 1024 * 5;
|
|
292
|
+
function decodeOutput(buf) {
|
|
293
|
+
if (!buf || buf.length === 0) return "";
|
|
294
|
+
try {
|
|
295
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(buf);
|
|
296
|
+
} catch {
|
|
297
|
+
for (const enc of ["gbk", "big5", "shift_jis", "latin1"]) {
|
|
298
|
+
try {
|
|
299
|
+
return new TextDecoder(enc).decode(buf);
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return buf.toString("latin1");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
var shellTasks = new ShellTaskManager();
|
|
307
|
+
|
|
308
|
+
// src/tools/git.ts
|
|
309
|
+
async function runGit(repoDir, args, timeout = 15e3) {
|
|
310
|
+
const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
|
|
311
|
+
cwd: repoDir,
|
|
312
|
+
timeoutMs: timeout,
|
|
313
|
+
maxBuffer: 1024 * 1024 * 5,
|
|
314
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
315
|
+
});
|
|
316
|
+
return {
|
|
317
|
+
status: res.status ?? 1,
|
|
318
|
+
stdout: decodeOutput(res.stdout),
|
|
319
|
+
stderr: decodeOutput(res.stderr) || (res.error?.message ?? "")
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function resolveRepoRoot(workspace, pathArg) {
|
|
323
|
+
let dir = workspace;
|
|
324
|
+
if (pathArg) {
|
|
325
|
+
const abs = path.resolve(workspace, pathArg);
|
|
326
|
+
try {
|
|
327
|
+
dir = fs.statSync(abs).isDirectory() ? abs : path.dirname(abs);
|
|
328
|
+
} catch {
|
|
329
|
+
dir = abs;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const res = await runGit(dir, ["rev-parse", "--show-toplevel"], 1e4);
|
|
333
|
+
if (res.status === 0 && res.stdout.trim()) return res.stdout.trim();
|
|
334
|
+
return workspace;
|
|
335
|
+
}
|
|
336
|
+
function repoDirArg(workspace, raw) {
|
|
337
|
+
if (typeof raw !== "string") return void 0;
|
|
338
|
+
const v = raw.trim();
|
|
339
|
+
if (!v) return void 0;
|
|
340
|
+
const base = path.resolve(workspace);
|
|
341
|
+
const abs = path.resolve(base, v);
|
|
342
|
+
const rel = path.relative(base, abs);
|
|
343
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) return void 0;
|
|
344
|
+
return abs;
|
|
345
|
+
}
|
|
346
|
+
function toPathspec(repoRoot, workspace, pathArg) {
|
|
347
|
+
if (!pathArg) return null;
|
|
348
|
+
const abs = path.resolve(workspace, pathArg);
|
|
349
|
+
let rel = path.relative(repoRoot, abs);
|
|
350
|
+
if (rel === "") return ".";
|
|
351
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
352
|
+
try {
|
|
353
|
+
const realRepo = fs.realpathSync(repoRoot);
|
|
354
|
+
const realAbs = fs.realpathSync(abs);
|
|
355
|
+
const rel2 = path.relative(realRepo, realAbs);
|
|
356
|
+
if (rel2 === "") return ".";
|
|
357
|
+
if (!rel2.startsWith("..") && !path.isAbsolute(rel2)) return rel2;
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
return rel;
|
|
363
|
+
}
|
|
3
364
|
function makeGitLog(workspace) {
|
|
4
|
-
return function git_log(args) {
|
|
365
|
+
return async function git_log(args) {
|
|
5
366
|
const filePath = args.path ? String(args.path) : void 0;
|
|
6
367
|
const maxCount = Math.min(Number(args.max_count ?? 20), 200);
|
|
7
368
|
if (maxCount < 1) return { error: "max_count \u5FC5\u987B >= 1" };
|
|
369
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);
|
|
8
370
|
const cmd = [
|
|
9
|
-
"-C",
|
|
10
|
-
workspace,
|
|
11
371
|
"log",
|
|
12
372
|
`-n${maxCount}`,
|
|
13
373
|
"--pretty=format:%h%x1f%an%x1f%ad%x1f%s",
|
|
14
374
|
"--date=short"
|
|
15
375
|
];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (parts.length === 4) {
|
|
22
|
-
return { hash: parts[0], author: parts[1], date: parts[2], subject: parts[3] };
|
|
23
|
-
}
|
|
24
|
-
return { raw: line };
|
|
25
|
-
});
|
|
26
|
-
return { count: commits.length, commits };
|
|
27
|
-
} catch (exc) {
|
|
28
|
-
const err = exc;
|
|
29
|
-
return { error: (err.stderr ?? err.message ?? "git log failed").slice(0, 300) };
|
|
376
|
+
const pathspec = toPathspec(repo, workspace, filePath);
|
|
377
|
+
if (pathspec) cmd.push("--", pathspec);
|
|
378
|
+
const res = await runGit(repo, cmd);
|
|
379
|
+
if (res.status !== 0) {
|
|
380
|
+
return { error: (res.stderr || "git log failed").trim().slice(0, 300) };
|
|
30
381
|
}
|
|
382
|
+
const commits = res.stdout.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0).map((line) => {
|
|
383
|
+
const parts = line.split("");
|
|
384
|
+
if (parts.length === 4) {
|
|
385
|
+
return { hash: parts[0], author: parts[1], date: parts[2], subject: parts[3] };
|
|
386
|
+
}
|
|
387
|
+
return { raw: line };
|
|
388
|
+
});
|
|
389
|
+
return { repo, count: commits.length, commits };
|
|
31
390
|
};
|
|
32
391
|
}
|
|
33
392
|
function makeGitStatus(workspace) {
|
|
34
|
-
return function git_status(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
const lines = out.trim().split("\n").filter(Boolean);
|
|
42
|
-
const entries = lines.map((line) => {
|
|
43
|
-
const status = line.slice(0, 2).trim();
|
|
44
|
-
const file = line.slice(3);
|
|
45
|
-
return { status, file };
|
|
46
|
-
});
|
|
47
|
-
return {
|
|
48
|
-
clean: entries.length === 0,
|
|
49
|
-
entries,
|
|
50
|
-
count: entries.length
|
|
51
|
-
};
|
|
52
|
-
} catch (exc) {
|
|
53
|
-
const err = exc;
|
|
54
|
-
if ((err.stderr ?? "").toLowerCase().includes("not a git repository")) {
|
|
393
|
+
return async function git_status(args) {
|
|
394
|
+
const filePath = args.path ? String(args.path) : void 0;
|
|
395
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);
|
|
396
|
+
const res = await runGit(repo, ["status", "--porcelain"], 1e4);
|
|
397
|
+
if (res.status !== 0) {
|
|
398
|
+
const msg = res.stderr;
|
|
399
|
+
if (msg.toLowerCase().includes("not a git repository")) {
|
|
55
400
|
return { error: "\u4E0D\u662F git \u4ED3\u5E93" };
|
|
56
401
|
}
|
|
57
|
-
return { error: (
|
|
402
|
+
return { error: (msg || "git status failed").trim().slice(0, 300) };
|
|
58
403
|
}
|
|
404
|
+
const lines = res.stdout.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0);
|
|
405
|
+
const entries = lines.map((line) => {
|
|
406
|
+
const status = line.slice(0, 2).trim();
|
|
407
|
+
const index = line.slice(0, 1).trim();
|
|
408
|
+
const worktree = line.slice(1, 2).trim();
|
|
409
|
+
let file = line.slice(3);
|
|
410
|
+
const renameArrow = file.indexOf(" -> ");
|
|
411
|
+
if (renameArrow >= 0) file = file.slice(renameArrow + 4);
|
|
412
|
+
return { status, file, index, worktree };
|
|
413
|
+
});
|
|
414
|
+
return { repo, clean: entries.length === 0, entries, count: entries.length };
|
|
59
415
|
};
|
|
60
416
|
}
|
|
61
417
|
function makeGitDiff(workspace) {
|
|
62
|
-
return function git_diff(args) {
|
|
418
|
+
return async function git_diff(args) {
|
|
63
419
|
const staged = args.staged === true;
|
|
64
420
|
const filePath = args.path ? String(args.path) : void 0;
|
|
65
421
|
const contextLines = Math.min(Number(args.context ?? 3), 10);
|
|
422
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);
|
|
66
423
|
const cmd = ["diff"];
|
|
67
424
|
if (staged) cmd.push("--staged");
|
|
68
425
|
cmd.push(`-U${contextLines}`);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
maxBuffer: 1024 * 1024 * 2
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
staged,
|
|
78
|
-
path: filePath ?? null,
|
|
79
|
-
diff: out.slice(0, 5e4),
|
|
80
|
-
truncated: out.length > 5e4
|
|
81
|
-
};
|
|
82
|
-
} catch (exc) {
|
|
83
|
-
const err = exc;
|
|
84
|
-
return { error: (err.stderr ?? err.message ?? "git diff failed").slice(0, 300) };
|
|
426
|
+
const pathspec = toPathspec(repo, workspace, filePath);
|
|
427
|
+
if (pathspec) cmd.push("--", pathspec);
|
|
428
|
+
const res = await runGit(repo, cmd);
|
|
429
|
+
if (res.status !== 0) {
|
|
430
|
+
return { error: (res.stderr || "git diff failed").trim().slice(0, 300) };
|
|
85
431
|
}
|
|
432
|
+
return {
|
|
433
|
+
repo,
|
|
434
|
+
staged,
|
|
435
|
+
path: filePath ?? null,
|
|
436
|
+
diff: res.stdout.slice(0, 5e4),
|
|
437
|
+
truncated: res.stdout.length > 5e4
|
|
438
|
+
};
|
|
86
439
|
};
|
|
87
440
|
}
|
|
88
441
|
function registerGitTools(registry, workspace) {
|
|
@@ -94,11 +447,11 @@ function registerGitTools(registry, workspace) {
|
|
|
94
447
|
type: "function",
|
|
95
448
|
function: {
|
|
96
449
|
name: "git_log",
|
|
97
|
-
description: "\u67E5\u770B\u6700\u8FD1\u7684 git \u63D0\u4EA4\u5386\u53F2 (hash, \u4F5C\u8005, \u65E5\u671F, \u4E3B\u9898)\u3002\u53EF\u9009\u5730\u9650\u5B9A\u5230\u67D0\u4E2A\u8DEF\u5F84\u3002\u7528\u4E8E\u5728\u7F16\u8F91\u524D\u4E86\u89E3\u4EE3\u7801\u7684\u6F14\u8FDB\u8FC7\u7A0B\u3002\u53EA\u8BFB\u3002",
|
|
450
|
+
description: "\u67E5\u770B\u6700\u8FD1\u7684 git \u63D0\u4EA4\u5386\u53F2 (hash, \u4F5C\u8005, \u65E5\u671F, \u4E3B\u9898)\u3002\u53EF\u9009\u5730\u9650\u5B9A\u5230\u67D0\u4E2A\u8DEF\u5F84\uFF08\u8BE5\u8DEF\u5F84\u6240\u5728\u4ED3\u5E93\u4F1A\u88AB\u81EA\u52A8\u5B9A\u4F4D\uFF09\u3002\u7528\u4E8E\u5728\u7F16\u8F91\u524D\u4E86\u89E3\u4EE3\u7801\u7684\u6F14\u8FDB\u8FC7\u7A0B\u3002\u53EA\u8BFB\u3002",
|
|
98
451
|
parameters: {
|
|
99
452
|
type: "object",
|
|
100
453
|
properties: {
|
|
101
|
-
path: { type: "string", description: "\u53EF\u9009\uFF1A\u9650\u5B9A\u5230\u67D0\u4E2A\u6587\u4EF6\u6216\u76EE\u5F55" },
|
|
454
|
+
path: { type: "string", description: "\u53EF\u9009\uFF1A\u9650\u5B9A\u5230\u67D0\u4E2A\u6587\u4EF6\u6216\u76EE\u5F55\uFF08\u5E76\u6309\u5176\u6240\u5728\u4ED3\u5E93\u67E5\u8BE2\uFF09" },
|
|
102
455
|
max_count: { type: "integer", description: "\u8FD4\u56DE\u591A\u5C11\u6761\u63D0\u4EA4 (\u9ED8\u8BA4 20\uFF0C\u6700\u5927 200)" }
|
|
103
456
|
}
|
|
104
457
|
}
|
|
@@ -116,7 +469,9 @@ function registerGitTools(registry, workspace) {
|
|
|
116
469
|
description: "\u67E5\u770B\u5DE5\u4F5C\u533A git \u72B6\u6001 (\u5DF2\u4FEE\u6539\u3001\u5DF2\u6682\u5B58\u3001\u672A\u8DDF\u8E2A\u7684\u6587\u4EF6)\u3002\u53EA\u8BFB\u3002",
|
|
117
470
|
parameters: {
|
|
118
471
|
type: "object",
|
|
119
|
-
properties: {
|
|
472
|
+
properties: {
|
|
473
|
+
path: { type: "string", description: "\u53EF\u9009\uFF1A\u4F18\u5148\u67E5\u8BE2\u8BE5\u8DEF\u5F84\u6240\u5728\u7684\u4ED3\u5E93\uFF08\u9ED8\u8BA4\u5DE5\u4F5C\u533A\u6839\uFF09" }
|
|
474
|
+
}
|
|
120
475
|
}
|
|
121
476
|
}
|
|
122
477
|
}
|
|
@@ -134,7 +489,7 @@ function registerGitTools(registry, workspace) {
|
|
|
134
489
|
type: "object",
|
|
135
490
|
properties: {
|
|
136
491
|
staged: { type: "boolean", description: "\u662F\u5426\u67E5\u770B\u5DF2\u6682\u5B58\u7684\u66F4\u6539 (\u9ED8\u8BA4 false)" },
|
|
137
|
-
path: { type: "string", description: "\u53EF\u9009\uFF1A\u9650\u5B9A\u5230\u67D0\u4E2A\u6587\u4EF6" },
|
|
492
|
+
path: { type: "string", description: "\u53EF\u9009\uFF1A\u9650\u5B9A\u5230\u67D0\u4E2A\u6587\u4EF6\uFF08\u5E76\u6309\u5176\u5728\u4ED3\u5E93\u4E2D\u7684\u8DEF\u5F84\uFF09" },
|
|
138
493
|
context: { type: "integer", description: "\u4E0A\u4E0B\u6587\u884C\u6570 (\u9ED8\u8BA4 3)" }
|
|
139
494
|
}
|
|
140
495
|
}
|
|
@@ -142,7 +497,378 @@ function registerGitTools(registry, workspace) {
|
|
|
142
497
|
}
|
|
143
498
|
);
|
|
144
499
|
}
|
|
500
|
+
var OP_OUTPUT_LIMIT = 8e3;
|
|
501
|
+
function opResult(res) {
|
|
502
|
+
const stdout = res.stdout.trim().slice(-OP_OUTPUT_LIMIT);
|
|
503
|
+
const stderr = res.stderr.trim().slice(-OP_OUTPUT_LIMIT);
|
|
504
|
+
if (res.status !== 0) {
|
|
505
|
+
return {
|
|
506
|
+
ok: false,
|
|
507
|
+
stdout,
|
|
508
|
+
stderr,
|
|
509
|
+
error: (stderr || stdout || "git \u547D\u4EE4\u5931\u8D25").slice(0, 500)
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return { ok: true, stdout, stderr };
|
|
513
|
+
}
|
|
514
|
+
function failed(error) {
|
|
515
|
+
return { ok: false, stdout: "", stderr: "", error };
|
|
516
|
+
}
|
|
517
|
+
function gitRefProblem(name) {
|
|
518
|
+
const v = name.trim();
|
|
519
|
+
if (!v) return "\u5206\u652F\u540D\u4E0D\u80FD\u4E3A\u7A7A";
|
|
520
|
+
if (v.startsWith("-")) return "\u5206\u652F\u540D\u4E0D\u80FD\u4EE5 - \u5F00\u5934";
|
|
521
|
+
if (/[\s~^:?*\[\\\x00-\x1f\x7f]/.test(v)) return "\u5206\u652F\u540D\u5305\u542B\u7A7A\u767D\u6216\u975E\u6CD5\u5B57\u7B26";
|
|
522
|
+
if (v.includes("..") || v.includes("//") || v.endsWith("/") || v.endsWith(".lock")) {
|
|
523
|
+
return "\u5206\u652F\u540D\u4E0D\u5408\u6CD5";
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
function normalizePaths(raw) {
|
|
528
|
+
const list = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
529
|
+
return list.map((p) => String(p).trim()).filter((p) => p.length > 0);
|
|
530
|
+
}
|
|
531
|
+
function toPathspecs(repoRoot, workspace, paths) {
|
|
532
|
+
const out = [];
|
|
533
|
+
for (const p of paths) {
|
|
534
|
+
const spec = toPathspec(repoRoot, workspace, p);
|
|
535
|
+
if (spec) out.push(spec);
|
|
536
|
+
}
|
|
537
|
+
return out;
|
|
538
|
+
}
|
|
539
|
+
function makeGitBranches(workspace) {
|
|
540
|
+
return async function git_branches(args) {
|
|
541
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
542
|
+
const list = await runGit(
|
|
543
|
+
repo,
|
|
544
|
+
[
|
|
545
|
+
"for-each-ref",
|
|
546
|
+
"--sort=-committerdate",
|
|
547
|
+
// 制表符作字段分隔(git 的 --format 不支持 %x1f 之类的十六进制转义)
|
|
548
|
+
"--format=%(refname:short) %(HEAD) %(upstream:short)",
|
|
549
|
+
"refs/heads"
|
|
550
|
+
],
|
|
551
|
+
1e4
|
|
552
|
+
);
|
|
553
|
+
if (list.status !== 0) {
|
|
554
|
+
const msg = list.stderr;
|
|
555
|
+
if (msg.toLowerCase().includes("not a git repository")) return { error: "\u4E0D\u662F git \u4ED3\u5E93" };
|
|
556
|
+
return { error: (msg || "git branch failed").trim().slice(0, 300) };
|
|
557
|
+
}
|
|
558
|
+
const head = await runGit(repo, ["symbolic-ref", "--short", "HEAD"], 1e4);
|
|
559
|
+
const detached = head.status !== 0;
|
|
560
|
+
let current = detached ? "" : head.stdout.trim();
|
|
561
|
+
if (detached) {
|
|
562
|
+
const rev = await runGit(repo, ["rev-parse", "--short", "HEAD"], 1e4);
|
|
563
|
+
current = rev.status === 0 ? rev.stdout.trim() : "";
|
|
564
|
+
}
|
|
565
|
+
const branches = list.stdout.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0).map((line) => {
|
|
566
|
+
const [name, headMark, upstream] = line.split(" ");
|
|
567
|
+
return {
|
|
568
|
+
name,
|
|
569
|
+
current: headMark?.trim() === "*" || !!current && name === current,
|
|
570
|
+
...upstream ? { upstream } : {}
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
return { repo, current, detached, branches };
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function gitObjectProblem(name) {
|
|
577
|
+
const v = name.trim();
|
|
578
|
+
if (!v) return "\u63D0\u4EA4\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A";
|
|
579
|
+
if (v.startsWith("-")) return "\u63D0\u4EA4\u6807\u8BC6\u4E0D\u80FD\u4EE5 - \u5F00\u5934";
|
|
580
|
+
if (/[\s~^:?*\[\\\x00-\x1f\x7f]/.test(v)) return "\u63D0\u4EA4\u6807\u8BC6\u5305\u542B\u7A7A\u767D\u6216\u975E\u6CD5\u5B57\u7B26";
|
|
581
|
+
if (v.includes("..") || v.includes("//") || v.endsWith("/") || v.endsWith(".lock")) {
|
|
582
|
+
return "\u63D0\u4EA4\u6807\u8BC6\u4E0D\u5408\u6CD5";
|
|
583
|
+
}
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
function makeGitRefs(workspace) {
|
|
587
|
+
return async function git_refs(args) {
|
|
588
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
589
|
+
const list = await runGit(
|
|
590
|
+
repo,
|
|
591
|
+
[
|
|
592
|
+
"for-each-ref",
|
|
593
|
+
"--sort=-committerdate",
|
|
594
|
+
"--format=%(refname) %(refname:short) %(HEAD) %(upstream:short) %(objectname:short)",
|
|
595
|
+
"refs/heads",
|
|
596
|
+
"refs/remotes",
|
|
597
|
+
"refs/tags"
|
|
598
|
+
],
|
|
599
|
+
15e3
|
|
600
|
+
);
|
|
601
|
+
if (list.status !== 0) {
|
|
602
|
+
const msg = list.stderr;
|
|
603
|
+
if (msg.toLowerCase().includes("not a git repository")) return { error: "\u4E0D\u662F git \u4ED3\u5E93" };
|
|
604
|
+
return { error: (msg || "git for-each-ref failed").trim().slice(0, 300) };
|
|
605
|
+
}
|
|
606
|
+
const head = await runGit(repo, ["symbolic-ref", "--short", "HEAD"], 1e4);
|
|
607
|
+
const detached = head.status !== 0;
|
|
608
|
+
let current = detached ? "" : head.stdout.trim();
|
|
609
|
+
if (detached) {
|
|
610
|
+
const rev = await runGit(repo, ["rev-parse", "--short", "HEAD"], 1e4);
|
|
611
|
+
current = rev.status === 0 ? rev.stdout.trim() : "";
|
|
612
|
+
}
|
|
613
|
+
const local = [];
|
|
614
|
+
const remote = [];
|
|
615
|
+
const tags = [];
|
|
616
|
+
for (const raw of list.stdout.split("\n")) {
|
|
617
|
+
const line = raw.replace(/\r$/, "");
|
|
618
|
+
if (!line) continue;
|
|
619
|
+
const [refname, short, headMark, upstream, target] = line.split(" ");
|
|
620
|
+
if (!refname || !short) continue;
|
|
621
|
+
if (refname.startsWith("refs/heads/")) {
|
|
622
|
+
local.push({
|
|
623
|
+
name: short,
|
|
624
|
+
current: headMark?.trim() === "*" || !!current && short === current,
|
|
625
|
+
...upstream ? { upstream } : {}
|
|
626
|
+
});
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (refname.startsWith("refs/remotes/")) {
|
|
630
|
+
if (refname.endsWith("/HEAD")) continue;
|
|
631
|
+
remote.push({ name: short });
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (refname.startsWith("refs/tags/")) {
|
|
635
|
+
tags.push({ name: short, ...target ? { target } : {} });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return { repo, current, detached, local, remote, tags };
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function makeGitShow(workspace) {
|
|
642
|
+
return async function git_show(args) {
|
|
643
|
+
const hash = String(args.hash ?? "").trim();
|
|
644
|
+
const problem = gitObjectProblem(hash);
|
|
645
|
+
if (problem) return { error: problem };
|
|
646
|
+
const filePath = args.path ? String(args.path) : void 0;
|
|
647
|
+
const contextLines = Math.min(Number(args.context ?? 3), 10);
|
|
648
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);
|
|
649
|
+
const metaRes = await runGit(
|
|
650
|
+
repo,
|
|
651
|
+
[
|
|
652
|
+
"show",
|
|
653
|
+
"-s",
|
|
654
|
+
"--first-parent",
|
|
655
|
+
"--date=iso-strict",
|
|
656
|
+
"--format=%H%x1f%h%x1f%an%x1f%ae%x1f%ad%x1f%s%x1f%b",
|
|
657
|
+
hash
|
|
658
|
+
],
|
|
659
|
+
15e3
|
|
660
|
+
);
|
|
661
|
+
if (metaRes.status !== 0) {
|
|
662
|
+
return { error: (metaRes.stderr || "git show failed").trim().slice(0, 300) };
|
|
663
|
+
}
|
|
664
|
+
const parts = metaRes.stdout.split("");
|
|
665
|
+
const commit = {
|
|
666
|
+
hash: (parts[0] ?? "").trim(),
|
|
667
|
+
short: (parts[1] ?? "").trim(),
|
|
668
|
+
author: (parts[2] ?? "").trim(),
|
|
669
|
+
authorEmail: (parts[3] ?? "").trim(),
|
|
670
|
+
date: (parts[4] ?? "").trim(),
|
|
671
|
+
subject: (parts[5] ?? "").trim(),
|
|
672
|
+
body: (parts.slice(6).join("") ?? "").trim()
|
|
673
|
+
};
|
|
674
|
+
if (!commit.hash) return { error: "\u672A\u627E\u5230\u8BE5\u63D0\u4EA4" };
|
|
675
|
+
const filesRes = await runGit(
|
|
676
|
+
repo,
|
|
677
|
+
["show", "--name-status", "--format=", "--first-parent", commit.hash],
|
|
678
|
+
15e3
|
|
679
|
+
);
|
|
680
|
+
const files = filesRes.stdout.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.trim().length > 0).map((line) => {
|
|
681
|
+
const cols = line.split(" ");
|
|
682
|
+
const status = (cols[0] ?? "").trim();
|
|
683
|
+
const file = (cols[cols.length - 1] ?? "").trim();
|
|
684
|
+
return { status, file };
|
|
685
|
+
}).filter((f) => f.file.length > 0);
|
|
686
|
+
const cmd = ["show", "--format=", "--first-parent", `-U${contextLines}`, commit.hash];
|
|
687
|
+
const pathspec = toPathspec(repo, workspace, filePath);
|
|
688
|
+
if (pathspec) cmd.push("--", pathspec);
|
|
689
|
+
const diffRes = await runGit(repo, cmd, 2e4);
|
|
690
|
+
if (diffRes.status !== 0) {
|
|
691
|
+
return { error: (diffRes.stderr || "git show diff failed").trim().slice(0, 300) };
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
repo,
|
|
695
|
+
commit,
|
|
696
|
+
files,
|
|
697
|
+
path: filePath ?? null,
|
|
698
|
+
diff: diffRes.stdout.slice(0, 5e4),
|
|
699
|
+
truncated: diffRes.stdout.length > 5e4
|
|
700
|
+
};
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
function makeGitStage(workspace) {
|
|
704
|
+
return async function git_stage(args) {
|
|
705
|
+
const paths = normalizePaths(args.paths);
|
|
706
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
707
|
+
const specs = paths.length > 0 ? toPathspecs(repo, workspace, paths) : [];
|
|
708
|
+
if (paths.length > 0 && specs.length === 0) return failed("\u8DEF\u5F84\u4E0D\u5728\u4ED3\u5E93\u5185");
|
|
709
|
+
const cmd = specs.length > 0 ? ["add", "--", ...specs] : ["add", "-A"];
|
|
710
|
+
return opResult(await runGit(repo, cmd, 3e4));
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function makeGitUnstage(workspace) {
|
|
714
|
+
return async function git_unstage(args) {
|
|
715
|
+
const paths = normalizePaths(args.paths);
|
|
716
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
717
|
+
const specs = paths.length > 0 ? toPathspecs(repo, workspace, paths) : [];
|
|
718
|
+
if (paths.length > 0 && specs.length === 0) return failed("\u8DEF\u5F84\u4E0D\u5728\u4ED3\u5E93\u5185");
|
|
719
|
+
const cmd = specs.length > 0 ? ["restore", "--staged", "--", ...specs] : ["reset", "-q"];
|
|
720
|
+
return opResult(await runGit(repo, cmd, 3e4));
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function makeGitCommit(workspace) {
|
|
724
|
+
return async function git_commit(args) {
|
|
725
|
+
const message = String(args.message ?? "").trim();
|
|
726
|
+
if (!message) return failed("\u63D0\u4EA4\u8BF4\u660E\u4E0D\u80FD\u4E3A\u7A7A");
|
|
727
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
728
|
+
return opResult(await runGit(repo, ["commit", "-m", message], 6e4));
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function makeGitCheckout(workspace) {
|
|
732
|
+
return async function git_checkout(args) {
|
|
733
|
+
const branch = String(args.branch ?? "").trim();
|
|
734
|
+
const problem = gitRefProblem(branch);
|
|
735
|
+
if (problem) return failed(problem);
|
|
736
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
737
|
+
const cmd = args.create === true ? ["checkout", "-b", branch] : ["checkout", branch];
|
|
738
|
+
return opResult(await runGit(repo, cmd, 3e4));
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function makeGitPull(workspace) {
|
|
742
|
+
return async function git_pull(args) {
|
|
743
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
744
|
+
return opResult(await runGit(repo, ["pull"], 6e4));
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
function makeGitPush(workspace) {
|
|
748
|
+
return async function git_push(args) {
|
|
749
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
750
|
+
const upstream = await runGit(
|
|
751
|
+
repo,
|
|
752
|
+
["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
|
|
753
|
+
1e4
|
|
754
|
+
);
|
|
755
|
+
if (upstream.status !== 0) {
|
|
756
|
+
const head = await runGit(repo, ["symbolic-ref", "--short", "HEAD"], 1e4);
|
|
757
|
+
const remotes = await runGit(repo, ["remote"], 1e4);
|
|
758
|
+
const hasOrigin = remotes.status === 0 && remotes.stdout.split("\n").some((r) => r.trim() === "origin");
|
|
759
|
+
if (head.status === 0 && hasOrigin) {
|
|
760
|
+
return opResult(await runGit(repo, ["push", "-u", "origin", head.stdout.trim()], 6e4));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return opResult(await runGit(repo, ["push"], 6e4));
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function makeGitFetch(workspace) {
|
|
767
|
+
return async function git_fetch(args) {
|
|
768
|
+
const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));
|
|
769
|
+
const remotes = await runGit(repo, ["remote"], 1e4);
|
|
770
|
+
if (remotes.status !== 0) {
|
|
771
|
+
const msg = remotes.stderr;
|
|
772
|
+
if (msg.toLowerCase().includes("not a git repository")) return failed("\u4E0D\u662F git \u4ED3\u5E93");
|
|
773
|
+
return failed((msg || "git remote failed").trim().slice(0, 300));
|
|
774
|
+
}
|
|
775
|
+
if (!remotes.stdout.trim()) return failed("\u672A\u914D\u7F6E\u8FDC\u7AEF\u4ED3\u5E93");
|
|
776
|
+
return opResult(await runGit(repo, ["fetch", "--all", "--prune", "--tags"], 6e4));
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".hg", ".svn"]);
|
|
780
|
+
var SCAN_DEFAULT_DEPTH = 3;
|
|
781
|
+
var SCAN_MAX_DIRS = 2e3;
|
|
782
|
+
function readRepoHead(repoDir) {
|
|
783
|
+
const dotGit = path.join(repoDir, ".git");
|
|
784
|
+
try {
|
|
785
|
+
let headFile;
|
|
786
|
+
if (fs.statSync(dotGit).isDirectory()) {
|
|
787
|
+
headFile = path.join(dotGit, "HEAD");
|
|
788
|
+
} else {
|
|
789
|
+
const raw = fs.readFileSync(dotGit, "utf-8");
|
|
790
|
+
const m = /^gitdir:\s*(.+)$/m.exec(raw);
|
|
791
|
+
if (!m) return {};
|
|
792
|
+
headFile = path.join(path.resolve(repoDir, m[1].trim()), "HEAD");
|
|
793
|
+
}
|
|
794
|
+
const head = fs.readFileSync(headFile, "utf-8").trim();
|
|
795
|
+
const prefix = "ref: refs/heads/";
|
|
796
|
+
if (head.startsWith(prefix)) return { branch: head.slice(prefix.length), detached: false };
|
|
797
|
+
if (/^[0-9a-f]{7,40}$/i.test(head)) return { branch: head.slice(0, 7), detached: true };
|
|
798
|
+
} catch {
|
|
799
|
+
}
|
|
800
|
+
return {};
|
|
801
|
+
}
|
|
802
|
+
function scanGitRepos(workspace, depth = SCAN_DEFAULT_DEPTH) {
|
|
803
|
+
const root = path.resolve(workspace);
|
|
804
|
+
const maxDepth = Number.isFinite(depth) ? Math.min(Math.max(Math.floor(depth), 0), 6) : SCAN_DEFAULT_DEPTH;
|
|
805
|
+
const repos = [];
|
|
806
|
+
const stack = [{ dir: root, depth: 0 }];
|
|
807
|
+
let visited = 0;
|
|
808
|
+
let truncated = false;
|
|
809
|
+
while (stack.length > 0) {
|
|
810
|
+
const { dir, depth: d } = stack.pop();
|
|
811
|
+
visited += 1;
|
|
812
|
+
if (visited > SCAN_MAX_DIRS) {
|
|
813
|
+
truncated = true;
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
const rel = path.relative(root, dir).split(path.sep).join("/");
|
|
817
|
+
if (fs.existsSync(path.join(dir, ".git"))) {
|
|
818
|
+
repos.push({
|
|
819
|
+
path: rel,
|
|
820
|
+
name: rel ? path.basename(dir) : path.basename(root),
|
|
821
|
+
...readRepoHead(dir)
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
if (d >= maxDepth) continue;
|
|
825
|
+
let entries;
|
|
826
|
+
try {
|
|
827
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
828
|
+
} catch {
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
for (const e of entries) {
|
|
832
|
+
if (!e.isDirectory() || SCAN_SKIP_DIRS.has(e.name)) continue;
|
|
833
|
+
stack.push({ dir: path.join(dir, e.name), depth: d + 1 });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
repos.sort(
|
|
837
|
+
(a, b) => a.path === "" ? -1 : b.path === "" ? 1 : a.path.localeCompare(b.path)
|
|
838
|
+
);
|
|
839
|
+
return { repos, ...truncated ? { truncated: true } : {} };
|
|
840
|
+
}
|
|
841
|
+
function makeGitRepos(workspace) {
|
|
842
|
+
return async function git_repos(args) {
|
|
843
|
+
const root = path.resolve(workspace);
|
|
844
|
+
try {
|
|
845
|
+
if (!fs.statSync(root).isDirectory()) return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u4E0D\u662F\u76EE\u5F55" };
|
|
846
|
+
} catch {
|
|
847
|
+
return { root, repos: [], error: "\u5DE5\u4F5C\u533A\u76EE\u5F55\u4E0D\u5B58\u5728" };
|
|
848
|
+
}
|
|
849
|
+
const rawDepth = Number(args.depth);
|
|
850
|
+
const depth = Number.isFinite(rawDepth) ? rawDepth : SCAN_DEFAULT_DEPTH;
|
|
851
|
+
return { root, ...scanGitRepos(root, depth) };
|
|
852
|
+
};
|
|
853
|
+
}
|
|
145
854
|
export {
|
|
146
|
-
|
|
855
|
+
gitObjectProblem,
|
|
856
|
+
gitRefProblem,
|
|
857
|
+
makeGitBranches,
|
|
858
|
+
makeGitCheckout,
|
|
859
|
+
makeGitCommit,
|
|
860
|
+
makeGitDiff,
|
|
861
|
+
makeGitFetch,
|
|
862
|
+
makeGitLog,
|
|
863
|
+
makeGitPull,
|
|
864
|
+
makeGitPush,
|
|
865
|
+
makeGitRefs,
|
|
866
|
+
makeGitRepos,
|
|
867
|
+
makeGitShow,
|
|
868
|
+
makeGitStage,
|
|
869
|
+
makeGitStatus,
|
|
870
|
+
makeGitUnstage,
|
|
871
|
+
registerGitTools,
|
|
872
|
+
scanGitRepos
|
|
147
873
|
};
|
|
148
874
|
//# sourceMappingURL=git.js.map
|