zen-gitsync 2.13.23 → 2.13.25
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/package.json +1 -1
- package/src/gitCommit.js +2 -2
- package/src/ui/public/assets/AppVersionBadge-CIAzLihu.js +2 -0
- package/src/ui/public/assets/AppVersionBadge-CTUXSSX1.css +1 -0
- package/src/ui/public/assets/{BranchSelector-Dm7nENer.js → BranchSelector-BoztZ6c2.js} +1 -1
- package/src/ui/public/assets/{CommandConsole-C1W9ZQkE.js → CommandConsole-CZA9PDIm.js} +2 -2
- package/src/ui/public/assets/{CommitForm-C76W1ZZH.js → CommitForm-DZ8aU8PB.js} +1 -1
- package/src/ui/public/assets/{CustomCommandManager-JKx8JYqn.js → CustomCommandManager-CieZYBTz.js} +1 -1
- package/src/ui/public/assets/{EditorView-CriUYDvi.js → EditorView-BzTJPZPp.js} +0 -0
- package/src/ui/public/assets/{FileDiffViewer-DNWsiIGt.js → FileDiffViewer-DhpNUTJt.js} +1 -1
- package/src/ui/public/assets/{FlowExecutionViewer-CTDh2bCD.js → FlowExecutionViewer-DlYmmGkP.js} +1 -1
- package/src/ui/public/assets/{FlowOrchestrationWorkspace-Chah2aGN.js → FlowOrchestrationWorkspace-gdihO9k1.js} +1 -1
- package/src/ui/public/assets/{GitStatus-DP-DQsiS.js → GitStatus-B4hnJzqy.js} +1 -1
- package/src/ui/public/assets/{ImagePreview-D8klNa6F.js → ImagePreview-Cil0rDfG.js} +1 -1
- package/src/ui/public/assets/{LogList-Z76_5USQ.js → LogList-BKqV6ERt.js} +1 -1
- package/src/ui/public/assets/{ProjectStartupButton-C5oF4unq.js → ProjectStartupButton-CPXyIqHS.js} +1 -1
- package/src/ui/public/assets/{RemoteRepoCard-B-fGjb8Y.js → RemoteRepoCard-CspjnxaZ.js} +1 -1
- package/src/ui/public/assets/{ResetToRemoteButton-BixSoEVs.js → ResetToRemoteButton-CAHhKkZ5.js} +1 -1
- package/src/ui/public/assets/{SourceMapView-C9HZiJ9N.js → SourceMapView-Bb8pfASG.js} +1 -1
- package/src/ui/public/assets/{TemplateManager-C7SuKq0f.js → TemplateManager-WSreg-op.js} +1 -1
- package/src/ui/public/assets/{UserInputNode-CYy0UpzR.js → UserInputNode-DR6fUbfy.js} +1 -1
- package/src/ui/public/assets/{WorkbenchView-A9Oik707.js → WorkbenchView-402ZzsT0.js} +1 -1
- package/src/ui/public/assets/{_plugin-vue_export-helper-BfbhkIv4.js → _plugin-vue_export-helper-BVVLmyjZ.js} +2 -2
- package/src/ui/public/assets/{index-DPSGvy34.js → index-D_T4XWSf.js} +3 -3
- package/src/ui/public/index.html +2 -2
- package/src/ui/server/index.js +3 -3
- package/src/ui/server/routes/branchStatus.js +4 -4
- package/src/ui/server/routes/config.js +8 -8
- package/src/ui/server/routes/exec.js +7 -1
- package/src/ui/server/routes/fs.js +2 -2
- package/src/ui/server/routes/git/diff.js +17 -16
- package/src/ui/server/routes/git/diffUtils.js +21 -2
- package/src/ui/server/routes/git/stash.js +38 -42
- package/src/ui/server/routes/git/tags.js +10 -10
- package/src/ui/server/routes/git.js +15 -26
- package/src/ui/server/routes/gitOps.js +91 -125
- package/src/ui/server/routes/status.js +3 -3
- package/src/utils/index.js +32 -29
- package/src/ui/public/assets/AppVersionBadge-BUS8w2f8.js +0 -2
- package/src/ui/public/assets/AppVersionBadge-DVn027LE.css +0 -1
package/src/ui/public/index.html
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-D_T4XWSf.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CMxvf4Kt.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/monaco-DC7mqInI.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/element-plus-CJjFCgLP.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/assets/vendor-0YbzMuxw.js">
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BVVLmyjZ.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/assets/socket-io-z4n873W1.js">
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/monaco-UXx6YYK1.css">
|
|
21
21
|
<link rel="stylesheet" crossorigin href="/assets/element-plus-D36sOm0a.css">
|
package/src/ui/server/index.js
CHANGED
|
@@ -110,7 +110,7 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
110
110
|
let dirPath = process.cwd();
|
|
111
111
|
try {
|
|
112
112
|
if(showConsole) console.log(`记录最近打开目录`)
|
|
113
|
-
const { stdout } = await execGitCommand('
|
|
113
|
+
const { stdout } = await execGitCommand(['rev-parse', '--show-toplevel']);
|
|
114
114
|
const root = stdout?.trim();
|
|
115
115
|
if (root) dirPath = root;
|
|
116
116
|
} catch (_) {
|
|
@@ -290,7 +290,7 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
// 只获取porcelain格式状态,不再获取完整的git status
|
|
293
|
-
const { stdout: porcelainOutput } = await execGitCommand('
|
|
293
|
+
const { stdout: porcelainOutput } = await execGitCommand(['status', '--porcelain', '--untracked-files=all']);
|
|
294
294
|
|
|
295
295
|
// 广播到当前项目房间的所有客户端
|
|
296
296
|
io.to(projectRoomId).emit('git_status_update', {
|
|
@@ -309,7 +309,7 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
309
309
|
// 检查当前目录是否是Git仓库
|
|
310
310
|
let isGitRepo = false;
|
|
311
311
|
try {
|
|
312
|
-
const { stdout } = await execGitCommand('
|
|
312
|
+
const { stdout } = await execGitCommand(['rev-parse', '--is-inside-work-tree'], { log: false });
|
|
313
313
|
isGitRepo = stdout.trim() === 'true';
|
|
314
314
|
} catch (error) {
|
|
315
315
|
isGitRepo = false;
|
|
@@ -24,7 +24,7 @@ export function registerBranchStatusRoutes({
|
|
|
24
24
|
// 获取当前分支 - 直接读取,不缓存(git symbolic-ref 极快,<5ms)
|
|
25
25
|
app.get('/api/branch', async (req, res) => {
|
|
26
26
|
try {
|
|
27
|
-
const { stdout } = await execGitCommand('
|
|
27
|
+
const { stdout } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
|
|
28
28
|
res.json({ branch: stdout.trim() });
|
|
29
29
|
} catch (error) {
|
|
30
30
|
res.status(500).json({ error: error.message });
|
|
@@ -70,11 +70,11 @@ export function registerBranchStatusRoutes({
|
|
|
70
70
|
console.log(`使用5秒缓存的分支名: ${currentBranch} -> ${upstreamBranch}`);
|
|
71
71
|
} else {
|
|
72
72
|
// 直接读取,不再走5分钟长缓存
|
|
73
|
-
const { stdout: branchOut } = await execGitCommand('
|
|
73
|
+
const { stdout: branchOut } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
|
|
74
74
|
currentBranch = branchOut.trim();
|
|
75
75
|
|
|
76
76
|
const { stdout: upstreamOut } = await execGitCommand(
|
|
77
|
-
'
|
|
77
|
+
['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'],
|
|
78
78
|
{ ignoreError: true }
|
|
79
79
|
);
|
|
80
80
|
upstreamBranch = upstreamOut.trim() || null;
|
|
@@ -88,7 +88,7 @@ export function registerBranchStatusRoutes({
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
const { stdout: aheadBehindOutput } = await execGitCommand(
|
|
91
|
-
|
|
91
|
+
['rev-list', '--left-right', '--count', `${currentBranch}...${upstreamBranch}`]
|
|
92
92
|
);
|
|
93
93
|
const [ahead, behind] = aheadBehindOutput.trim().split('\t').map(Number);
|
|
94
94
|
|
|
@@ -101,7 +101,7 @@ async function collectDiffForAi({ execGitCommand, getCurrentProjectPath, selecte
|
|
|
101
101
|
let fileList = []
|
|
102
102
|
try {
|
|
103
103
|
// 1. 拿到工作区状态,识别 untracked 文件
|
|
104
|
-
const { stdout: statusOut } = await execGitCommand('
|
|
104
|
+
const { stdout: statusOut } = await execGitCommand(['status', '--porcelain=1', '--untracked-files=all'], cwdOpts)
|
|
105
105
|
const untracked = []
|
|
106
106
|
const trackedChanges = []
|
|
107
107
|
for (const line of (statusOut || '').split('\n')) {
|
|
@@ -129,9 +129,8 @@ async function collectDiffForAi({ execGitCommand, getCurrentProjectPath, selecte
|
|
|
129
129
|
const batchSize = 20
|
|
130
130
|
for (let i = 0; i < untracked.length; i += batchSize) {
|
|
131
131
|
const batch = untracked.slice(i, i + batchSize)
|
|
132
|
-
const quoted = batch.map(p => `"${p.replace(/"/g, '\\"')}"`).join(' ')
|
|
133
132
|
try {
|
|
134
|
-
await execGitCommand(
|
|
133
|
+
await execGitCommand(['add', '-N', '--force', ...batch], cwdOpts)
|
|
135
134
|
} catch (e) {
|
|
136
135
|
// 单批失败不影响整体
|
|
137
136
|
console.warn('[generate-commit] git add -N failed for batch:', e?.message)
|
|
@@ -142,12 +141,13 @@ async function collectDiffForAi({ execGitCommand, getCurrentProjectPath, selecte
|
|
|
142
141
|
// 3. 合并 staged + unstaged diff
|
|
143
142
|
// 用 --no-color 避免 ANSI 干扰, --no-ext-diff 避免外接 diff 工具
|
|
144
143
|
// 选择模式下用 `-- <paths>` 限定,避免拉取无关 staged 改动
|
|
145
|
-
const pathArgs = selectedSet
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
const pathArgs = selectedSet ? [...selectedSet] : []
|
|
145
|
+
const diffBase = ['diff', '--no-color', '--no-ext-diff']
|
|
146
|
+
const stagedArgs = ['diff', '--staged', '--no-color', '--no-ext-diff', ...(pathArgs.length ? ['--', ...pathArgs] : [])]
|
|
147
|
+
const unstagedArgs = [...diffBase, ...(pathArgs.length ? ['--', ...pathArgs] : [])]
|
|
148
148
|
const [stagedRes, unstagedRes] = await Promise.all([
|
|
149
|
-
execGitCommand(
|
|
150
|
-
execGitCommand(
|
|
149
|
+
execGitCommand(stagedArgs, cwdOpts).catch(() => ({ stdout: '' })),
|
|
150
|
+
execGitCommand(unstagedArgs, cwdOpts).catch(() => ({ stdout: '' }))
|
|
151
151
|
])
|
|
152
152
|
let combined = ''
|
|
153
153
|
if (stagedRes?.stdout) combined += stagedRes.stdout.trim() + '\n'
|
|
@@ -16,6 +16,12 @@ import path from 'path';
|
|
|
16
16
|
import iconv from 'iconv-lite';
|
|
17
17
|
import { spawn } from 'child_process';
|
|
18
18
|
|
|
19
|
+
// 把 "git config --global key value" 这种空格分隔命令拆成 argv 数组,
|
|
20
|
+
// 注意:简单 split 不处理引号/转义;保持与之前 shell 模式一致的语义。
|
|
21
|
+
function splitCommandArgs(command) {
|
|
22
|
+
return command.trim().split(/\s+/).filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
export function registerExecRoutes({
|
|
20
26
|
app,
|
|
21
27
|
execGitCommand,
|
|
@@ -33,7 +39,7 @@ export function registerExecRoutes({
|
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
try {
|
|
36
|
-
const { stdout = '', stderr = '' } = await execGitCommand(command, { log: false });
|
|
42
|
+
const { stdout = '', stderr = '' } = await execGitCommand(splitCommandArgs(command), { log: false });
|
|
37
43
|
return res.json({ success: true, stdout, stderr });
|
|
38
44
|
} catch (err) {
|
|
39
45
|
return res.status(400).json({ success: false, error: err?.message || String(err) });
|
|
@@ -45,7 +45,7 @@ export function registerFsRoutes({
|
|
|
45
45
|
|
|
46
46
|
// 检查当前目录是否是Git仓库
|
|
47
47
|
try {
|
|
48
|
-
await execGitCommand('
|
|
48
|
+
await execGitCommand(['rev-parse', '--is-inside-work-tree']);
|
|
49
49
|
} catch (error) {
|
|
50
50
|
return res.status(400).json({
|
|
51
51
|
error: '当前目录不是一个Git仓库',
|
|
@@ -86,7 +86,7 @@ export function registerFsRoutes({
|
|
|
86
86
|
|
|
87
87
|
// 检查新目录是否是Git仓库
|
|
88
88
|
try {
|
|
89
|
-
await execGitCommand('
|
|
89
|
+
await execGitCommand(['rev-parse', '--is-inside-work-tree']);
|
|
90
90
|
|
|
91
91
|
// 更新全局变量
|
|
92
92
|
setCurrentProjectPath(newProjectPath);
|
|
@@ -34,10 +34,11 @@ export function registerGitDiffRoutes({
|
|
|
34
34
|
return res.status(400).json({ error: '缺少文件路径参数' });
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const
|
|
37
|
+
const diffArgs = ['diff', '--', filePath];
|
|
38
38
|
|
|
39
|
-
// 使用优化的检查函数
|
|
40
|
-
|
|
39
|
+
// 使用优化的检查函数(diffCommand 用于内部 numstat 检测的字符串转换,
|
|
40
|
+
// 实际走 git 时用 argv 数组,避免 Windows cmd.exe 拼引号问题)
|
|
41
|
+
const skipCheck = await checkShouldSkipDiff(filePath, `git diff -- "${filePath}"`);
|
|
41
42
|
if (skipCheck.shouldSkip) {
|
|
42
43
|
return res.json({
|
|
43
44
|
diff: skipCheck.reason,
|
|
@@ -47,7 +48,7 @@ export function registerGitDiffRoutes({
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// 执行git diff命令获取文件差异
|
|
50
|
-
const { stdout } = await execGitCommand(
|
|
51
|
+
const { stdout } = await execGitCommand(diffArgs);
|
|
51
52
|
|
|
52
53
|
// 检查实际diff大小
|
|
53
54
|
const sizeCheck = checkDiffSize(stdout, 500);
|
|
@@ -72,10 +73,10 @@ export function registerGitDiffRoutes({
|
|
|
72
73
|
return res.status(400).json({ error: '缺少文件路径参数' });
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
const
|
|
76
|
+
const diffArgs = ['diff', '--cached', '--', filePath];
|
|
76
77
|
|
|
77
78
|
// 使用优化的检查函数
|
|
78
|
-
const skipCheck = await checkShouldSkipDiff(filePath,
|
|
79
|
+
const skipCheck = await checkShouldSkipDiff(filePath, `git diff --cached -- "${filePath}"`);
|
|
79
80
|
if (skipCheck.shouldSkip) {
|
|
80
81
|
return res.json({
|
|
81
82
|
diff: skipCheck.reason,
|
|
@@ -85,7 +86,7 @@ export function registerGitDiffRoutes({
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
// 执行git diff --cached命令获取已暂存文件差异
|
|
88
|
-
const { stdout } = await execGitCommand(
|
|
89
|
+
const { stdout } = await execGitCommand(diffArgs);
|
|
89
90
|
|
|
90
91
|
// 检查实际diff大小
|
|
91
92
|
const sizeCheck = checkDiffSize(stdout, 500);
|
|
@@ -105,7 +106,7 @@ export function registerGitDiffRoutes({
|
|
|
105
106
|
// 获取全量 diff(git diff HEAD,含已暂存与未暂存的所有变更)
|
|
106
107
|
app.get('/api/diff-head', async (req, res) => {
|
|
107
108
|
try {
|
|
108
|
-
const { stdout } = await execGitCommand('
|
|
109
|
+
const { stdout } = await execGitCommand(['diff', 'HEAD']);
|
|
109
110
|
const MAX = 500 * 1024;
|
|
110
111
|
const content = stdout.length > MAX
|
|
111
112
|
? stdout.slice(0, MAX) + '\n\n[内容过大,已截断]'
|
|
@@ -172,7 +173,7 @@ export function registerGitDiffRoutes({
|
|
|
172
173
|
|
|
173
174
|
let sizeBytes = 0;
|
|
174
175
|
try {
|
|
175
|
-
const { stdout: sizeOut } = await execGitCommand(
|
|
176
|
+
const { stdout: sizeOut } = await execGitCommand(['cat-file', '-s', spec], { log: false });
|
|
176
177
|
sizeBytes = parseInt(String(sizeOut).trim(), 10) || 0;
|
|
177
178
|
} catch (e) {
|
|
178
179
|
return res.json({ success: true, notFound: true, content: '' });
|
|
@@ -187,7 +188,7 @@ export function registerGitDiffRoutes({
|
|
|
187
188
|
});
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
const { stdout } = await execGitCommand(
|
|
191
|
+
const { stdout } = await execGitCommand(['show', spec], { log: false });
|
|
191
192
|
return res.json({ success: true, content: stdout ?? '' });
|
|
192
193
|
} catch (error) {
|
|
193
194
|
res.status(500).json({ success: false, error: error.message });
|
|
@@ -253,7 +254,7 @@ export function registerGitDiffRoutes({
|
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
// 检查文件状态:未跟踪文件需要删除,修改文件需要恢复
|
|
256
|
-
const { stdout: statusOutput } = await execGitCommand(
|
|
257
|
+
const { stdout: statusOutput } = await execGitCommand(['status', '--porcelain', '--', filePath]);
|
|
257
258
|
|
|
258
259
|
// 未跟踪的文件 (??), 需要删除它
|
|
259
260
|
if (statusOutput.startsWith('??')) {
|
|
@@ -270,12 +271,12 @@ export function registerGitDiffRoutes({
|
|
|
270
271
|
// 已暂存的文件,先取消暂存
|
|
271
272
|
else if (statusOutput.startsWith('A ') || statusOutput.startsWith('M ') || statusOutput.startsWith('D ')) {
|
|
272
273
|
// 先取消暂存
|
|
273
|
-
await execGitCommand(
|
|
274
|
+
await execGitCommand(['reset', 'HEAD', '--', filePath]);
|
|
274
275
|
}
|
|
275
276
|
|
|
276
277
|
// 已修改文件,取消所有本地修改
|
|
277
278
|
if (statusOutput) {
|
|
278
|
-
await execGitCommand(
|
|
279
|
+
await execGitCommand(['checkout', '--', filePath]);
|
|
279
280
|
return res.json({ success: true, message: '文件修改已撤回' });
|
|
280
281
|
} else {
|
|
281
282
|
return res.status(400).json({
|
|
@@ -307,7 +308,7 @@ export function registerGitDiffRoutes({
|
|
|
307
308
|
for (const filePath of filePaths) {
|
|
308
309
|
try {
|
|
309
310
|
// 检查文件状态:未跟踪 ??、已暂存 A/M/D、已修改(空状态会返回空字符串)
|
|
310
|
-
const { stdout: statusOutput } = await execGitCommand(
|
|
311
|
+
const { stdout: statusOutput } = await execGitCommand(['status', '--porcelain', '--', filePath])
|
|
311
312
|
|
|
312
313
|
// 未跟踪的文件 (??) → 直接删除
|
|
313
314
|
if (statusOutput.startsWith('??')) {
|
|
@@ -324,12 +325,12 @@ export function registerGitDiffRoutes({
|
|
|
324
325
|
|
|
325
326
|
// 已暂存的文件,先取消暂存(不影响工作区)
|
|
326
327
|
if (statusOutput.startsWith('A ') || statusOutput.startsWith('M ') || statusOutput.startsWith('D ')) {
|
|
327
|
-
await execGitCommand(
|
|
328
|
+
await execGitCommand(['reset', 'HEAD', '--', filePath])
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
// 已修改文件:丢弃工作区修改
|
|
331
332
|
if (statusOutput) {
|
|
332
|
-
await execGitCommand(
|
|
333
|
+
await execGitCommand(['checkout', '--', filePath])
|
|
333
334
|
results.push({ path: filePath, success: true, message: '文件修改已撤回' })
|
|
334
335
|
successCount++
|
|
335
336
|
} else {
|
|
@@ -31,8 +31,27 @@ export function createDiffHelpers({ execGitCommand }) {
|
|
|
31
31
|
|
|
32
32
|
// 2. 使用 --numstat 快速检查变更量(不获取实际内容,速度快)
|
|
33
33
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// 把传入的 diffCommand 字符串改写成 git diff/show 加 --numstat 的 argv 数组。
|
|
35
|
+
// 不再走 shell 模式,避免 Windows cmd.exe 下引号兼容问题(参 utils execGitCommand execFile 化)。
|
|
36
|
+
const numstatCommand = diffCommand.replace(/git (diff|show)/, 'git $1 --numstat')
|
|
37
|
+
const argvMatch = numstatCommand.trim().match(/^git\s+(\S+)\s*(.*)$/)
|
|
38
|
+
let numstatArgs
|
|
39
|
+
if (argvMatch) {
|
|
40
|
+
const subCmd = argvMatch[1]
|
|
41
|
+
const rest = argvMatch[2]
|
|
42
|
+
// 简单按空白拆分,带引号段整体保留 -- "--" + 路径
|
|
43
|
+
// 这里最常见形态是 'git diff/show -- "--path"' 或 'git diff/show hash -- "--path"'
|
|
44
|
+
const tokens = []
|
|
45
|
+
const re = /"([^"]*)"|(\S+)/g
|
|
46
|
+
let m
|
|
47
|
+
while ((m = re.exec(rest)) !== null) tokens.push(m[1] !== undefined ? m[1] : m[2])
|
|
48
|
+
// 找到 '--numstat' 位置,把它加到 subCmd 后面作为第一个参数
|
|
49
|
+
numstatArgs = [subCmd, '--numstat', ...tokens]
|
|
50
|
+
} else {
|
|
51
|
+
// fallback: 走旧逻辑(字符串)
|
|
52
|
+
numstatArgs = numstatCommand
|
|
53
|
+
}
|
|
54
|
+
const { stdout: numstat } = await execGitCommand(numstatArgs, { log: false });
|
|
36
55
|
|
|
37
56
|
if (numstat.trim()) {
|
|
38
57
|
const lines = numstat.trim().split('\n');
|
|
@@ -20,7 +20,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
20
20
|
// 获取stash列表
|
|
21
21
|
app.get('/api/stash-list', async (req, res) => {
|
|
22
22
|
try {
|
|
23
|
-
const { stdout } = await execGitCommand('
|
|
23
|
+
const { stdout } = await execGitCommand(['stash', 'list']);
|
|
24
24
|
|
|
25
25
|
// 解析stash列表
|
|
26
26
|
const stashList = stdout.split('\n')
|
|
@@ -53,7 +53,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
53
53
|
if (excludeLocked) {
|
|
54
54
|
const lockedFiles = await configManager.getLockedFiles();
|
|
55
55
|
// 包含未跟踪文件,确保状态与 UI 一致
|
|
56
|
-
const { stdout: statusStdout } = await execGitCommand('
|
|
56
|
+
const { stdout: statusStdout } = await execGitCommand(['status', '--porcelain', '--untracked-files=all'], { log: false });
|
|
57
57
|
const changedFiles = statusStdout
|
|
58
58
|
.split('\n')
|
|
59
59
|
.filter(line => line.trim())
|
|
@@ -100,7 +100,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
100
100
|
if (includeUntracked) {
|
|
101
101
|
try {
|
|
102
102
|
// 使用 git 列出该目录下的未跟踪和已修改文件
|
|
103
|
-
const { stdout: listStdout } = await execGitCommand(
|
|
103
|
+
const { stdout: listStdout } = await execGitCommand(['ls-files', '-mo', '--exclude-standard', '--', filename], { log: false });
|
|
104
104
|
const listed = listStdout
|
|
105
105
|
.split('\n')
|
|
106
106
|
.map(l => l.trim())
|
|
@@ -145,14 +145,13 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
145
145
|
// 在执行 stash 前进行候选校验:
|
|
146
146
|
// 1) 仍有跟踪差异的文件
|
|
147
147
|
try {
|
|
148
|
-
const
|
|
149
|
-
const { stdout: diffNames } = await execGitCommand(`git diff --name-only -- ${args}`, { log: false });
|
|
148
|
+
const { stdout: diffNames } = await execGitCommand(['diff', '--name-only', '--', ...filesToStash], { log: false });
|
|
150
149
|
const trackedChanged = new Set(diffNames.split('\n').map(s => s.trim()).filter(Boolean));
|
|
151
150
|
|
|
152
151
|
// 2) 仍为未跟踪的文件(当 includeUntracked 才检查)
|
|
153
152
|
let untrackedExisting = new Set();
|
|
154
153
|
if (includeUntracked) {
|
|
155
|
-
const { stdout: others } = await execGitCommand(
|
|
154
|
+
const { stdout: others } = await execGitCommand(['ls-files', '--others', '--exclude-standard', '--', ...filesToStash], { log: false });
|
|
156
155
|
untrackedExisting = new Set(others.split('\n').map(s => s.trim()).filter(Boolean));
|
|
157
156
|
}
|
|
158
157
|
|
|
@@ -174,27 +173,22 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
174
173
|
return res.json({ success: false, message: '没有可储藏的更改(可能刚刚已储藏,或被锁定过滤)' });
|
|
175
174
|
}
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
if (message)
|
|
179
|
-
if (includeUntracked)
|
|
180
|
-
|
|
181
|
-
command += ` -- ${args}`;
|
|
176
|
+
const stashArgs = ['stash', 'push'];
|
|
177
|
+
if (message) stashArgs.push('-m', message);
|
|
178
|
+
if (includeUntracked) stashArgs.push('--include-untracked');
|
|
179
|
+
if (filesToStash.length > 0) stashArgs.push('--', ...filesToStash);
|
|
182
180
|
|
|
183
|
-
const { stdout } = await execGitCommand(
|
|
181
|
+
const { stdout } = await execGitCommand(stashArgs);
|
|
184
182
|
if (stdout.includes('No local changes to save')) {
|
|
185
183
|
return res.json({ success: false, message: '没有本地更改需要保存' });
|
|
186
184
|
}
|
|
187
185
|
return res.json({ success: true, message: '成功保存未锁定的工作区更改', output: stdout });
|
|
188
186
|
}
|
|
189
187
|
|
|
190
|
-
|
|
191
|
-
if (message)
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
if (includeUntracked) {
|
|
195
|
-
command += ' --include-untracked';
|
|
196
|
-
}
|
|
197
|
-
const { stdout } = await execGitCommand(command);
|
|
188
|
+
const stashArgs = ['stash', 'push'];
|
|
189
|
+
if (message) stashArgs.push('-m', message);
|
|
190
|
+
if (includeUntracked) stashArgs.push('--include-untracked');
|
|
191
|
+
const { stdout } = await execGitCommand(stashArgs);
|
|
198
192
|
if (stdout.includes('No local changes to save')) {
|
|
199
193
|
return res.json({ success: false, message: '没有本地更改需要保存' });
|
|
200
194
|
}
|
|
@@ -220,19 +214,18 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
220
214
|
}
|
|
221
215
|
|
|
222
216
|
// 构建 git stash push 命令
|
|
223
|
-
|
|
217
|
+
const stashArgs = ['stash', 'push'];
|
|
224
218
|
if (message) {
|
|
225
|
-
|
|
219
|
+
stashArgs.push('-m', message);
|
|
226
220
|
}
|
|
227
221
|
if (includeUntracked) {
|
|
228
|
-
|
|
222
|
+
stashArgs.push('--include-untracked');
|
|
229
223
|
}
|
|
230
224
|
|
|
231
225
|
// 添加文件列表
|
|
232
|
-
|
|
233
|
-
command += ` -- ${args}`;
|
|
226
|
+
stashArgs.push('--', ...files);
|
|
234
227
|
|
|
235
|
-
const { stdout } = await execGitCommand(
|
|
228
|
+
const { stdout } = await execGitCommand(stashArgs);
|
|
236
229
|
|
|
237
230
|
if (stdout.includes('No local changes to save')) {
|
|
238
231
|
return res.json({ success: false, message: '没有本地更改需要保存' });
|
|
@@ -266,7 +259,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
266
259
|
}
|
|
267
260
|
|
|
268
261
|
// 决定是使用apply(保留stash)还是pop(应用后删除stash)
|
|
269
|
-
const command = pop ?
|
|
262
|
+
const command = pop ? ['stash', 'pop', stashId] : ['stash', 'apply', stashId];
|
|
270
263
|
|
|
271
264
|
try {
|
|
272
265
|
const { stdout } = await execGitCommand(command);
|
|
@@ -306,7 +299,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
306
299
|
});
|
|
307
300
|
}
|
|
308
301
|
|
|
309
|
-
const { stdout } = await execGitCommand(
|
|
302
|
+
const { stdout } = await execGitCommand(['stash', 'drop', stashId]);
|
|
310
303
|
|
|
311
304
|
res.json({
|
|
312
305
|
success: true,
|
|
@@ -322,7 +315,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
322
315
|
// 清空所有stash
|
|
323
316
|
app.post('/api/stash-clear', async (req, res) => {
|
|
324
317
|
try {
|
|
325
|
-
const { stdout } = await execGitCommand('
|
|
318
|
+
const { stdout } = await execGitCommand(['stash', 'clear']);
|
|
326
319
|
|
|
327
320
|
res.json({
|
|
328
321
|
success: true,
|
|
@@ -350,7 +343,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
350
343
|
console.log(`获取stash文件列表: stashId=${stashId}`);
|
|
351
344
|
|
|
352
345
|
// 0) 解析出当前 stash 提交及其父提交哈希,避免在 Windows 上使用 ^ 语法
|
|
353
|
-
const { stdout: parentsLine } = await execGitCommand(
|
|
346
|
+
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1', stashId], { log: false });
|
|
354
347
|
const hashes = parentsLine.trim().split(/\s+/).filter(Boolean);
|
|
355
348
|
const stashCommit = hashes[0] || '';
|
|
356
349
|
const parent1 = hashes[1] || '';
|
|
@@ -359,14 +352,14 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
359
352
|
// 1) 跟踪文件的变更列表:父1 与 stash 提交的差异(若无父1则为空)
|
|
360
353
|
let trackedFiles = [];
|
|
361
354
|
if (parent1) {
|
|
362
|
-
const { stdout: trackedOut } = await execGitCommand(
|
|
355
|
+
const { stdout: trackedOut } = await execGitCommand(['diff', '--name-only', parent1, stashCommit], { log: false });
|
|
363
356
|
trackedFiles = trackedOut.split('\n').map(s => s.trim()).filter(Boolean);
|
|
364
357
|
}
|
|
365
358
|
|
|
366
359
|
// 2) 未跟踪文件:来自第三父(若存在)
|
|
367
360
|
let untrackedFiles = [];
|
|
368
361
|
if (parent3) {
|
|
369
|
-
const { stdout: untrackedOut } = await execGitCommand(
|
|
362
|
+
const { stdout: untrackedOut } = await execGitCommand(['ls-tree', '-r', '--name-only', parent3], { log: false });
|
|
370
363
|
untrackedFiles = untrackedOut.split('\n').map(s => s.trim()).filter(Boolean);
|
|
371
364
|
}
|
|
372
365
|
|
|
@@ -403,7 +396,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
403
396
|
console.log(`获取stash文件差异: stashId=${stashId}, file=${file}`);
|
|
404
397
|
|
|
405
398
|
// 先解析父提交哈希,避免使用 ^ 语法
|
|
406
|
-
const { stdout: parentsLine } = await execGitCommand(
|
|
399
|
+
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1', stashId], { log: false });
|
|
407
400
|
const hashes = parentsLine.trim().split(/\s+/).filter(Boolean);
|
|
408
401
|
const stashCommit = hashes[0] || '';
|
|
409
402
|
const parent1 = hashes[1] || '';
|
|
@@ -413,7 +406,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
413
406
|
let isFromThirdParent = false;
|
|
414
407
|
if (parent3) {
|
|
415
408
|
try {
|
|
416
|
-
await execGitCommand(
|
|
409
|
+
await execGitCommand(['cat-file', '-e', `${parent3}:${file}`], { log: false });
|
|
417
410
|
isFromThirdParent = true;
|
|
418
411
|
} catch (_) {
|
|
419
412
|
isFromThirdParent = false;
|
|
@@ -422,7 +415,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
422
415
|
|
|
423
416
|
if (isFromThirdParent) {
|
|
424
417
|
// 未跟踪文件:读取第三父中的内容,构造新增文件的统一diff
|
|
425
|
-
const { stdout: blob } = await execGitCommand(
|
|
418
|
+
const { stdout: blob } = await execGitCommand(['show', `${parent3}:${file}`], { log: false });
|
|
426
419
|
|
|
427
420
|
// 检查文件大小
|
|
428
421
|
const sizeCheck = checkDiffSize(blob, 500);
|
|
@@ -457,10 +450,13 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
457
450
|
}
|
|
458
451
|
|
|
459
452
|
// 否则,使用原有方式获取与父1的变更
|
|
460
|
-
|
|
453
|
+
// checkShouldSkipDiff 接受字符串命令用于日志展示,这里只用于大小判断;
|
|
454
|
+
// 走 execGitCommand 时用 argv 数组,避免 Windows 下 cmd.exe 拼引号被破坏。
|
|
455
|
+
const diffCommandForCheck = `git show ${stashCommit} -- "${file}"`;
|
|
456
|
+
const diffCommandArgs = ['show', stashCommit, '--', file];
|
|
461
457
|
|
|
462
458
|
// 使用优化的检查函数
|
|
463
|
-
const skipCheck = await checkShouldSkipDiff(file,
|
|
459
|
+
const skipCheck = await checkShouldSkipDiff(file, diffCommandForCheck);
|
|
464
460
|
if (skipCheck.shouldSkip) {
|
|
465
461
|
return res.json({
|
|
466
462
|
success: true,
|
|
@@ -470,7 +466,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
470
466
|
});
|
|
471
467
|
}
|
|
472
468
|
|
|
473
|
-
const { stdout } = await execGitCommand(
|
|
469
|
+
const { stdout } = await execGitCommand(diffCommandArgs);
|
|
474
470
|
|
|
475
471
|
console.log(`获取到差异内容,长度: ${stdout.length}`);
|
|
476
472
|
|
|
@@ -503,7 +499,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
503
499
|
}
|
|
504
500
|
|
|
505
501
|
// 解析父提交哈希
|
|
506
|
-
const { stdout: parentsLine } = await execGitCommand(
|
|
502
|
+
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1', stashId], { log: false });
|
|
507
503
|
const hashes = parentsLine.trim().split(/\s+/).filter(Boolean);
|
|
508
504
|
const stashCommit = hashes[0] || '';
|
|
509
505
|
const parent1 = hashes[1] || '';
|
|
@@ -513,7 +509,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
513
509
|
let isFromThirdParent = false;
|
|
514
510
|
if (parent3) {
|
|
515
511
|
try {
|
|
516
|
-
await execGitCommand(
|
|
512
|
+
await execGitCommand(['cat-file', '-e', `${parent3}:${file}`], { log: false });
|
|
517
513
|
isFromThirdParent = true;
|
|
518
514
|
} catch (_) {
|
|
519
515
|
isFromThirdParent = false;
|
|
@@ -524,7 +520,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
524
520
|
let original = '';
|
|
525
521
|
if (!isFromThirdParent && parent1) {
|
|
526
522
|
try {
|
|
527
|
-
const { stdout: origOut } = await execGitCommand(
|
|
523
|
+
const { stdout: origOut } = await execGitCommand(['show', `${parent1}:${file}`], { log: false });
|
|
528
524
|
original = origOut ?? '';
|
|
529
525
|
} catch (_) {
|
|
530
526
|
original = ''; // 文件在储藏前不存在
|
|
@@ -536,7 +532,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
536
532
|
const modRef = isFromThirdParent ? parent3 : stashCommit;
|
|
537
533
|
if (modRef) {
|
|
538
534
|
try {
|
|
539
|
-
const { stdout: modOut } = await execGitCommand(
|
|
535
|
+
const { stdout: modOut } = await execGitCommand(['show', `${modRef}:${file}`], { log: false });
|
|
540
536
|
modified = modOut ?? '';
|
|
541
537
|
} catch (_) {
|
|
542
538
|
modified = ''; // 文件已被删除
|
|
@@ -24,25 +24,25 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
24
24
|
return res.status(400).json({ success: false, error: '缺少标签名称' });
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
const tagArgs = ['tag'];
|
|
28
28
|
|
|
29
29
|
if (type === 'annotated') {
|
|
30
30
|
// 附注标签
|
|
31
31
|
if (!message) {
|
|
32
32
|
return res.status(400).json({ success: false, error: '附注标签需要提供说明信息' });
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
tagArgs.push('-a', tagName, '-m', message);
|
|
35
35
|
} else {
|
|
36
36
|
// 轻量标签
|
|
37
|
-
|
|
37
|
+
tagArgs.push(tagName);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// 如果指定了commit,添加到命令中
|
|
41
41
|
if (commit && commit.trim()) {
|
|
42
|
-
|
|
42
|
+
tagArgs.push(commit.trim());
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const { stdout } = await execGitCommand(
|
|
45
|
+
const { stdout } = await execGitCommand(tagArgs);
|
|
46
46
|
|
|
47
47
|
res.json({
|
|
48
48
|
success: true,
|
|
@@ -60,7 +60,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
60
60
|
try {
|
|
61
61
|
// 使用 git tag -n --format 获取详细信息
|
|
62
62
|
const { stdout } = await execGitCommand(
|
|
63
|
-
'
|
|
63
|
+
['tag', '-n', '--format=%(refname:short)|%(objectname:short)|%(creatordate:iso8601)|%(subject)']
|
|
64
64
|
);
|
|
65
65
|
|
|
66
66
|
if (!stdout.trim()) {
|
|
@@ -82,7 +82,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
82
82
|
for (const tag of tags) {
|
|
83
83
|
try {
|
|
84
84
|
const { stdout: typeCheck } = await execGitCommand(
|
|
85
|
-
|
|
85
|
+
['cat-file', '-t', tag.name],
|
|
86
86
|
{ log: false }
|
|
87
87
|
);
|
|
88
88
|
if (typeCheck.trim() === 'tag') {
|
|
@@ -109,7 +109,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
109
109
|
return res.status(400).json({ success: false, error: '缺少标签名称' });
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
const { stdout } = await execGitCommand(
|
|
112
|
+
const { stdout } = await execGitCommand(['push', 'origin', tagName]);
|
|
113
113
|
|
|
114
114
|
res.json({
|
|
115
115
|
success: true,
|
|
@@ -125,7 +125,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
125
125
|
// 推送所有标签到远程
|
|
126
126
|
app.post('/api/push-all-tags', async (req, res) => {
|
|
127
127
|
try {
|
|
128
|
-
const { stdout } = await execGitCommand('
|
|
128
|
+
const { stdout } = await execGitCommand(['push', 'origin', '--tags']);
|
|
129
129
|
|
|
130
130
|
res.json({
|
|
131
131
|
success: true,
|
|
@@ -147,7 +147,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
147
147
|
return res.status(400).json({ success: false, error: '缺少标签名称' });
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
const { stdout } = await execGitCommand(
|
|
150
|
+
const { stdout } = await execGitCommand(['tag', '-d', tagName]);
|
|
151
151
|
|
|
152
152
|
res.json({
|
|
153
153
|
success: true,
|