pindo 5.18.5 → 5.18.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c5570599c0e84edb77c22267e890c57f736800bc046581d7a902e28b4b5c33f
4
- data.tar.gz: 46b689df14719e8a7b0ef3af23e209d9e70484b3915b45a02c8c014ab1d2ddb3
3
+ metadata.gz: 507433d1ad0c9e38e5c143ed5bd5464a0a4935b274803aa8ec28d48f8edd7d8c
4
+ data.tar.gz: 1e5e3fa81ef4c65da10dd1a64d234e68098a5539c29e8f13674245b7600a6ad5
5
5
  SHA512:
6
- metadata.gz: c2e5bb57d0a922210eff69f20fc7aabdf42660f01073e2a56378f32ff2380173eedd37b00e54f368051566903ac95c37f7139b17760c19c17ce889422e7078a8
7
- data.tar.gz: 84d21199d7e08ad161a286090fbbc40c0a421f021f654dfde9b663fffc2c0921927e6a95a7cc0db21b074a1a4af0dcd28e354183bcf4bd5bb318dc178cf0a37b
6
+ metadata.gz: e49b021ce357d5f50a53bf308ff4267fa82cd2ff89bbdf8dd3649b0b871eaac6909d65abce3e9e72145724ccde41aae8a9ded3f94052b880f520f1eeb947693d
7
+ data.tar.gz: 27bda2c8b2a7cd1e41fdfcf510508628cc4b2fbe3481b2a83077075a68097989b69215d3a4e3cb07257dec05a0f6ad812645923d83b4a5bb5ba9050fb570bba7
@@ -2,7 +2,7 @@ require 'highline/import'
2
2
  require 'fileutils'
3
3
  require 'pindo/base/executable'
4
4
  require 'pindo/module/build/build_helper'
5
- require 'pindo/module/build/git_repo_helper'
5
+ require 'pindo/module/utils/git_repo_helper'
6
6
  require 'pindo/module/android/android_build_helper'
7
7
  require 'pindo/module/android/android_config_helper'
8
8
  require 'pindo/module/task/task_manager'
@@ -4,7 +4,7 @@ require 'find'
4
4
  require 'fileutils'
5
5
  require 'pindo/base/executable'
6
6
  require 'pindo/module/build/build_helper'
7
- require 'pindo/module/build/git_repo_helper'
7
+ require 'pindo/module/utils/git_repo_helper'
8
8
  require 'pindo/module/xcode/xcode_build_config'
9
9
  require 'pindo/module/xcode/xcode_build_helper'
10
10
  require 'pindo/config/ios_config_parser'
@@ -4,7 +4,7 @@ require 'find'
4
4
  require 'fileutils'
5
5
  require 'pindo/base/executable'
6
6
  require 'pindo/module/build/build_helper'
7
- require 'pindo/module/build/git_repo_helper'
7
+ require 'pindo/module/utils/git_repo_helper'
8
8
  require 'pindo/module/xcode/xcode_build_config'
9
9
  require 'pindo/module/xcode/xcode_build_helper'
10
10
  require 'pindo/module/task/task_manager'
@@ -3,7 +3,7 @@ require 'fileutils'
3
3
  require 'json'
4
4
  require 'faraday'
5
5
  require 'pindo/module/pgyer/pgyerhelper'
6
- require 'pindo/module/build/git_repo_helper'
6
+ require 'pindo/module/utils/git_repo_helper'
7
7
  require 'pindo/module/build/build_helper'
8
8
  require 'pindo/module/task/task_manager'
9
9
  require 'pindo/module/task/model/jps/jps_upload_task'
@@ -5,7 +5,7 @@ require 'fileutils'
5
5
  require 'pindo/base/executable'
6
6
  require 'pindo/module/unity/unity_helper'
7
7
  require 'pindo/module/build/build_helper'
8
- require 'pindo/module/build/git_repo_helper'
8
+ require 'pindo/module/utils/git_repo_helper'
9
9
  require 'pindo/module/task/pindo_task'
10
10
  require 'pindo/module/task/task_manager'
11
11
  require 'pindo/module/task/model/build_task'
@@ -1,6 +1,8 @@
1
1
  require 'highline/import'
2
2
  require 'fileutils'
3
3
  require 'pindo/options/options'
4
+ require 'pindo/module/utils/git_repo_helper'
5
+ require 'pindo/module/utils/git_hook_helper'
4
6
 
5
7
  module Pindo
6
8
  class Command
@@ -8,28 +10,31 @@ module Pindo
8
10
  class Repoinit < Utils
9
11
 
10
12
  # 命令的简要说明 - 初始化git仓库配置
11
- self.summary = '初始化git仓库中git cliff的配置'
13
+ self.summary = '初始化git仓库配置(.gitignore、commit统计hook、git-cliff'
12
14
 
13
15
  # 命令的详细说明,包含用法示例
14
16
  self.description = <<-DESC
15
- 初始化git仓库中git cliff的配置。
17
+ 初始化git仓库配置。
16
18
 
17
19
  支持功能:
18
20
 
19
- * 初始化git cliff配置
21
+ * 检查并补充 .gitignore 规则(默认)
20
22
 
21
- * 生成配置文件
23
+ * 安装 commit 变更统计 hook(默认,支持普通仓库/子模块/Husky)
24
+
25
+ * 初始化 git-cliff 配置(需指定 --cliff)
22
26
 
23
27
  使用示例:
24
28
 
