zen-gitsync 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -292,705 +292,705 @@ async function startUIServer(noOpen = false, savePort = false) {
292
292
  res.status(500).json({ error: error.message });
293
293
  }
294
294
  });
295
-
296
- // 获取所有分支
297
- app.get('/api/branches', async (req, res) => {
298
- try {
299
- // 获取本地分支 - 使用简单的git branch命令
300
- const { stdout: localBranches } = await execGitCommand('git branch');
301
-
302
- // 获取远程分支
303
- const { stdout: remoteBranches } = await execGitCommand('git branch -r');
304
-
305
- // 处理本地分支 - 正确解析git branch的标准输出格式
306
- const localBranchList = localBranches.split('\n')
307
- .filter(Boolean)
308
- .map(b => b.trim())
309
- .map(b => b.startsWith('* ') ? b.substring(2) : b); // 移除星号并保留分支名
310
-
311
- // 处理远程分支,保留完整的远程分支名称
312
- const remoteBranchList = remoteBranches.split('\n')
313
- .filter(Boolean)
314
- .map(b => b.trim())
315
- .filter(b => b !== 'origin' && !b.includes('HEAD')); // 过滤掉单纯的origin和HEAD引用
316
-
317
- // 合并分支列表
318
- const allBranches = [
319
- ...localBranchList,
320
- ...remoteBranchList
321
- ];
322
-
323
- console.log('本地分支:', localBranchList);
324
- console.log('远程分支:', remoteBranchList);
325
- console.log('所有分支:', allBranches);
326
-
327
- res.json({ branches: allBranches });
328
- } catch (error) {
329
- console.error('获取分支列表失败:', error);
330
- res.status(500).json({ error: error.message });
331
- }
332
- });
333
-
334
- // 创建新分支
335
- app.post('/api/create-branch', express.json(), async (req, res) => {
336
- try {
337
- const { newBranchName, baseBranch } = req.body;
338
-
339
- if (!newBranchName) {
340
- return res.status(400).json({ success: false, error: '分支名称不能为空' });
341
- }
342
-
343
- // 构建创建分支的命令
344
- let command = `git branch ${newBranchName}`;
345
-
346
- // 如果指定了基础分支,则基于该分支创建
347
- if (baseBranch) {
348
- command = `git branch ${newBranchName} ${baseBranch}`;
349
- }
350
-
351
- // 执行创建分支命令
352
- await execGitCommand(command);
353
-
354
- // 切换到新创建的分支
355
- await execGitCommand(`git checkout ${newBranchName}`);
356
-
357
- // 清除分支缓存,因为分支已切换
358
- clearBranchCache();
359
-
360
- res.json({ success: true, branch: newBranchName });
361
- } catch (error) {
362
- console.error('创建分支失败:', error);
363
- res.status(500).json({ success: false, error: error.message });
364
- }
365
- });
366
-
367
- // 切换分支
368
- app.post('/api/checkout', async (req, res) => {
369
- try {
370
- const { branch } = req.body;
371
- if (!branch) {
372
- return res.status(400).json({ success: false, error: '分支名称不能为空' });
373
- }
374
-
375
- // 执行分支切换
376
- await execGitCommand(`git checkout ${branch}`);
377
-
378
- // 清除分支缓存,因为分支已切换
379
- clearBranchCache();
380
-
381
- res.json({ success: true });
382
- } catch (error) {
383
- console.error('切换分支失败:', error);
384
- res.status(500).json({ success: false, error: error.message });
385
- }
386
- });
387
-
388
- // 合并分支
389
- app.post('/api/merge', async (req, res) => {
390
- try {
391
- const { branch, noCommit, noFf, squash, message } = req.body;
392
-
393
- if (!branch) {
394
- return res.status(400).json({ success: false, error: '分支名称不能为空' });
395
- }
396
-
397
- // 构建Git合并命令 - 直接使用传入的分支名(可能包含origin/前缀)
398
- let command = `git merge ${branch}`;
399
-
400
- // 添加可选参数
401
- if (noCommit) {
402
- command += ' --no-commit';
403
- }
404
-
405
- if (noFf) {
406
- command += ' --no-ff';
407
- }
408
-
409
- if (squash) {
410
- command += ' --squash';
411
- }
412
-
413
- if (message) {
414
- command += ` -m "${message}"`;
415
- }
416
-
417
- try {
418
- // 执行合并命令
419
- const { stdout } = await execGitCommand(command);
420
-
421
- res.json({
422
- success: true,
423
- message: '分支合并成功',
424
- output: stdout
425
- });
426
- } catch (error) {
427
- // 检查是否有合并冲突
428
- const errorMsg = error.message || '';
429
- const hasConflicts = errorMsg.includes('CONFLICT') ||
430
- errorMsg.includes('Automatic merge failed');
431
-
432
- if (hasConflicts) {
433
- res.status(409).json({
434
- success: false,
435
- hasConflicts: true,
436
- error: '合并过程中发生冲突,需要手动解决',
437
- details: errorMsg
438
- });
439
- } else {
440
- throw error;
441
- }
442
- }
443
- } catch (error) {
444
- console.error('合并分支失败:', error);
445
- res.status(500).json({
446
- success: false,
447
- error: `合并分支失败: ${error.message}`
448
- });
449
- }
450
- });
451
-
452
- // 获取Git用户配置信息
453
- app.get('/api/user-info', async (req, res) => {
454
- try {
455
- // 获取全局用户名
456
- const { stdout: userName } = await execGitCommand('git config --global user.name');
457
- // 获取全局用户邮箱
458
- const { stdout: userEmail } = await execGitCommand('git config --global user.email');
459
-
460
- res.json({
461
- name: userName.trim(),
462
- email: userEmail.trim()
463
- });
464
- } catch (error) {
465
- res.status(500).json({ error: error.message });
466
- }
467
- });
468
-
469
- // 新增获取当前工作目录接口
470
- app.get('/api/current_directory', async (req, res) => {
471
- try {
472
- const directory = process.cwd();
473
-
474
- // 检查当前目录是否是Git仓库
475
- try {
476
- await execGitCommand('git rev-parse --is-inside-work-tree');
477
- } catch (error) {
478
- return res.status(400).json({
479
- error: '当前目录不是一个Git仓库',
480
- directory,
481
- isGitRepo: false
482
- });
483
- }
484
-
485
- res.json({
486
- directory,
487
- isGitRepo: true
488
- });
489
- } catch (error) {
490
- res.status(500).json({ error: error.message });
491
- }
492
- });
493
-
494
- // 新增切换工作目录接口
495
- app.post('/api/change_directory', async (req, res) => {
496
- try {
497
- const { path } = req.body;
498
-
499
- if (!path) {
500
- return res.status(400).json({ success: false, error: '目录路径不能为空' });
501
- }
502
-
503
- try {
504
- process.chdir(path);
505
- const newDirectory = process.cwd();
506
-
507
- // 检查新目录是否是Git仓库
508
- try {
509
- await execGitCommand('git rev-parse --is-inside-work-tree');
510
- // 确保切换后立即初始化该项目的配置条目
511
- try {
512
- const currentCfg = await configManager.loadConfig();
513
- await configManager.saveConfig(currentCfg);
514
- // 将新目录加入最近目录
515
- await configManager.saveRecentDirectory(newDirectory);
516
- } catch (e) {
517
- console.warn('初始化项目配置失败:', e?.message || e);
518
- }
519
-
520
- // 初始化文件监控
521
- initFileSystemWatcher();
522
-
523
- res.json({
524
- success: true,
525
- directory: newDirectory,
526
- isGitRepo: true
527
- });
528
- } catch (error) {
529
- // 不是Git仓库,停止监控
530
- if (watcher) {
531
- watcher.close().catch(err => console.error('关闭监控器失败:', err));
532
- watcher = null;
533
- }
534
-
535
- // 即使不是Git仓库也初始化当前目录配置(使用CWD作为项目键)
536
- try {
537
- const currentCfg = await configManager.loadConfig();
538
- await configManager.saveConfig(currentCfg);
539
- // 将新目录加入最近目录
540
- await configManager.saveRecentDirectory(newDirectory);
541
- } catch (e) {
542
- console.warn('非Git目录初始化项目配置失败:', e?.message || e);
543
- }
544
-
545
- res.json({
546
- success: true,
547
- directory: newDirectory,
548
- isGitRepo: false,
549
- warning: '新目录不是一个Git仓库'
550
- });
551
- }
552
- } catch (error) {
553
- res.status(400).json({
554
- success: false,
555
- error: `切换到目录 "${path}" 失败: ${error.message}`
556
- });
557
- }
558
- } catch (error) {
559
- res.status(500).json({ error: error.message });
560
- }
561
- });
562
-
563
- // 获取目录内容(用于浏览目录)
564
- app.get('/api/browse_directory', async (req, res) => {
565
- try {
566
-
567
- // 获取要浏览的目录路径,如果没有提供,则使用当前目录
568
- const directoryPath = req.query.path || process.cwd();
569
-
570
- try {
571
- // 读取目录内容
572
- const items = await fs.readdir(directoryPath, { withFileTypes: true });
573
-
574
- // 分离文件夹和文件
575
- const directories = [];
576
- const files = [];
577
-
578
- for (const item of items) {
579
- const fullPath = path.join(directoryPath, item.name);
580
- if (item.isDirectory()) {
581
- directories.push({
582
- name: item.name,
583
- path: fullPath,
584
- type: 'directory'
585
- });
586
- } else if (item.isFile()) {
587
- files.push({
588
- name: item.name,
589
- path: fullPath,
590
- type: 'file'
591
- });
592
- }
593
- }
594
-
595
- // 优先显示目录,然后是文件,都按字母排序
596
- directories.sort((a, b) => a.name.localeCompare(b.name));
597
- files.sort((a, b) => a.name.localeCompare(b.name));
598
-
599
- // 获取父目录路径
600
- const parentPath = path.dirname(directoryPath);
601
-
602
- res.json({
603
- success: true,
604
- currentPath: directoryPath,
605
- parentPath: parentPath !== directoryPath ? parentPath : null,
606
- items: [...directories, ...files]
607
- });
608
- } catch (error) {
609
- res.status(400).json({
610
- success: false,
611
- error: `无法读取目录 "${directoryPath}": ${error.message}`
612
- });
613
- }
614
- } catch (error) {
615
- res.status(500).json({ error: error.message });
616
- }
617
- });
618
-
619
- // POST接口版本的浏览目录功能
620
- app.post('/api/browse_directory', async (req, res) => {
621
- try {
622
- // 获取要浏览的目录路径,如果没有提供,则使用当前目录
623
- const directoryPath = req.body.currentPath || process.cwd();
624
-
625
- try {
626
- // 读取目录内容
627
- const items = await fs.readdir(directoryPath, { withFileTypes: true });
628
-
629
- // 分离文件夹和文件
630
- const directories = [];
631
- const files = [];
632
-
633
- for (const item of items) {
634
- const fullPath = path.join(directoryPath, item.name);
635
- if (item.isDirectory()) {
636
- directories.push({
637
- name: item.name,
638
- path: fullPath,
639
- type: 'directory'
640
- });
641
- }
642
- }
643
-
644
- // 只返回目录,不返回文件
645
- directories.sort((a, b) => a.name.localeCompare(b.name));
646
-
647
- // 获取父目录路径
648
- const parentPath = path.dirname(directoryPath);
649
-
650
- // 返回选择的目录路径
651
- res.json({
652
- success: true,
653
- path: directoryPath,
654
- parentPath: parentPath !== directoryPath ? parentPath : null,
655
- items: directories
656
- });
657
- } catch (error) {
658
- res.status(400).json({
659
- success: false,
660
- error: `无法读取目录 "${directoryPath}": ${error.message}`
661
- });
662
- }
663
- } catch (error) {
664
- res.status(500).json({
665
- success: false,
666
- error: error.message
667
- });
668
- }
669
- });
670
-
671
- // 获取最近访问的目录列表
672
- app.get('/api/recent_directories', async (req, res) => {
673
- try {
674
- // 尝试从配置中获取最近的目录
675
- const recentDirs = await configManager.getRecentDirectories();
676
- res.json({
677
- success: true,
678
- directories: recentDirs || []
679
- });
680
- } catch (error) {
681
- res.status(500).json({
682
- success: false,
683
- error: error.message
684
- });
685
- }
686
- });
687
-
688
- // 在资源管理器/访达中打开当前目录
689
- app.post('/api/open_directory', async (req, res) => {
690
- try {
691
- // 获取要打开的目录路径,如果没有提供,则使用当前目录
692
- const directoryPath = req.body.path || process.cwd();
693
-
694
- try {
695
- // 检查目录是否存在
696
- await fs.access(directoryPath);
697
-
698
- // 使用open模块打开目录,自动处理不同操作系统
699
- await open(directoryPath, { wait: false });
700
-
701
- res.json({
702
- success: true,
703
- message: '已在文件管理器中打开目录'
704
- });
705
- } catch (error) {
706
- res.status(400).json({
707
- success: false,
708
- error: `无法打开目录 "${directoryPath}": ${error.message}`
709
- });
710
- }
711
- } catch (error) {
712
- res.status(500).json({
713
- success: false,
714
- error: error.message
715
- });
716
- }
717
- });
718
-
719
- // 保存最近访问的目录
720
- app.post('/api/save_recent_directory', async (req, res) => {
721
- try {
722
- const { path } = req.body;
723
-
724
- if (!path) {
725
- return res.status(400).json({
726
- success: false,
727
- error: '目录路径不能为空'
728
- });
729
- }
730
-
731
- // 保存到配置
732
- await configManager.saveRecentDirectory(path);
733
-
734
- res.json({
735
- success: true
736
- });
737
- } catch (error) {
738
- res.status(500).json({
739
- success: false,
740
- error: error.message
741
- });
742
- }
743
- });
744
-
745
- // 获取配置
746
- app.get('/api/config/getConfig', async (req, res) => {
747
- try {
748
- const config = await configManager.loadConfig()
749
- res.json(config)
750
- } catch (error) {
751
- res.status(500).json({ error: error.message })
752
- }
753
- })
754
-
755
- // 保存默认提交信息
756
- app.post('/api/config/saveDefaultMessage', express.json(), async (req, res) => {
757
- try {
758
- const { defaultCommitMessage } = req.body
759
-
760
- if (!defaultCommitMessage) {
761
- return res.status(400).json({ success: false, error: '缺少必要参数' })
762
- }
763
-
764
- const config = await configManager.loadConfig()
765
-
766
- // 更新默认提交信息
767
- config.defaultCommitMessage = defaultCommitMessage
768
- await configManager.saveConfig(config)
769
-
770
- res.json({ success: true })
771
- } catch (error) {
772
- res.status(500).json({ success: false, error: error.message })
773
- }
774
- })
775
-
776
- // 保存所有配置
777
- app.post('/api/config/saveAll', express.json(), async (req, res) => {
778
- try {
779
- const { config } = req.body
780
-
781
- if (!config) {
782
- return res.status(400).json({ success: false, error: '缺少必要参数' })
783
- }
784
-
785
- await configManager.saveConfig(config)
786
-
787
- res.json({ success: true })
788
- } catch (error) {
789
- res.status(500).json({ success: false, error: error.message })
790
- }
791
- })
792
-
793
- // 保存模板
794
- app.post('/api/config/save-template', express.json(), async (req, res) => {
795
- try {
796
- const { template, type } = req.body
797
-
798
- if (!template || !type) {
799
- return res.status(400).json({ success: false, error: '缺少必要参数' })
800
- }
801
-
802
- const config = await configManager.loadConfig()
803
-
804
- if (type === 'description') {
805
- // 确保描述模板数组存在
806
- if (!config.descriptionTemplates) {
807
- config.descriptionTemplates = []
808
- }
809
-
810
- // 检查是否已存在相同模板
811
- if (!config.descriptionTemplates.includes(template)) {
812
- config.descriptionTemplates.push(template)
813
- await configManager.saveConfig(config)
814
- }
815
- } else if (type === 'scope') {
816
- // 确保作用域模板数组存在
817
- if (!config.scopeTemplates) {
818
- config.scopeTemplates = []
819
- }
820
-
821
- // 检查是否已存在相同模板
822
- if (!config.scopeTemplates.includes(template)) {
823
- config.scopeTemplates.push(template)
824
- await configManager.saveConfig(config)
825
- }
826
- } else if (type === 'message') {
827
- // 确保提交信息模板数组存在
828
- if (!config.messageTemplates) {
829
- config.messageTemplates = []
830
- }
831
-
832
- // 检查是否已存在相同模板
833
- if (!config.messageTemplates.includes(template)) {
834
- config.messageTemplates.push(template)
835
- await configManager.saveConfig(config)
836
- }
837
- } else {
838
- return res.status(400).json({ success: false, error: '不支持的模板类型' })
839
- }
840
-
841
- res.json({ success: true })
842
- } catch (error) {
843
- res.status(500).json({ success: false, error: error.message })
844
- }
845
- })
846
-
847
- // 删除模板
848
- app.post('/api/config/delete-template', express.json(), async (req, res) => {
849
- try {
850
- const { template, type } = req.body
851
-
852
- if (!template || !type) {
853
- return res.status(400).json({ success: false, error: '缺少必要参数' })
854
- }
855
-
856
- const config = await configManager.loadConfig()
857
-
858
- if (type === 'description') {
859
- // 确保描述模板数组存在
860
- if (config.descriptionTemplates) {
861
- const index = config.descriptionTemplates.indexOf(template)
862
- if (index !== -1) {
863
- config.descriptionTemplates.splice(index, 1)
864
- await configManager.saveConfig(config)
865
- }
866
- }
867
- } else if (type === 'scope') {
868
- // 确保作用域模板数组存在
869
- if (config.scopeTemplates) {
870
- const index = config.scopeTemplates.indexOf(template)
871
- if (index !== -1) {
872
- config.scopeTemplates.splice(index, 1)
873
- await configManager.saveConfig(config)
874
- }
875
- }
876
- } else if (type === 'message') {
877
- // 确保提交信息模板数组存在
878
- if (config.messageTemplates) {
879
- const index = config.messageTemplates.indexOf(template)
880
- if (index !== -1) {
881
- config.messageTemplates.splice(index, 1)
882
- await configManager.saveConfig(config)
883
- }
884
- }
885
- } else {
886
- return res.status(400).json({ success: false, error: '不支持的模板类型' })
887
- }
888
-
889
- res.json({ success: true })
890
- } catch (error) {
891
- res.status(500).json({ success: false, error: error.message })
892
- }
893
- })
894
-
895
- // 更新模板
896
- app.post('/api/config/update-template', express.json(), async (req, res) => {
897
- try {
898
- const { oldTemplate, newTemplate, type } = req.body
899
-
900
- if (!oldTemplate || !newTemplate || !type) {
901
- return res.status(400).json({ success: false, error: '缺少必要参数' })
902
- }
903
-
904
- const config = await configManager.loadConfig()
905
-
906
- if (type === 'description') {
907
- // 确保描述模板数组存在
908
- if (config.descriptionTemplates) {
909
- const index = config.descriptionTemplates.indexOf(oldTemplate)
910
- if (index !== -1) {
911
- config.descriptionTemplates[index] = newTemplate
912
- await configManager.saveConfig(config)
913
- } else {
914
- return res.status(404).json({ success: false, error: '未找到原模板' })
915
- }
916
- } else {
917
- return res.status(404).json({ success: false, error: '模板列表不存在' })
918
- }
919
- } else if (type === 'scope') {
920
- // 确保作用域模板数组存在
921
- if (config.scopeTemplates) {
922
- const index = config.scopeTemplates.indexOf(oldTemplate)
923
- if (index !== -1) {
924
- config.scopeTemplates[index] = newTemplate
925
- await configManager.saveConfig(config)
926
- } else {
927
- return res.status(404).json({ success: false, error: '未找到原模板' })
928
- }
929
- } else {
930
- return res.status(404).json({ success: false, error: '模板列表不存在' })
931
- }
932
- } else if (type === 'message') {
933
- // 确保提交信息模板数组存在
934
- if (config.messageTemplates) {
935
- const index = config.messageTemplates.indexOf(oldTemplate)
936
- if (index !== -1) {
937
- config.messageTemplates[index] = newTemplate
938
- await configManager.saveConfig(config)
939
- } else {
940
- return res.status(404).json({ success: false, error: '未找到原模板' })
941
- }
942
- } else {
943
- return res.status(404).json({ success: false, error: '模板列表不存在' })
944
- }
945
- } else {
946
- return res.status(400).json({ success: false, error: '不支持的模板类型' })
947
- }
948
-
949
- res.json({ success: true })
950
- } catch (error) {
951
- res.status(500).json({ success: false, error: error.message })
952
- }
953
- })
954
-
955
- // 提交更改
956
- app.post('/api/commit', express.json(), async (req, res) => {
957
- try {
958
- const { message, hasNewlines, noVerify } = req.body;
959
-
960
- // 构建 git commit 命令
961
- let commitCommand = 'git commit';
962
-
963
- // 如果消息包含换行符,使用文件方式提交
964
- if (hasNewlines) {
965
- // 创建临时文件存储提交信息
966
- const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
967
- await fs.writeFile(tempFile, message);
968
- commitCommand += ` -F "${tempFile}"`;
969
- } else {
970
- // 否则直接在命令行中提供消息
971
- commitCommand += ` -m "${message}"`;
972
- }
973
-
974
- // 添加 --no-verify 参数
975
- if (noVerify) {
976
- commitCommand += ' --no-verify';
977
- }
978
-
979
- console.log(`commitCommand ==>`, commitCommand);
980
- // 执行提交命令
981
- await execGitCommand(commitCommand);
982
-
983
- // 如果使用了临时文件,删除它
984
- if (hasNewlines) {
985
- const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
986
- await fs.unlink(tempFile).catch(() => {});
987
- }
988
-
989
- res.json({ success: true });
990
- } catch (error) {
991
- res.status(500).json({ success: false, error: error.message });
992
- }
993
- });
295
+
296
+ // 获取所有分支
297
+ app.get('/api/branches', async (req, res) => {
298
+ try {
299
+ // 获取本地分支 - 使用简单的git branch命令
300
+ const { stdout: localBranches } = await execGitCommand('git branch');
301
+
302
+ // 获取远程分支
303
+ const { stdout: remoteBranches } = await execGitCommand('git branch -r');
304
+
305
+ // 处理本地分支 - 正确解析git branch的标准输出格式
306
+ const localBranchList = localBranches.split('\n')
307
+ .filter(Boolean)
308
+ .map(b => b.trim())
309
+ .map(b => b.startsWith('* ') ? b.substring(2) : b); // 移除星号并保留分支名
310
+
311
+ // 处理远程分支,保留完整的远程分支名称
312
+ const remoteBranchList = remoteBranches.split('\n')
313
+ .filter(Boolean)
314
+ .map(b => b.trim())
315
+ .filter(b => b !== 'origin' && !b.includes('HEAD')); // 过滤掉单纯的origin和HEAD引用
316
+
317
+ // 合并分支列表
318
+ const allBranches = [
319
+ ...localBranchList,
320
+ ...remoteBranchList
321
+ ];
322
+
323
+ console.log('本地分支:', localBranchList);
324
+ console.log('远程分支:', remoteBranchList);
325
+ console.log('所有分支:', allBranches);
326
+
327
+ res.json({ branches: allBranches });
328
+ } catch (error) {
329
+ console.error('获取分支列表失败:', error);
330
+ res.status(500).json({ error: error.message });
331
+ }
332
+ });
333
+
334
+ // 创建新分支
335
+ app.post('/api/create-branch', express.json(), async (req, res) => {
336
+ try {
337
+ const { newBranchName, baseBranch } = req.body;
338
+
339
+ if (!newBranchName) {
340
+ return res.status(400).json({ success: false, error: '分支名称不能为空' });
341
+ }
342
+
343
+ // 构建创建分支的命令
344
+ let command = `git branch ${newBranchName}`;
345
+
346
+ // 如果指定了基础分支,则基于该分支创建
347
+ if (baseBranch) {
348
+ command = `git branch ${newBranchName} ${baseBranch}`;
349
+ }
350
+
351
+ // 执行创建分支命令
352
+ await execGitCommand(command);
353
+
354
+ // 切换到新创建的分支
355
+ await execGitCommand(`git checkout ${newBranchName}`);
356
+
357
+ // 清除分支缓存,因为分支已切换
358
+ clearBranchCache();
359
+
360
+ res.json({ success: true, branch: newBranchName });
361
+ } catch (error) {
362
+ console.error('创建分支失败:', error);
363
+ res.status(500).json({ success: false, error: error.message });
364
+ }
365
+ });
366
+
367
+ // 切换分支
368
+ app.post('/api/checkout', async (req, res) => {
369
+ try {
370
+ const { branch } = req.body;
371
+ if (!branch) {
372
+ return res.status(400).json({ success: false, error: '分支名称不能为空' });
373
+ }
374
+
375
+ // 执行分支切换
376
+ await execGitCommand(`git checkout ${branch}`);
377
+
378
+ // 清除分支缓存,因为分支已切换
379
+ clearBranchCache();
380
+
381
+ res.json({ success: true });
382
+ } catch (error) {
383
+ console.error('切换分支失败:', error);
384
+ res.status(500).json({ success: false, error: error.message });
385
+ }
386
+ });
387
+
388
+ // 合并分支
389
+ app.post('/api/merge', async (req, res) => {
390
+ try {
391
+ const { branch, noCommit, noFf, squash, message } = req.body;
392
+
393
+ if (!branch) {
394
+ return res.status(400).json({ success: false, error: '分支名称不能为空' });
395
+ }
396
+
397
+ // 构建Git合并命令 - 直接使用传入的分支名(可能包含origin/前缀)
398
+ let command = `git merge ${branch}`;
399
+
400
+ // 添加可选参数
401
+ if (noCommit) {
402
+ command += ' --no-commit';
403
+ }
404
+
405
+ if (noFf) {
406
+ command += ' --no-ff';
407
+ }
408
+
409
+ if (squash) {
410
+ command += ' --squash';
411
+ }
412
+
413
+ if (message) {
414
+ command += ` -m "${message}"`;
415
+ }
416
+
417
+ try {
418
+ // 执行合并命令
419
+ const { stdout } = await execGitCommand(command);
420
+
421
+ res.json({
422
+ success: true,
423
+ message: '分支合并成功',
424
+ output: stdout
425
+ });
426
+ } catch (error) {
427
+ // 检查是否有合并冲突
428
+ const errorMsg = error.message || '';
429
+ const hasConflicts = errorMsg.includes('CONFLICT') ||
430
+ errorMsg.includes('Automatic merge failed');
431
+
432
+ if (hasConflicts) {
433
+ res.status(409).json({
434
+ success: false,
435
+ hasConflicts: true,
436
+ error: '合并过程中发生冲突,需要手动解决',
437
+ details: errorMsg
438
+ });
439
+ } else {
440
+ throw error;
441
+ }
442
+ }
443
+ } catch (error) {
444
+ console.error('合并分支失败:', error);
445
+ res.status(500).json({
446
+ success: false,
447
+ error: `合并分支失败: ${error.message}`
448
+ });
449
+ }
450
+ });
451
+
452
+ // 获取Git用户配置信息
453
+ app.get('/api/user-info', async (req, res) => {
454
+ try {
455
+ // 获取全局用户名
456
+ const { stdout: userName } = await execGitCommand('git config --global user.name');
457
+ // 获取全局用户邮箱
458
+ const { stdout: userEmail } = await execGitCommand('git config --global user.email');
459
+
460
+ res.json({
461
+ name: userName.trim(),
462
+ email: userEmail.trim()
463
+ });
464
+ } catch (error) {
465
+ res.status(500).json({ error: error.message });
466
+ }
467
+ });
468
+
469
+ // 新增获取当前工作目录接口
470
+ app.get('/api/current_directory', async (req, res) => {
471
+ try {
472
+ const directory = process.cwd();
473
+
474
+ // 检查当前目录是否是Git仓库
475
+ try {
476
+ await execGitCommand('git rev-parse --is-inside-work-tree');
477
+ } catch (error) {
478
+ return res.status(400).json({
479
+ error: '当前目录不是一个Git仓库',
480
+ directory,
481
+ isGitRepo: false
482
+ });
483
+ }
484
+
485
+ res.json({
486
+ directory,
487
+ isGitRepo: true
488
+ });
489
+ } catch (error) {
490
+ res.status(500).json({ error: error.message });
491
+ }
492
+ });
493
+
494
+ // 新增切换工作目录接口
495
+ app.post('/api/change_directory', async (req, res) => {
496
+ try {
497
+ const { path } = req.body;
498
+
499
+ if (!path) {
500
+ return res.status(400).json({ success: false, error: '目录路径不能为空' });
501
+ }
502
+
503
+ try {
504
+ process.chdir(path);
505
+ const newDirectory = process.cwd();
506
+
507
+ // 检查新目录是否是Git仓库
508
+ try {
509
+ await execGitCommand('git rev-parse --is-inside-work-tree');
510
+ // 确保切换后立即初始化该项目的配置条目
511
+ try {
512
+ const currentCfg = await configManager.loadConfig();
513
+ await configManager.saveConfig(currentCfg);
514
+ // 将新目录加入最近目录
515
+ await configManager.saveRecentDirectory(newDirectory);
516
+ } catch (e) {
517
+ console.warn('初始化项目配置失败:', e?.message || e);
518
+ }
519
+
520
+ // 初始化文件监控
521
+ initFileSystemWatcher();
522
+
523
+ res.json({
524
+ success: true,
525
+ directory: newDirectory,
526
+ isGitRepo: true
527
+ });
528
+ } catch (error) {
529
+ // 不是Git仓库,停止监控
530
+ if (watcher) {
531
+ watcher.close().catch(err => console.error('关闭监控器失败:', err));
532
+ watcher = null;
533
+ }
534
+
535
+ // 即使不是Git仓库也初始化当前目录配置(使用CWD作为项目键)
536
+ try {
537
+ const currentCfg = await configManager.loadConfig();
538
+ await configManager.saveConfig(currentCfg);
539
+ // 将新目录加入最近目录
540
+ await configManager.saveRecentDirectory(newDirectory);
541
+ } catch (e) {
542
+ console.warn('非Git目录初始化项目配置失败:', e?.message || e);
543
+ }
544
+
545
+ res.json({
546
+ success: true,
547
+ directory: newDirectory,
548
+ isGitRepo: false,
549
+ warning: '新目录不是一个Git仓库'
550
+ });
551
+ }
552
+ } catch (error) {
553
+ res.status(400).json({
554
+ success: false,
555
+ error: `切换到目录 "${path}" 失败: ${error.message}`
556
+ });
557
+ }
558
+ } catch (error) {
559
+ res.status(500).json({ error: error.message });
560
+ }
561
+ });
562
+
563
+ // 获取目录内容(用于浏览目录)
564
+ app.get('/api/browse_directory', async (req, res) => {
565
+ try {
566
+
567
+ // 获取要浏览的目录路径,如果没有提供,则使用当前目录
568
+ const directoryPath = req.query.path || process.cwd();
569
+
570
+ try {
571
+ // 读取目录内容
572
+ const items = await fs.readdir(directoryPath, { withFileTypes: true });
573
+
574
+ // 分离文件夹和文件
575
+ const directories = [];
576
+ const files = [];
577
+
578
+ for (const item of items) {
579
+ const fullPath = path.join(directoryPath, item.name);
580
+ if (item.isDirectory()) {
581
+ directories.push({
582
+ name: item.name,
583
+ path: fullPath,
584
+ type: 'directory'
585
+ });
586
+ } else if (item.isFile()) {
587
+ files.push({
588
+ name: item.name,
589
+ path: fullPath,
590
+ type: 'file'
591
+ });
592
+ }
593
+ }
594
+
595
+ // 优先显示目录,然后是文件,都按字母排序
596
+ directories.sort((a, b) => a.name.localeCompare(b.name));
597
+ files.sort((a, b) => a.name.localeCompare(b.name));
598
+
599
+ // 获取父目录路径
600
+ const parentPath = path.dirname(directoryPath);
601
+
602
+ res.json({
603
+ success: true,
604
+ currentPath: directoryPath,
605
+ parentPath: parentPath !== directoryPath ? parentPath : null,
606
+ items: [...directories, ...files]
607
+ });
608
+ } catch (error) {
609
+ res.status(400).json({
610
+ success: false,
611
+ error: `无法读取目录 "${directoryPath}": ${error.message}`
612
+ });
613
+ }
614
+ } catch (error) {
615
+ res.status(500).json({ error: error.message });
616
+ }
617
+ });
618
+
619
+ // POST接口版本的浏览目录功能
620
+ app.post('/api/browse_directory', async (req, res) => {
621
+ try {
622
+ // 获取要浏览的目录路径,如果没有提供,则使用当前目录
623
+ const directoryPath = req.body.currentPath || process.cwd();
624
+
625
+ try {
626
+ // 读取目录内容
627
+ const items = await fs.readdir(directoryPath, { withFileTypes: true });
628
+
629
+ // 分离文件夹和文件
630
+ const directories = [];
631
+ const files = [];
632
+
633
+ for (const item of items) {
634
+ const fullPath = path.join(directoryPath, item.name);
635
+ if (item.isDirectory()) {
636
+ directories.push({
637
+ name: item.name,
638
+ path: fullPath,
639
+ type: 'directory'
640
+ });
641
+ }
642
+ }
643
+
644
+ // 只返回目录,不返回文件
645
+ directories.sort((a, b) => a.name.localeCompare(b.name));
646
+
647
+ // 获取父目录路径
648
+ const parentPath = path.dirname(directoryPath);
649
+
650
+ // 返回选择的目录路径
651
+ res.json({
652
+ success: true,
653
+ path: directoryPath,
654
+ parentPath: parentPath !== directoryPath ? parentPath : null,
655
+ items: directories
656
+ });
657
+ } catch (error) {
658
+ res.status(400).json({
659
+ success: false,
660
+ error: `无法读取目录 "${directoryPath}": ${error.message}`
661
+ });
662
+ }
663
+ } catch (error) {
664
+ res.status(500).json({
665
+ success: false,
666
+ error: error.message
667
+ });
668
+ }
669
+ });
670
+
671
+ // 获取最近访问的目录列表
672
+ app.get('/api/recent_directories', async (req, res) => {
673
+ try {
674
+ // 尝试从配置中获取最近的目录
675
+ const recentDirs = await configManager.getRecentDirectories();
676
+ res.json({
677
+ success: true,
678
+ directories: recentDirs || []
679
+ });
680
+ } catch (error) {
681
+ res.status(500).json({
682
+ success: false,
683
+ error: error.message
684
+ });
685
+ }
686
+ });
687
+
688
+ // 在资源管理器/访达中打开当前目录
689
+ app.post('/api/open_directory', async (req, res) => {
690
+ try {
691
+ // 获取要打开的目录路径,如果没有提供,则使用当前目录
692
+ const directoryPath = req.body.path || process.cwd();
693
+
694
+ try {
695
+ // 检查目录是否存在
696
+ await fs.access(directoryPath);
697
+
698
+ // 使用open模块打开目录,自动处理不同操作系统
699
+ await open(directoryPath, { wait: false });
700
+
701
+ res.json({
702
+ success: true,
703
+ message: '已在文件管理器中打开目录'
704
+ });
705
+ } catch (error) {
706
+ res.status(400).json({
707
+ success: false,
708
+ error: `无法打开目录 "${directoryPath}": ${error.message}`
709
+ });
710
+ }
711
+ } catch (error) {
712
+ res.status(500).json({
713
+ success: false,
714
+ error: error.message
715
+ });
716
+ }
717
+ });
718
+
719
+ // 保存最近访问的目录
720
+ app.post('/api/save_recent_directory', async (req, res) => {
721
+ try {
722
+ const { path } = req.body;
723
+
724
+ if (!path) {
725
+ return res.status(400).json({
726
+ success: false,
727
+ error: '目录路径不能为空'
728
+ });
729
+ }
730
+
731
+ // 保存到配置
732
+ await configManager.saveRecentDirectory(path);
733
+
734
+ res.json({
735
+ success: true
736
+ });
737
+ } catch (error) {
738
+ res.status(500).json({
739
+ success: false,
740
+ error: error.message
741
+ });
742
+ }
743
+ });
744
+
745
+ // 获取配置
746
+ app.get('/api/config/getConfig', async (req, res) => {
747
+ try {
748
+ const config = await configManager.loadConfig()
749
+ res.json(config)
750
+ } catch (error) {
751
+ res.status(500).json({ error: error.message })
752
+ }
753
+ })
754
+
755
+ // 保存默认提交信息
756
+ app.post('/api/config/saveDefaultMessage', express.json(), async (req, res) => {
757
+ try {
758
+ const { defaultCommitMessage } = req.body
759
+
760
+ if (!defaultCommitMessage) {
761
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
762
+ }
763
+
764
+ const config = await configManager.loadConfig()
765
+
766
+ // 更新默认提交信息
767
+ config.defaultCommitMessage = defaultCommitMessage
768
+ await configManager.saveConfig(config)
769
+
770
+ res.json({ success: true })
771
+ } catch (error) {
772
+ res.status(500).json({ success: false, error: error.message })
773
+ }
774
+ })
775
+
776
+ // 保存所有配置
777
+ app.post('/api/config/saveAll', express.json(), async (req, res) => {
778
+ try {
779
+ const { config } = req.body
780
+
781
+ if (!config) {
782
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
783
+ }
784
+
785
+ await configManager.saveConfig(config)
786
+
787
+ res.json({ success: true })
788
+ } catch (error) {
789
+ res.status(500).json({ success: false, error: error.message })
790
+ }
791
+ })
792
+
793
+ // 保存模板
794
+ app.post('/api/config/save-template', express.json(), async (req, res) => {
795
+ try {
796
+ const { template, type } = req.body
797
+
798
+ if (!template || !type) {
799
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
800
+ }
801
+
802
+ const config = await configManager.loadConfig()
803
+
804
+ if (type === 'description') {
805
+ // 确保描述模板数组存在
806
+ if (!config.descriptionTemplates) {
807
+ config.descriptionTemplates = []
808
+ }
809
+
810
+ // 检查是否已存在相同模板
811
+ if (!config.descriptionTemplates.includes(template)) {
812
+ config.descriptionTemplates.push(template)
813
+ await configManager.saveConfig(config)
814
+ }
815
+ } else if (type === 'scope') {
816
+ // 确保作用域模板数组存在
817
+ if (!config.scopeTemplates) {
818
+ config.scopeTemplates = []
819
+ }
820
+
821
+ // 检查是否已存在相同模板
822
+ if (!config.scopeTemplates.includes(template)) {
823
+ config.scopeTemplates.push(template)
824
+ await configManager.saveConfig(config)
825
+ }
826
+ } else if (type === 'message') {
827
+ // 确保提交信息模板数组存在
828
+ if (!config.messageTemplates) {
829
+ config.messageTemplates = []
830
+ }
831
+
832
+ // 检查是否已存在相同模板
833
+ if (!config.messageTemplates.includes(template)) {
834
+ config.messageTemplates.push(template)
835
+ await configManager.saveConfig(config)
836
+ }
837
+ } else {
838
+ return res.status(400).json({ success: false, error: '不支持的模板类型' })
839
+ }
840
+
841
+ res.json({ success: true })
842
+ } catch (error) {
843
+ res.status(500).json({ success: false, error: error.message })
844
+ }
845
+ })
846
+
847
+ // 删除模板
848
+ app.post('/api/config/delete-template', express.json(), async (req, res) => {
849
+ try {
850
+ const { template, type } = req.body
851
+
852
+ if (!template || !type) {
853
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
854
+ }
855
+
856
+ const config = await configManager.loadConfig()
857
+
858
+ if (type === 'description') {
859
+ // 确保描述模板数组存在
860
+ if (config.descriptionTemplates) {
861
+ const index = config.descriptionTemplates.indexOf(template)
862
+ if (index !== -1) {
863
+ config.descriptionTemplates.splice(index, 1)
864
+ await configManager.saveConfig(config)
865
+ }
866
+ }
867
+ } else if (type === 'scope') {
868
+ // 确保作用域模板数组存在
869
+ if (config.scopeTemplates) {
870
+ const index = config.scopeTemplates.indexOf(template)
871
+ if (index !== -1) {
872
+ config.scopeTemplates.splice(index, 1)
873
+ await configManager.saveConfig(config)
874
+ }
875
+ }
876
+ } else if (type === 'message') {
877
+ // 确保提交信息模板数组存在
878
+ if (config.messageTemplates) {
879
+ const index = config.messageTemplates.indexOf(template)
880
+ if (index !== -1) {
881
+ config.messageTemplates.splice(index, 1)
882
+ await configManager.saveConfig(config)
883
+ }
884
+ }
885
+ } else {
886
+ return res.status(400).json({ success: false, error: '不支持的模板类型' })
887
+ }
888
+
889
+ res.json({ success: true })
890
+ } catch (error) {
891
+ res.status(500).json({ success: false, error: error.message })
892
+ }
893
+ })
894
+
895
+ // 更新模板
896
+ app.post('/api/config/update-template', express.json(), async (req, res) => {
897
+ try {
898
+ const { oldTemplate, newTemplate, type } = req.body
899
+
900
+ if (!oldTemplate || !newTemplate || !type) {
901
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
902
+ }
903
+
904
+ const config = await configManager.loadConfig()
905
+
906
+ if (type === 'description') {
907
+ // 确保描述模板数组存在
908
+ if (config.descriptionTemplates) {
909
+ const index = config.descriptionTemplates.indexOf(oldTemplate)
910
+ if (index !== -1) {
911
+ config.descriptionTemplates[index] = newTemplate
912
+ await configManager.saveConfig(config)
913
+ } else {
914
+ return res.status(404).json({ success: false, error: '未找到原模板' })
915
+ }
916
+ } else {
917
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
918
+ }
919
+ } else if (type === 'scope') {
920
+ // 确保作用域模板数组存在
921
+ if (config.scopeTemplates) {
922
+ const index = config.scopeTemplates.indexOf(oldTemplate)
923
+ if (index !== -1) {
924
+ config.scopeTemplates[index] = newTemplate
925
+ await configManager.saveConfig(config)
926
+ } else {
927
+ return res.status(404).json({ success: false, error: '未找到原模板' })
928
+ }
929
+ } else {
930
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
931
+ }
932
+ } else if (type === 'message') {
933
+ // 确保提交信息模板数组存在
934
+ if (config.messageTemplates) {
935
+ const index = config.messageTemplates.indexOf(oldTemplate)
936
+ if (index !== -1) {
937
+ config.messageTemplates[index] = newTemplate
938
+ await configManager.saveConfig(config)
939
+ } else {
940
+ return res.status(404).json({ success: false, error: '未找到原模板' })
941
+ }
942
+ } else {
943
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
944
+ }
945
+ } else {
946
+ return res.status(400).json({ success: false, error: '不支持的模板类型' })
947
+ }
948
+
949
+ res.json({ success: true })
950
+ } catch (error) {
951
+ res.status(500).json({ success: false, error: error.message })
952
+ }
953
+ })
954
+
955
+ // 提交更改
956
+ app.post('/api/commit', express.json(), async (req, res) => {
957
+ try {
958
+ const { message, hasNewlines, noVerify } = req.body;
959
+
960
+ // 构建 git commit 命令
961
+ let commitCommand = 'git commit';
962
+
963
+ // 如果消息包含换行符,使用文件方式提交
964
+ if (hasNewlines) {
965
+ // 创建临时文件存储提交信息
966
+ const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
967
+ await fs.writeFile(tempFile, message);
968
+ commitCommand += ` -F "${tempFile}"`;
969
+ } else {
970
+ // 否则直接在命令行中提供消息
971
+ commitCommand += ` -m "${message}"`;
972
+ }
973
+
974
+ // 添加 --no-verify 参数
975
+ if (noVerify) {
976
+ commitCommand += ' --no-verify';
977
+ }
978
+
979
+ console.log(`commitCommand ==>`, commitCommand);
980
+ // 执行提交命令
981
+ await execGitCommand(commitCommand);
982
+
983
+ // 如果使用了临时文件,删除它
984
+ if (hasNewlines) {
985
+ const tempFile = path.join(os.tmpdir(), `commit-msg-${Date.now()}.txt`);
986
+ await fs.unlink(tempFile).catch(() => {});
987
+ }
988
+
989
+ res.json({ success: true });
990
+ } catch (error) {
991
+ res.status(500).json({ success: false, error: error.message });
992
+ }
993
+ });
994
994
 
