zen-gitsync 2.0.6 → 2.0.8
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/ui/client/components.d.ts +1 -0
- package/src/ui/client/src/components/CommitForm.vue +1032 -178
- package/src/ui/client/src/components/GitStatus.vue +572 -135
- package/src/ui/client/src/components/LogList.vue +907 -223
- package/src/ui/client/src/stores/gitLogStore.ts +25 -5
- package/src/ui/client/src/stores/gitStore.ts +129 -1
- package/src/ui/client/stats.html +1 -1
- package/src/ui/client/vite.config.ts +2 -0
- package/src/ui/public/assets/index-C3BbS3PG.css +1 -0
- package/src/ui/public/assets/index-P9BcPWc5.js +20 -0
- package/src/ui/public/assets/vendor-eqaTZKOh.js +45 -0
- package/src/ui/public/index.html +3 -3
- package/src/ui/server/index.js +473 -50
- package/src/ui/public/assets/index-DaPynzAr.js +0 -10
- package/src/ui/public/assets/index-qfIezZmd.css +0 -1
- package/src/ui/public/assets/vendor-BcSuWc8z.js +0 -45
package/src/ui/public/index.html
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Zen-GitSync - Git同步工具</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-P9BcPWc5.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-eqaTZKOh.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/vendor-Dp0FkvMe.css">
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C3BbS3PG.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"></div>
|
package/src/ui/server/index.js
CHANGED
|
@@ -69,6 +69,74 @@ async function startUIServer() {
|
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
// 获取分支与远程的差异状态(领先/落后提交数)
|
|
73
|
+
app.get('/api/branch-status', async (req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
// 先获取当前分支名
|
|
76
|
+
const { stdout: branchName } = await execGitCommand('git rev-parse --abbrev-ref HEAD');
|
|
77
|
+
const currentBranch = branchName.trim();
|
|
78
|
+
|
|
79
|
+
// 获取远程仓库名称
|
|
80
|
+
const { stdout: remoteName } = await execGitCommand('git remote');
|
|
81
|
+
const remote = remoteName.trim() || 'origin';
|
|
82
|
+
|
|
83
|
+
// 尝试获取当前分支的上游分支
|
|
84
|
+
let upstreamBranch = '';
|
|
85
|
+
let hasUpstream = false;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const { stdout: upstream } = await execGitCommand(`git rev-parse --abbrev-ref ${currentBranch}@{upstream}`);
|
|
89
|
+
upstreamBranch = upstream.trim();
|
|
90
|
+
hasUpstream = true;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// 没有上游分支设置,这是正常的
|
|
93
|
+
console.log(`分支 ${currentBranch} 没有设置上游分支`);
|
|
94
|
+
hasUpstream = false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 如果分支没有上游设置,直接返回结果
|
|
98
|
+
if (!hasUpstream) {
|
|
99
|
+
return res.json({
|
|
100
|
+
branch: currentBranch,
|
|
101
|
+
remote,
|
|
102
|
+
hasUpstream: false,
|
|
103
|
+
ahead: 0,
|
|
104
|
+
behind: 0
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 获取领先和落后的提交数
|
|
109
|
+
const { stdout: statusCompare } = await execGitCommand(`git rev-list --count --left-right ${currentBranch}...${upstreamBranch}`);
|
|
110
|
+
|
|
111
|
+
// 解析结果,格式通常是 "1 2" 表示领先1个提交,落后2个提交
|
|
112
|
+
let ahead = 0;
|
|
113
|
+
let behind = 0;
|
|
114
|
+
|
|
115
|
+
if (statusCompare && statusCompare.trim()) {
|
|
116
|
+
const parts = statusCompare.trim().split(/\s+/);
|
|
117
|
+
if (parts.length >= 2) {
|
|
118
|
+
ahead = parseInt(parts[0]) || 0;
|
|
119
|
+
behind = parseInt(parts[1]) || 0;
|
|
120
|
+
} else if (parts.length === 1) {
|
|
121
|
+
// 只有一个数字的情况,通常是领先的提交数
|
|
122
|
+
ahead = parseInt(parts[0]) || 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
res.json({
|
|
127
|
+
branch: currentBranch,
|
|
128
|
+
remote,
|
|
129
|
+
upstreamBranch,
|
|
130
|
+
hasUpstream: true,
|
|
131
|
+
ahead,
|
|
132
|
+
behind
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("获取分支状态失败:", error);
|
|
136
|
+
res.status(500).json({ error: error.message });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
72
140
|
// 获取所有分支
|
|
73
141
|
app.get('/api/branches', async (req, res) => {
|
|
74
142
|
try {
|
|
@@ -301,7 +369,28 @@ async function startUIServer() {
|
|
|
301
369
|
}
|
|
302
370
|
})
|
|
303
371
|
|
|
304
|
-
//
|
|
372
|
+
// 保存默认提交信息
|
|
373
|
+
app.post('/api/config/saveDefaultMessage', express.json(), async (req, res) => {
|
|
374
|
+
try {
|
|
375
|
+
const { defaultCommitMessage } = req.body
|
|
376
|
+
|
|
377
|
+
if (!defaultCommitMessage) {
|
|
378
|
+
return res.status(400).json({ success: false, error: '缺少必要参数' })
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const config = await configManager.loadConfig()
|
|
382
|
+
|
|
383
|
+
// 更新默认提交信息
|
|
384
|
+
config.defaultCommitMessage = defaultCommitMessage
|
|
385
|
+
await configManager.saveConfig(config)
|
|
386
|
+
|
|
387
|
+
res.json({ success: true })
|
|
388
|
+
} catch (error) {
|
|
389
|
+
res.status(500).json({ success: false, error: error.message })
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
// 保存模板
|
|
305
394
|
app.post('/api/config/save-template', express.json(), async (req, res) => {
|
|
306
395
|
try {
|
|
307
396
|
const { template, type } = req.body
|
|
@@ -334,6 +423,17 @@ async function startUIServer() {
|
|
|
334
423
|
config.scopeTemplates.push(template)
|
|
335
424
|
await configManager.saveConfig(config)
|
|
336
425
|
}
|
|
426
|
+
} else if (type === 'message') {
|
|
427
|
+
// 确保提交信息模板数组存在
|
|
428
|
+
if (!config.messageTemplates) {
|
|
429
|
+
config.messageTemplates = []
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 检查是否已存在相同模板
|
|
433
|
+
if (!config.messageTemplates.includes(template)) {
|
|
434
|
+
config.messageTemplates.push(template)
|
|
435
|
+
await configManager.saveConfig(config)
|
|
436
|
+
}
|
|
337
437
|
} else {
|
|
338
438
|
return res.status(400).json({ success: false, error: '不支持的模板类型' })
|
|
339
439
|
}
|
|
@@ -344,7 +444,7 @@ async function startUIServer() {
|
|
|
344
444
|
}
|
|
345
445
|
})
|
|
346
446
|
|
|
347
|
-
//
|
|
447
|
+
// 删除模板
|
|
348
448
|
app.post('/api/config/delete-template', express.json(), async (req, res) => {
|
|
349
449
|
try {
|
|
350
450
|
const { template, type } = req.body
|
|
@@ -373,6 +473,15 @@ async function startUIServer() {
|
|
|
373
473
|
await configManager.saveConfig(config)
|
|
374
474
|
}
|
|
375
475
|
}
|
|
476
|
+
} else if (type === 'message') {
|
|
477
|
+
// 确保提交信息模板数组存在
|
|
478
|
+
if (config.messageTemplates) {
|
|
479
|
+
const index = config.messageTemplates.indexOf(template)
|
|
480
|
+
if (index !== -1) {
|
|
481
|
+
config.messageTemplates.splice(index, 1)
|
|
482
|
+
await configManager.saveConfig(config)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
376
485
|
} else {
|
|
377
486
|
return res.status(400).json({ success: false, error: '不支持的模板类型' })
|
|
378
487
|
}
|
|
@@ -420,6 +529,19 @@ async function startUIServer() {
|
|
|
420
529
|
} else {
|
|
421
530
|
return res.status(404).json({ success: false, error: '模板列表不存在' })
|
|
422
531
|
}
|
|
532
|
+
} else if (type === 'message') {
|
|
533
|
+
// 确保提交信息模板数组存在
|
|
534
|
+
if (config.messageTemplates) {
|
|
535
|
+
const index = config.messageTemplates.indexOf(oldTemplate)
|
|
536
|
+
if (index !== -1) {
|
|
537
|
+
config.messageTemplates[index] = newTemplate
|
|
538
|
+
await configManager.saveConfig(config)
|
|
539
|
+
} else {
|
|
540
|
+
return res.status(404).json({ success: false, error: '未找到原模板' })
|
|
541
|
+
}
|
|
542
|
+
} else {
|
|
543
|
+
return res.status(404).json({ success: false, error: '模板列表不存在' })
|
|
544
|
+
}
|
|
423
545
|
} else {
|
|
424
546
|
return res.status(400).json({ success: false, error: '不支持的模板类型' })
|
|
425
547
|
}
|
|
@@ -524,73 +646,262 @@ async function startUIServer() {
|
|
|
524
646
|
// 推送更改
|
|
525
647
|
app.post('/api/push', async (req, res) => {
|
|
526
648
|
try {
|
|
527
|
-
await execGitCommand('git push');
|
|
528
|
-
res.json({ success: true });
|
|
649
|
+
const { stdout } = await execGitCommand('git push');
|
|
650
|
+
res.json({ success: true, message: stdout });
|
|
529
651
|
} catch (error) {
|
|
530
|
-
res.status(500).json({ error: error.message });
|
|
652
|
+
res.status(500).json({ success: false, error: error.message });
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// 添加git pull API端点
|
|
657
|
+
app.post('/api/pull', async (req, res) => {
|
|
658
|
+
try {
|
|
659
|
+
const { stdout } = await execGitCommand('git pull');
|
|
660
|
+
res.json({ success: true, message: stdout });
|
|
661
|
+
} catch (error) {
|
|
662
|
+
res.status(500).json({ success: false, error: error.message });
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// 添加git fetch --all API端点
|
|
667
|
+
app.post('/api/fetch-all', async (req, res) => {
|
|
668
|
+
try {
|
|
669
|
+
const { stdout } = await execGitCommand('git fetch --all');
|
|
670
|
+
res.json({ success: true, message: stdout });
|
|
671
|
+
} catch (error) {
|
|
672
|
+
res.status(500).json({ success: false, error: error.message });
|
|
531
673
|
}
|
|
532
674
|
});
|
|
533
675
|
|
|
534
676
|
// 获取日志
|
|
535
677
|
app.get('/api/log', async (req, res) => {
|
|
536
678
|
try {
|
|
537
|
-
//
|
|
538
|
-
const
|
|
679
|
+
// 获取分页参数
|
|
680
|
+
const page = parseInt(req.query.page) || 1;
|
|
681
|
+
const limit = parseInt(req.query.limit) || 100;
|
|
682
|
+
const skip = (page - 1) * limit;
|
|
683
|
+
|
|
684
|
+
// 获取筛选参数
|
|
685
|
+
const author = req.query.author ? req.query.author.split(',') : [];
|
|
686
|
+
const message = req.query.message || '';
|
|
687
|
+
const dateFrom = req.query.dateFrom || '';
|
|
688
|
+
const dateTo = req.query.dateTo || '';
|
|
689
|
+
const branch = req.query.branch ? req.query.branch.split(',') : [];
|
|
690
|
+
|
|
691
|
+
// 构建Git命令选项
|
|
692
|
+
let commandOptions = [];
|
|
693
|
+
|
|
694
|
+
// 修改分支筛选处理 - 使用正确的引用格式
|
|
695
|
+
if (branch.length > 0) {
|
|
696
|
+
// 不再简单拼接分支名,而是将它们作为引用路径处理
|
|
697
|
+
// 如果指定了分支,不再使用--all参数,而是直接用分支名
|
|
698
|
+
commandOptions = commandOptions.filter(opt => opt !== '--all');
|
|
699
|
+
|
|
700
|
+
// 将分支名格式化为Git可理解的引用格式
|
|
701
|
+
const branchRefs = branch.map(b => b.trim()).join(' ');
|
|
702
|
+
|
|
703
|
+
// 直接将分支名作为命令参数,并确保后面添加 -- 分隔符防止歧义
|
|
704
|
+
return executeGitLogCommand(res, branchRefs, author, message, dateFrom, dateTo, limit, skip, req.query.all === 'true');
|
|
705
|
+
}
|
|
539
706
|
|
|
540
|
-
//
|
|
541
|
-
//
|
|
707
|
+
// 如果没有指定分支,则使用--all参数
|
|
708
|
+
// 作者筛选(支持多作者,使用正则表达式OR操作)
|
|
709
|
+
if (author.length > 0) {
|
|
710
|
+
// 过滤掉空作者
|
|
711
|
+
const validAuthors = author.filter(a => a.trim() !== '');
|
|
712
|
+
|
|
713
|
+
if (validAuthors.length === 1) {
|
|
714
|
+
// 单个作者,直接使用--author
|
|
715
|
+
commandOptions.push(`--author="${validAuthors[0].trim()}"`);
|
|
716
|
+
} else if (validAuthors.length > 1) {
|
|
717
|
+
// 多个作者,使用正则表达式OR条件
|
|
718
|
+
// 只转义OR运算符,保持其他内容不变
|
|
719
|
+
const authorPattern = validAuthors
|
|
720
|
+
.map(a => a.trim())
|
|
721
|
+
.join('\\|'); // 在JavaScript字符串中\\|会变成\|,在shell中会成为|
|
|
722
|
+
|
|
723
|
+
commandOptions.push(`--author="${authorPattern}"`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
542
726
|
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
727
|
+
// 日期范围筛选
|
|
728
|
+
if (dateFrom && dateTo) {
|
|
729
|
+
commandOptions.push(`--after="${dateFrom}" --before="${dateTo} 23:59:59"`);
|
|
730
|
+
} else if (dateFrom) {
|
|
731
|
+
commandOptions.push(`--after="${dateFrom}"`);
|
|
732
|
+
} else if (dateTo) {
|
|
733
|
+
commandOptions.push(`--before="${dateTo} 23:59:59"`);
|
|
734
|
+
}
|
|
547
735
|
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
736
|
+
// 提交信息筛选
|
|
737
|
+
if (message) {
|
|
738
|
+
commandOptions.push(`--grep="${message}"`);
|
|
739
|
+
}
|
|
551
740
|
|
|
552
|
-
//
|
|
553
|
-
|
|
741
|
+
// 如果all=true,则不使用限制,否则按页码和limit精确获取
|
|
742
|
+
// 修复:只获取当前页的数据,而不是累计所有之前页的数据
|
|
743
|
+
const limitOption = req.query.all === 'true' ? '' : `-n ${limit} --skip=${skip}`;
|
|
554
744
|
|
|
555
|
-
//
|
|
556
|
-
const
|
|
745
|
+
// 合并所有命令选项
|
|
746
|
+
const options = [...commandOptions, limitOption].filter(Boolean).join(' ');
|
|
557
747
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
748
|
+
console.log(`执行Git命令: git log --all --pretty=format:"%H|%an|%ae|%ad|%B|%D" --date=short ${options}`);
|
|
749
|
+
|
|
750
|
+
// 使用 git log 命令获取提交历史
|
|
751
|
+
let { stdout: logOutput } = await execGitCommand(
|
|
752
|
+
`git log --all --pretty=format:"%H|%an|%ae|%ad|%B|%D" --date=short ${options}`
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
// 获取总提交数量(考虑筛选条件)
|
|
756
|
+
let totalCommits = 0;
|
|
757
|
+
try {
|
|
758
|
+
// 构建计数命令,包含相同的筛选条件
|
|
759
|
+
// 对于作者筛选,使用 rev-list 并手动计数,避免 --count 与复杂作者筛选结合可能的问题
|
|
760
|
+
if (author.length > 1) {
|
|
761
|
+
// 多作者情况,使用 wc -l 手动计数
|
|
762
|
+
const countCommand = `git rev-list --all ${commandOptions.join(' ')}`;
|
|
763
|
+
console.log(`执行计数命令(多作者): ${countCommand}`);
|
|
764
|
+
|
|
765
|
+
const { stdout: countOutput } = await execGitCommand(countCommand);
|
|
766
|
+
// 计算行数作为提交数
|
|
767
|
+
totalCommits = countOutput.trim().split('\n').filter(line => line.trim() !== '').length;
|
|
768
|
+
} else {
|
|
769
|
+
// 单作者或无作者筛选,可以直接使用 --count
|
|
770
|
+
const countCommand = `git rev-list --all --count ${commandOptions.join(' ')}`;
|
|
771
|
+
console.log(`执行计数命令(单作者): ${countCommand}`);
|
|
772
|
+
|
|
773
|
+
const { stdout: countOutput } = await execGitCommand(countCommand);
|
|
774
|
+
totalCommits = parseInt(countOutput.trim());
|
|
775
|
+
}
|
|
776
|
+
} catch (error) {
|
|
777
|
+
console.error('获取提交总数失败:', error);
|
|
778
|
+
totalCommits = 0;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
processAndSendLogOutput(res, logOutput, totalCommits, page, limit);
|
|
782
|
+
} catch (error) {
|
|
783
|
+
console.error('获取Git日志失败:', error);
|
|
784
|
+
res.status(500).json({ error: '获取日志失败: ' + error.message });
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
// 抽取执行Git日志命令的函数
|
|
789
|
+
async function executeGitLogCommand(res, branchRefs, author, message, dateFrom, dateTo, limit, skip, isAll) {
|
|
790
|
+
try {
|
|
791
|
+
// 构建命令选项
|
|
792
|
+
const commandOptions = [];
|
|
793
|
+
|
|
794
|
+
// 作者筛选
|
|
795
|
+
if (author.length > 0) {
|
|
796
|
+
const validAuthors = author.filter(a => a.trim() !== '');
|
|
578
797
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
798
|
+
if (validAuthors.length === 1) {
|
|
799
|
+
commandOptions.push(`--author="${validAuthors[0].trim()}"`);
|
|
800
|
+
} else if (validAuthors.length > 1) {
|
|
801
|
+
const authorPattern = validAuthors.map(a => a.trim()).join('\\|');
|
|
802
|
+
commandOptions.push(`--author="${authorPattern}"`);
|
|
584
803
|
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// 日期范围筛选
|
|
807
|
+
if (dateFrom && dateTo) {
|
|
808
|
+
commandOptions.push(`--after="${dateFrom}" --before="${dateTo} 23:59:59"`);
|
|
809
|
+
} else if (dateFrom) {
|
|
810
|
+
commandOptions.push(`--after="${dateFrom}"`);
|
|
811
|
+
} else if (dateTo) {
|
|
812
|
+
commandOptions.push(`--before="${dateTo} 23:59:59"`);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// 提交信息筛选
|
|
816
|
+
if (message) {
|
|
817
|
+
commandOptions.push(`--grep="${message}"`);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// 限制选项
|
|
821
|
+
const limitOption = isAll ? '' : `-n ${limit} --skip=${skip}`;
|
|
822
|
+
|
|
823
|
+
// 合并所有选项
|
|
824
|
+
const options = [...commandOptions, limitOption].filter(Boolean).join(' ');
|
|
825
|
+
|
|
826
|
+
// 准备分支引用,确保它们被正确识别为分支而不是文件名
|
|
827
|
+
// 使用 refs/heads/ 前缀明确指示这是分支
|
|
828
|
+
const formattedBranchRefs = branchRefs.split(' ')
|
|
829
|
+
.map(branch => {
|
|
830
|
+
// 检查是否已经是完整引用
|
|
831
|
+
if (branch.startsWith('refs/') || branch.includes('/')) {
|
|
832
|
+
return branch;
|
|
833
|
+
}
|
|
834
|
+
// 添加refs/heads/前缀
|
|
835
|
+
return `refs/heads/${branch}`;
|
|
836
|
+
})
|
|
837
|
+
.join(' ');
|
|
838
|
+
|
|
839
|
+
// 构建执行的命令
|
|
840
|
+
const command = `git log ${formattedBranchRefs} --pretty=format:"%H|%an|%ae|%ad|%B|%D" --date=short ${options}`;
|
|
841
|
+
console.log(`执行Git命令(带分支引用): ${command}`);
|
|
842
|
+
|
|
843
|
+
// 执行命令
|
|
844
|
+
const { stdout: logOutput } = await execGitCommand(command);
|
|
845
|
+
|
|
846
|
+
// 获取总提交数
|
|
847
|
+
let totalCommits = 0;
|
|
848
|
+
try {
|
|
849
|
+
// 构建计数命令
|
|
850
|
+
const countCommand = `git rev-list ${formattedBranchRefs} --count ${commandOptions.join(' ')}`;
|
|
851
|
+
console.log(`执行计数命令(分支): ${countCommand}`);
|
|
585
852
|
|
|
586
|
-
|
|
587
|
-
|
|
853
|
+
const { stdout: countOutput } = await execGitCommand(countCommand);
|
|
854
|
+
totalCommits = parseInt(countOutput.trim());
|
|
855
|
+
} catch (error) {
|
|
856
|
+
console.error('获取提交总数失败:', error);
|
|
857
|
+
totalCommits = 0;
|
|
858
|
+
}
|
|
588
859
|
|
|
589
|
-
res
|
|
860
|
+
processAndSendLogOutput(res, logOutput, totalCommits, skip / limit + 1, limit);
|
|
590
861
|
} catch (error) {
|
|
591
|
-
|
|
862
|
+
console.error('执行Git日志命令失败:', error);
|
|
863
|
+
res.status(500).json({ error: '获取日志失败: ' + error.message });
|
|
592
864
|
}
|
|
593
|
-
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// 抽取处理输出并发送响应的函数
|
|
868
|
+
function processAndSendLogOutput(res, logOutput, totalCommits, page, limit) {
|
|
869
|
+
// 替换提交记录之间的换行符
|
|
870
|
+
logOutput = logOutput.replace(/\n(?=[a-f0-9]{40}\|)/g, "<<<RECORD_SEPARATOR>>>");
|
|
871
|
+
|
|
872
|
+
// 按分隔符拆分日志条目
|
|
873
|
+
const logEntries = logOutput.split("<<<RECORD_SEPARATOR>>>");
|
|
874
|
+
|
|
875
|
+
// 处理每个日志条目
|
|
876
|
+
const data = logEntries.map(entry => {
|
|
877
|
+
const parts = entry.split('|');
|
|
878
|
+
if (parts.length >= 5) {
|
|
879
|
+
return {
|
|
880
|
+
hash: parts[0],
|
|
881
|
+
author: parts[1],
|
|
882
|
+
email: parts[2],
|
|
883
|
+
date: parts[3],
|
|
884
|
+
message: parts[4],
|
|
885
|
+
branch: parts[5] || ''
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
}).filter(item => item !== null);
|
|
890
|
+
|
|
891
|
+
// 计算是否有更多数据
|
|
892
|
+
const hasMore = page * limit < totalCommits;
|
|
893
|
+
|
|
894
|
+
console.log(`分页查询 - 页码: ${page}, 每页数量: ${limit}, 总数: ${totalCommits}, 返回数量: ${data.length}, 是否有更多: ${hasMore}`);
|
|
895
|
+
|
|
896
|
+
// 返回提交历史数据,包括是否有更多数据的标志
|
|
897
|
+
res.json({
|
|
898
|
+
data: data,
|
|
899
|
+
total: totalCommits,
|
|
900
|
+
page: page,
|
|
901
|
+
limit: limit,
|
|
902
|
+
hasMore: hasMore
|
|
903
|
+
});
|
|
904
|
+
}
|
|
594
905
|
|
|
595
906
|
// 获取文件差异
|
|
596
907
|
app.get('/api/diff', async (req, res) => {
|
|
@@ -610,6 +921,27 @@ async function startUIServer() {
|
|
|
610
921
|
}
|
|
611
922
|
});
|
|
612
923
|
|
|
924
|
+
// 获取文件内容 (用于未跟踪文件)
|
|
925
|
+
app.get('/api/file-content', async (req, res) => {
|
|
926
|
+
try {
|
|
927
|
+
const filePath = req.query.file;
|
|
928
|
+
|
|
929
|
+
if (!filePath) {
|
|
930
|
+
return res.status(400).json({ error: '缺少文件路径参数' });
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
try {
|
|
934
|
+
// 读取文件内容
|
|
935
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
936
|
+
res.json({ success: true, content });
|
|
937
|
+
} catch (readError) {
|
|
938
|
+
res.status(500).json({ success: false, error: `无法读取文件: ${readError.message}` });
|
|
939
|
+
}
|
|
940
|
+
} catch (error) {
|
|
941
|
+
res.status(500).json({ error: error.message });
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
|
|
613
945
|
// 撤回文件修改
|
|
614
946
|
app.post('/api/revert_file', async (req, res) => {
|
|
615
947
|
try {
|
|
@@ -772,6 +1104,66 @@ async function startUIServer() {
|
|
|
772
1104
|
}
|
|
773
1105
|
});
|
|
774
1106
|
|
|
1107
|
+
// 撤销某个提交 (revert)
|
|
1108
|
+
app.post('/api/revert-commit', async (req, res) => {
|
|
1109
|
+
try {
|
|
1110
|
+
const { hash } = req.body;
|
|
1111
|
+
|
|
1112
|
+
if (!hash) {
|
|
1113
|
+
return res.status(400).json({
|
|
1114
|
+
success: false,
|
|
1115
|
+
error: '缺少提交哈希参数'
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
console.log(`执行撤销提交操作: hash=${hash}`);
|
|
1120
|
+
|
|
1121
|
+
// 执行git revert命令
|
|
1122
|
+
await execGitCommand(`git revert --no-edit ${hash}`);
|
|
1123
|
+
|
|
1124
|
+
res.json({
|
|
1125
|
+
success: true,
|
|
1126
|
+
message: `已成功撤销提交 ${hash}`
|
|
1127
|
+
});
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
console.error('撤销提交失败:', error);
|
|
1130
|
+
res.status(500).json({
|
|
1131
|
+
success: false,
|
|
1132
|
+
error: `撤销提交失败: ${error.message}`
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Cherry-pick某个提交
|
|
1138
|
+
app.post('/api/cherry-pick-commit', async (req, res) => {
|
|
1139
|
+
try {
|
|
1140
|
+
const { hash } = req.body;
|
|
1141
|
+
|
|
1142
|
+
if (!hash) {
|
|
1143
|
+
return res.status(400).json({
|
|
1144
|
+
success: false,
|
|
1145
|
+
error: '缺少提交哈希参数'
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
console.log(`执行Cherry-pick操作: hash=${hash}`);
|
|
1150
|
+
|
|
1151
|
+
// 执行git cherry-pick命令
|
|
1152
|
+
await execGitCommand(`git cherry-pick ${hash}`);
|
|
1153
|
+
|
|
1154
|
+
res.json({
|
|
1155
|
+
success: true,
|
|
1156
|
+
message: `已成功Cherry-pick提交 ${hash}`
|
|
1157
|
+
});
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
console.error('Cherry-pick提交失败:', error);
|
|
1160
|
+
res.status(500).json({
|
|
1161
|
+
success: false,
|
|
1162
|
+
error: `Cherry-pick提交失败: ${error.message}`
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
|
|
775
1167
|
// 添加清理Git锁定文件的接口
|
|
776
1168
|
app.post('/api/remove-lock', async (req, res) => {
|
|
777
1169
|
try {
|
|
@@ -856,6 +1248,37 @@ async function startUIServer() {
|
|
|
856
1248
|
}
|
|
857
1249
|
});
|
|
858
1250
|
|
|
1251
|
+
// 获取所有作者列表
|
|
1252
|
+
app.get('/api/authors', async (req, res) => {
|
|
1253
|
+
try {
|
|
1254
|
+
// 使用git命令获取所有提交者,不依赖Unix命令
|
|
1255
|
+
const { stdout } = await execGitCommand('git log --format="%an"');
|
|
1256
|
+
|
|
1257
|
+
// 将结果按行分割并过滤空行
|
|
1258
|
+
const lines = stdout.split('\n').filter(author => author.trim() !== '');
|
|
1259
|
+
|
|
1260
|
+
// 手动去重,不依赖Unix的uniq命令
|
|
1261
|
+
const uniqueAuthors = Array.from(new Set(lines)).sort();
|
|
1262
|
+
|
|
1263
|
+
// 控制台输出一下搜索示例,方便调试
|
|
1264
|
+
if (uniqueAuthors.length > 1) {
|
|
1265
|
+
const searchExample = uniqueAuthors.slice(0, 2).join('|');
|
|
1266
|
+
console.log(`多作者搜索示例: git log --author="${searchExample}"`);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
res.json({
|
|
1270
|
+
success: true,
|
|
1271
|
+
authors: uniqueAuthors
|
|
1272
|
+
});
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
console.error('获取作者列表失败:', error);
|
|
1275
|
+
res.status(500).json({
|
|
1276
|
+
success: false,
|
|
1277
|
+
error: '获取作者列表失败: ' + error.message
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
|
|
859
1282
|
// Socket.io 实时更新
|
|
860
1283
|
io.on('connection', (socket) => {
|
|
861
1284
|
console.log('客户端已连接:', socket.id);
|