zen-gitsync 2.12.2 → 2.12.3
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/LICENSE +190 -21
- package/index.js +25 -11
- package/package.json +2 -2
- package/scripts/convert-colors-to-vars.cjs +286 -272
- package/scripts/convert-fontsize-to-vars.cjs +221 -207
- package/scripts/convert-spacing-to-vars.cjs +256 -242
- package/scripts/convert-to-standard-vars.cjs +282 -268
- package/scripts/release.js +599 -585
- package/src/config.js +350 -336
- package/src/gitCommit.js +455 -440
- package/src/ui/public/assets/EditorView-CbqSI9nw.css +1 -0
- package/src/ui/public/assets/{EditorView-bnJmBq-i.js → EditorView-GS5cmh99.js} +2 -2
- package/src/ui/public/assets/SourceMapView-DyMK80hS.css +1 -0
- package/src/ui/public/assets/{SourceMapView-Rz5SD0A0.js → SourceMapView-_YRtzmZZ.js} +3 -3
- package/src/ui/public/assets/{index-bOs5P8fz.css → index-ML5Y-5lO.css} +1 -1
- package/src/ui/public/assets/{index-Bo3tntQh.js → index-yky0Sd13.js} +11 -11
- package/src/ui/public/index.html +2 -2
- package/src/ui/server/index.js +410 -396
- package/src/ui/server/middleware/requestLogger.js +51 -37
- package/src/ui/server/routes/branchStatus.js +101 -87
- package/src/ui/server/routes/code.js +110 -96
- package/src/ui/server/routes/codeAnalysis.js +995 -981
- package/src/ui/server/routes/config.js +1172 -1158
- package/src/ui/server/routes/exec.js +272 -258
- package/src/ui/server/routes/fileOpen.js +279 -265
- package/src/ui/server/routes/fs.js +701 -687
- package/src/ui/server/routes/git/diff.js +352 -338
- package/src/ui/server/routes/git/diffUtils.js +128 -114
- package/src/ui/server/routes/git/stash.js +552 -538
- package/src/ui/server/routes/git/tags.js +172 -158
- package/src/ui/server/routes/git.js +190 -176
- package/src/ui/server/routes/gitOps.js +1179 -1165
- package/src/ui/server/routes/instances.js +14 -0
- package/src/ui/server/routes/npm.js +1023 -1009
- package/src/ui/server/routes/process.js +82 -68
- package/src/ui/server/routes/status.js +67 -53
- package/src/ui/server/routes/terminal.js +319 -305
- package/src/ui/server/socket/registerUiSocketHandlers.js +226 -212
- package/src/ui/server/utils/createSavePortToFile.js +46 -32
- package/src/ui/server/utils/instanceRegistry.js +14 -0
- package/src/ui/server/utils/pathGuard.js +14 -0
- package/src/ui/server/utils/pathGuard.test.js +14 -0
- package/src/ui/server/utils/randomStartPort.js +14 -0
- package/src/ui/server/utils/startServerOnAvailablePort.js +101 -87
- package/src/utils/index.js +14 -0
- package/src/ui/public/assets/EditorView-CHBjgiZc.css +0 -1
- package/src/ui/public/assets/SourceMapView-DhQX0K7t.css +0 -1
|
@@ -1,338 +1,352 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
res.json({
|
|
100
|
-
} catch (error) {
|
|
101
|
-
res.status(500).json({
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
app.get('/api/
|
|
107
|
-
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
res.json({
|
|
211
|
-
success:
|
|
212
|
-
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
import fs from 'fs/promises';
|
|
16
|
+
|
|
17
|
+
import { createDiffHelpers } from './diffUtils.js';
|
|
18
|
+
|
|
19
|
+
export function registerGitDiffRoutes({
|
|
20
|
+
app,
|
|
21
|
+
execGitCommand
|
|
22
|
+
}) {
|
|
23
|
+
const { checkShouldSkipDiff, checkDiffSize, getDiffStats } = createDiffHelpers({ execGitCommand });
|
|
24
|
+
|
|
25
|
+
const skipExtensions = /\.(min\.js|umd\.cjs|bundle\.js|dist\.js|prod\.js|map|wasm|exe|dll|so|dylib|bin|zip|tar|gz|rar|7z|jar|war|ear|pdf|doc|docx|xls|xlsx|ppt|pptx|jpg|jpeg|png|gif|bmp|ico|mp3|mp4|avi|mov|wmv|flv|webm|mkv|ttf|woff|woff2|eot|otf)$/i;
|
|
26
|
+
const maxBytes = 1024 * 1024;
|
|
27
|
+
|
|
28
|
+
// 获取文件差异
|
|
29
|
+
app.get('/api/diff', async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const filePath = req.query.file;
|
|
32
|
+
|
|
33
|
+
if (!filePath) {
|
|
34
|
+
return res.status(400).json({ error: '缺少文件路径参数' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const diffCommand = `git diff -- "${filePath}"`;
|
|
38
|
+
|
|
39
|
+
// 使用优化的检查函数
|
|
40
|
+
const skipCheck = await checkShouldSkipDiff(filePath, diffCommand);
|
|
41
|
+
if (skipCheck.shouldSkip) {
|
|
42
|
+
return res.json({
|
|
43
|
+
diff: skipCheck.reason,
|
|
44
|
+
isLargeFile: true,
|
|
45
|
+
stats: skipCheck.stats
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 执行git diff命令获取文件差异
|
|
50
|
+
const { stdout } = await execGitCommand(diffCommand);
|
|
51
|
+
|
|
52
|
+
// 检查实际diff大小
|
|
53
|
+
const sizeCheck = checkDiffSize(stdout, 500);
|
|
54
|
+
if (sizeCheck) {
|
|
55
|
+
return res.json(sizeCheck);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 统计增加和删除行数
|
|
59
|
+
const stats = getDiffStats(stdout);
|
|
60
|
+
|
|
61
|
+
res.json({ diff: stdout, stats });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
res.status(500).json({ error: error.message });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// 获取已暂存文件差异
|
|
67
|
+
app.get('/api/diff-cached', async (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
const filePath = req.query.file;
|
|
70
|
+
|
|
71
|
+
if (!filePath) {
|
|
72
|
+
return res.status(400).json({ error: '缺少文件路径参数' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const diffCommand = `git diff --cached -- "${filePath}"`;
|
|
76
|
+
|
|
77
|
+
// 使用优化的检查函数
|
|
78
|
+
const skipCheck = await checkShouldSkipDiff(filePath, diffCommand);
|
|
79
|
+
if (skipCheck.shouldSkip) {
|
|
80
|
+
return res.json({
|
|
81
|
+
diff: skipCheck.reason,
|
|
82
|
+
isLargeFile: true,
|
|
83
|
+
stats: skipCheck.stats
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 执行git diff --cached命令获取已暂存文件差异
|
|
88
|
+
const { stdout } = await execGitCommand(diffCommand);
|
|
89
|
+
|
|
90
|
+
// 检查实际diff大小
|
|
91
|
+
const sizeCheck = checkDiffSize(stdout, 500);
|
|
92
|
+
if (sizeCheck) {
|
|
93
|
+
return res.json(sizeCheck);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 统计增加和删除行数
|
|
97
|
+
const stats = getDiffStats(stdout);
|
|
98
|
+
|
|
99
|
+
res.json({ diff: stdout, stats });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
res.status(500).json({ error: error.message });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// 获取全量 diff(git diff HEAD,含已暂存与未暂存的所有变更)
|
|
106
|
+
app.get('/api/diff-head', async (req, res) => {
|
|
107
|
+
try {
|
|
108
|
+
const { stdout } = await execGitCommand('git diff HEAD');
|
|
109
|
+
const MAX = 500 * 1024;
|
|
110
|
+
const content = stdout.length > MAX
|
|
111
|
+
? stdout.slice(0, MAX) + '\n\n[内容过大,已截断]'
|
|
112
|
+
: stdout;
|
|
113
|
+
res.json({ success: true, diff: content });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
res.status(500).json({ success: false, error: error.message });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 获取文件内容 (用于未跟踪文件)
|
|
120
|
+
app.get('/api/file-content', async (req, res) => {
|
|
121
|
+
try {
|
|
122
|
+
const filePath = req.query.file;
|
|
123
|
+
|
|
124
|
+
if (!filePath) {
|
|
125
|
+
return res.status(400).json({ error: '缺少文件路径参数' });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// 二进制/产物文件:直接告知前端 isBinary,不读取内容
|
|
130
|
+
if (skipExtensions.test(String(filePath))) {
|
|
131
|
+
const isImage = /\.(png|jpg|jpeg|gif|webp|bmp|ico|svg)$/i.test(String(filePath))
|
|
132
|
+
return res.json({
|
|
133
|
+
success: true,
|
|
134
|
+
isBinary: true,
|
|
135
|
+
isImage,
|
|
136
|
+
content: isImage
|
|
137
|
+
? '⚠️ 该文件是图片,建议在预览中查看。'
|
|
138
|
+
: '⚠️ 检测到二进制/编译产物文件,不支持以文本形式显示完整内容。'
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 读取文件内容
|
|
143
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
144
|
+
res.json({ success: true, content });
|
|
145
|
+
} catch (readError) {
|
|
146
|
+
res.status(500).json({ success: false, error: `无法读取文件: ${readError.message}` });
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
res.status(500).json({ error: error.message });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
app.get('/api/git-file-content', async (req, res) => {
|
|
154
|
+
try {
|
|
155
|
+
const filePath = req.query.file;
|
|
156
|
+
const rev = req.query.rev;
|
|
157
|
+
|
|
158
|
+
if (!filePath || !rev) {
|
|
159
|
+
return res.status(400).json({ success: false, error: '缺少必要参数' });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (skipExtensions.test(String(filePath))) {
|
|
163
|
+
return res.json({
|
|
164
|
+
success: true,
|
|
165
|
+
isBinary: true,
|
|
166
|
+
content: '⚠️ 检测到二进制/编译产物文件,不支持以文本形式显示完整内容。'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const r = String(rev);
|
|
171
|
+
const spec = r === ':' ? `:${filePath}` : `${r}:${filePath}`;
|
|
172
|
+
|
|
173
|
+
let sizeBytes = 0;
|
|
174
|
+
try {
|
|
175
|
+
const { stdout: sizeOut } = await execGitCommand(`git cat-file -s "${spec}"`, { log: false });
|
|
176
|
+
sizeBytes = parseInt(String(sizeOut).trim(), 10) || 0;
|
|
177
|
+
} catch (e) {
|
|
178
|
+
return res.json({ success: true, notFound: true, content: '' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (sizeBytes > maxBytes) {
|
|
182
|
+
return res.json({
|
|
183
|
+
success: true,
|
|
184
|
+
isLargeFile: true,
|
|
185
|
+
size: sizeBytes,
|
|
186
|
+
content: `⚠️ 文件内容过大 (${(sizeBytes / 1024).toFixed(1)} KB),已跳过显示以避免浏览器卡顿。`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const { stdout } = await execGitCommand(`git show "${spec}"`, { log: false });
|
|
191
|
+
return res.json({ success: true, content: stdout ?? '' });
|
|
192
|
+
} catch (error) {
|
|
193
|
+
res.status(500).json({ success: false, error: error.message });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// 解决冲突:保存解决后的文件内容
|
|
198
|
+
app.post('/api/resolve-conflict', async (req, res) => {
|
|
199
|
+
try {
|
|
200
|
+
const { filePath, content } = req.body;
|
|
201
|
+
|
|
202
|
+
if (!filePath) {
|
|
203
|
+
return res.status(400).json({
|
|
204
|
+
success: false,
|
|
205
|
+
error: '缺少文件路径参数'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (content === undefined) {
|
|
210
|
+
return res.status(400).json({
|
|
211
|
+
success: false,
|
|
212
|
+
error: '缺少文件内容参数'
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// 写入解决后的内容到文件
|
|
218
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
219
|
+
|
|
220
|
+
// 不自动添加到暂存区,让用户手动决定
|
|
221
|
+
// Git 会自动将冲突已解决的文件标记为"已修改"状态
|
|
222
|
+
// await execGitCommand(`git add "${filePath}"`);
|
|
223
|
+
|
|
224
|
+
res.json({
|
|
225
|
+
success: true,
|
|
226
|
+
message: '冲突已解决,文件已更新'
|
|
227
|
+
});
|
|
228
|
+
} catch (writeError) {
|
|
229
|
+
res.status(500).json({
|
|
230
|
+
success: false,
|
|
231
|
+
error: `保存文件失败: ${writeError.message}`
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('解决冲突失败:', error);
|
|
236
|
+
res.status(500).json({
|
|
237
|
+
success: false,
|
|
238
|
+
error: `解决冲突失败: ${error.message}`
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// 撤回文件修改
|
|
244
|
+
app.post('/api/revert_file', async (req, res) => {
|
|
245
|
+
try {
|
|
246
|
+
const { filePath } = req.body;
|
|
247
|
+
|
|
248
|
+
if (!filePath) {
|
|
249
|
+
return res.status(400).json({
|
|
250
|
+
success: false,
|
|
251
|
+
error: '缺少文件路径参数'
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 检查文件状态:未跟踪文件需要删除,修改文件需要恢复
|
|
256
|
+
const { stdout: statusOutput } = await execGitCommand(`git status --porcelain -- "${filePath}"`);
|
|
257
|
+
|
|
258
|
+
// 未跟踪的文件 (??), 需要删除它
|
|
259
|
+
if (statusOutput.startsWith('??')) {
|
|
260
|
+
try {
|
|
261
|
+
await fs.unlink(filePath);
|
|
262
|
+
return res.json({ success: true, message: '未跟踪的文件已删除' });
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return res.status(500).json({
|
|
265
|
+
success: false,
|
|
266
|
+
error: `删除文件失败: ${error.message}`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// 已暂存的文件,先取消暂存
|
|
271
|
+
else if (statusOutput.startsWith('A ') || statusOutput.startsWith('M ') || statusOutput.startsWith('D ')) {
|
|
272
|
+
// 先取消暂存
|
|
273
|
+
await execGitCommand(`git reset HEAD -- "${filePath}"`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 已修改文件,取消所有本地修改
|
|
277
|
+
if (statusOutput) {
|
|
278
|
+
await execGitCommand(`git checkout -- "${filePath}"`);
|
|
279
|
+
return res.json({ success: true, message: '文件修改已撤回' });
|
|
280
|
+
} else {
|
|
281
|
+
return res.status(400).json({
|
|
282
|
+
success: false,
|
|
283
|
+
error: '文件没有修改或不存在'
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error('撤回文件修改失败:', error);
|
|
288
|
+
res.status(500).json({
|
|
289
|
+
success: false,
|
|
290
|
+
error: `撤回文件修改失败: ${error.message}`
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// 批量撤回文件修改(未跟踪删除,已修改 checkout 还原)
|
|
296
|
+
// body: { filePaths: string[] }
|
|
297
|
+
// 返回: { success, count, results: [{ path, success, error? }] }
|
|
298
|
+
app.post('/api/revert_files', async (req, res) => {
|
|
299
|
+
const filePaths = Array.isArray(req.body?.filePaths) ? req.body.filePaths : []
|
|
300
|
+
if (filePaths.length === 0) {
|
|
301
|
+
return res.status(400).json({ success: false, error: '缺少文件路径参数' })
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const results = []
|
|
305
|
+
let successCount = 0
|
|
306
|
+
|
|
307
|
+
for (const filePath of filePaths) {
|
|
308
|
+
try {
|
|
309
|
+
// 检查文件状态:未跟踪 ??、已暂存 A/M/D、已修改(空状态会返回空字符串)
|
|
310
|
+
const { stdout: statusOutput } = await execGitCommand(`git status --porcelain -- "${filePath}"`)
|
|
311
|
+
|
|
312
|
+
// 未跟踪的文件 (??) → 直接删除
|
|
313
|
+
if (statusOutput.startsWith('??')) {
|
|
314
|
+
try {
|
|
315
|
+
await fs.unlink(filePath)
|
|
316
|
+
results.push({ path: filePath, success: true, message: '未跟踪的文件已删除' })
|
|
317
|
+
successCount++
|
|
318
|
+
continue
|
|
319
|
+
} catch (err) {
|
|
320
|
+
results.push({ path: filePath, success: false, error: `删除文件失败: ${err?.message || err}` })
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 已暂存的文件,先取消暂存(不影响工作区)
|
|
326
|
+
if (statusOutput.startsWith('A ') || statusOutput.startsWith('M ') || statusOutput.startsWith('D ')) {
|
|
327
|
+
await execGitCommand(`git reset HEAD -- "${filePath}"`)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// 已修改文件:丢弃工作区修改
|
|
331
|
+
if (statusOutput) {
|
|
332
|
+
await execGitCommand(`git checkout -- "${filePath}"`)
|
|
333
|
+
results.push({ path: filePath, success: true, message: '文件修改已撤回' })
|
|
334
|
+
successCount++
|
|
335
|
+
} else {
|
|
336
|
+
// 文件已无修改(可能在并发中被处理掉了)
|
|
337
|
+
results.push({ path: filePath, success: true, message: '文件无修改' })
|
|
338
|
+
successCount++
|
|
339
|
+
}
|
|
340
|
+
} catch (err) {
|
|
341
|
+
results.push({ path: filePath, success: false, error: err?.message || String(err) })
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
res.json({
|
|
346
|
+
success: true,
|
|
347
|
+
count: filePaths.length,
|
|
348
|
+
successCount,
|
|
349
|
+
results
|
|
350
|
+
})
|
|
351
|
+
});
|
|
352
|
+
}
|