pindo 5.14.7 → 5.15.1

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pindo/base/aeshelper.rb +53 -22
  3. data/lib/pindo/base/git_handler.rb +295 -32
  4. data/lib/pindo/client/giteeclient.rb +15 -6
  5. data/lib/pindo/command/android/autobuild.rb +7 -5
  6. data/lib/pindo/command/appstore/autobuild.rb +5 -2
  7. data/lib/pindo/command/appstore/cert.rb +6 -0
  8. data/lib/pindo/command/appstore/iap.rb +2 -3
  9. data/lib/pindo/command/ios/autobuild.rb +31 -21
  10. data/lib/pindo/command/ios/cert.rb +1 -1
  11. data/lib/pindo/command/jps/apptest.rb +1 -1
  12. data/lib/pindo/command/jps/bind.rb +6 -2
  13. data/lib/pindo/command/jps/media.rb +6 -2
  14. data/lib/pindo/command/jps/upload.rb +6 -4
  15. data/lib/pindo/command/repo/clone.rb +110 -32
  16. data/lib/pindo/command/repo/subgit.rb +91 -0
  17. data/lib/pindo/command/repo.rb +1 -0
  18. data/lib/pindo/command/unity/autobuild.rb +6 -4
  19. data/lib/pindo/command/unity/packbuild.rb +14 -7
  20. data/lib/pindo/command/utils/tag.rb +5 -3
  21. data/lib/pindo/command/web/autobuild.rb +7 -5
  22. data/lib/pindo/command.rb +1 -1
  23. data/lib/pindo/config/build_info_manager.rb +37 -14
  24. data/lib/pindo/module/appstore/bundleid_helper.rb +7 -3
  25. data/lib/pindo/module/build/git_repo_helper.rb +14 -35
  26. data/lib/pindo/module/cert/cert_helper.rb +33 -9
  27. data/lib/pindo/module/cert/xcode_cert_helper.rb +17 -7
  28. data/lib/pindo/module/pgyer/pgyerhelper.rb +110 -22
  29. data/lib/pindo/module/task/model/build/android_build_dev_task.rb +3 -2
  30. data/lib/pindo/module/task/model/build/ios_build_dev_task.rb +3 -2
  31. data/lib/pindo/module/task/model/git/git_commit_task.rb +107 -48
  32. data/lib/pindo/module/task/model/git/git_tag_task.rb +21 -20
  33. data/lib/pindo/module/task/model/git_task.rb +49 -0
  34. data/lib/pindo/module/task/model/jps/jps_bind_package_task.rb +8 -5
  35. data/lib/pindo/module/task/model/jps/jps_upload_media_task.rb +21 -13
  36. data/lib/pindo/module/task/model/jps/jps_workflow_message_task.rb +8 -4
  37. data/lib/pindo/module/task/model/resign/ipa_local_resign_task.rb +5 -0
  38. data/lib/pindo/options/core/option_item.rb +4 -5
  39. data/lib/pindo/options/groups/git_options.rb +40 -0
  40. data/lib/pindo/options/helpers/git_constants.rb +28 -0
  41. data/lib/pindo/version.rb +1 -1
  42. metadata +11 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e61c4fa7bc1f944011a7d2da6ea5f88e6964cf4156fbfc820a82f87f8376b15
4
- data.tar.gz: '0826f4b21c1fdd8145763b8357dddfa7f6c06a461b128bb458b7c75886ba5208'
3
+ metadata.gz: f82f06b4cb4ed845ca62c0db43e4684b141d87b8271b705a2a4de0e0a9c8355b
4
+ data.tar.gz: 7b77b67d53e8f1ca601f32190d8d6534d146837a302facce49c7afa782ecd47f
5
5
  SHA512:
6
- metadata.gz: a1379dd0f212e82aad3c889d9c06374810441728700616d9a2293c647bbe0272318d744dcfd4056edf69d8bde847a9394225c334a3909e419f34321d38d9f29f
7
- data.tar.gz: df9c68524dbdbd81f29e3134c9b1a25de3e6fc57e29346b5beae93bcccb8ac1186bbfe7e3f4a86dbfb81ebc5356f91eeb852cf9a6f504144225c3715e9a63975
6
+ metadata.gz: f051bfa4fb0aa773eaf7d7a20d489e17d065bdede2c78652dd172ec2a3da5f95ae19700d05e6748f3442487fd96e18a7b3040043a4f906e257415353b73a392a
7
+ data.tar.gz: f056935b0928d4e48dd0e6bb88f321d64a2b118eee92ef3ce767697c039868ebc987394e36deacbbcb6ba1111cc8486b93bbc99c71a6f4c71a297bc9a4f3f575
@@ -21,26 +21,7 @@ module Pindo
21
21
  unless password
22
22
  puts "\e[33m[DEBUG] Keychain中未找到密码,需要用户输入: #{server_name}\e[0m" if ENV['PINDO_DEBUG']
