zen-gitsync 2.16.31 → 2.16.33
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 +31 -0
- package/package.json +2 -2
- package/src/config.js +12 -2
- package/src/ui/public/assets/AgentView-D9y5maA3.js +4 -0
- package/src/ui/public/assets/AppVersionBadge-CsKq0HLG.js +2 -0
- package/src/ui/public/assets/BranchSelector-C09wdMBR.js +1 -0
- package/src/ui/public/assets/CommitForm-wTVAlj8K.js +6 -0
- package/src/ui/public/assets/CommonDialog-gF_3v5QY.js +1 -0
- package/src/ui/public/assets/EditorView-BYaXYY5H.js +1 -0
- package/src/ui/public/assets/FlowExecutionViewer-DvSe3GqC.js +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-FYLZxzMC.js +21 -0
- package/src/ui/public/assets/LogList-BB56Ma_-.js +7 -0
- package/src/ui/public/assets/MindmapView-B57NJ-tK.js +2 -0
- package/src/ui/public/assets/MonitorView-DujOLv6G.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-B0vIxM8Y.js +1 -0
- package/src/ui/public/assets/RemoteRepoCard-Tdhzf-Jb.js +5 -0
- package/src/ui/public/assets/SourceMapView-Bf7__bEW.js +3 -0
- package/src/ui/public/assets/SvgIcon-DV0F5fm7.js +1 -0
- package/src/ui/public/assets/UserInputNode-ZatmWGxI.js +1 -0
- package/src/ui/public/assets/WorkbenchView-DVjvpQMi.js +28 -0
- package/src/ui/public/assets/_plugin-vue_export-helper-DkdPQgM8.js +17 -0
- package/src/ui/public/assets/configStore-C2xoYf_U.js +1 -0
- package/src/ui/public/assets/element-plus-BrZIKMTV.js +47 -0
- package/src/ui/public/assets/{flow-mindmap-C0_IdibG.css → flow-mindmap-DnzM9SlK.css} +1 -1
- package/src/ui/public/assets/flow-mindmap-uF7lcnUF.js +139 -0
- package/src/ui/public/assets/index-DDa4fUq_.js +79 -0
- package/src/ui/public/assets/index-DkMUEB4_.css +1 -0
- package/src/ui/public/assets/office-docx-DZfJaVF6.js +32 -0
- package/src/ui/public/assets/office-excel-BhBLQBXN.js +170 -0
- package/src/ui/public/assets/office-pptx-C0yb5pVU.js +74 -0
- package/src/ui/public/assets/toolsStore-Dpb3rc-o.js +1 -0
- package/src/ui/public/assets/{vendor-Cx8PA1NP.js → vendor-BuJWEuf4.js} +43 -43
- package/src/ui/public/assets/vue-flow-CrEicpfB.js +39 -0
- package/src/ui/public/index.html +12 -12
- package/src/ui/server/index.js +9 -23
- package/src/ui/server/middleware/originGuard.js +138 -0
- package/src/ui/server/middleware/originGuard.test.js +125 -0
- package/src/ui/server/routes/fs.js +83 -3
- package/src/ui/server/routes/git/diff.js +12 -4
- package/src/ui/server/routes/git/stash.js +39 -29
- package/src/ui/server/routes/git/tags.js +23 -16
- package/src/ui/server/routes/git.js +17 -11
- package/src/ui/server/routes/gitOps.js +126 -47
- package/src/ui/server/routes/npm.js +36 -10
- package/src/ui/server/utils/gitArgs.js +165 -0
- package/src/ui/server/utils/gitArgs.test.js +215 -0
- package/src/ui/server/utils/officePreview.js +80 -0
- package/src/ui/server/utils/officePreview.test.js +21 -0
- package/src/ui/server/utils/startServerOnAvailablePort.js +20 -2
- package/src/ui/server/utils/startServerOnAvailablePort.test.js +181 -0
- package/src/ui/public/assets/AgentView-DPan52IZ.js +0 -4
- package/src/ui/public/assets/AppVersionBadge-CiorBQrl.js +0 -2
- package/src/ui/public/assets/BranchSelector-OQWGKJq8.js +0 -1
- package/src/ui/public/assets/CommitForm-Cq1UUNM4.js +0 -6
- package/src/ui/public/assets/CommonDialog-DJqOwkbm.js +0 -1
- package/src/ui/public/assets/EditorView-0HmbFAex.js +0 -1
- package/src/ui/public/assets/FlowExecutionViewer-DjIAKDQt.js +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CCs7sMOO.js +0 -21
- package/src/ui/public/assets/LogList-De9E23D1.js +0 -7
- package/src/ui/public/assets/MindmapView-CpyaWr7b.js +0 -2
- package/src/ui/public/assets/MonitorView-DVwS51YV.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-DiaZ8xiI.js +0 -1
- package/src/ui/public/assets/RemoteRepoCard-B299cQhO.js +0 -5
- package/src/ui/public/assets/SourceMapView-C7vO_5Hz.js +0 -3
- package/src/ui/public/assets/SvgIcon-B9XhBoIQ.js +0 -1
- package/src/ui/public/assets/UserInputNode-CeNehn29.js +0 -1
- package/src/ui/public/assets/WorkbenchView-Be4GcjdO.js +0 -28
- package/src/ui/public/assets/_plugin-vue_export-helper-aENhMEEX.js +0 -17
- package/src/ui/public/assets/configStore-BvJZNumP.js +0 -1
- package/src/ui/public/assets/element-plus-B3pTS_5R.js +0 -32
- package/src/ui/public/assets/flow-mindmap-hUZCS6o1.js +0 -138
- package/src/ui/public/assets/index-CuD0oK0z.js +0 -79
- package/src/ui/public/assets/index-zmReBTKj.css +0 -1
- package/src/ui/public/assets/toolsStore-D9gXnqwO.js +0 -1
- package/src/ui/public/assets/vue-flow-COROsN-V.js +0 -39
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { createDiffHelpers } from './diffUtils.js';
|
|
16
16
|
import logger from '../../utils/logger.js'
|
|
17
17
|
import { asyncRoute, HttpError } from '../../utils/asyncRoute.js';
|
|
18
|
+
import { assertGitRef } from '../../utils/gitArgs.js';
|
|
18
19
|
|
|
19
20
|
export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
20
21
|
const { checkShouldSkipDiff, checkDiffSize, getDiffStats } = createDiffHelpers({ execGitCommand });
|
|
@@ -260,8 +261,11 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
260
261
|
});
|
|
261
262
|
}
|
|
262
263
|
|
|
264
|
+
// stashId 形如 stash@{0}(不含黑名单字符,能通过 ref 校验);
|
|
265
|
+
// 以 - 开头的输入会被 git 当选项,必须挡掉
|
|
266
|
+
const safeStashId = assertGitRef(stashId, 'stash ID');
|
|
263
267
|
// 决定是使用apply(保留stash)还是pop(应用后删除stash)
|
|
264
|
-
const command = pop ? ['stash', 'pop',
|
|
268
|
+
const command = pop ? ['stash', 'pop', safeStashId] : ['stash', 'apply', safeStashId];
|
|
265
269
|
|
|
266
270
|
try {
|
|
267
271
|
const { stdout } = await execGitCommand(command);
|
|
@@ -285,7 +289,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
285
289
|
}
|
|
286
290
|
} catch (error) {
|
|
287
291
|
logger.error('应用stash失败:', error);
|
|
288
|
-
res.status(500).json({ success: false, error: error.message });
|
|
292
|
+
res.status(error?.statusCode || 500).json({ success: false, error: error.message });
|
|
289
293
|
}
|
|
290
294
|
}));
|
|
291
295
|
|
|
@@ -301,7 +305,8 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
301
305
|
});
|
|
302
306
|
}
|
|
303
307
|
|
|
304
|
-
const
|
|
308
|
+
const safeStashId = assertGitRef(stashId, 'stash ID');
|
|
309
|
+
const { stdout } = await execGitCommand(['stash', 'drop', safeStashId]);
|
|
305
310
|
|
|
306
311
|
res.json({
|
|
307
312
|
success: true,
|
|
@@ -310,7 +315,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
310
315
|
});
|
|
311
316
|
} catch (error) {
|
|
312
317
|
logger.error('删除stash失败:', error);
|
|
313
|
-
res.status(500).json({ success: false, error: error.message });
|
|
318
|
+
res.status(error?.statusCode || 500).json({ success: false, error: error.message });
|
|
314
319
|
}
|
|
315
320
|
}));
|
|
316
321
|
|
|
@@ -341,11 +346,12 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
341
346
|
error: '缺少stash ID参数'
|
|
342
347
|
});
|
|
343
348
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
349
|
+
const safeStashId = assertGitRef(stashId, 'stash ID');
|
|
350
|
+
|
|
351
|
+
logger.info(`获取stash文件列表: stashId=${safeStashId}`);
|
|
352
|
+
|
|
347
353
|
// 0) 解析出当前 stash 提交及其父提交哈希,避免在 Windows 上使用 ^ 语法
|
|
348
|
-
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1',
|
|
354
|
+
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1', safeStashId], { log: false });
|
|
349
355
|
const hashes = parentsLine.trim().split(/\s+/).filter(Boolean);
|
|
350
356
|
const stashCommit = hashes[0] || '';
|
|
351
357
|
const parent1 = hashes[1] || '';
|
|
@@ -376,7 +382,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
376
382
|
});
|
|
377
383
|
} catch (error) {
|
|
378
384
|
logger.error('获取stash文件列表失败:', error);
|
|
379
|
-
res.status(500).json({
|
|
385
|
+
res.status(error?.statusCode || 500).json({
|
|
380
386
|
success: false,
|
|
381
387
|
error: `获取stash文件列表失败: ${error.message}`
|
|
382
388
|
});
|
|
@@ -394,11 +400,13 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
394
400
|
error: '缺少必要参数'
|
|
395
401
|
});
|
|
396
402
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
403
|
+
const safeStashId = assertGitRef(stashId, 'stash ID');
|
|
404
|
+
const safeFile = assertGitPath(file, '文件路径');
|
|
405
|
+
|
|
406
|
+
logger.info(`获取stash文件差异: stashId=${safeStashId}, file=${safeFile}`);
|
|
407
|
+
|
|
400
408
|
// 先解析父提交哈希,避免使用 ^ 语法
|
|
401
|
-
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1',
|
|
409
|
+
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1', safeStashId], { log: false });
|
|
402
410
|
const hashes = parentsLine.trim().split(/\s+/).filter(Boolean);
|
|
403
411
|
const stashCommit = hashes[0] || '';
|
|
404
412
|
const parent1 = hashes[1] || '';
|
|
@@ -408,7 +416,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
408
416
|
let isFromThirdParent = false;
|
|
409
417
|
if (parent3) {
|
|
410
418
|
try {
|
|
411
|
-
await execGitCommand(['cat-file', '-e', `${parent3}:${
|
|
419
|
+
await execGitCommand(['cat-file', '-e', `${parent3}:${safeFile}`], { log: false });
|
|
412
420
|
isFromThirdParent = true;
|
|
413
421
|
} catch (_) {
|
|
414
422
|
isFromThirdParent = false;
|
|
@@ -417,7 +425,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
417
425
|
|
|
418
426
|
if (isFromThirdParent) {
|
|
419
427
|
// 未跟踪文件:读取第三父中的内容,构造新增文件的统一diff
|
|
420
|
-
const { stdout: blob } = await execGitCommand(['show', `${parent3}:${
|
|
428
|
+
const { stdout: blob } = await execGitCommand(['show', `${parent3}:${safeFile}`], { log: false });
|
|
421
429
|
|
|
422
430
|
// 检查文件大小
|
|
423
431
|
const sizeCheck = checkDiffSize(blob, 500);
|
|
@@ -440,10 +448,10 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
440
448
|
|
|
441
449
|
const plusLines = lines.map(l => `+${l}`).join('\n');
|
|
442
450
|
const diffText = [
|
|
443
|
-
`diff --git a/${
|
|
451
|
+
`diff --git a/${safeFile} b/${safeFile}`,
|
|
444
452
|
`new file mode 100644`,
|
|
445
453
|
`--- /dev/null`,
|
|
446
|
-
`+++ b/${
|
|
454
|
+
`+++ b/${safeFile}`,
|
|
447
455
|
`@@ -0,0 +${lineCount} @@`,
|
|
448
456
|
`${plusLines}`
|
|
449
457
|
].join('\n');
|
|
@@ -454,11 +462,11 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
454
462
|
// 否则,使用原有方式获取与父1的变更
|
|
455
463
|
// checkShouldSkipDiff 接受字符串命令用于日志展示,这里只用于大小判断;
|
|
456
464
|
// 走 execGitCommand 时用 argv 数组,避免 Windows 下 cmd.exe 拼引号被破坏。
|
|
457
|
-
const diffCommandForCheck = `git show ${stashCommit} -- "${
|
|
458
|
-
const diffCommandArgs = ['show', stashCommit, '--',
|
|
459
|
-
|
|
465
|
+
const diffCommandForCheck = `git show ${stashCommit} -- "${safeFile}"`;
|
|
466
|
+
const diffCommandArgs = ['show', stashCommit, '--', safeFile];
|
|
467
|
+
|
|
460
468
|
// 使用优化的检查函数
|
|
461
|
-
const skipCheck = await checkShouldSkipDiff(
|
|
469
|
+
const skipCheck = await checkShouldSkipDiff(safeFile, diffCommandForCheck);
|
|
462
470
|
if (skipCheck.shouldSkip) {
|
|
463
471
|
return res.json({
|
|
464
472
|
success: true,
|
|
@@ -484,7 +492,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
484
492
|
res.json({ success: true, diff: stdout, stats });
|
|
485
493
|
} catch (error) {
|
|
486
494
|
logger.error('获取stash文件差异失败:', error);
|
|
487
|
-
res.status(500).json({
|
|
495
|
+
res.status(error?.statusCode || 500).json({
|
|
488
496
|
success: false,
|
|
489
497
|
error: `获取stash文件差异失败: ${error.message}`
|
|
490
498
|
});
|
|
@@ -495,13 +503,15 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
495
503
|
app.get('/api/stash-file-compare', asyncRoute(async (req, res) => {
|
|
496
504
|
try {
|
|
497
505
|
const { stashId, file } = req.query;
|
|
498
|
-
|
|
506
|
+
|
|
499
507
|
if (!stashId || !file) {
|
|
500
508
|
throw new HttpError(400, '缺少必要参数');
|
|
501
509
|
}
|
|
502
|
-
|
|
510
|
+
const safeStashId = assertGitRef(stashId, 'stash ID');
|
|
511
|
+
const safeFile = assertGitPath(file, '文件路径');
|
|
512
|
+
|
|
503
513
|
// 解析父提交哈希
|
|
504
|
-
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1',
|
|
514
|
+
const { stdout: parentsLine } = await execGitCommand(['rev-list', '--parents', '-n', '1', safeStashId], { log: false });
|
|
505
515
|
const hashes = parentsLine.trim().split(/\s+/).filter(Boolean);
|
|
506
516
|
const stashCommit = hashes[0] || '';
|
|
507
517
|
const parent1 = hashes[1] || '';
|
|
@@ -511,7 +521,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
511
521
|
let isFromThirdParent = false;
|
|
512
522
|
if (parent3) {
|
|
513
523
|
try {
|
|
514
|
-
await execGitCommand(['cat-file', '-e', `${parent3}:${
|
|
524
|
+
await execGitCommand(['cat-file', '-e', `${parent3}:${safeFile}`], { log: false });
|
|
515
525
|
isFromThirdParent = true;
|
|
516
526
|
} catch (_) {
|
|
517
527
|
isFromThirdParent = false;
|
|
@@ -522,7 +532,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
522
532
|
let original = '';
|
|
523
533
|
if (!isFromThirdParent && parent1) {
|
|
524
534
|
try {
|
|
525
|
-
const { stdout: origOut } = await execGitCommand(['show', `${parent1}:${
|
|
535
|
+
const { stdout: origOut } = await execGitCommand(['show', `${parent1}:${safeFile}`], { log: false });
|
|
526
536
|
original = origOut ?? '';
|
|
527
537
|
} catch (_) {
|
|
528
538
|
original = ''; // 文件在储藏前不存在
|
|
@@ -534,7 +544,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
534
544
|
const modRef = isFromThirdParent ? parent3 : stashCommit;
|
|
535
545
|
if (modRef) {
|
|
536
546
|
try {
|
|
537
|
-
const { stdout: modOut } = await execGitCommand(['show', `${modRef}:${
|
|
547
|
+
const { stdout: modOut } = await execGitCommand(['show', `${modRef}:${safeFile}`], { log: false });
|
|
538
548
|
modified = modOut ?? '';
|
|
539
549
|
} catch (_) {
|
|
540
550
|
modified = ''; // 文件已被删除
|
|
@@ -544,7 +554,7 @@ export function registerGitStashRoutes({ app, execGitCommand, configManager }) {
|
|
|
544
554
|
res.json({ success: true, original, modified });
|
|
545
555
|
} catch (error) {
|
|
546
556
|
logger.error('获取stash文件对比失败:', error);
|
|
547
|
-
res.status(500).json({ success: false, error: `获取stash文件对比失败: ${error.message}` });
|
|
557
|
+
res.status(error?.statusCode || 500).json({ success: false, error: `获取stash文件对比失败: ${error.message}` });
|
|
548
558
|
}
|
|
549
559
|
}));
|
|
550
560
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
//
|
|
15
15
|
import { asyncRoute, HttpError } from '../../utils/asyncRoute.js'
|
|
16
|
+
import { assertGitRef, assertGitHash } from '../../utils/gitArgs.js'
|
|
16
17
|
|
|
17
18
|
import logger from '../../utils/logger.js'
|
|
18
19
|
|
|
@@ -27,27 +28,29 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
27
28
|
if (!tagName) {
|
|
28
29
|
throw new HttpError(400, '缺少标签名称');
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
// tagName 与 commit 都落在 git 的参数位上,以 - 开头就会被当选项
|
|
32
|
+
const safeTagName = assertGitRef(tagName, '标签名');
|
|
33
|
+
|
|
31
34
|
const tagArgs = ['tag'];
|
|
32
|
-
|
|
35
|
+
|
|
33
36
|
if (type === 'annotated') {
|
|
34
37
|
// 附注标签
|
|
35
38
|
if (!message) {
|
|
36
39
|
throw new HttpError(400, '附注标签需要提供说明信息');
|
|
37
40
|
}
|
|
38
|
-
tagArgs.push('-a',
|
|
41
|
+
tagArgs.push('-a', safeTagName, '-m', message);
|
|
39
42
|
} else {
|
|
40
43
|
// 轻量标签
|
|
41
|
-
tagArgs.push(
|
|
44
|
+
tagArgs.push(safeTagName);
|
|
42
45
|
}
|
|
43
|
-
|
|
46
|
+
|
|
44
47
|
// 如果指定了commit,添加到命令中
|
|
45
|
-
if (commit && commit.trim()) {
|
|
46
|
-
tagArgs.push(commit
|
|
48
|
+
if (commit && String(commit).trim()) {
|
|
49
|
+
tagArgs.push(assertGitHash(commit));
|
|
47
50
|
}
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
const { stdout } = await execGitCommand(tagArgs);
|
|
50
|
-
|
|
53
|
+
|
|
51
54
|
res.json({
|
|
52
55
|
success: true,
|
|
53
56
|
message: '标签创建成功',
|
|
@@ -55,7 +58,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
55
58
|
});
|
|
56
59
|
} catch (error) {
|
|
57
60
|
logger.error('创建标签失败:', error);
|
|
58
|
-
res.status(500).json({ success: false, error: error.message });
|
|
61
|
+
res.status(error?.statusCode || 500).json({ success: false, error: error.message });
|
|
59
62
|
}
|
|
60
63
|
}));
|
|
61
64
|
|
|
@@ -113,8 +116,11 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
113
116
|
throw new HttpError(400, '缺少标签名称');
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
// tagName 落在 git 的参数位上,不校验的话传 '--upload-pack=curl x|sh'
|
|
120
|
+
// 就能让 git 执行命令(execFile 无 shell 也拦不住)。
|
|
121
|
+
const safeTagName = assertGitRef(tagName, '标签名');
|
|
122
|
+
const { stdout } = await execGitCommand(['push', 'origin', safeTagName]);
|
|
123
|
+
|
|
118
124
|
res.json({
|
|
119
125
|
success: true,
|
|
120
126
|
message: '标签推送成功',
|
|
@@ -122,7 +128,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
122
128
|
});
|
|
123
129
|
} catch (error) {
|
|
124
130
|
logger.error('推送标签失败:', error);
|
|
125
|
-
res.status(500).json({ success: false, error: error.message });
|
|
131
|
+
res.status(error?.statusCode || 500).json({ success: false, error: error.message });
|
|
126
132
|
}
|
|
127
133
|
}));
|
|
128
134
|
|
|
@@ -151,8 +157,9 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
151
157
|
throw new HttpError(400, '缺少标签名称');
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
const
|
|
155
|
-
|
|
160
|
+
const safeTagName = assertGitRef(tagName, '标签名');
|
|
161
|
+
const { stdout } = await execGitCommand(['tag', '-d', safeTagName]);
|
|
162
|
+
|
|
156
163
|
res.json({
|
|
157
164
|
success: true,
|
|
158
165
|
message: '标签删除成功',
|
|
@@ -160,7 +167,7 @@ export function registerGitTagRoutes({ app, execGitCommand, clearCommandHistory
|
|
|
160
167
|
});
|
|
161
168
|
} catch (error) {
|
|
162
169
|
logger.error('删除标签失败:', error);
|
|
163
|
-
res.status(500).json({ success: false, error: error.message });
|
|
170
|
+
res.status(error?.statusCode || 500).json({ success: false, error: error.message });
|
|
164
171
|
}
|
|
165
172
|
}));
|
|
166
173
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import express from 'express';
|
|
16
16
|
import logger from '../utils/logger.js'
|
|
17
17
|
import { asyncRoute, HttpError } from '../utils/asyncRoute.js';
|
|
18
|
+
import { assertGitRef } from '../utils/gitArgs.js';
|
|
18
19
|
|
|
19
20
|
export function registerGitRoutes({
|
|
20
21
|
app,
|
|
@@ -63,28 +64,30 @@ export function registerGitRoutes({
|
|
|
63
64
|
if (!newBranchName) {
|
|
64
65
|
throw new HttpError(400, '分支名称不能为空');
|
|
65
66
|
}
|
|
67
|
+
// 分支名落在 git 的参数位上,不校验的话 --upload-pack=... 这类能被当选项解析
|
|
68
|
+
const safeBranchName = assertGitRef(newBranchName, '分支名');
|
|
66
69
|
|
|
67
70
|
// 构建创建分支的命令
|
|
68
|
-
let commandArgs = ['branch',
|
|
71
|
+
let commandArgs = ['branch', safeBranchName];
|
|
69
72
|
|
|
70
73
|
// 如果指定了基础分支,则基于该分支创建
|
|
71
74
|
if (baseBranch) {
|
|
72
|
-
commandArgs = ['branch',
|
|
75
|
+
commandArgs = ['branch', safeBranchName, assertGitRef(baseBranch, '基础分支名')];
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
// 执行创建分支命令
|
|
76
79
|
await execGitCommand(commandArgs);
|
|
77
80
|
|
|
78
81
|
// 切换到新创建的分支
|
|
79
|
-
await execGitCommand(['checkout',
|
|
82
|
+
await execGitCommand(['checkout', safeBranchName]);
|
|
80
83
|
|
|
81
84
|
// 清除分支缓存,因为分支已切换
|
|
82
85
|
clearBranchCache();
|
|
83
86
|
|
|
84
|
-
res.json({ success: true, branch:
|
|
87
|
+
res.json({ success: true, branch: safeBranchName });
|
|
85
88
|
} catch (error) {
|
|
86
89
|
logger.error('创建分支失败:', error);
|
|
87
|
-
res.status(500).json({ success: false, error: error.message });
|
|
90
|
+
res.status(error?.statusCode || 500).json({ success: false, error: error.message });
|
|
88
91
|
}
|
|
89
92
|
});
|
|
90
93
|
|
|
@@ -95,20 +98,23 @@ export function registerGitRoutes({
|
|
|
95
98
|
if (!branch) {
|
|
96
99
|
throw new HttpError(400, '分支名称不能为空');
|
|
97
100
|
}
|
|
101
|
+
// 下面的 refs/remotes/<branch> 是拼字符串(有前缀,不以 - 开头,安全),
|
|
102
|
+
// 但 checkout 的参数位必须校验
|
|
103
|
+
const safeBranch = assertGitRef(branch, '分支名');
|
|
98
104
|
|
|
99
|
-
let finalBranch =
|
|
105
|
+
let finalBranch = safeBranch;
|
|
100
106
|
|
|
101
107
|
// 远程分支(如 origin/xxx):直接 checkout 远程引用会进入 detached HEAD,
|
|
102
108
|
// 需要先解析出同名本地分支——已存在则直接切换,不存在则基于远程分支创建并跟踪。
|
|
103
|
-
if (
|
|
109
|
+
if (safeBranch.includes('/')) {
|
|
104
110
|
const { stdout: remoteRef } = await execGitCommand(
|
|
105
|
-
['rev-parse', '--verify', '--quiet', `refs/remotes/${
|
|
111
|
+
['rev-parse', '--verify', '--quiet', `refs/remotes/${safeBranch}`],
|
|
106
112
|
{ ignoreError: true, log: false }
|
|
107
113
|
);
|
|
108
114
|
|
|
109
115
|
if (remoteRef.trim()) {
|
|
110
116
|
// 去掉第一个路径段(远程名),得到本地分支名,支持 origin/feature/x 这类多级名称
|
|
111
|
-
const localName =
|
|
117
|
+
const localName = safeBranch.substring(safeBranch.indexOf('/') + 1);
|
|
112
118
|
const { stdout: localRef } = await execGitCommand(
|
|
113
119
|
['rev-parse', '--verify', '--quiet', `refs/heads/${localName}`],
|
|
114
120
|
{ ignoreError: true, log: false }
|
|
@@ -119,7 +125,7 @@ export function registerGitRoutes({
|
|
|
119
125
|
finalBranch = localName;
|
|
120
126
|
} else {
|
|
121
127
|
// 本地没有:创建同名本地分支并跟踪该远程分支
|
|
122
|
-
await execGitCommand(['checkout', '--track',
|
|
128
|
+
await execGitCommand(['checkout', '--track', safeBranch]);
|
|
123
129
|
clearBranchCache();
|
|
124
130
|
res.json({ success: true, branch: localName, created: true });
|
|
125
131
|
return;
|
|
@@ -136,7 +142,7 @@ export function registerGitRoutes({
|
|
|
136
142
|
res.json({ success: true, branch: finalBranch });
|
|
137
143
|
} catch (error) {
|
|
138
144
|
logger.error('切换分支失败:', error);
|
|
139
|
-
res.status(500).json({ success: false, error: error.message });
|
|
145
|
+
res.status(error?.statusCode || 500).json({ success: false, error: error.message });
|
|
140
146
|
}
|
|
141
147
|
}));
|
|
142
148
|
|