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