23
23
  password = FastlaneCore::Helper.ask_password(message: "请输入证书仓库的加密密码: ", confirm: true)
24
-
25
- # 尝试添加密码到Keychain
26
- begin
27
- # 先尝试删除旧密码(如果存在)
28
- begin
29
- Security::InternetPassword.delete(server: server_name)
30
- puts "\e[33m[DEBUG] 删除Keychain中的旧密码项: #{server_name}\e[0m" if ENV['PINDO_DEBUG']
31
- rescue => delete_error
32
- # 如果不存在就忽略删除错误
33
- puts "\e[33m[DEBUG] 旧密码项不存在或删除失败: #{delete_error.message}\e[0m" if ENV['PINDO_DEBUG']
34
- end
35
-
36
- # 添加新密码到Keychain
37
- Security::InternetPassword.add(server_name, "", password)
38
- puts "\e[32m✓ 密码已保存到Keychain: #{server_name}\e[0m"
39
- rescue => e
40
- # 如果保存失败,警告用户但不影响继续使用
41
- puts "\e[31m⚠ 密码保存到Keychain失败: #{e.message}\e[0m"
42
- puts "\e[31m⚠ 下次使用时需要重新输入密码\e[0m"
43
- end
24
+ puts "\e[33m[DEBUG] 用户输入密码成功,等待验证后保存到Keychain\e[0m" if ENV['PINDO_DEBUG']
44
25
  else
45
26
  puts "\e[32m[DEBUG] 从Keychain获取密码成功: #{server_name}\e[0m" if ENV['PINDO_DEBUG']
46
27
  end
@@ -50,12 +31,62 @@ module Pindo
50
31
 
51
32
  def self.delete_password(keychain_name:nil)
52
33
  server_name = ["match", keychain_name].join("_")
53
- Security::InternetPassword.delete(server:server_name)
34
+ # 静默删除密码,不输出 keychain 详细信息(文件描述符级别重定向)
35
+ begin
36
+ old_stdout = STDOUT.dup
37
+ old_stderr = STDERR.dup
38
+ STDOUT.reopen(File::NULL, 'w')
39
+ STDERR.reopen(File::NULL, 'w')
40
+
41
+ Security::InternetPassword.delete(server:server_name)
42
+ ensure
43
+ STDOUT.reopen(old_stdout)
44
+ STDERR.reopen(old_stderr)
45
+ old_stdout.close
46
+ old_stderr.close
47
+ end
48
+ rescue => e
49
+ # 忽略错误(密码可能不存在)
54
50
  end
55
51
 
56
52
  def self.store_password(keychain_name:nil, password:nil)
57
53
  server_name = ["match", keychain_name].join("_")
58
- Security::InternetPassword.add(server_name, "", password)
54
+
55
+ # 先检查密码是否已存在,如果存在则无需重复保存
56
+ begin
57
+ item = Security::InternetPassword.find(server: server_name)
58
+ if item && item.password == password
59
+ puts "\e[33m[DEBUG] 密码已存在于Keychain,跳过保存: #{server_name}\e[0m" if ENV['PINDO_DEBUG']
60
+ return
61
+ end
62
+ rescue => e
63
+ # 密码不存在,继续保存流程
64
+ end
65
+
66
+ # 静默操作,避免输出错误信息(文件描述符级别重定向)
67
+ begin
68
+ old_stdout = STDOUT.dup
69
+ old_stderr = STDERR.dup
70
+ STDOUT.reopen(File::NULL, 'w')
71
+ STDERR.reopen(File::NULL, 'w')
72
+
73
+ # 先尝试删除旧密码(如果存在但不同)
74
+ begin
75
+ Security::InternetPassword.delete(server:server_name)
76
+ rescue => e
77
+ # 忽略删除错误(密码可能不存在)
78
+ end
79
+
80
+ # 添加新密码
81
+ Security::InternetPassword.add(server_name, "", password)
82
+ ensure
83
+ STDOUT.reopen(old_stdout)
84
+ STDERR.reopen(old_stderr)
85
+ old_stdout.close
86
+ old_stderr.close
87
+ end
88
+ rescue => e
89
+ # 忽略错误,不影响主流程
59
90
  end
60
91
 
61
92
 
@@ -4,6 +4,7 @@ require 'pindo/base/informative'
4
4
  require 'etc'
5
5
  require 'claide'
6
6
  require 'highline/import'
7
+ require 'pindo/options/helpers/git_constants'
7
8
 
8
9
  module Pindo
9
10
  # Git 操作处理类
@@ -517,17 +518,20 @@ module Pindo
517
518
 
518
519
  # 根据 process_type 执行对应操作
519
520
  case process_type
