zen-gitsync 2.9.8 → 2.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -148
- package/package.json +1 -1
- package/src/ui/public/assets/{index-C-ZzwQYX.css → index-CzUoE1WP.css} +1 -1
- package/src/ui/public/assets/index-GE6lIBHK.js +79 -0
- package/src/ui/public/index.html +2 -2
- package/src/ui/server/index.js +143 -5311
- package/src/ui/server/index_pro.js +5483 -0
- package/src/ui/server/middleware/requestLogger.js +37 -0
- package/src/ui/server/routes/branchStatus.js +168 -0
- package/src/ui/server/routes/config.js +586 -0
- package/src/ui/server/routes/exec.js +247 -0
- package/src/ui/server/routes/fileOpen.js +173 -0
- package/src/ui/server/routes/fs.js +443 -0
- package/src/ui/server/routes/git/diff.js +206 -0
- package/src/ui/server/routes/git/diffUtils.js +114 -0
- package/src/ui/server/routes/git/stash.js +481 -0
- package/src/ui/server/routes/git/tags.js +158 -0
- package/src/ui/server/routes/git.js +176 -0
- package/src/ui/server/routes/gitOps.js +974 -0
- package/src/ui/server/routes/npm.js +981 -0
- package/src/ui/server/routes/process.js +68 -0
- package/src/ui/server/routes/status.js +24 -0
- package/src/ui/server/routes/terminal.js +244 -0
- package/src/ui/server/socket/registerUiSocketHandlers.js +212 -0
- package/src/ui/server/utils/createSavePortToFile.js +32 -0
- package/src/ui/server/utils/startServerOnAvailablePort.js +87 -0
- package/src/ui/public/assets/index-DXmNo3gw.js +0 -79
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
|
|
7
|
+
import { registerGitDiffRoutes } from './git/diff.js';
|
|
8
|
+
import { createDiffHelpers } from './git/diffUtils.js';
|
|
9
|
+
import { registerGitStashRoutes } from './git/stash.js';
|
|
10
|
+
import { registerGitTagRoutes } from './git/tags.js';
|
|
11
|
+
|
|
12
|
+
export function registerGitOpsRoutes({
|
|
13
|
+
app,
|
|
14
|
+
execGitCommand,
|
|
15
|
+
configManager,
|
|
16
|
+
execGitAddWithLockFilter,
|
|
17
|
+
addCommandToHistory,
|
|
18
|
+
clearCommandHistory,
|
|
19
|
+
getIsGitRepo,
|
|
20
|
+
setRecentPushStatus
|
|
21
|
+
}) {
|
|
22
|
+
// 提交更改
|
|
23
|
+
app.post('/api/commit', express.json(), async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const { message, hasNewlines, noVerify } = req.body;
|
|
26
|
+
|
|
27
|
+
// 构建 git commit 命令
|
|
28
|
+
let commitCommand = 'git commit';
|
|
29
|
+
|
|
30
|
+
// 如果消息包含换行符,使用文件方式提交
|
|
31
|
+
if (hasNewlines) {
|
|
32
|
+
// 创建临时文件存储提交信息
|
|
33
|
+
const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
|
|
34
|
+
await fs.writeFile(tempFile, message);
|
|
35
|
+
commitCommand += ` -F "${tempFile}"`;
|
|
36
|
+
} else {
|
|
37
|
+
// 否则直接在命令行中提供消息
|
|
38
|
+
commitCommand += ` -m "${message}"`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 添加 --no-verify 参数
|
|
42
|
+
if (noVerify) {
|
|
43
|
+
commitCommand += ' --no-verify';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`commitCommand ==>`, commitCommand);
|
|
47
|
+
// 执行提交命令
|
|
48
|
+
await execGitCommand(commitCommand);
|
|
49
|
+
|
|
50
|
+
// 如果使用了临时文件,删除它
|
|
51
|
+
if (hasNewlines) {
|
|
52
|
+
const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
|
|
53
|
+
await fs.unlink(tempFile).catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
res.json({ success: true });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
res.status(500).json({ success: false, error: error.message });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 添加 add 接口
|
|
63
|
+
app.post('/api/add', async (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
// 直接执行 git add . - 前端已经做了锁定文件过滤判断
|
|
66
|
+
await execGitCommand('git add .');
|
|
67
|
+
res.json({ success: true });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
res.status(500).json({ success: false, error: error.message });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 添加带锁定文件过滤的 add 接口
|
|
74
|
+
app.post('/api/add-filtered', async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
// 使用带锁定文件过滤的 git add
|
|
77
|
+
await execGitAddWithLockFilter();
|
|
78
|
+
res.json({ success: true });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
res.status(500).json({ success: false, error: error.message });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 添加 add-all 接口(直接执行 git add . 不考虑锁定文件)
|
|
85
|
+
app.post('/api/add-all', async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
// 直接执行 git add . 不考虑锁定文件
|
|
88
|
+
await execGitCommand('git add .');
|
|
89
|
+
res.json({ success: true });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
res.status(500).json({ success: false, error: error.message });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 添加单个文件到暂存区
|
|
96
|
+
app.post('/api/add-file', async (req, res) => {
|
|
97
|
+
try {
|
|
98
|
+
const { filePath } = req.body;
|
|
99
|
+
|
|
100
|
+
if (!filePath) {
|
|
101
|
+
return res.status(400).json({
|
|
102
|
+
success: false,
|
|
103
|
+
error: '缺少文件路径参数'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 执行 git add 命令添加特定文件
|
|
108
|
+
await execGitCommand(`git add "${filePath}"`);
|
|
109
|
+
res.json({ success: true });
|
|
110
|
+
} catch (error) {
|
|
111
|
+
res.status(500).json({ success: false, error: error.message });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// 从暂存区移除单个文件
|
|
116
|
+
app.post('/api/unstage-file', async (req, res) => {
|
|
117
|
+
try {
|
|
118
|
+
const { filePath } = req.body;
|
|
119
|
+
|
|
120
|
+
if (!filePath) {
|
|
121
|
+
return res.status(400).json({
|
|
122
|
+
success: false,
|
|
123
|
+
error: '缺少文件路径参数'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 执行 git reset HEAD 命令移除特定文件的暂存
|
|
128
|
+
await execGitCommand(`git reset HEAD -- "${filePath}"`);
|
|
129
|
+
res.json({ success: true });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
res.status(500).json({ success: false, error: error.message });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 推送更改
|
|
136
|
+
app.post('/api/push', async (req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
const { stdout } = await execGitCommand('git push');
|
|
139
|
+
|
|
140
|
+
// 推送成功后,设置推送状态标记
|
|
141
|
+
setRecentPushStatus({
|
|
142
|
+
justPushed: true,
|
|
143
|
+
pushTime: Date.now(),
|
|
144
|
+
validDuration: 10000 // 10秒内认为分支状态是同步的
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
console.log('推送成功,已设置推送状态标记');
|
|
148
|
+
res.json({ success: true, message: stdout });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
res.status(500).json({ success: false, error: error.message });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// 带进度的推送更改 (SSE)
|
|
155
|
+
app.post('/api/push-with-progress', async (req, res) => {
|
|
156
|
+
// 设置SSE响应头
|
|
157
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
158
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
159
|
+
res.setHeader('Connection', 'keep-alive');
|
|
160
|
+
res.flushHeaders();
|
|
161
|
+
|
|
162
|
+
const sendProgress = (data) => {
|
|
163
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// 获取当前工作目录 - 与execGitCommand保持一致
|
|
168
|
+
const cwdArg = process.argv.find(arg => arg.startsWith('--path')) || process.argv.find(arg => arg.startsWith('--cwd'));
|
|
169
|
+
let workDir = process.cwd();
|
|
170
|
+
if (cwdArg) {
|
|
171
|
+
const [, value] = cwdArg.split('=');
|
|
172
|
+
workDir = value || process.cwd();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('开始推送,工作目录:', workDir);
|
|
176
|
+
|
|
177
|
+
// 记录开始时间
|
|
178
|
+
const startTime = Date.now();
|
|
179
|
+
|
|
180
|
+
// 发送开始消息
|
|
181
|
+
sendProgress({
|
|
182
|
+
type: 'progress',
|
|
183
|
+
message: 'Starting to push to the remote repository...'
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 使用spawn执行git push --progress
|
|
187
|
+
const gitPush = spawn('git', ['push', '--progress'], {
|
|
188
|
+
cwd: workDir,
|
|
189
|
+
env: {
|
|
190
|
+
...process.env,
|
|
191
|
+
GIT_CONFIG_PARAMETERS: "'core.quotepath=false'" // 关闭路径转义
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
let errorOutput = '';
|
|
196
|
+
let standardOutput = '';
|
|
197
|
+
|
|
198
|
+
// Git的进度信息在stderr中
|
|
199
|
+
gitPush.stderr.on('data', (data) => {
|
|
200
|
+
const output = data.toString();
|
|
201
|
+
errorOutput += output;
|
|
202
|
+
|
|
203
|
+
// 解析进度信息
|
|
204
|
+
const lines = output.split('\n');
|
|
205
|
+
for (const line of lines) {
|
|
206
|
+
if (line.trim()) {
|
|
207
|
+
// 发送原始行
|
|
208
|
+
sendProgress({
|
|
209
|
+
type: 'progress',
|
|
210
|
+
message: line.trim()
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// 识别不同阶段并解析百分比
|
|
214
|
+
const percentMatch = line.match(/(\d+)%/);
|
|
215
|
+
if (percentMatch) {
|
|
216
|
+
const percent = parseInt(percentMatch[1]);
|
|
217
|
+
let stage = 'unknown';
|
|
218
|
+
|
|
219
|
+
if (line.includes('Enumerating objects')) {
|
|
220
|
+
stage = 'enumerating';
|
|
221
|
+
} else if (line.includes('Counting objects')) {
|
|
222
|
+
stage = 'counting';
|
|
223
|
+
} else if (line.includes('Compressing objects')) {
|
|
224
|
+
stage = 'compressing';
|
|
225
|
+
} else if (line.includes('Writing objects')) {
|
|
226
|
+
stage = 'writing';
|
|
227
|
+
} else if (line.includes('Resolving deltas')) {
|
|
228
|
+
stage = 'resolving';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
sendProgress({
|
|
232
|
+
type: 'stage-progress',
|
|
233
|
+
stage: stage,
|
|
234
|
+
percent: percent,
|
|
235
|
+
message: line.trim()
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
gitPush.stdout.on('data', (data) => {
|
|
243
|
+
standardOutput += data.toString();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
gitPush.on('close', (code) => {
|
|
247
|
+
console.log(`Git push 进程结束,退出码: ${code}`);
|
|
248
|
+
console.log('标准输出:', standardOutput);
|
|
249
|
+
console.log('错误输出:', errorOutput);
|
|
250
|
+
|
|
251
|
+
// 计算执行时间
|
|
252
|
+
const executionTime = Date.now() - startTime;
|
|
253
|
+
|
|
254
|
+
if (code === 0) {
|
|
255
|
+
// 推送成功
|
|
256
|
+
setRecentPushStatus({
|
|
257
|
+
justPushed: true,
|
|
258
|
+
pushTime: Date.now(),
|
|
259
|
+
validDuration: 10000
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// 添加到命令历史
|
|
263
|
+
addCommandToHistory(
|
|
264
|
+
'git push --progress',
|
|
265
|
+
standardOutput,
|
|
266
|
+
errorOutput,
|
|
267
|
+
null,
|
|
268
|
+
executionTime
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
sendProgress({
|
|
272
|
+
type: 'complete',
|
|
273
|
+
success: true,
|
|
274
|
+
message: standardOutput || errorOutput || 'Push successful'
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
// 推送失败
|
|
278
|
+
console.error('推送失败:', errorOutput || standardOutput);
|
|
279
|
+
|
|
280
|
+
// 添加到命令历史(失败情况)
|
|
281
|
+
addCommandToHistory(
|
|
282
|
+
'git push --progress',
|
|
283
|
+
standardOutput,
|
|
284
|
+
errorOutput,
|
|
285
|
+
errorOutput || standardOutput || `Push failed with code ${code}`,
|
|
286
|
+
executionTime
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
sendProgress({
|
|
290
|
+
type: 'complete',
|
|
291
|
+
success: false,
|
|
292
|
+
error: errorOutput || standardOutput || `Push failed with code ${code}`
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
res.end();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
gitPush.on('error', (error) => {
|
|
299
|
+
console.error('Git push 进程错误:', error);
|
|
300
|
+
|
|
301
|
+
// 计算执行时间
|
|
302
|
+
const executionTime = Date.now() - startTime;
|
|
303
|
+
|
|
304
|
+
// 添加到命令历史(错误情况)
|
|
305
|
+
addCommandToHistory(
|
|
306
|
+
'git push --progress',
|
|
307
|
+
'',
|
|
308
|
+
'',
|
|
309
|
+
error.message,
|
|
310
|
+
executionTime
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
sendProgress({
|
|
314
|
+
type: 'complete',
|
|
315
|
+
success: false,
|
|
316
|
+
error: error.message
|
|
317
|
+
});
|
|
318
|
+
res.end();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
} catch (error) {
|
|
322
|
+
// 添加到命令历史(异常情况)
|
|
323
|
+
addCommandToHistory(
|
|
324
|
+
'git push --progress',
|
|
325
|
+
'',
|
|
326
|
+
'',
|
|
327
|
+
error.message,
|
|
328
|
+
0
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
sendProgress({
|
|
332
|
+
type: 'complete',
|
|
333
|
+
success: false,
|
|
334
|
+
error: error.message
|
|
335
|
+
});
|
|
336
|
+
res.end();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// 添加git pull API端点
|
|
341
|
+
app.post('/api/pull', async (req, res) => {
|
|
342
|
+
try {
|
|
343
|
+
const { stdout } = await execGitCommand('git pull');
|
|
344
|
+
res.json({ success: true, message: stdout });
|
|
345
|
+
} catch (error) {
|
|
346
|
+
// 改进错误处理,检查是否需要合并
|
|
347
|
+
const errorMsg = error.message || '';
|
|
348
|
+
const needsMerge = errorMsg.includes('merge') ||
|
|
349
|
+
errorMsg.includes('需要合并') ||
|
|
350
|
+
errorMsg.includes('CONFLICT') ||
|
|
351
|
+
errorMsg.includes('冲突');
|
|
352
|
+
|
|
353
|
+
// 返回更详细的错误信息和标记
|
|
354
|
+
res.status(500).json({
|
|
355
|
+
success: false,
|
|
356
|
+
error: error.message,
|
|
357
|
+
needsMerge: needsMerge,
|
|
358
|
+
// 包含完整的错误输出
|
|
359
|
+
fullError: error.stderr || error.message,
|
|
360
|
+
pullOutput: error.stdout || ''
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// 添加git fetch --all API端点
|
|
366
|
+
app.post('/api/fetch-all', async (req, res) => {
|
|
367
|
+
try {
|
|
368
|
+
const { stdout } = await execGitCommand('git fetch --all');
|
|
369
|
+
res.json({ success: true, message: stdout });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
res.status(500).json({ success: false, error: error.message });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// 获取日志
|
|
376
|
+
app.get('/api/log', async (req, res) => {
|
|
377
|
+
try {
|
|
378
|
+
// 获取分页参数
|
|
379
|
+
const page = parseInt(req.query.page) || 1;
|
|
380
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
381
|
+
const skip = (page - 1) * limit;
|
|
382
|
+
|
|
383
|
+
// 获取筛选参数
|
|
384
|
+
const author = req.query.author ? req.query.author.split(',') : [];
|
|
385
|
+
const message = req.query.message || '';
|
|
386
|
+
const dateFrom = req.query.dateFrom || '';
|
|
387
|
+
const dateTo = req.query.dateTo || '';
|
|
388
|
+
const branch = req.query.branch ? req.query.branch.split(',') : [];
|
|
389
|
+
const withParents = req.query.with_parents === 'true';
|
|
390
|
+
|
|
391
|
+
// 构建Git命令选项
|
|
392
|
+
let commandOptions = [];
|
|
393
|
+
|
|
394
|
+
// 修改分支筛选处理 - 使用正确的引用格式
|
|
395
|
+
if (branch.length > 0) {
|
|
396
|
+
// 不再简单拼接分支名,而是将它们作为引用路径处理
|
|
397
|
+
// 如果指定了分支,不再使用--all参数,而是直接用分支名
|
|
398
|
+
commandOptions = commandOptions.filter(opt => opt !== '--all');
|
|
399
|
+
|
|
400
|
+
// 将分支名格式化为Git可理解的引用格式
|
|
401
|
+
const branchRefs = branch.map(b => b.trim()).join(' ');
|
|
402
|
+
|
|
403
|
+
// 直接将分支名作为命令参数,并确保后面添加 -- 分隔符防止歧义
|
|
404
|
+
return executeGitLogCommand(res, branchRefs, author, message, dateFrom, dateTo, limit, skip, req.query.all === 'true', withParents);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 如果没有指定分支,则使用--all参数
|
|
408
|
+
// 作者筛选(支持多作者,使用正则表达式OR操作)
|
|
409
|
+
if (author.length > 0) {
|
|
410
|
+
// 过滤掉空作者
|
|
411
|
+
const validAuthors = author.filter(a => a.trim() !== '');
|
|
412
|
+
|
|
413
|
+
if (validAuthors.length === 1) {
|
|
414
|
+
// 单个作者,直接使用--author
|
|
415
|
+
commandOptions.push(`--author="${validAuthors[0].trim()}"`);
|
|
416
|
+
} else if (validAuthors.length > 1) {
|
|
417
|
+
// 多个作者,使用正则表达式OR条件
|
|
418
|
+
// 只转义OR运算符,保持其他内容不变
|
|
419
|
+
const authorPattern = validAuthors
|
|
420
|
+
.map(a => a.trim())
|
|
421
|
+
.join('\\|'); // 在JavaScript字符串中\\|会变成\|,在shell中会成为|
|
|
422
|
+
|
|
423
|
+
commandOptions.push(`--author="${authorPattern}"`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 日期范围筛选
|
|
428
|
+
if (dateFrom && dateTo) {
|
|
429
|
+
commandOptions.push(`--after="${dateFrom}" --before="${dateTo} 23:59:59"`);
|
|
430
|
+
} else if (dateFrom) {
|
|
431
|
+
commandOptions.push(`--after="${dateFrom}"`);
|
|
432
|
+
} else if (dateTo) {
|
|
433
|
+
commandOptions.push(`--before="${dateTo} 23:59:59"`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 提交信息筛选
|
|
437
|
+
if (message) {
|
|
438
|
+
commandOptions.push(`--grep="${message}"`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 如果all=true,则不使用限制,否则按页码和limit精确获取
|
|
442
|
+
// 修复:只获取当前页的数据,而不是累计所有之前页的数据
|
|
443
|
+
const limitOption = req.query.all === 'true' ? '' : `-n ${limit} --skip=${skip}`;
|
|
444
|
+
|
|
445
|
+
// 合并所有命令选项
|
|
446
|
+
const options = [...commandOptions, limitOption].filter(Boolean).join(' ');
|
|
447
|
+
|
|
448
|
+
// 添加父提交信息的格式
|
|
449
|
+
let formatString = '%H%x1E%an%x1E%ae%x1E%ad%x1E%B%x1E%D';
|
|
450
|
+
if (withParents) {
|
|
451
|
+
formatString = '%H%x1E%an%x1E%ae%x1E%ad%x1E%B%x1E%D%x1E%P';
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// console.log(`执行Git命令: git log --all --pretty=format:"${formatString}" --date=format-local:"%Y-%m-%d %H:%M" ${options}`);
|
|
455
|
+
|
|
456
|
+
// 使用 git log 命令获取提交历史
|
|
457
|
+
let { stdout: logOutput } = await execGitCommand(
|
|
458
|
+
`git log --all --pretty=format:"${formatString}" --date=format-local:"%Y-%m-%d %H:%M" ${options}`
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
// 分页加载优化:不需要获取总数,通过实际返回的数据量判断是否还有更多
|
|
462
|
+
// 这里直接处理已获取的数据,通过返回数据量判断是否还有更多
|
|
463
|
+
processAndSendLogOutput(res, logOutput, page, limit, withParents);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
console.error('获取Git日志失败:', error);
|
|
466
|
+
res.status(500).json({ error: '获取日志失败: ' + error.message });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// 抽取执行Git日志命令的函数
|
|
471
|
+
async function executeGitLogCommand(res, branchRefs, author, message, dateFrom, dateTo, limit, skip, isAll, withParents = false) {
|
|
472
|
+
try {
|
|
473
|
+
// 构建命令选项
|
|
474
|
+
const commandOptions = [];
|
|
475
|
+
|
|
476
|
+
// 作者筛选
|
|
477
|
+
if (author.length > 0) {
|
|
478
|
+
const validAuthors = author.filter(a => a.trim() !== '');
|
|
479
|
+
|
|
480
|
+
if (validAuthors.length === 1) {
|
|
481
|
+
commandOptions.push(`--author="${validAuthors[0].trim()}"`);
|
|
482
|
+
} else if (validAuthors.length > 1) {
|
|
483
|
+
const authorPattern = validAuthors.map(a => a.trim()).join('\\|');
|
|
484
|
+
commandOptions.push(`--author="${authorPattern}"`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// 日期范围筛选
|
|
489
|
+
if (dateFrom && dateTo) {
|
|
490
|
+
commandOptions.push(`--after="${dateFrom}" --before="${dateTo} 23:59:59"`);
|
|
491
|
+
} else if (dateFrom) {
|
|
492
|
+
commandOptions.push(`--after="${dateFrom}"`);
|
|
493
|
+
} else if (dateTo) {
|
|
494
|
+
commandOptions.push(`--before="${dateTo} 23:59:59"`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 提交信息筛选
|
|
498
|
+
if (message) {
|
|
499
|
+
commandOptions.push(`--grep="${message}"`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// 限制选项
|
|
503
|
+
const limitOption = isAll ? '' : `-n ${limit} --skip=${skip}`;
|
|
504
|
+
|
|
505
|
+
// 合并所有选项
|
|
506
|
+
const options = [...commandOptions, limitOption].filter(Boolean).join(' ');
|
|
507
|
+
|
|
508
|
+
// 准备分支引用,确保它们被正确识别为分支而不是文件名
|
|
509
|
+
// 使用 refs/heads/ 前缀明确指示这是分支
|
|
510
|
+
const formattedBranchRefs = branchRefs.split(' ')
|
|
511
|
+
.map(branch => {
|
|
512
|
+
// 检查是否已经是完整引用
|
|
513
|
+
if (branch.startsWith('refs/') || branch.includes('/')) {
|
|
514
|
+
return branch;
|
|
515
|
+
}
|
|
516
|
+
// 添加refs/heads/前缀
|
|
517
|
+
return `refs/heads/${branch}`;
|
|
518
|
+
})
|
|
519
|
+
.join(' ');
|
|
520
|
+
|
|
521
|
+
// 添加父提交信息的格式
|
|
522
|
+
// 确认格式字符串使用 %x1E 作为分隔符
|
|
523
|
+
let formatString = '%H%x1E%an%x1E%ae%x1E%ad%x1E%B%x1E%D';
|
|
524
|
+
if (withParents) {
|
|
525
|
+
formatString = '%H%x1E%an%x1E%ae%x1E%ad%x1E%B%x1E%D%x1E%P';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 构建执行的命令
|
|
529
|
+
const command = `git log ${formattedBranchRefs} --pretty=format:"${formatString}" --date=format-local:"%Y-%m-%d %H:%M" ${options}`;
|
|
530
|
+
console.log(`执行Git命令(带分支引用): ${command}`);
|
|
531
|
+
|
|
532
|
+
// 执行命令
|
|
533
|
+
const { stdout: logOutput } = await execGitCommand(command);
|
|
534
|
+
|
|
535
|
+
// 分页加载优化:不需要获取总数,通过实际返回的数据量判断是否还有更多
|
|
536
|
+
processAndSendLogOutput(res, logOutput, skip / limit + 1, limit, withParents);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error('执行Git日志命令失败:', error);
|
|
539
|
+
res.status(500).json({ error: '获取日志失败: ' + error.message });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// 抽取处理输出并发送响应的函数(优化版本:不再依赖总数计算)
|
|
544
|
+
function processAndSendLogOutput(res, logOutput, page, limit, withParents = false) {
|
|
545
|
+
// 替换提交记录之间的换行符 - 使用 ASCII 控制字符 \x1E 匹配
|
|
546
|
+
logOutput = logOutput.replace(/\n(?=[a-f0-9]{40}\x1E)/g, "<<<RECORD_SEPARATOR>>>");
|
|
547
|
+
|
|
548
|
+
// 按分隔符拆分日志条目
|
|
549
|
+
const logEntries = logOutput.split("<<<RECORD_SEPARATOR>>>");
|
|
550
|
+
|
|
551
|
+
// 处理每个日志条目
|
|
552
|
+
const data = logEntries.map(entry => {
|
|
553
|
+
// 使用 ASCII 控制字符 \x1E 拆分字段
|
|
554
|
+
const parts = entry.split('\x1E');
|
|
555
|
+
if (parts.length >= 5) {
|
|
556
|
+
const result = {
|
|
557
|
+
hash: parts[0],
|
|
558
|
+
author: parts[1],
|
|
559
|
+
email: parts[2],
|
|
560
|
+
date: parts[3],
|
|
561
|
+
message: parts[4],
|
|
562
|
+
branch: parts[5] || ''
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// 如果请求了父提交信息,添加到结果中
|
|
566
|
+
if (withParents && parts[6]) {
|
|
567
|
+
result.parents = parts[6].split(' ');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}).filter(item => item !== null);
|
|
574
|
+
|
|
575
|
+
// 优化:通过返回的数据量判断是否有更多数据,而不依赖总数计算
|
|
576
|
+
// 如果返回的数据量等于请求的limit,说明可能还有更多数据
|
|
577
|
+
// 如果返回的数据量小于limit,说明已经到底了
|
|
578
|
+
const hasMore = data.length === limit;
|
|
579
|
+
|
|
580
|
+
// console.log(`分页查询 - 页码: ${page}, 每页数量: ${limit}, 返回数量: ${data.length}, 是否有更多: ${hasMore} (优化版本,不计算总数)`);
|
|
581
|
+
|
|
582
|
+
// 返回提交历史数据,包括是否有更多数据的标志
|
|
583
|
+
res.json({
|
|
584
|
+
data: data,
|
|
585
|
+
total: -1, // 不再提供总数,前端不应依赖此字段
|
|
586
|
+
page: page,
|
|
587
|
+
limit: limit,
|
|
588
|
+
hasMore: hasMore
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ========== Diff 大文件检测和过滤工具函数 ==========
|
|
593
|
+
|
|
594
|
+
const { checkShouldSkipDiff, checkDiffSize, getDiffStats } = createDiffHelpers({ execGitCommand });
|
|
595
|
+
|
|
596
|
+
registerGitDiffRoutes({
|
|
597
|
+
app,
|
|
598
|
+
execGitCommand
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
registerGitStashRoutes({
|
|
602
|
+
app,
|
|
603
|
+
execGitCommand,
|
|
604
|
+
configManager
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
registerGitTagRoutes({
|
|
608
|
+
app,
|
|
609
|
+
execGitCommand,
|
|
610
|
+
clearCommandHistory
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// 重置暂存区 (git reset HEAD)
|
|
614
|
+
app.post('/api/reset-head', async (req, res) => {
|
|
615
|
+
try {
|
|
616
|
+
// 执行 git reset HEAD 命令
|
|
617
|
+
await execGitCommand('git reset HEAD');
|
|
618
|
+
res.json({ success: true });
|
|
619
|
+
} catch (error) {
|
|
620
|
+
console.error('重置暂存区失败:', error);
|
|
621
|
+
res.status(500).json({
|
|
622
|
+
success: false,
|
|
623
|
+
error: `重置暂存区失败: ${error.message}`
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// 重置到远程分支 (git reset --hard origin/branch)
|
|
629
|
+
app.post('/api/reset-to-remote', async (req, res) => {
|
|
630
|
+
try {
|
|
631
|
+
const { branch } = req.body;
|
|
632
|
+
|
|
633
|
+
if (!branch) {
|
|
634
|
+
return res.status(400).json({
|
|
635
|
+
success: false,
|
|
636
|
+
error: '缺少分支名称参数'
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// 执行 git reset --hard origin/branch 命令
|
|
641
|
+
await execGitCommand(`git reset --hard origin/${branch}`);
|
|
642
|
+
res.json({ success: true });
|
|
643
|
+
} catch (error) {
|
|
644
|
+
console.error('重置到远程分支失败:', error);
|
|
645
|
+
res.status(500).json({
|
|
646
|
+
success: false,
|
|
647
|
+
error: `重置到远程分支失败: ${error.message}`
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// 获取提交的文件列表
|
|
653
|
+
app.get('/api/commit-files', async (req, res) => {
|
|
654
|
+
try {
|
|
655
|
+
const hash = req.query.hash;
|
|
656
|
+
|
|
657
|
+
if (!hash) {
|
|
658
|
+
return res.status(400).json({
|
|
659
|
+
success: false,
|
|
660
|
+
error: '缺少提交哈希参数'
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
console.log(`获取提交文件列表: hash=${hash}`);
|
|
665
|
+
|
|
666
|
+
// 执行命令获取提交中修改的文件列表
|
|
667
|
+
const { stdout } = await execGitCommand(`git show --name-only --format="" ${hash}`);
|
|
668
|
+
|
|
669
|
+
// 将输出按行分割,并过滤掉空行
|
|
670
|
+
const files = stdout.split('\n').filter(line => line.trim());
|
|
671
|
+
console.log(`找到${files.length}个文件:`, files);
|
|
672
|
+
|
|
673
|
+
res.json({
|
|
674
|
+
success: true,
|
|
675
|
+
files
|
|
676
|
+
});
|
|
677
|
+
} catch (error) {
|
|
678
|
+
console.error('获取提交文件列表失败:', error);
|
|
679
|
+
res.status(500).json({
|
|
680
|
+
success: false,
|
|
681
|
+
error: `获取提交文件列表失败: ${error.message}`
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// 获取提交中特定文件的差异
|
|
687
|
+
app.get('/api/commit-file-diff', async (req, res) => {
|
|
688
|
+
try {
|
|
689
|
+
const hash = req.query.hash;
|
|
690
|
+
const filePath = req.query.file;
|
|
691
|
+
|
|
692
|
+
if (!hash || !filePath) {
|
|
693
|
+
return res.status(400).json({
|
|
694
|
+
success: false,
|
|
695
|
+
error: '缺少必要参数'
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
console.log(`获取提交文件差异: hash=${hash}, file=${filePath}`);
|
|
700
|
+
|
|
701
|
+
const diffCommand = `git show ${hash} -- "${filePath}"`;
|
|
702
|
+
|
|
703
|
+
// 使用优化的检查函数
|
|
704
|
+
const skipCheck = await checkShouldSkipDiff(filePath, diffCommand);
|
|
705
|
+
if (skipCheck.shouldSkip) {
|
|
706
|
+
return res.json({
|
|
707
|
+
success: true,
|
|
708
|
+
diff: skipCheck.reason,
|
|
709
|
+
isLargeFile: true,
|
|
710
|
+
stats: skipCheck.stats
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// 执行命令获取文件差异
|
|
715
|
+
const { stdout } = await execGitCommand(diffCommand);
|
|
716
|
+
|
|
717
|
+
console.log(`获取到差异内容,长度: ${stdout.length}`);
|
|
718
|
+
|
|
719
|
+
// 检查实际diff大小
|
|
720
|
+
const sizeCheck = checkDiffSize(stdout, 500);
|
|
721
|
+
if (sizeCheck) {
|
|
722
|
+
return res.json({
|
|
723
|
+
success: true,
|
|
724
|
+
...sizeCheck
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// 统计增加和删除行数
|
|
729
|
+
const stats = getDiffStats(stdout);
|
|
730
|
+
|
|
731
|
+
res.json({
|
|
732
|
+
success: true,
|
|
733
|
+
diff: stdout,
|
|
734
|
+
stats
|
|
735
|
+
});
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.error('获取提交文件差异失败:', error);
|
|
738
|
+
res.status(500).json({
|
|
739
|
+
success: false,
|
|
740
|
+
error: `获取提交文件差异失败: ${error.message}`
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// 撤销某个提交 (revert)
|
|
746
|
+
app.post('/api/revert-commit', async (req, res) => {
|
|
747
|
+
try {
|
|
748
|
+
const { hash } = req.body;
|
|
749
|
+
|
|
750
|
+
if (!hash) {
|
|
751
|
+
return res.status(400).json({
|
|
752
|
+
success: false,
|
|
753
|
+
error: '缺少提交哈希参数'
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
console.log(`执行撤销提交操作: hash=${hash}`);
|
|
758
|
+
|
|
759
|
+
// 执行git revert命令
|
|
760
|
+
await execGitCommand(`git revert --no-edit ${hash}`);
|
|
761
|
+
|
|
762
|
+
res.json({
|
|
763
|
+
success: true,
|
|
764
|
+
message: `已成功撤销提交 ${hash}`
|
|
765
|
+
});
|
|
766
|
+
} catch (error) {
|
|
767
|
+
console.error('撤销提交失败:', error);
|
|
768
|
+
res.status(500).json({
|
|
769
|
+
success: false,
|
|
770
|
+
error: `撤销提交失败: ${error.message}`
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// Cherry-pick某个提交
|
|
776
|
+
app.post('/api/cherry-pick-commit', async (req, res) => {
|
|
777
|
+
try {
|
|
778
|
+
const { hash } = req.body;
|
|
779
|
+
|
|
780
|
+
if (!hash) {
|
|
781
|
+
return res.status(400).json({
|
|
782
|
+
success: false,
|
|
783
|
+
error: '缺少提交哈希参数'
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
console.log(`执行Cherry-pick操作: hash=${hash}`);
|
|
788
|
+
|
|
789
|
+
// 执行git cherry-pick命令
|
|
790
|
+
await execGitCommand(`git cherry-pick ${hash}`);
|
|
791
|
+
|
|
792
|
+
res.json({
|
|
793
|
+
success: true,
|
|
794
|
+
message: `已成功Cherry-pick提交 ${hash}`
|
|
795
|
+
});
|
|
796
|
+
} catch (error) {
|
|
797
|
+
console.error('Cherry-pick提交失败:', error);
|
|
798
|
+
res.status(500).json({
|
|
799
|
+
success: false,
|
|
800
|
+
error: `Cherry-pick提交失败: ${error.message}`
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// 重置到指定提交(hard)
|
|
806
|
+
app.post('/api/reset-to-commit', async (req, res) => {
|
|
807
|
+
try {
|
|
808
|
+
const { hash } = req.body;
|
|
809
|
+
|
|
810
|
+
if (!hash) {
|
|
811
|
+
return res.status(400).json({
|
|
812
|
+
success: false,
|
|
813
|
+
error: '缺少提交哈希参数'
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
console.log(`执行重置到指定提交操作: hash=${hash}`);
|
|
818
|
+
|
|
819
|
+
// 执行git reset --hard命令
|
|
820
|
+
await execGitCommand(`git reset --hard ${hash}`);
|
|
821
|
+
|
|
822
|
+
res.json({
|
|
823
|
+
success: true,
|
|
824
|
+
message: `已成功重置到提交 ${hash}`
|
|
825
|
+
});
|
|
826
|
+
} catch (error) {
|
|
827
|
+
console.error('重置到指定提交失败:', error);
|
|
828
|
+
res.status(500).json({
|
|
829
|
+
success: false,
|
|
830
|
+
error: `重置到指定提交失败: ${error.message}`
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// 添加清理Git锁定文件的接口
|
|
836
|
+
app.post('/api/remove-lock', async (req, res) => {
|
|
837
|
+
try {
|
|
838
|
+
const gitDir = path.join(process.cwd(), '.git')
|
|
839
|
+
const indexLockFile = path.join(gitDir, 'index.lock')
|
|
840
|
+
|
|
841
|
+
// 检查文件是否存在
|
|
842
|
+
try {
|
|
843
|
+
await fs.access(indexLockFile)
|
|
844
|
+
// 如果文件存在,尝试删除它
|
|
845
|
+
await fs.unlink(indexLockFile)
|
|
846
|
+
res.json({ success: true, message: '已清理锁定文件' })
|
|
847
|
+
} catch (error) {
|
|
848
|
+
// 如果文件不存在,也返回成功
|
|
849
|
+
if (error.code === 'ENOENT') {
|
|
850
|
+
res.json({ success: true, message: '没有发现锁定文件' })
|
|
851
|
+
} else {
|
|
852
|
+
throw error
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
} catch (error) {
|
|
856
|
+
console.error('清理锁定文件失败:', error)
|
|
857
|
+
res.status(500).json({
|
|
858
|
+
success: false,
|
|
859
|
+
error: `清理锁定文件失败: ${error.message}`
|
|
860
|
+
})
|
|
861
|
+
}
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
// 清除Git用户配置
|
|
865
|
+
app.post('/api/clear-user-config', async (req, res) => {
|
|
866
|
+
try {
|
|
867
|
+
// 检查全局配置是否存在,如果存在才删除
|
|
868
|
+
try {
|
|
869
|
+
const { stdout: userName } = await execGitCommand('git config --global user.name');
|
|
870
|
+
if (userName.trim()) {
|
|
871
|
+
await execGitCommand('git config --global --unset user.name');
|
|
872
|
+
}
|
|
873
|
+
} catch (error) {
|
|
874
|
+
console.log('全局用户名配置检查失败,可能不存在:', error.message);
|
|
875
|
+
// 忽略错误继续执行
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
try {
|
|
879
|
+
const { stdout: userEmail } = await execGitCommand('git config --global user.email');
|
|
880
|
+
if (userEmail.trim()) {
|
|
881
|
+
await execGitCommand('git config --global --unset user.email');
|
|
882
|
+
}
|
|
883
|
+
} catch (error) {
|
|
884
|
+
console.log('全局邮箱配置检查失败,可能不存在:', error.message);
|
|
885
|
+
// 忽略错误继续执行
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
res.json({ success: true, message: '已清除全局Git用户配置' });
|
|
889
|
+
} catch (error) {
|
|
890
|
+
res.status(500).json({
|
|
891
|
+
success: false,
|
|
892
|
+
error: `清除全局Git用户配置失败: ${error.message}`
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
// 恢复Git用户配置
|
|
898
|
+
app.post('/api/restore-user-config', async (req, res) => {
|
|
899
|
+
try {
|
|
900
|
+
const { name, email } = req.body;
|
|
901
|
+
if (!name || !email) {
|
|
902
|
+
return res.status(400).json({
|
|
903
|
+
success: false,
|
|
904
|
+
error: '需要提供用户名和邮箱'
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
await execGitCommand(`git config --global user.name "${name}"`);
|
|
909
|
+
await execGitCommand(`git config --global user.email "${email}"`);
|
|
910
|
+
res.json({ success: true, message: '已更新全局Git用户配置' });
|
|
911
|
+
} catch (error) {
|
|
912
|
+
res.status(500).json({
|
|
913
|
+
success: false,
|
|
914
|
+
error: `更新全局Git用户配置失败: ${error.message}`
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// 获取远程仓库URL的API
|
|
920
|
+
app.get('/api/remote-url', async (req, res) => {
|
|
921
|
+
try {
|
|
922
|
+
// 检查当前目录是否是Git仓库
|
|
923
|
+
if (!getIsGitRepo()) {
|
|
924
|
+
return res.json({ success: false, error: '当前目录不是Git仓库' });
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// 执行git命令获取远程仓库URL
|
|
928
|
+
const { stdout } = await execGitCommand('git config --get remote.origin.url');
|
|
929
|
+
|
|
930
|
+
// 返回远程仓库URL
|
|
931
|
+
res.json({
|
|
932
|
+
success: true,
|
|
933
|
+
url: stdout.trim()
|
|
934
|
+
});
|
|
935
|
+
} catch (error) {
|
|
936
|
+
console.error('获取远程仓库URL失败:', error);
|
|
937
|
+
res.json({
|
|
938
|
+
success: false,
|
|
939
|
+
error: error.message || '获取远程仓库URL失败'
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// 获取所有作者列表
|
|
945
|
+
app.get('/api/authors', async (req, res) => {
|
|
946
|
+
try {
|
|
947
|
+
// 使用git命令获取所有提交者,不依赖Unix命令
|
|
948
|
+
const { stdout } = await execGitCommand('git log --format="%an"');
|
|
949
|
+
|
|
950
|
+
// 将结果按行分割并过滤空行
|
|
951
|
+
const lines = stdout.split('\n').filter(author => author.trim() !== '');
|
|
952
|
+
|
|
953
|
+
// 手动去重,不依赖Unix的uniq命令
|
|
954
|
+
const uniqueAuthors = Array.from(new Set(lines)).sort();
|
|
955
|
+
|
|
956
|
+
// 控制台输出一下搜索示例,方便调试
|
|
957
|
+
if (uniqueAuthors.length > 1) {
|
|
958
|
+
const searchExample = uniqueAuthors.slice(0, 2).join('|');
|
|
959
|
+
console.log(`多作者搜索示例: git log --author="${searchExample}"`);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
res.json({
|
|
963
|
+
success: true,
|
|
964
|
+
authors: uniqueAuthors
|
|
965
|
+
});
|
|
966
|
+
} catch (error) {
|
|
967
|
+
console.error('获取作者列表失败:', error);
|
|
968
|
+
res.status(500).json({
|
|
969
|
+
success: false,
|
|
970
|
+
error: '获取作者列表失败: ' + error.message
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
}
|