995
995
  // 添加 add 接口
996
996
  app.post('/api/add', async (req, res) => {
@@ -1940,6 +1940,77 @@ async function startUIServer(noOpen = false, savePort = false) {
1940
1940
  res.status(500).json({ success: false, error: error.message });
1941
1941
  }
1942
1942
  });
1943
+
1944
+ // 获取stash中的文件列表
1945
+ app.get('/api/stash-files', async (req, res) => {
1946
+ try {
1947
+ const { stashId } = req.query;
1948
+
1949
+ if (!stashId) {
1950
+ return res.status(400).json({
1951
+ success: false,
1952
+ error: '缺少stash ID参数'
1953
+ });
1954
+ }
1955
+
1956
+ console.log(`获取stash文件列表: stashId=${stashId}`);
1957
+
1958
+ // 执行git stash show --name-only命令获取文件列表
1959
+ const { stdout } = await execGitCommand(`git stash show --name-only ${stashId}`);
1960
+
1961
+ // 将输出按行分割,并过滤掉空行
1962
+ const files = stdout.split('\n').filter(line => line.trim());
1963
+ console.log(`找到${files.length}个stash文件:`, files);
1964
+
1965
+ res.json({
1966
+ success: true,
1967
+ files
1968
+ });
1969
+ } catch (error) {
1970
+ console.error('获取stash文件列表失败:', error);
1971
+ res.status(500).json({
1972
+ success: false,
1973
+ error: `获取stash文件列表失败: ${error.message}`
1974
+ });
1975
+ }
1976
+ });
1977
+
1978
+ // 获取stash中特定文件的差异
1979
+ app.get('/api/stash-file-diff', async (req, res) => {
1980
+ try {
1981
+ const { stashId, file } = req.query;
1982
+
1983
+ if (!stashId || !file) {
1984
+ return res.status(400).json({
1985
+ success: false,
1986
+ error: '缺少必要参数'
1987
+ });
1988
+ }
1989
+
1990
+ console.log(`获取stash文件差异: stashId=${stashId}, file=${file}`);
1991
+
1992
+ // 执行git stash show -p命令获取特定文件的差异
1993
+ // 需要使用正确的语法:git show stashId:file 或者 git stash show -p stashId -- file
1994
+ const { stdout } = await execGitCommand(`git show ${stashId} -- "${file}"`);
1995
+
1996
+ console.log(`获取到差异内容,长度: ${stdout.length}`);
1997
+ // 如果差异内容太长,只打印前100个字符
1998
+ if (stdout.length > 100) {
1999
+ console.log(`差异内容预览: ${stdout.substring(0, 100)}...`);
2000
+ }
2001
+
2002
+ res.json({
2003
+ success: true,
2004
+ diff: stdout
2005
+ });
2006
+ } catch (error) {
2007
+ console.error('获取stash文件差异失败:', error);
2008
+ res.status(500).json({
2009
+ success: false,
2010
+ error: `获取stash文件差异失败: ${error.message}`
2011
+ });
2012
+ }
2013
+ });
1943
2014
 
1944
2015
  // Socket.io 实时更新
1945
2016
  io.on('connection', (socket) => {