520
- when 'commit', '全部提交'
521
+ when Pindo::UncommittedFilesProcessType::NONE
522
+ # 没有未提交的文件,无需处理
523
+ return
524
+ when Pindo::UncommittedFilesProcessType::COMMIT
521
525
  handle_commit_all(current_project_dir, coding_branch, commit_message)
522
- when 'reset', '全部丢弃更改,回滚代码', 'delete', '全部删除'
526
+ when Pindo::UncommittedFilesProcessType::RESET
523
527
  handle_delete_all(current_project_dir, coding_branch)
524
- when 'stash', '保存到stash区域', '保存到stash区域(改代码本次不生效)'
528
+ when Pindo::UncommittedFilesProcessType::STASH
525
529
  handle_stash(current_project_dir, coding_branch)
526
- when 'skip', '跳过'
530
+ when Pindo::UncommittedFilesProcessType::SKIP
527
531
  # 跳过处理,不做任何操作
528
532
  Funlog.instance.warning("跳过提交,可能导致 build number 与代码仓库不一致")
529
533
  return
530
- when 'exit', '先退出,手动来处理退出'
534
+ when Pindo::UncommittedFilesProcessType::EXIT
531
535
  raise Informative, "请手动处理未提交的文件!!!"
532
536
  else
533
537
  raise ArgumentError, "不支持的处理方式: #{process_type}"
@@ -620,13 +624,16 @@ module Pindo
620
624
 
621
625
  # 生成 stash 信息,包含分支名和时间戳
622
626
  timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
623
- stash_message = "pindo on #{branch} - #{timestamp}"
627
+ # 统一使用 pindo_stash 前缀,方便查找和还原
628
+ stash_name = "pindo_stash_#{Time.now.strftime('%Y%m%d%H%M%S')}_#{rand(1000)}"
629
+ stash_message = "#{stash_name} (on #{branch} at #{timestamp})"
624
630
 
625
631
  # 保存到 stash(包含未追踪的文件)