25
- $ pindo utils repoinit # 初始化配置
29
+ $ pindo utils repoinit # 检查 .gitignore + 安装统计 hook
30
+ $ pindo utils repoinit --cliff # 同时初始化 git-cliff
26
31
  DESC
27
32
 
28
33
  # 命令的参数列表
29
34
  self.arguments = []
30
35
 
31
36
  def self.option_items
32
- @option_items ||= Pindo::Options::UtilsOptions.select(:mode, :retag)
37
+ @option_items ||= Pindo::Options::UtilsOptions.select(:cliff)
33
38
  end
34
39
 
35
40
  def self.options
@@ -39,22 +44,34 @@ module Pindo
39
44
  def initialize(argv)
40
45
  @options = initialize_options(argv)
41
46
 
42
- @mode = @options[:mode] || 'minor'
43
- @force_retag = @options[:retag] || false
44
-
45
- unless ['major', 'minor', 'patch'].include?(@mode)
46
- raise Informative, "mode参数必须是 major, minor 或 patch"
47
- end
47
+ @enable_cliff = @options[:cliff] || false
48
48
 
49
49
  super
50
50
  @additional_args = argv.remainder!
51
51
  end
52
52
 
53
53
  def run
54
-
54
+
55
55
  pindo_project_dir = Dir.pwd
56
- build_helper = Pindo::BuildHelper.share_instance
57
- Pindo::GitRepoHelper.share_instance.check_check_and_install_cliff(pindo_project_dir)
56
+
57
+ unless Pindo::GitHandler.is_git_directory?(local_repo_dir: pindo_project_dir)
58
+ Funlog.instance.fancyinfo_warning("当前目录不是 Git 仓库,跳过初始化")
59
+ return
60
+ end
61
+
62
+ current_git_root_path = Pindo::GitHandler.git_root_directory(local_repo_dir: pindo_project_dir)
63
+ git_repo_helper = Pindo::GitRepoHelper.share_instance
64
+
65
+ # 1. 检查并补充 .gitignore 规则
66
+ git_repo_helper.check_gitignore(current_git_root_path)
67
+
68
+ # 2. 安装 commit 变更统计 hook(主仓库 + 子模块,支持 Husky)
69
+ Pindo::GitHookHelper.share_instance.install_commit_stats_hook(current_git_root_path)
70
+
71
+ # 3. 指定 --cliff 参数时,才检测安装 git-cliff 并初始化配置
72
+ if @enable_cliff
73
+ git_repo_helper.check_and_install_cliff(pindo_project_dir)
74
+ end
58
75
 
59
76
  end
60
77
 
@@ -1,7 +1,7 @@
1
1
  require 'highline/import'
2
2
  require 'fileutils'
3
3
  require 'pindo/base/git_handler'
4
- require 'pindo/module/build/git_repo_helper'
4
+ require 'pindo/module/utils/git_repo_helper'
5
5
  require 'pindo/module/task/task_manager'
6
6
  require 'pindo/module/task/model/git/git_commit_task'
7
7
  require 'pindo/module/task/model/git/git_tag_task'
@@ -6,7 +6,7 @@ require_relative 'android_project_helper'
6
6
  require_relative '../../base/executable'
7
7
  require_relative 'android_res_helper'
8
8
  require 'pindo/module/utils/file_downloader'
9
- require 'pindo/module/build/git_repo_helper'
9
+ require 'pindo/module/utils/git_repo_helper'
10
10
 
11
11
  module Pindo
12
12
 
@@ -1443,7 +1443,6 @@ module Pindo
1443
1443
 
1444
1444
  def get_app_list_in_pgyer()
1445
1445
 
