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.
@@ -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-DaPynzAr.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/vendor-BcSuWc8z.js">
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-qfIezZmd.css">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-C3BbS3PG.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="app"></div>
@@ -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
- // 获取请求参数中的数量限制,默认为30
538
- const limit = req.query.all === 'true' ? '' : '-n 30';
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
- // graph参数保留但不做特殊处理,避免前端代码重复调用API
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
- // 修改 git log 命令,添加 %ae 参数来获取作者邮箱
544
- // 使用 %H 获取完整哈希值(而不是短哈希 %h)
545
- // 使用 %B 获取完整提交信息(包括正文)
546
- const { stdout } = await execGitCommand(`git log --all --pretty=format:"%H|%an|%ae|%ad|%B|%D" --date=short ${limit}`);
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
- const recordSeparator = "\n<<<RECORD_SEPARATOR>>>\n";
550
- const fieldSeparator = "<<<FIELD_SEPARATOR>>>";
736
+ // 提交信息筛选
737
+ if (message) {
738
+ commandOptions.push(`--grep="${message}"`);
739
+ }
551
740
 
552
- // 预处理输出,替换提交记录之间的换行符
553
- const processedOutput = stdout.replace(/\n(?=[a-f0-9]{40}\|)/g, recordSeparator);
741
+ // 如果all=true,则不使用限制,否则按页码和limit精确获取
742
+ // 修复:只获取当前页的数据,而不是累计所有之前页的数据
743
+ const limitOption = req.query.all === 'true' ? '' : `-n ${limit} --skip=${skip}`;
554
744
 
555
- // 按记录分隔符拆分日志条目
556
- const logEntries = processedOutput.split(recordSeparator);
745
+ // 合并所有命令选项
746
+ const options = [...commandOptions, limitOption].filter(Boolean).join(' ');
557
747
 
558
- const logs = logEntries.map(entry => {
559
- // 使用第一个|分隔哈希值,其余部分作为整体
560
- const hashEndIndex = entry.indexOf('|');
561
- const hash = entry.substring(0, hashEndIndex);
562
- const restPart = entry.substring(hashEndIndex + 1);
563
-
564
- // 使用最后一个|分隔引用信息,其余部分作为整体
565
- const lastPipeIndex = restPart.lastIndexOf('|');
566
- const refs = restPart.substring(lastPipeIndex + 1).trim();
567
- const middlePart = restPart.substring(0, lastPipeIndex);
568
-
569
- // 分隔作者、邮箱、日期和提交信息
570
- const parts = middlePart.split('|');
571
-
572
- // 确保即使分隔出的部分不足,也能提供默认值
573
- const author = parts[0] || '';
574
- const email = parts[1] || '';
575
- const date = parts[2] || '';
576
- // 提交信息可能包含多行
577
- const message = parts.slice(3).join('|').trim();
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
- let branch = null;
581
- if (refs) {
582
- // 提取所有引用信息,而不仅仅是第一个匹配
583
- branch = refs.trim();
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
- return { hash, author, email, date, message, branch };
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.json(logs);
860
+ processAndSendLogOutput(res, logOutput, totalCommits, skip / limit + 1, limit);
590
861
  } catch (error) {
591
- res.status(500).json({ error: error.message });
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);