626
632
  git!(%W(-C #{project_dir} stash push -u -m #{stash_message}))
627
633
 
628
634
  Funlog.instance.fancyinfo_success("文件已保存到 stash 区域!")
629
- Funlog.instance.fancyinfo_success("stash 信息: #{stash_message}")
635
+ Funlog.instance.fancyinfo_success("stash key: #{stash_name}")
636
+ Funlog.instance.fancyinfo_success("stash msg: #{stash_message}")
630
637
 
631
638
  # 显示当前 stash 列表
632
639
  stash_list = git!(%W(-C #{project_dir} stash list))
@@ -662,8 +669,12 @@ module Pindo
662
669
 
663
670
  # 获取最新的符合规范的 commit
664
671
  # @param project_dir [String] 项目目录路径
672
+ # @param tag_prefix [String] tag前缀,用于检查 HEAD 是否有版本 tag,默认 'v'
665
673
  # @return [Hash] 包含 :commit_id, :commit_time, :commit_desc
666
- def get_latest_conventional_commit(project_dir:nil)
674
+ # 优先级:
675
+ # 1. 如果 HEAD 存在版本 tag,直接返回 HEAD(认为有 tag 的 commit 是有效的发布节点)
676
+ # 2. 否则,返回最新的符合 Conventional Commits 规范的提交
677
+ def get_latest_conventional_commit(project_dir:nil, tag_prefix: 'v')
667
678
  result = {
668
679
  commit_id: nil,
669
680
  commit_time: nil,
@@ -672,9 +683,27 @@ module Pindo
672
683
 
673
684
  return result if project_dir.nil? || !File.exist?(project_dir)
674
685
 
675
- Dir.chdir(project_dir) do
676
- # 获取最近 15 条 commit 历史
677
- output = `git log -15 --format="%H|%ci|%s" 2>/dev/null`.strip
686
+ # 获取 git 根目录
687
+ git_root = git_root_directory(local_repo_dir: project_dir)
688
+ return result unless git_root
689
+
690
+ begin
691
+ # 优先级 1: 检查 HEAD 是否有版本 tag
692
+ if head_has_version_tag?(project_dir: git_root, tag_prefix: tag_prefix)
693
+ # HEAD 有版本 tag,直接返回 HEAD 的 commit 信息
694
+ head_info = git!(%W(-C #{git_root} log -1 --format=%H|%ci|%s HEAD)).strip
695
+ if !head_info.empty?
696
+ parts = head_info.split('|', 3)
697
+ result[:commit_id] = parts[0]
698
+ result[:commit_time] = parts[1]
699
+ result[:commit_desc] = parts[2]&.force_encoding('UTF-8')&.scrub('')
700
+ puts " HEAD 存在版本 tag,使用 HEAD commit: #{result[:commit_desc]}"
701
+ return result
702
+ end
703
+ end
704
+
705
+ # 优先级 2: 获取最近 15 条 commit 历史(排除 merge commits)
706
+ output = git!(%W(-C #{git_root} log -15 --no-merges --format=%H|%ci|%s)).strip
678
707
 
679
708
  if output.empty?
680
709
  Funlog.instance.warning("未找到 git commit 历史")
@@ -687,17 +716,20 @@ module Pindo
687
716
  {
688
717
  commit_id: parts[0],
689
718
  commit_time: parts[1],
690
- commit_desc: parts[2]
719
+ commit_desc: parts[2]&.force_encoding('UTF-8')&.scrub('')
691
720
  }
692
721
  end
693
722
 
694
- # 定义符合规范的提交类型
695
- valid_prefixes = ['feat:', 'fix:', 'docs:', 'style:', 'refactor:', 'perf:', 'test:', 'build:', 'ci:', 'chore:', 'revert:']
723
+ # 定义符合规范的提交类型(Conventional Commits 规范)
724
+ valid_types = ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']
696
725
 
697
- # 筛选符合规范的提交
726
+ # 筛选符合规范的提交(支持 type: 和 type(scope): 两种格式)
698
727
  conventional_commits = commits.select do |commit|
699
- desc = commit[:commit_desc].to_s.strip
700
- valid_prefixes.any? { |prefix| desc.downcase.start_with?(prefix) }
728
+ desc = commit[:commit_desc].to_s.strip.downcase
729
+ # 匹配 "type:" 或 "type(" 格式(允许 scope)
730
+ valid_types.any? do |type|
731
+ desc.start_with?("#{type}:") || desc.start_with?("#{type}(")
732
+ end
701
733
  end
702
734
 
703
735
  if conventional_commits.empty?
@@ -717,6 +749,9 @@ module Pindo
717
749
 
718
750
  puts " 找到符合规范的提交: #{result[:commit_desc]}"
719
751
  end
752
+ rescue StandardError => e
753
+ Funlog.instance.warning("获取 conventional commit 失败: #{e.message}")
754
+ return result
720
755
  end
721
756
 
722
757
  result
@@ -780,6 +815,20 @@ module Pindo
780
815
  new_tag
781
816
  end
782
817
 
818
+ # 检查 HEAD 是否已有版本 tag
819
+ # @param project_dir [String] 项目目录
820
+ # @param tag_prefix [String] tag前缀,默认 'v'
821
+ # @return [Boolean] HEAD 是否有版本 tag
822
+ def head_has_version_tag?(project_dir:, tag_prefix: 'v')
823
+ latest_tag = get_latest_version_tag(
824
+ project_dir: project_dir,
825
+ tag_prefix: tag_prefix
826
+ )
827
+ return false if latest_tag.nil?
828
+
829
+ is_tag_at_head?(git_root_dir: project_dir, tag_name: latest_tag)
830
+ end
831
+
783
832
  # 检查仓库是否有未提交的更改(包括未暂存和已暂存的更改)
784
833
  # @param git_root_dir [String] 项目目录路径
785
834
  # @return [Boolean] 如果有未提交的更改返回true,否则返回false
@@ -804,8 +853,8 @@ module Pindo
804
853
  return false if git_root_dir.nil? || tag_name.nil?
805
854
 
806
855
  begin
807
- # 获取tagcommit hash
808
- tag_commit = git!(%W(-C #{git_root_dir} rev-parse #{tag_name})).strip
856
+ # 获取tag指向的commit hash(使用^{commit}解引用,支持annotated tag和lightweight tag)
857
+ tag_commit = git!(%W(-C #{git_root_dir} rev-parse #{tag_name}^{commit})).strip
809
858
  # 获取HEAD的commit hash
810
859
  head_commit = git!(%W(-C #{git_root_dir} rev-parse HEAD)).strip
811
860
 
@@ -819,26 +868,26 @@ module Pindo
819
868
  # 获取未提交文件的处理方式
820
869
  # @param project_dir [String] 项目目录路径
821
870
  # @param interactive [Boolean] 是否允许交互,默认为 true
822
- # @param default_process_type [String] 当没有未提交文件时的默认处理方式,默认为 'skip'
823
- # @return [String] 处理方式 ('commit', 'skip', 'reset', 'stash', 'exit')
824
- def get_uncommitted_files_process_type(project_dir:, interactive: true, default_process_type: 'skip')
871
+ # @param default_process_type [Symbol, nil] 非交互模式下的默认处理方式,默认为 nil
872
+ # @return [Symbol] UncommittedFilesProcessType 枚举常量
873
+ def get_uncommitted_files_process_type(project_dir:, interactive: true, default_process_type: nil)
825
874
  # 1. 检查是否有未提交的文件
826
875
  uncommitted_files = get_uncommitted_files(project_dir: project_dir)
827
876
 
828
- # 2. 如果没有未提交的文件,返回默认处理方式
877
+ # 2. 如果没有未提交的文件,返回 NONE 类型
829
878
  return default_process_type if uncommitted_files.nil? || uncommitted_files.empty?
830
879
 
831
- # 3. 如果非交互模式,默认跳过
880
+ # 3. 如果非交互模式,使用指定的处理方式
832
881
  unless interactive
833
- Funlog.instance.info("当前处于非交互模式,默认跳过未提交文件的处理。")
834
- return 'skip'
882
+ Funlog.instance.info("当前处于非交互模式,使用指定的处理方式: #{default_process_type}")
883
+ return default_process_type
835
884
  end
836
885
 
837
886
  # 4. 获取 Git 根目录并获取当前分支名
838
887
  git_root = git_root_directory(local_repo_dir: project_dir)
839
888
  unless git_root
840
889
  Funlog.instance.fancyinfo_warning("当前目录不是Git仓库")
841
- return 'skip'
890
+ return Pindo::UncommittedFilesProcessType::SKIP
842
891
  end
843
892
 
844
893
  current_branch = git!(%W(-C #{git_root} rev-parse --abbrev-ref HEAD)).strip
@@ -849,11 +898,11 @@ module Pindo
849
898
  cli.choose do |menu|
850
899
  menu.header = "仓库有未提交的修改,请选择处理方式"
851
900
  menu.prompt = "请输入选项(1/2/3/4/5):"
852
- menu.choice("(commit) 全部提交") { 'commit' }
853
- menu.choice("(skip) 跳过并且不作任何修改,继续执行") { 'skip' }
854
- menu.choice("(reset) 全部丢弃更改,回滚代码") { 'reset' }
855
- menu.choice("(stash) 保存到stash区域(该部分代码本次不生效)") { 'stash' }
856
- menu.choice("(exit) 先退出,手动来处理退出") { 'exit' }
901
+ menu.choice("(commit) 全部提交") { Pindo::UncommittedFilesProcessType::COMMIT }
902
+ menu.choice("(skip) 跳过并且不作任何修改,继续执行") { Pindo::UncommittedFilesProcessType::SKIP }
903
+ menu.choice("(reset) 全部丢弃更改,回滚代码") { Pindo::UncommittedFilesProcessType::RESET }
904
+ menu.choice("(stash) 保存到stash区域(该部分代码本次不生效)") { Pindo::UncommittedFilesProcessType::STASH }
905
+ menu.choice("(exit) 先退出,手动来处理退出") { Pindo::UncommittedFilesProcessType::EXIT }
857
906
  end
858
907
  end
859
908
 
@@ -1067,6 +1116,220 @@ module Pindo
1067
1116
  end
1068
1117
  end
1069
1118
 
1119
+ # 初始化并递归更新所有 Git 子模块
1120
+ # @param local_repo_dir [String] Git 仓库目录路径
1121
+ # @param force [Boolean] 是否强制重新初始化子模块
1122
+ # @return [Boolean] 成功返回 true,无子模块返回 false
1123
+ def update_submodules(local_repo_dir:, force: false)
1124
+ raise ArgumentError, "项目目录不能为空" if local_repo_dir.nil?
1125
+
1126
+ # 验证是否是 Git 仓库
1127
+ unless is_git_directory?(local_repo_dir: local_repo_dir)
1128
+ raise Informative, "#{local_repo_dir} 不是有效的 Git 仓库!"
1129
+ end
1130
+
1131
+ # 检查 .gitmodules 文件是否存在
1132
+ git_root = git_root_directory(local_repo_dir: local_repo_dir)
1133
+ gitmodules_path = File.join(git_root, '.gitmodules')
1134
+
1135
+ unless File.exist?(gitmodules_path)
1136
+ Funlog.instance.fancyinfo_warning("当前仓库没有子模块配置文件 (.gitmodules)")
1137
+ return false
1138
+ end
1139
+
1140
+ # 显示子模块信息
1141
+ Funlog.instance.fancyinfo_start("正在读取子模块配置...")
1142
+ submodules_count = parse_gitmodules_count(gitmodules_path)
1143
+ Funlog.instance.fancyinfo_success("检测到 #{submodules_count} 个子模块")
1144
+
1145
+ # 检查子模块路径冲突
1146
+ conflicts = check_submodule_path_conflicts(git_root, gitmodules_path)
1147
+ unless conflicts.empty?
1148
+ if force
1149
+ # 使用 --force 参数,自动清理冲突路径
1150
+ Funlog.instance.fancyinfo_start("检测到 #{conflicts.size} 个路径冲突,正在清理...")
1151
+ conflicts.each do |conflict|
1152
+ begin
1153
+ FileUtils.rm_rf(conflict[:full_path])
1154
+ Funlog.instance.fancyinfo_success("已清理: #{conflict[:path]} (包含 #{conflict[:files]} 个文件)")
1155
+ rescue => e
1156
+ Funlog.instance.fancyinfo_error("清理失败: #{conflict[:path]} - #{e.message}")
1157
+ raise Informative, "无法清理路径冲突:#{conflict[:path]}\n#{e.message}"
1158
+ end
1159
+ end
1160
+ else
1161
+ # 不使用 --force,报错退出
1162
+ Funlog.instance.fancyinfo_error("检测到 #{conflicts.size} 个子模块路径冲突")
1163
+ puts "\n以下路径已存在但不是 Git 仓库,其中的文件会污染子模块:\n".yellow
1164
+ conflicts.each do |conflict|
1165
+ puts " • #{conflict[:path]} (包含 #{conflict[:files]} 个文件)".yellow
1166
+ end
1167
+ puts "\n请选择处理方式:".yellow
1168
+ puts " 1. 使用 --force 参数自动清理这些目录".yellow
1169
+ puts " 2. 手动删除或备份这些目录后重新执行\n".yellow
1170
+ raise Informative, "子模块路径冲突!请处理后重试。"
1171
+ end
1172
+ end
1173
+
1174
+ # 构建 git submodule update 命令
1175
+ args = %W(-C #{git_root} submodule update --init --recursive)
1176
+ args << '--force' if force
1177
+ args << '--progress'
1178
+
1179
+ # 执行子模块更新
1180
+ begin
1181
+ Funlog.instance.fancyinfo_start("正在初始化和更新所有子模块...")
1182
+ output = git!(args)
1183
+ Funlog.instance.fancyinfo_success("子模块更新完成!")
1184
+
1185
+ # 显示子模块状态
1186
+ display_submodules_status(git_root)
1187
+
1188
+ # 检查子模块是否有未提交的内容
1189
+ check_submodule_dirty_state(git_root, gitmodules_path)
1190
+
1191
+ true
1192
+ rescue StandardError => e
1193
+ Funlog.instance.fancyinfo_error("子模块更新失败: #{e.message}")
1194
+ raise Informative, "子模块更新失败!\n详细错误:#{e.message}"
1195
+ end
1196
+ end
1197
+
1198
+ # 解析 .gitmodules 文件,统计子模块数量
1199
+ # @param gitmodules_path [String] .gitmodules 文件路径
1200
+ # @return [Integer] 子模块数量
1201
+ def parse_gitmodules_count(gitmodules_path)
1202
+ return 0 unless File.exist?(gitmodules_path)
1203
+
1204
+ content = File.read(gitmodules_path)
1205
+ # 统计 [submodule "xxx"] 的数量
1206
+ content.scan(/\[submodule\s+"[^"]+"\]/).count
1207
+ end
1208
+
1209
+ # 显示子模块状态信息
1210
+ # @param git_root [String] Git 根目录
1211
+ def display_submodules_status(git_root)
1212
+ begin
1213
+ status_output = git!(%W(-C #{git_root} submodule status --recursive))
1214
+
1215
+ unless status_output.nil? || status_output.strip.empty?
1216
+ puts "\n" + "═" * 60
1217
+ puts "子模块状态".center(60)
1218
+ puts "═" * 60
1219
+ puts status_output
1220
+ puts "═" * 60 + "\n"
1221
+ end
1222
+ rescue StandardError => e
1223
+ Funlog.instance.warning("无法获取子模块状态: #{e.message}")
1224
+ end
1225
+ end
1226
+
1227
+ # 解析 .gitmodules 文件,提取所有子模块路径
1228
+ # @param gitmodules_path [String] .gitmodules 文件路径
1229
+ # @return [Array<String>] 子模块路径列表
1230
+ def parse_submodule_paths(gitmodules_path)
1231
+ return [] unless File.exist?(gitmodules_path)
1232
+
1233
+ content = File.read(gitmodules_path)
1234
+ paths = []
1235
+
1236
+ # 匹配 path = xxx 行
1237
+ content.scan(/^\s*path\s*=\s*(.+)$/) do |match|
1238
+ paths << match[0].strip
1239
+ end
1240
+
1241
+ paths
1242
+ end
1243
+
1244
+ # 检查子模块路径冲突
1245
+ # @param git_root [String] Git 根目录
1246
+ # @param gitmodules_path [String] .gitmodules 文件路径
1247
+ # @return [Array<Hash>] 冲突列表,每个元素包含 :path, :full_path, :files
1248
+ def check_submodule_path_conflicts(git_root, gitmodules_path)
1249
+ submodule_paths = parse_submodule_paths(gitmodules_path)
1250
+ conflicts = []
1251
+
1252
+ submodule_paths.each do |path|
1253
+ full_path = File.join(git_root, path)
1254
+
1255
+ # 检查:路径存在 && 不是 git 仓库 && 不为空
1256
+ if File.exist?(full_path) && File.directory?(full_path)
1257
+ git_dir = File.join(full_path, '.git')
1258
+
1259
+ unless File.exist?(git_dir)
1260
+ # 检查目录是否为空
1261
+ unless Dir.empty?(full_path)
1262
+ conflicts << {
1263
+ path: path,
1264
+ full_path: full_path,
1265
+ files: Dir.children(full_path).size
1266
+ }
1267
+ end
1268
+ end
1269
+ end
1270
+ end
1271
+
1272
+ conflicts
1273
+ end
1274
+
1275
+ # 检查子模块是否有未提交的内容(脏数据)
1276
+ # @param git_root [String] Git 根目录
1277
+ # @param gitmodules_path [String] .gitmodules 文件路径
1278
+ def check_submodule_dirty_state(git_root, gitmodules_path)
1279
+ submodule_paths = parse_submodule_paths(gitmodules_path)
1280
+ dirty_submodules = []
1281
+
1282
+ submodule_paths.each do |path|
1283
+ full_path = File.join(git_root, path)
1284
+ next unless File.exist?(full_path)
1285
+
1286
+ begin
1287
+ # 使用 git status --porcelain 检查是否有修改
1288
+ status = git!(%W(-C #{full_path} status --porcelain))
1289
+
1290
+ unless status.nil? || status.strip.empty?
1291
+ # 统计修改类型
1292
+ lines = status.strip.split("\n")
1293
+ untracked = lines.count { |line| line.start_with?('??') }
1294
+ modified = lines.count { |line| line.start_with?(' M', 'M ', 'MM') }
1295
+ added = lines.count { |line| line.start_with?('A ') }
1296
+ deleted = lines.count { |line| line.start_with?(' D', 'D ') }
1297
+
1298
+ dirty_submodules << {
1299
+ path: path,
1300
+ untracked: untracked,
1301
+ modified: modified,
1302
+ added: added,
1303
+ deleted: deleted,
1304
+ total: lines.size
1305
+ }
1306
+ end
1307
+ rescue => e
1308
+ # 忽略错误,继续检查其他子模块
1309
+ end
1310
+ end
1311
+
1312
+ # 如果有脏数据,显示警告
1313
+ unless dirty_submodules.empty?
1314
+ puts "\n"
1315
+ Funlog.instance.fancyinfo_warning("⚠️ 检测到 #{dirty_submodules.size} 个子模块包含未提交的内容")
1316
+ puts "\n以下子模块有未提交的修改:\n".yellow
1317
+
1318
+ dirty_submodules.each do |submodule|
1319
+ puts " • #{submodule[:path]}:".yellow
1320
+ puts " - 未跟踪文件: #{submodule[:untracked]} 个".yellow if submodule[:untracked] > 0
1321
+ puts " - 已修改文件: #{submodule[:modified]} 个".yellow if submodule[:modified] > 0
1322
+ puts " - 已添加文件: #{submodule[:added]} 个".yellow if submodule[:added] > 0
1323
+ puts " - 已删除文件: #{submodule[:deleted]} 个".yellow if submodule[:deleted] > 0
1324
+ end
1325
+
1326
+ puts "\n建议处理方式:".yellow
1327
+ puts " 1. 进入子模块目录处理这些修改:cd #{dirty_submodules.first[:path]}".yellow
1328
+ puts " 2. 在子模块中提交修改:git add . && git commit -m 'message'".yellow
1329
+ puts " 3. 或者丢弃修改:git clean -fd && git reset --hard\n".yellow
1330
+ end
1331
+ end
1332
+
1070
1333
  end
1071
1334
  end
1072
1335
  end
@@ -41,21 +41,30 @@ module Pindo
41
41
 
42
42
  # puts body_str
43
43
 
44
- con = Faraday.new
44
+ con = Faraday.new
45
45
  res = con.post do |req|
46
46
  req.url url
47
47
  req.headers['Content-Type'] = 'application/json'
48
48
  req.body = body_str
49
49
  end
50
50
 
51
- # puts res.body
51
+ # 解析 JSON 响应
52
+ begin
53
+ response_body = JSON.parse(res.body)
54
+ rescue JSON::ParserError => e
55
+ puts "[❌] Gitee API 响应解析失败: #{e.message}"
56
+ puts "响应内容: #{res.body}"
57
+ return false
58
+ end
52
59
 
53
- # puts res.body
54
- if res.body['error'].nil?
55
- puts "Create success !!!"
60
+ # 检查响应状态
61
+ if response_body['error'].nil? && response_body['message'].nil?
62
+ puts "[✔] Gitee 仓库创建成功: #{repo_name}"
56
63
  return true
57
64
  else
58
- puts res.body['error']
65
+ error_msg = response_body['error'] || response_body['message'] || '未知错误'
66
+ puts "[❌] Gitee 仓库创建失败: #{error_msg}"
67
+ puts "响应详情: #{response_body}" if ENV['PINDO_DEBUG']
59
68
  return false
60
69
  end
61
70
  end
@@ -130,9 +130,10 @@ module Pindo
130
130
 
131
131
  # Git 参数
132
132
  @args_release_branch = @options[:release_branch] || 'master'
133
- @args_ver_inc = Pindo::Options::GitOptions.parse_version_increase_type(@options[:ver_inc] || 'mini')
134
- @args_tag_type = Pindo::Options::GitOptions.parse_create_tag_type(@options[:tag_type] || 'new')
133
+ @args_ver_inc = Pindo::Options::GitOptions.parse_version_increase_type(@options[:ver_inc])
134
+ @args_tag_type = Pindo::Options::GitOptions.parse_create_tag_type(@options[:tag_type])
135
135
  @args_tag_pre = @options[:tag_pre] || 'v'
136
+ @args_git_commit = Pindo::Options::GitOptions.parse_git_commit_type(@options[:git_commit])
136
137
 
137
138
  super
138
139
  @additional_args = argv.remainder!
@@ -212,7 +213,8 @@ module Pindo
212
213
  # 提前询问用户如何处理未提交的文件
213
214
  process_type = Pindo::GitHandler.get_uncommitted_files_process_type(
214
215
  project_dir: config[:project_path],
215
- interactive: true
216
+ interactive: @args_git_commit.nil?,
217
+ default_process_type: @args_git_commit
216
218
  )
217
219
  git_commit_task = Pindo::TaskSystem::GitCommitTask.new(
218
220
  config[:project_path],
@@ -341,7 +343,7 @@ module Pindo
341
343
  git_app_info_obj, git_workflow_info = PgyerHelper.share_instace.prepare_upload(
342
344
  working_directory: config[:project_path],
343
345
  proj_name: @args_proj_name,
344
- package_type: "", # package_type 在 manage_type=git 时会被忽略
346
+ package_type: nil, # package_type 在 manage_type=git 时会被忽略
345
347
  manage_type: "git"
346
348
  )
347
349
 
@@ -379,7 +381,7 @@ module Pindo
379
381
  git_app_info_obj, git_workflow_info = PgyerHelper.share_instace.prepare_upload(
380
382
  working_directory: config[:project_path],
381
383
  proj_name: @args_proj_name,
382
- package_type: "",
384
+ package_type: nil,
383
385
  manage_type: "git"
384
386
  )
385
387
 
@@ -6,6 +6,7 @@ require 'fileutils'
6
6
  require 'pindo/base/executable'
7
7
  require 'pindo/base/git_handler'
8
8
  require 'pindo/module/build/build_helper'
9
+ require 'pindo/options/helpers/git_constants'
9
10
  require 'pindo/module/xcode/xcode_app_config'
10
11
  require 'pindo/module/xcode/xcode_build_helper'
11
12
  require 'pindo/config/ios_config_parser'
@@ -94,8 +95,9 @@ module Pindo
94
95
 
95
96
  # Git 参数
96
97
  @args_release_branch = @options[:release_branch] || 'master'
97
- @args_tag_type = Pindo::Options::GitOptions.parse_create_tag_type(@options[:tag_type] || 'new')
98
+ @args_tag_type = Pindo::Options::GitOptions.parse_create_tag_type(@options[:tag_type])
98
99
  @args_tag_pre = @options[:tag_pre] || 'ios_release_'
100
+ @args_git_commit = Pindo::Options::GitOptions.parse_git_commit_type(@options[:git_commit]) || Pindo::UncommittedFilesProcessType::SKIP
99
101
 
100
102
  super(argv)
101
103
  end
@@ -191,7 +193,8 @@ module Pindo
191
193
  # 提前询问用户如何处理未提交的文件
192
194
  process_type = Pindo::GitHandler.get_uncommitted_files_process_type(
193
195
  project_dir: project_path,
194
- interactive: true
196
+ interactive: false,
197
+ default_process_type: @args_git_commit
195
198
  )
196
199
 
197
200
  git_commit_task = Pindo::TaskSystem::GitCommitTask.new(
@@ -166,7 +166,13 @@ module Pindo
166
166
  renew_flag: @renew_cert_flag
167
167
  )
168
168
  config = FastlaneCore::Configuration.create(Match::Options.available_options, values)
169
+
170
+ # 注意:fastlane match 可能会产生 '--template_name' 弃用警告
171
+ # 这是 fastlane 内部使用的参数,在 App Store Connect API v3.8.0 中已弃用
172
+ # 该警告不影响功能,fastlane 会自动处理兼容性
173
+ # 参考:https://docs.fastlane.tools/actions/match/#managed-capabilities
169
174
  Match::Runner.new.run(config)
175
+
170
176
  provisioning_info_array = Pindo::XcodeCertHelper.create_provisioning_info_array(
171
177
  build_type: @cert_type,
172
178
  platform_type: @platform_type