1446
-
1447
1446
  params = {
1448
1447
  pageNo:1,
1449
1448
  pageSize:1000
@@ -1452,28 +1451,21 @@ module Pindo
1452
1451
  app_info_list = Pindoconfig.instance.get_pgyerapps_info_list()
1453
1452
  if app_info_list.nil?
1454
1453
  res_data = @pgyer_client.get_project_list(params:params)
1455
- # puts JSON.pretty_generate(res_data)
1456
- if !res_data["data"].nil? && res_data["data"].size > 0
1457
- app_info_list = res_data["data"]
1458
- Pindoconfig.instance.set_pgyerapps_info_list(app_info_list:app_info_list)
1459
- end
1460
- end
1461
-
1462
- if app_info_list.nil?
1463
- res_data = @pgyer_client.get_project_list(params:params)
1464
- # puts JSON.pretty_generate(res_data)
1465
- if !res_data["data"].nil? && res_data["data"].size > 0
1454
+ if res_data && !res_data["data"].nil? && res_data["data"].size > 0
1466
1455
  app_info_list = res_data["data"]
1467
1456
  Pindoconfig.instance.set_pgyerapps_info_list(app_info_list:app_info_list)
1457
+ elsif res_data
1458
+ error_msg = res_data["msg"] || "未知错误"
1459
+ error_code = res_data["code"]
1460
+ Funlog.instance.fancyinfo_error("拉取项目列表失败: [#{error_code}] #{error_msg}")
1468
1461
  end
1469
1462
  end
1470
1463
 
1471
1464
  if app_info_list.nil?
1472
1465
  Funlog.instance.fancyinfo_error("拉取app信息列表失败!")
1473
- raise Informative, "JPS网络数据异常!!!"
1466
+ raise Informative, "JPS网络数据异常,请检查网络连接或重新登录(pindo repo login)"
1474
1467
  end
1475
1468
 
1476
-
1477
1469
  return app_info_list
1478
1470
  end
1479
1471
 
@@ -1,6 +1,6 @@
1
1
  require_relative 'android_build_task'
2
2
  require 'pindo/module/build/build_helper'
3
- require 'pindo/module/build/git_repo_helper'
3
+ require 'pindo/module/utils/git_repo_helper'
4
4
  require 'pindo/base/git_handler'
5
5
  require 'pindo/module/android/android_config_helper'
6
6
  require 'pindo/module/android/android_build_helper'
@@ -1,7 +1,7 @@
1
1
  require_relative 'ios_build_task'
2
2
  require 'pindo/base/git_handler'
3
3
  require 'pindo/module/build/build_helper'
4
- require 'pindo/module/build/git_repo_helper'
4
+ require 'pindo/module/utils/git_repo_helper'
5
5
  require 'pindo/module/xcode/xcode_build_config'
6
6
  require 'pindo/module/xcode/xcode_build_helper'
7
7
  require 'pindo/module/xcode/xcode_app_config'
@@ -222,7 +222,7 @@ module Pindo
222
222
  increase_buildnumber_in_file(app_config_file)
223
223
 
224
224
  # 提交到配置仓库
225
- require 'pindo/module/build/git_repo_helper'
225
+ require 'pindo/module/utils/git_repo_helper'
226
226
  git_helper = Pindo::GitRepoHelper.share_instance
227
227
  git_helper.git_addpush_repo(
228
228
  path: app_config_repo_dir,
@@ -271,7 +271,7 @@ module Pindo
271
271
  build_verify_json = nil
272
272
  end
273
273
 
274
- require 'pindo/module/build/git_repo_helper'
274
+ require 'pindo/module/utils/git_repo_helper'
275
275
  git_helper = Pindo::GitRepoHelper.share_instance
276
276
  release_code_commit = git_helper.git_latest_commit_id(local_repo_dir: @project_path)
277
277
 
@@ -1,7 +1,6 @@
1
1
  require_relative 'ios_build_task'
2
2
  require 'pindo/module/build/build_helper'
3
- require 'pindo/module/build/git_repo_helper'
4
- require 'pindo/module/build/git_repo_helper'
3
+ require 'pindo/module/utils/git_repo_helper'
5
4
  require 'pindo/module/xcode/xcode_build_config'
6
5
  require 'pindo/module/xcode/xcode_build_helper'
7
6
  require 'pindo/module/xcode/xcode_app_config'
@@ -170,7 +169,7 @@ module Pindo
170
169
  increase_buildnumber_in_file(app_config_file)
171
170
 
172
171
  # 提交到配置仓库
173
- require 'pindo/module/build/git_repo_helper'
172
+ require 'pindo/module/utils/git_repo_helper'
174
173
  git_helper = Pindo::GitRepoHelper.share_instance
175
174
  git_helper.git_addpush_repo(
176
175
  path: app_config_repo_dir,
@@ -219,7 +218,7 @@ module Pindo
219
218
  build_verify_json = nil
220
219
  end
221
220
 
222
- require 'pindo/module/build/git_repo_helper'
221
+ require 'pindo/module/utils/git_repo_helper'
223
222
  git_helper = Pindo::GitRepoHelper.share_instance
224
223
  release_code_commit = git_helper.git_latest_commit_id(local_repo_dir: @project_path)
225
224
 
@@ -1,7 +1,6 @@
1
1
  require_relative 'ios_build_task'
2
2
  require 'pindo/module/build/build_helper'
3
- require 'pindo/module/build/git_repo_helper'
4
- require 'pindo/module/build/git_repo_helper'
3
+ require 'pindo/module/utils/git_repo_helper'
5
4
  require 'pindo/module/xcode/xcode_build_config'
6
5
  require 'pindo/module/xcode/xcode_build_helper'
7
6
  require 'pindo/module/xcode/cocoapods_helper'
@@ -1,6 +1,7 @@
1
1
  require 'pindo/module/task/model/git_task'
2
2
  require 'pindo/base/git_handler'
3
- require 'pindo/module/build/git_repo_helper'
3
+ require 'pindo/module/utils/git_repo_helper'
4
+ require 'pindo/module/utils/git_hook_helper'
4
5
  require 'pindo/options/helpers/git_constants'
5
6
 
6
7
  module Pindo
@@ -86,7 +87,10 @@ module Pindo
86
87
  # 2. 检查并修复 .gitignore(可能创建新提交,但 fixed_version 已经确定)
87
88
  git_repo_helper.check_gitignore(root_dir)
88
89
 
89
- # 3. 检查并处理未提交的文件(使用 GitHandler)
90
+ # 3. 检查并安装 commit 变更统计 hook(主仓库 + 子模块)
91
+ Pindo::GitHookHelper.share_instance.install_commit_stats_hook(root_dir)
92
+
93
+ # 4. 检查并处理未提交的文件(使用 GitHandler)
90
94
  # process_type 已经在初始化时确定(由外部传入或默认为 skip)
91
95
  begin
92
96
  Pindo::GitHandler.process_need_add_files(
@@ -120,7 +124,7 @@ module Pindo
120
124
  }
121
125
  end
122
126
 
123
- # 4. Stash 工作目录的残留修改(为分支同步做准备)
127
+ # 5. Stash 工作目录的残留修改(为分支同步做准备)
124
128
  coding_branch = nil
125
129
  stash_saved = false
126
130
  stash_name = "pindo_stash_#{Time.now.strftime('%Y%m%d%H%M%S')}_#{rand(1000)}"
@@ -132,13 +136,13 @@ module Pindo
132
136
  end
133
137
 
134
138
  begin
135
- # 5. 获取当前分支
139
+ # 6. 获取当前分支
136
140
  coding_branch = get_current_branch_name
137
141
 
138
- # 6. 检查并推送本地提交到远程(确保本地和远程同步)
142
+ # 7. 检查并推送本地提交到远程(确保本地和远程同步)
139
143
  Pindo::GitHandler.check_unpushed_commits(project_dir: root_dir, branch: coding_branch)
140
144
 
141
- # 7. 分支同步:将 coding_branch 和 release_branch 双向合并(确保两分支在同一节点)
145
+ # 8. 分支同步:将 coding_branch 和 release_branch 双向合并(确保两分支在同一节点)
142
146
  if coding_branch != @release_branch
143
147
  Funlog.instance.fancyinfo_start("开始同步 #{coding_branch} 和 #{@release_branch} 分支")
144
148
  Pindo::GitHandler.merge_branches(
@@ -150,7 +154,7 @@ module Pindo
150
154
  Funlog.instance.fancyinfo_success("分支同步完成,#{coding_branch} 和 #{@release_branch} 现在在同一节点")
151
155
  end
152
156
 
153
- # 8. 计算 build_version
157
+ # 9. 计算 build_version
154
158
  # 优先级 1: 如果指定了 fixed_version(外部传入或从 HEAD tag 获取),使用它
155
159
  if @fixed_version && !@fixed_version.empty?
156
160
  @build_version = @fixed_version
@@ -172,7 +176,7 @@ module Pindo
172
176
  end
173
177
 
174
178
  ensure
175
- # 9. 还原 stash(无论成功或失败都执行)
179
+ # 10. 还原 stash(无论成功或失败都执行)
176
180
  if stash_saved
177
181
  begin
178
182
  stash_list = Pindo::GitHandler.git!(%W(-C #{root_dir} stash list)).strip
@@ -193,7 +197,7 @@ module Pindo
193
197
  end
194
198
 
195
199
 
196
- # 10. 显示主仓库和所有子仓库的 commit info
200
+ # 11. 显示主仓库和所有子仓库的 commit info
197
201
  display_all_repos_commit_info(root_dir)
198
202
 
199
203
  {
@@ -205,12 +209,12 @@ module Pindo
205
209
  end
206
210
 
207
211
  # ----------------------------------------------------
208
- # 10. 显示主仓库和所有子仓库的 commit id 和 commit message
212
+ # 11. 显示主仓库和所有子仓库的 commit id 和 commit message
209
213
  # ----------------------------------------------------
210
214
  def display_all_repos_commit_info(root_dir)
211
215
  Funlog.instance.fancyinfo_start("仓库及子模块 Commit 信息概览")
212
216
 
213
- # 10.1 主仓库
217
+ # 11.1 主仓库
214
218
  begin
215
219
  main_info = Pindo::GitHandler.git!(%W(-C #{root_dir} log -1 --format=%h|%s)).strip
216
220
  main_id, main_msg = main_info.split('|', 2)
@@ -221,7 +225,7 @@ module Pindo
221
225
  Funlog.instance.warning("获取主仓库信息失败: #{e.message}")
222
226
  end
223
227
 
224
- # 10.2 子模块
228
+ # 11.2 子模块
225
229
  begin
226
230
  # 使用 git submodule status 获取列表,更可靠
227
231
  submodule_status_output = Pindo::GitHandler.git!(%W(-C #{root_dir} submodule status --recursive)).strip
@@ -1,6 +1,6 @@
1
1
  require 'pindo/module/task/model/git_task'
2
2
  require 'pindo/base/git_handler'
3
- require 'pindo/module/build/git_repo_helper'
3
+ require 'pindo/module/utils/git_repo_helper'
4
4
 
5
5
  module Pindo
6
6
  module TaskSystem
@@ -1,6 +1,6 @@
1
1
  require 'pindo/module/task/pindo_task'
2
2
  require 'pindo/base/git_handler'
3
- require 'pindo/module/build/git_repo_helper'
3
+ require 'pindo/module/utils/git_repo_helper'
4
4
 
5
5
  module Pindo
6
6
  module TaskSystem
@@ -0,0 +1,370 @@
1
+ require 'singleton'
2
+ require 'fileutils'
3
+ require 'pindo/base/git_handler'
4
+
5
+ module Pindo
6
+ class GitHookHelper
7
+ include Singleton
8
+
9
+ class << self
10
+ def share_instance
11
+ instance
12
+ end
13
+ end
14
+
15
+ # Hook 版本号,修改 hook 内容时递增此版本
16
+ HOOK_VERSION = '1.0.0'
17
+
18
+ # 版本标记前缀(用于在 hook 文件中识别版本)
19
+ HOOK_VERSION_PREFIX = '# pindo_commit_stats_hook_version:'
20
+
21
+ # 变更统计关键字(用于旧版 hook 特征识别)
22
+ COMMIT_STATS_MARKER = '变更统计'
23
+
24
+ # 为仓库及其子模块安装 prepare-commit-msg hook(自动追加变更统计)
25
+ # 支持普通仓库、子模块、Husky 项目
26
+ # 版本匹配则跳过,版本不匹配或无版本则覆盖
27
+ # @param git_root_dir [String] Git 仓库根目录
28
+ def install_commit_stats_hook(git_root_dir)
29
+ Funlog.instance.fancyinfo_start("安装 commit 变更统计 hook (v#{HOOK_VERSION})...")
30
+
31
+ # 1. 主仓库
32
+ install_hook_to_repo(git_root_dir)
33
+
34
+ # 2. 子模块
35
+ begin
36
+ submodule_output = Pindo::GitHandler.git!(%W(-C #{git_root_dir} submodule status --recursive)).strip
37
+ unless submodule_output.empty?
38
+ submodule_output.each_line do |line|
39
+ parts = line.strip.split(' ')
40
+ next if parts.length < 2
41
+ sub_path = File.join(git_root_dir, parts[1])
42
+ if File.directory?(sub_path) && (File.directory?(File.join(sub_path, '.git')) || File.file?(File.join(sub_path, '.git')))
43
+ install_hook_to_repo(sub_path)
44
+ end
45
+ end
46
+ end
47
+ rescue => e
48
+ Funlog.instance.fancyinfo_warning("子模块检测跳过: #{e.message}") if ENV['PINDO_DEBUG']
49
+ end
50
+
51
+ Funlog.instance.fancyinfo_success("commit 变更统计 hook 安装完成!")
52
+ end
53
+
54
+ private
55
+
56
+ # 从文件内容中提取 hook 版本号
57
+ # @param content [String] 文件内容
58
+ # @return [String, nil] 版本号,未找到返回 nil
59
+ def extract_hook_version(content)
60
+ if content =~ /#{Regexp.escape(HOOK_VERSION_PREFIX)}\s*(.+)/
61
+ $1.strip
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ # 判断是否需要安装/更新 hook
68
+ # 通过 pindo 版本标记识别自己安装的 hook,绝不覆盖第三方 hook
69
+ # 同时兼容旧版批量脚本安装的无版本标记 hook(通过特征识别)
70
+ # @param content [String] 已有文件内容
71
+ # @return [Symbol]
72
+ # :skip — 版本一致,无需操作
73
+ # :update — pindo 安装的旧版本,需覆盖升级
74
+ # :install — 文件为空或不存在,首次安装
75
+ # :foreign — 非 pindo 安装的已有 hook,跳过不干预
76
+ def check_hook_status(content)
77
+ existing_version = extract_hook_version(content)
78
+
79
+ if existing_version
80
+ # 有 pindo 版本标记 → 是 pindo 自己安装的
81
+ existing_version == HOOK_VERSION ? :skip : :update
82
+ elsif pindo_legacy_hook?(content)
83
+ # 无版本标记但特征匹配旧版批量脚本安装的 hook → 视为旧版,允许升级
84
+ :update
85
+ elsif !content.strip.empty?
86
+ # 无任何 pindo 特征且文件非空 → 第三方 hook,不干预
87
+ :foreign
88
+ else
89
+ # 文件为空 → 首次安装
90
+ :install
91
+ end
92
+ end
93
+
94
+ # 判断是否为旧版批量脚本(apply-to-all-repos)安装的 hook
95
+ # 通过多个核心特征片段组合识别,至少命中 3 个才视为旧版 pindo hook
96
+ # @param content [String] 文件内容
97
+ # @return [Boolean]
98
+ def pindo_legacy_hook?(content)
99
+ fingerprints = [
100
+ 'COMMIT_MSG_FILE=$1', # hook 参数接收
101
+ 'git diff --cached -M --numstat', # 暂存区统计命令
102
+ "stats_msg=\"#{COMMIT_STATS_MARKER}:", # 统计消息拼接
103
+ 'COMMIT_SOURCE=$2', # hook 第二参数
104
+ ]
105
+ matched = fingerprints.count { |fp| content.include?(fp) }
106
+ matched >= 3
107
+ end
108
+
109
+ # 为单个仓库安装 hook
110
+ # @param repo_path [String] 仓库路径
111
+ def install_hook_to_repo(repo_path)
112
+ repo_name = File.basename(repo_path)
113
+
114
+ # 优先检测 Husky 项目
115
+ husky_dir = File.join(repo_path, '.husky')
116
+ if File.directory?(husky_dir)
117
+ install_hook_to_husky(repo_path, husky_dir)
118
+ return
119
+ end
120
+
121
+ # 普通仓库 / 子模块
122
+ git_dir = resolve_git_dir(repo_path)
123
+ unless git_dir && File.directory?(git_dir)
124
+ Funlog.instance.fancyinfo_error(" [#{repo_name}] 无法找到 Git 目录")
125
+ return
126
+ end
127
+
128
+ hook_path = File.join(git_dir, 'hooks', 'prepare-commit-msg')
129
+ FileUtils.mkdir_p(File.join(git_dir, 'hooks'))
130
+
131
+ if File.exist?(hook_path)
132
+ status = check_hook_status(File.read(hook_path))
133
+ case status
134
+ when :skip
135
+ Funlog.instance.fancyinfo_success(" [#{repo_name}] hook 已是最新版本 (v#{HOOK_VERSION}),跳过")
136
+ return
137
+ when :foreign
138
+ Funlog.instance.fancyinfo_warning(" [#{repo_name}] 检测到非 pindo 安装的 hook,跳过")
139
+ return
140
+ when :update
141
+ old_version = extract_hook_version(File.read(hook_path)) || '未知'
142
+ Funlog.instance.fancyinfo_update(" [#{repo_name}] hook 版本更新 (v#{old_version} → v#{HOOK_VERSION})")
143
+ end
144
+ end
145
+
146
+ File.write(hook_path, full_hook_content)
147
+ File.chmod(0755, hook_path)
148
+ Funlog.instance.fancyinfo_success(" [#{repo_name}] hook 已安装 (v#{HOOK_VERSION})")
149
+ end
150
+
151
+ # 安装 hook 到 Husky 项目
152
+ def install_hook_to_husky(repo_path, husky_dir)
153
+ repo_name = File.basename(repo_path)
154
+
155
+ # 修复 .husky/_/ 内部目录被误写入的情况(清理新版或旧版 pindo hook)
156
+ internal_hook = File.join(husky_dir, '_', 'prepare-commit-msg')
157
+ if File.exist?(internal_hook)
158
+ internal_content = File.read(internal_hook)
159
+ if extract_hook_version(internal_content) || pindo_legacy_hook?(internal_content)
160
+ internal_content.sub!(/# --- 变更统计 ---.*/m, '')
161
+ internal_content.sub!(/^#{Regexp.escape(HOOK_VERSION_PREFIX)}.*/m, '')
162
+ internal_content.sub!(/^COMMIT_MSG_FILE=\$1.*/m, '')
163
+ File.write(internal_hook, internal_content)
164
+ Funlog.instance.fancyinfo_success(" [#{repo_name}] 已修复 .husky/_/prepare-commit-msg")
165
+ end
166
+ end
167
+
168
+ # 查找已有的 prepare-commit-msg(排除 _/ 内部目录)
169
+ existing_hook = find_husky_hook(husky_dir)
170
+
171
+ if existing_hook
172
+ existing_content = File.read(existing_hook)
173
+ status = check_hook_status(existing_content)
174
+
175
+ case status
176
+ when :skip
177
+ Funlog.instance.fancyinfo_success(" [#{repo_name}] husky hook 已是最新版本 (v#{HOOK_VERSION}),跳过")
178
+ return
179
+ when :foreign
180
+ Funlog.instance.fancyinfo_warning(" [#{repo_name}] 检测到非 pindo 安装的 husky hook,跳过")
181
+ return
182
+ when :update
183
+ # 移除 pindo 旧版统计代码,再追加新版本
184
+ old_version = extract_hook_version(existing_content) || '未知'
185
+ cleaned = remove_old_stats_block(existing_content)
186
+ File.write(existing_hook, cleaned)
187
+ Funlog.instance.fancyinfo_update(" [#{repo_name}] husky hook 版本更新 (v#{old_version} → v#{HOOK_VERSION})")
188
+ when :install
189
+ # 文件存在但内容为空,直接追加
190
+ end
191
+
192
+ # :update 和 :install 都走到这里,追加新版本到文件末尾
193
+ File.open(existing_hook, 'a') do |f|
194
+ f.puts ""
195
+ f.puts "# --- 变更统计 ---"
196
+ f.puts hook_core_content
197
+ end
198
+ Funlog.instance.fancyinfo_success(" [#{repo_name}] 已追加到 #{existing_hook.sub(repo_path + '/', '')} (husky)")
199
+ else
200
+ # 新建文件,检测风格
201
+ hook_path = File.join(husky_dir, 'prepare-commit-msg')
202
+ style = detect_husky_style(husky_dir)
203
+
204
+ case style[:type]
205
+ when :bare
206
+ File.write(hook_path, hook_core_content)
207
+ when :shebang_with_source
208
+ content = "#!/usr/bin/env sh\n#{style[:source_line]}\n\n#{hook_core_content}"
209
+ File.write(hook_path, content)
210
+ else
211
+ content = "#!/usr/bin/env sh\n\n#{hook_core_content}"
212
+ File.write(hook_path, content)
213
+ end
214
+
215
+ File.chmod(0755, hook_path)
216
+ Funlog.instance.fancyinfo_success(" [#{repo_name}] 已新建 .husky/prepare-commit-msg (husky v#{HOOK_VERSION})")
217
+ end
218
+ end
219
+
220
+ # 移除已有内容中的旧统计代码块
221
+ # @param content [String] 文件内容
222
+ # @return [String] 清理后的内容
223
+ def remove_old_stats_block(content)
224
+ # 优先按 "# --- 变更统计 ---" 标记截断
225
+ if content.include?('# --- 变更统计 ---')
226
+ content.sub(/\n*# --- 变更统计 ---.*/m, '')
227
+ elsif content.include?('COMMIT_MSG_FILE=$1')
228
+ content.sub(/\n*COMMIT_MSG_FILE=\$1.*/m, '')
229
+ else
230
+ content
231
+ end
232
+ end
233
+
234
+ # 获取实际的 .git 目录路径(支持子模块)
235
+ def resolve_git_dir(repo_path)
236
+ git_path = File.join(repo_path, '.git')
237
+ if File.file?(git_path)
238
+ # 子模块:.git 是文件,内容为 "gitdir: xxx"
239
+ content = File.read(git_path).strip
240
+ if content.start_with?('gitdir:')
241
+ git_dir = content.sub('gitdir:', '').strip
242
+ git_dir = File.expand_path(git_dir, repo_path) unless git_dir.start_with?('/')
243
+ return git_dir
244
+ end
245
+ nil
246
+ elsif File.directory?(git_path)
247
+ git_path
248
+ else
249
+ nil
250
+ end
251
+ end
252
+
253
+ # 在 .husky 目录中查找已有的 prepare-commit-msg(排除 _/ 内部目录)
254
+ def find_husky_hook(husky_dir)
255
+ Dir.glob(File.join(husky_dir, '**', 'prepare-commit-msg')).find do |path|
256
+ !path.include?(File.join(husky_dir, '_'))
257
+ end
258
+ end
259
+
260
+ # 检测 Husky hook 文件风格
261
+ # @return [Hash] { type: :bare | :shebang | :shebang_with_source, source_line: String | nil }
262
+ def detect_husky_style(husky_dir)
263
+ # 找任意一个已有的 hook 文件作为参考(排除 _/ 内部目录和隐藏文件)
264
+ sample_hook = Dir.glob(File.join(husky_dir, '*')).find do |path|
265
+ File.file?(path) &&
266
+ !File.basename(path).start_with?('.') &&
267
+ File.extname(path) != '.sh' &&
268
+ !path.include?(File.join(husky_dir, '_'))
269
+ end
270
+
271
+ return { type: :shebang, source_line: nil } unless sample_hook
272
+
273
+ lines = File.readlines(sample_hook)
274
+ first_line = lines.first&.strip || ''
275
+
276
+ if first_line.start_with?('#!')
277
+ # 检查是否有 source 行
278
+ source_line = lines.find { |l| l.strip.start_with?('. "') }&.strip
279
+ if source_line
280
+ { type: :shebang_with_source, source_line: source_line }
281
+ else
282
+ { type: :shebang, source_line: nil }
283
+ end
284
+ else
285
+ { type: :bare, source_line: nil }
286
+ end
287
+ end
288
+
289
+ # 完整 hook 文件内容(带 shebang,用于 .git/hooks 直接写入)
290
+ # 版本标记由 hook_core_content 统一提供,此处不重复写入
291
+ def full_hook_content
292
+ "#!/bin/bash\n\n# Git Hook: prepare-commit-msg\n# 功能: 在提交信息末尾自动添加变更统计信息\n\n#{hook_core_content}"
293
+ end
294
+
295
+ # Hook 核心逻辑(不含 shebang,可追加到已有文件)
296
+ def hook_core_content
297
+ <<~HOOK
298
+ #{HOOK_VERSION_PREFIX} #{HOOK_VERSION}
299
+ COMMIT_MSG_FILE=$1
300
+ COMMIT_SOURCE=$2
301
+
302
+ # 只在正常提交时添加统计(不在 merge、amend 等情况下添加)
303
+ if [ "$COMMIT_SOURCE" = "message" ] || [ "$COMMIT_SOURCE" = "template" ] || [ -z "$COMMIT_SOURCE" ]; then
304
+
305
+ # 读取原始提交信息
306
+ original_msg=$(cat "$COMMIT_MSG_FILE")
307
+
308
+ # 检查最后一行是否已经包含变更统计
309
+ last_line=$(echo "$original_msg" | tail -n 1)
310
+ if echo "$last_line" | grep -q "变更统计:"; then
311
+ exit 0
312
+ fi
313
+
314
+ # 获取暂存区的变更统计
315
+ stats=$(git diff --cached -M --numstat)
316
+ stats_no_r=$(git diff --cached -M --numstat --diff-filter=DAMT)
317
+
318
+ if [ -n "$stats" ]; then
319
+ files_changed=$(echo "$stats" | wc -l | tr -d ' ')
320
+ insertions=$(echo "$stats_no_r" | awk '{sum += $1} END {print sum}')
321
+ deletions=$(echo "$stats_no_r" | awk '{sum += $2} END {print sum}')
322
+
323
+ name_status=$(git diff --cached -M --name-status)
324
+ new_files=$(echo "$name_status" | grep "^A" | wc -l | tr -d ' ')
325
+ modified_files=$(echo "$name_status" | grep "^M" | wc -l | tr -d ' ')
326
+ deleted_files=$(echo "$name_status" | grep "^D" | wc -l | tr -d ' ')
327
+ moved_files=$(echo "$name_status" | grep "^R" | awk -F'\\t' '{ n=split($2,a,"/"); m=split($3,b,"/"); if(a[n]==b[m]) sum+=1 } END {print sum+0}')
328
+ renamed_files=$(echo "$name_status" | grep "^R" | awk -F'\\t' '{ n=split($2,a,"/"); m=split($3,b,"/"); if(a[n]!=b[m]) sum+=1 } END {print sum+0}')
329
+
330
+ insertions=${insertions:-0}
331
+ deletions=${deletions:-0}
332
+ new_files=${new_files:-0}
333
+ modified_files=${modified_files:-0}
334
+ deleted_files=${deleted_files:-0}
335
+ moved_files=${moved_files:-0}
336
+ renamed_files=${renamed_files:-0}
337
+
338
+ file_changes=""
339
+ [ "$new_files" -gt 0 ] && file_changes="${file_changes}+${new_files}新增 "
340
+ [ "$modified_files" -gt 0 ] && file_changes="${file_changes}~${modified_files}修改 "
341
+ [ "$deleted_files" -gt 0 ] && file_changes="${file_changes}-${deleted_files}删除 "
342
+ [ "$moved_files" -gt 0 ] && file_changes="${file_changes}^${moved_files}移动 "
343
+ [ "$renamed_files" -gt 0 ] && file_changes="${file_changes}%${renamed_files}重命名 "
344
+ file_changes=$(echo "$file_changes" | sed 's/ $//')
345
+
346
+ line_changes=""
347
+ [ "$insertions" -gt 0 ] && line_changes="${line_changes}+${insertions}行"
348
+ [ "$deletions" -gt 0 ] && {
349
+ [ -n "$line_changes" ] && line_changes="${line_changes}/"
350
+ line_changes="${line_changes}-${deletions}行"
351
+ }
352
+
353
+ if [ -n "$file_changes" ] && [ -n "$line_changes" ]; then
354
+ stats_msg="变更统计: ${files_changed}个文件(${file_changes})(${line_changes})"
355
+ elif [ -n "$file_changes" ]; then
356
+ stats_msg="变更统计: ${files_changed}个文件(${file_changes})"
357
+ elif [ -n "$line_changes" ]; then
358
+ stats_msg="变更统计: ${files_changed}个文件(${line_changes})"
359
+ else
360
+ stats_msg="变更统计: ${files_changed}个文件"
361
+ fi
362
+
363
+ printf '%s\\n\\n%s\\n' "$original_msg" "$stats_msg" > "$COMMIT_MSG_FILE"
364
+ fi
365
+ fi
366
+ HOOK
367
+ end
368
+
369
+ end
370
+ end
@@ -18,7 +18,7 @@ module Pindo
18
18
 
19
19
  # 检查并安装 git-cliff
20
20
  # @param project_path [String] 项目路径
21
- def check_check_and_install_cliff(project_path)
21
+ def check_and_install_cliff(project_path)
22
22
  if Pindo::GitHandler.is_git_directory?(local_repo_dir: project_path)
23
23
  current_git_root_path = Pindo::GitHandler.git_root_directory(local_repo_dir: project_path)
24
24
  unless File.exist?(File.join(current_git_root_path, 'cliff.toml'))
@@ -277,10 +277,22 @@ module Pindo
277
277
  def self.add_single_scheme(plist_dict, scheme_value)
278
278
  return false if scheme_value.nil? || scheme_value.empty?
279
279
 
280
- # 检查是否已存在
281
- return false if plist_dict["CFBundleURLTypes"].any? { |item| item["CFBundleURLName"] == scheme_value }
280
+ # 查找 CFBundleURLName 匹配的已有条目
281
+ existing_item = plist_dict["CFBundleURLTypes"].find { |item| item["CFBundleURLName"] == scheme_value }
282
282
 
283
- # 创建新的URL Type
283
+ if existing_item
284
+ # 已有同名条目,检查 CFBundleURLSchemes 是否包含该值
285
+ schemes = existing_item["CFBundleURLSchemes"]
286
+ if schemes.is_a?(Array) && schemes.include?(scheme_value)
287
+ return false
288
+ end
289
+ # CFBundleURLSchemes 中没有该值,补充进去
290
+ existing_item["CFBundleURLSchemes"] = [] unless schemes.is_a?(Array)
291
+ existing_item["CFBundleURLSchemes"] << scheme_value
292
+ return true
293
+ end
294
+
295
+ # 不存在同名条目,创建新的 URL Type
284
296
  url_type = {
285
297
  "CFBundleTypeRole" => "Editor",
286
298
  "CFBundleURLName" => scheme_value,
@@ -117,6 +117,17 @@ module Pindo
117
117
  default_value: false,
118
118
  optional: true,
119
119
  example: 'pindo web run --debug'
120
+ ),
121
+
122
+ cliff: OptionItem.new(
123
+ key: :cliff,
124
+ name: 'git-cliff',
125
+ description: '检测并安装 git-cliff 工具,初始化 cliff 配置',
126
+ type: OptionItem::Boolean,
127
+ env_name: 'PINDO_CLIFF',
128
+ default_value: false,
129
+ optional: true,
130
+ example: 'pindo utils repoinit --cliff'
120
131
  )
121
132
  }
122
133
  end
data/lib/pindo/version.rb CHANGED
@@ -6,13 +6,13 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.18.5"
9
+ VERSION = "5.18.6"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
13
13
  VERSION_INFO_FILE = File.expand_path('~/.pindo/version_info.yml')
14
14
  CHECK_INTERVAL = 5 * 60 * 60 # 5小时检查一次
15
- CONFIG_MIN_VERSION = '1.8.0' # 硬编码的配置版本要求
15
+ CONFIG_MIN_VERSION = '1.9.0' # 硬编码的配置版本要求
16
16
 
17
17
  class << self
18
18
  # 主版本检查方法(保持向后兼容)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pindo
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.18.5
4
+ version: 5.18.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
@@ -109,20 +109,20 @@ dependencies:
109
109
  requirements:
110
110
  - - "~>"
111
111
  - !ruby/object:Gem::Version
112
- version: '2.2'
112
+ version: '2.4'
113
113
  - - ">="
114
114
  - !ruby/object:Gem::Version
115
- version: 2.2.0
115
+ version: 2.4.0
116
116
  type: :runtime
117
117
  prerelease: false
118
118
  version_requirements: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: '2.2'
122
+ version: '2.4'
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
- version: 2.2.0
125
+ version: 2.4.0
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rqrcode
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -393,7 +393,6 @@ files:
393
393
  - lib/pindo/module/appstore/itcapp_helper.rb
394
394
  - lib/pindo/module/build/build_helper.rb
395
395
  - lib/pindo/module/build/confuse_xcodeproj.rb
396
- - lib/pindo/module/build/git_repo_helper.rb
397
396
  - lib/pindo/module/build/swark_helper.rb
398
397
  - lib/pindo/module/cert/cert_helper.rb
399
398
  - lib/pindo/module/cert/keychain_helper.rb
@@ -463,6 +462,8 @@ files:
463
462
  - lib/pindo/module/unity/unity_helper.rb
464
463
  - lib/pindo/module/unity/unity_proc_helper.rb
465
464
  - lib/pindo/module/utils/file_downloader.rb
465
+ - lib/pindo/module/utils/git_hook_helper.rb
466
+ - lib/pindo/module/utils/git_repo_helper.rb
466
467
  - lib/pindo/module/webserver/brotli_file_handler.rb
467
468
  - lib/pindo/module/webserver/preview/preview.css
468
469
  - lib/pindo/module/webserver/preview/preview.html