pindo 5.18.6 → 5.18.9

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.
@@ -21,6 +21,31 @@ module Pindo
21
21
  # 变更统计关键字(用于旧版 hook 特征识别)
22
22
  COMMIT_STATS_MARKER = '变更统计'
23
23
 
24
+ # 全局 hook 目录(由 install_global_commit_stats_hook 使用)
25
+ # 不放在 ~/.pindo 下,便于后续 shell 脚本入口以同一路径写入
26
+ GLOBAL_HOOKS_DIR = File.expand_path('~/.git-hooks').freeze
27
+
28
+ # Git 官方支持的 hook 名称列表(来自 githooks(5))
29
+ # 不含 prepare-commit-msg —— 该 hook 由 pindo 装完整统计逻辑,不做转发
30
+ GIT_HOOK_NAMES = %w[
31
+ applypatch-msg pre-applypatch post-applypatch
32
+ pre-commit pre-merge-commit commit-msg
33
+ post-commit pre-rebase post-checkout
34
+ post-merge pre-push pre-receive
35
+ update proc-receive post-receive
36
+ post-update reference-transaction push-to-checkout
37
+ pre-auto-gc post-rewrite sendemail-validate
38
+ fsmonitor-watchman p4-changelist p4-prepare-changelist
39
+ p4-post-changelist p4-pre-submit post-index-change
40
+ ].freeze
41
+
42
+ # git-lfs 相关的 4 个 hook —— 使用 LFS-aware 转发桩
43
+ # 执行顺序: 1) 调 git lfs <name> 完成 LFS 工作 → 2) 无条件转发仓库本地同名 hook
44
+ # 标准 git-lfs 本地 hook 会让 LFS 被调用两次,但 LFS 是幂等的无副作用;
45
+ # 换取"用户自定义逻辑(含 hybrid 脚本)完整执行"的保证。
46
+ # 其余 23 个 hook 使用纯转发桩。
47
+ LFS_AWARE_HOOKS = %w[pre-push post-checkout post-commit post-merge].freeze
48
+
24
49
  # 为仓库及其子模块安装 prepare-commit-msg hook(自动追加变更统计)
25
50
  # 支持普通仓库、子模块、Husky 项目
26
51
  # 版本匹配则跳过,版本不匹配或无版本则覆盖
@@ -51,6 +76,60 @@ module Pindo
51
76
  Funlog.instance.fancyinfo_success("commit 变更统计 hook 安装完成!")
52
77
  end
53
78
 
79
+ # verbose 模式: PINDO_DEBUG 环境变量(由命令层 --verbose 透传)
80
+ def verbose?
81
+ !ENV['PINDO_DEBUG'].to_s.empty?
82
+ end
83
+
84
+ # verbose 模式下才打印的 start 消息
85
+ def vstart(msg)
86
+ Funlog.instance.fancyinfo_start(msg) if verbose?
87
+ end
88
+
89
+ # 全局安装 commit 变更统计 hook(通过 core.hooksPath 对所有 Git 仓库生效)
90
+ #
91
+ # 执行四件事:
92
+ # 1. 解析目标目录 —— 读 global + system 级 core.hooksPath:
93
+ # - 已指向某目录 → 沿用(adaptive,避免破坏已有全局 hook 体系)
94
+ # - 未设置 → 使用默认 GLOBAL_HOOKS_DIR (~/.git-hooks),并负责设置 core.hooksPath
95
+ # 2. 写入 prepare-commit-msg —— 主 hook,**完全接管**,不调用仓库本地同名 hook
96
+ # 3. 写入 27 个转发桩 —— 逐个独立判断:空位写入 / pindo 老版本升级 / foreign 保留
97
+ # 其中 LFS_AWARE_HOOKS (4 个) 使用 LFS-aware 模板,其余 23 个使用纯转发桩
98
+ # 4. 若是默认路径 → 设置 git config --global core.hooksPath
99
+ #
100
+ # @return [String] 实际使用的全局 hook 目录路径
101
+ def install_global_commit_stats_hook
102
+ vstart("开始安装全局 Git hook (pindo v#{HOOK_VERSION})")
103
+ vstart(" 包含: 1 个 prepare-commit-msg 主 hook + #{GIT_HOOK_NAMES.size} 个转发桩 (#{LFS_AWARE_HOOKS.size} LFS-aware + #{GIT_HOOK_NAMES.size - LFS_AWARE_HOOKS.size} 纯转发)")
104
+
105
+ vstart("[4.1] 解析目标 hooks 目录 (自适应 core.hooksPath)")
106
+ target_dir, need_set_config = resolve_target_hooks_dir
107
+
108
+ FileUtils.mkdir_p(target_dir)
109
+ unless File.writable?(target_dir)
110
+ Funlog.instance.fancyinfo_error(" 目标目录不可写: #{target_dir}")
111
+ raise Informative, "target hooks dir not writable: #{target_dir}"
112
+ end
113
+
114
+ vstart("[4.2] 安装 prepare-commit-msg 主 hook (完全接管,不调用仓库本地同名 hook)")
115
+ install_main_global_hook(target_dir)
116
+
117
+ vstart("[4.3] 安装 #{GIT_HOOK_NAMES.size} 个 hook 转发桩 (逐文件独立判断,foreign 保留不动)")
118
+ install_forwarder_stubs(target_dir)
119
+
120
+ vstart("[4.4] 配置 core.hooksPath")
121
+ if need_set_config
122
+ vstart(" 执行: git config --global core.hooksPath #{target_dir}")
123
+ Pindo::GitHandler.git!(%W(config --global core.hooksPath #{target_dir}))
124
+ Funlog.instance.fancyinfo_success(" core.hooksPath 已设置: #{target_dir}")
125
+ else
126
+ vstart(" core.hooksPath 已指向 #{target_dir},跳过")
127
+ end
128
+
129
+ Funlog.instance.fancyinfo_success("全局 Git hook 安装完成! (target: #{target_dir})")
130
+ target_dir
131
+ end
132
+
54
133
  private
55
134
 
56
135
  # 从文件内容中提取 hook 版本号
@@ -366,5 +445,210 @@ module Pindo
366
445
  HOOK
367
446
  end
368
447
 
448
+ # 安装全局主 hook (prepare-commit-msg)
449
+ # 复用 check_hook_status 做版本比对:已是最新 → 跳过;旧版 / foreign / 首次 → 覆盖
450
+ def install_main_global_hook(global_hooks_dir)
451
+ hook_path = File.join(global_hooks_dir, 'prepare-commit-msg')
452
+ vstart(" 检查 #{hook_path} 状态")
453
+
454
+ if File.exist?(hook_path)
455
+ status = check_hook_status(File.read(hook_path))
456
+ case status
457
+ when :skip
458
+ Funlog.instance.fancyinfo_success(" prepare-commit-msg 已是最新版本 (v#{HOOK_VERSION}),跳过")
459
+ return
460
+ when :foreign
461
+ Funlog.instance.fancyinfo_warning(" prepare-commit-msg 非 pindo 文件,即将强制覆盖(保证变更统计功能)")
462
+ when :update
463
+ old_version = extract_hook_version(File.read(hook_path)) || '未知'
464
+ Funlog.instance.fancyinfo_update(" prepare-commit-msg 升级 (v#{old_version} → v#{HOOK_VERSION})")
465
+ when :install
466
+ vstart(" prepare-commit-msg 文件为空,首次写入")
467
+ end
468
+ else
469
+ vstart(" prepare-commit-msg 不存在,首次写入")
470
+ end
471
+
472
+ File.write(hook_path, full_global_hook_content)
473
+ File.chmod(0755, hook_path)
474
+ Funlog.instance.fancyinfo_success(" prepare-commit-msg 已写入 (0755)")
475
+ end
476
+
477
+ # 安装 27 个 hook 转发桩(逐文件独立判断,尊重已有非 pindo 文件)
478
+ # - 空位: 写入
479
+ # - pindo 当前版本: 跳过
480
+ # - pindo 老版本 (含 "Pindo global hook forwarder" 标识): 升级
481
+ # - 非 pindo (foreign): 保留不动(尊重用户 / 其它工具放在这里的文件)
482
+ #
483
+ # 其中 LFS_AWARE_HOOKS 的 4 个 hook 使用 LFS-aware 模板(先调 git lfs,
484
+ # 再无条件转发本地同名 hook,接受 LFS 可能的幂等重复调用成本),
485
+ # 其余 23 个使用纯转发桩(仅转发到仓库本地同名 hook)。
486
+ def install_forwarder_stubs(target_dir)
487
+ vstart(" 遍历 #{GIT_HOOK_NAMES.size} 个 hook 名称,逐个判断 (安装/升级/跳过/保留)")
488
+ pure_tpl = hook_forwarder_content
489
+ lfs_tpl = lfs_aware_forwarder_content
490
+
491
+ installed = 0
492
+ upgraded = 0
493
+ skipped = 0
494
+ kept = 0
495
+
496
+ GIT_HOOK_NAMES.each do |name|
497
+ path = File.join(target_dir, name)
498
+ expected = LFS_AWARE_HOOKS.include?(name) ? lfs_tpl : pure_tpl
499
+
500
+ if !File.exist?(path)
501
+ File.write(path, expected)
502
+ File.chmod(0755, path)
503
+ installed += 1
504
+ next
505
+ end
506
+
507
+ existing = File.read(path)
508
+ if existing == expected
509
+ skipped += 1
510
+ elsif existing.include?('Pindo global hook forwarder')
511
+ # pindo 自己的老版本或不同类型桩 → 升级到当前期望版本
512
+ File.write(path, expected)
513
+ File.chmod(0755, path)
514
+ upgraded += 1
515
+ else
516
+ # foreign → 保留不动
517
+ kept += 1
518
+ end
519
+ end
520
+
521
+ Funlog.instance.fancyinfo_success(
522
+ " 转发桩: 安装 #{installed} / 升级 #{upgraded} / 跳过 #{skipped} / 保留用户文件 #{kept}"
523
+ )
524
+ if kept > 0
525
+ Funlog.instance.fancyinfo_warning(" #{kept} 个位置已存在非 pindo 文件,已保留。若需启用 pindo 转发,请手动移除它们后重跑")
526
+ end
527
+ end
528
+
529
+ # 解析目标 hooks 目录(自适应)
530
+ # 分别查询 global + system 级 core.hooksPath(不走 local 级,避免被仓库本地 Husky 等配置干扰):
531
+ # - 任一级已设置 → 沿用为 target,不修改 core.hooksPath
532
+ # - 都未设置 → 使用默认 GLOBAL_HOOKS_DIR,由调用方负责写入 global 级 core.hooksPath
533
+ #
534
+ # @return [Array<(String, Boolean)>] [target_dir, need_set_config]
535
+ def resolve_target_hooks_dir
536
+ vstart(" 读取 git config --global --get core.hooksPath")
537
+ global_val = git_config_get('--global', 'core.hooksPath')
538
+ vstart(" 读取 git config --system --get core.hooksPath")
539
+ system_val = git_config_get('--system', 'core.hooksPath')
540
+
541
+ effective = !global_val.empty? ? global_val : system_val
542
+ source = !global_val.empty? ? 'global' : 'system'
543
+
544
+ if effective.empty?
545
+ Funlog.instance.fancyinfo_success(" 未检测到 core.hooksPath,使用默认目标目录: #{GLOBAL_HOOKS_DIR}")
546
+ return [GLOBAL_HOOKS_DIR, true]
547
+ end
548
+
549
+ # /dev/null 是 Git 官方的"禁用 hooks"哨兵值 —— pindo 绝不在这种情况下写入
550
+ if effective == '/dev/null'
551
+ Funlog.instance.fancyinfo_error(" 检测到 #{source} 级 core.hooksPath=/dev/null (Git 标准的禁用 hooks 配置)")
552
+ Funlog.instance.fancyinfo_error(" pindo 不会在禁用状态下安装 hook。如需启用 pindo 全局 hook,请先执行:")
553
+ Funlog.instance.fancyinfo_error(" git config --#{source} --unset core.hooksPath")
554
+ raise Informative, "core.hooksPath is set to /dev/null (hooks disabled)"
555
+ end
556
+
557
+ # ~/ 由 git 自己负责展开,pindo 这里也做相同展开以保持一致
558
+ resolved = effective.start_with?('~') ? File.expand_path(effective) : effective
559
+
560
+ # 相对路径 —— git 会按 hook 运行目录解析,pindo 无法可靠对齐
561
+ unless resolved.start_with?('/')
562
+ Funlog.instance.fancyinfo_error(" 检测到 #{source} 级 core.hooksPath=#{effective} (相对路径)")
563
+ Funlog.instance.fancyinfo_error(" Git 会按 hook 运行目录解析相对 hooksPath,pindo 无法可靠定位此目录")
564
+ Funlog.instance.fancyinfo_error(" 请改为绝对路径后重试:")
565
+ Funlog.instance.fancyinfo_error(" git config --#{source} core.hooksPath /绝对路径/to/hooks")
566
+ raise Informative, "core.hooksPath is a relative path: #{effective}"
567
+ end
568
+
569
+ Funlog.instance.fancyinfo_success(" 检测到 #{source} 级 core.hooksPath,沿用为目标目录: #{resolved}")
570
+ [resolved, false]
571
+ end
572
+
573
+ # 安全读取 git config 某作用域下的指定 key
574
+ # 不存在返回空串,不抛异常
575
+ def git_config_get(scope, key)
576
+ Pindo::GitHandler.git!(%W(config #{scope} --get #{key})).strip
577
+ rescue
578
+ ''
579
+ end
580
+
581
+ # 纯转发桩内容(23 个非 LFS hook 共用)
582
+ # 用 exec 透传 stdin / stdout / 退出码;字符串比较防递归
583
+ def hook_forwarder_content
584
+ <<~SH
585
+ #!/bin/bash
586
+ # Pindo global hook forwarder (plain, v#{HOOK_VERSION})
587
+ # 作用: core.hooksPath 启用后转发到仓库本地同名 hook,保留原有行为
588
+ # 由 pindo utils gitconfig 安装,勿手动编辑
589
+
590
+ repo_git_dir=$(git rev-parse --git-dir 2>/dev/null)
591
+ if [ -n "$repo_git_dir" ]; then
592
+ local_hook="$repo_git_dir/hooks/$(basename "$0")"
593
+ if [ -x "$local_hook" ] && [ "$local_hook" != "$0" ]; then
594
+ exec "$local_hook" "$@"
595
+ fi
596
+ fi
597
+ exit 0
598
+ SH
599
+ end
600
+
601
+ # LFS-aware 转发桩内容(4 个 LFS hook 共用:pre-push / post-checkout / post-commit / post-merge)
602
+ # 顺序:
603
+ # 1) 调 git lfs <hook-name> 完成 LFS 工作
604
+ # 2) 始终转发到仓库本地同名 hook —— 无条件转发,保证用户自定义逻辑(包括
605
+ # "先跑 npm test 再调 LFS" 这种 hybrid 脚本)完整执行
606
+ #
607
+ # 设计权衡:
608
+ # - 标准 git-lfs install 生成的本地 hook 会让 LFS 被调用两次,但 git lfs
609
+ # 是幂等的:第二次调用检查到 LFS 对象已上传/已拉取,立即返回,无副作用
610
+ # - 换取"零数据丢失"的保证:用户写的 mixed hook 里的非-LFS 部分不会被吞
611
+ def lfs_aware_forwarder_content
612
+ <<~SH
613
+ #!/bin/bash
614
+ # Pindo global hook forwarder (LFS-aware, v#{HOOK_VERSION})
615
+ # 作用: 先调 git-lfs 完成 LFS 工作,再始终转发到仓库本地同名 hook
616
+ # 由 pindo utils gitconfig 安装,勿手动编辑
617
+
618
+ hook_name=$(basename "$0")
619
+
620
+ # 1. 调用 git-lfs 完成 LFS 相关逻辑
621
+ if command -v git-lfs >/dev/null 2>&1; then
622
+ git lfs "$hook_name" "$@" || exit $?
623
+ fi
624
+
625
+ # 2. 无条件转发到仓库本地同名 hook(保留用户自定义逻辑)
626
+ # 若本地是 git-lfs 标准脚本会让 LFS 被调用两次,但 git lfs 是幂等的,
627
+ # 第二次调用会检测到 LFS 对象已处理并立即返回,无副作用
628
+ repo_git_dir=$(git rev-parse --git-dir 2>/dev/null)
629
+ if [ -n "$repo_git_dir" ]; then
630
+ local_hook="$repo_git_dir/hooks/$hook_name"
631
+ if [ -x "$local_hook" ] && [ "$local_hook" != "$0" ]; then
632
+ exec "$local_hook" "$@"
633
+ fi
634
+ fi
635
+ exit 0
636
+ SH
637
+ end
638
+
639
+ # 全局 prepare-commit-msg 的完整内容
640
+ # 完全接管:不调用仓库本地 prepare-commit-msg,只运行 pindo 变更统计逻辑
641
+ # = shebang + 注释头 + hook_core_content
642
+ def full_global_hook_content
643
+ header = <<~SH
644
+ #!/bin/bash
645
+ # Git Hook: prepare-commit-msg (Pindo global)
646
+ # 功能: 完全接管,自动追加变更统计信息(不调用仓库本地 prepare-commit-msg)
647
+ # 由 pindo utils gitconfig 安装,勿手动编辑
648
+
649
+ SH
650
+ header + hook_core_content
651
+ end
652
+
369
653
  end
370
654
  end
@@ -127,39 +127,6 @@ module Pindo
127
127
  default_value: false,
128
128
  optional: true,
129
129
  example: 'pindo appstore cert --develop_id --platform=macos'
130
- ),
131
-
132
- skipclean: OptionItem.new(
133
- key: :skipclean,
134
- name: '跳过 Gradle Clean',
135
- description: '跳过 Android AAB 构建前的 Gradle clean 步骤(加速本地开发)',
136
- type: OptionItem::Boolean,
137
- env_name: 'PINDO_SKIP_GRADLE_CLEAN',
138
- default_value: false,
139
- optional: true,
140
- example: 'pindo android autobuild --skipclean'
141
- ),
142
-
143
- skipvalidate: OptionItem.new(
144
- key: :skipvalidate,
145
- name: '跳过 Unity 库校验',
146
- description: '跳过 Unity unityLibrary/libs 符号链接有效性校验',
147
- type: OptionItem::Boolean,
148
- env_name: 'PINDO_SKIP_UNITY_LIBS_VALIDATE',
149
- default_value: false,
150
- optional: true,
151
- example: 'pindo android autobuild --skipvalidate'
152
- ),
153
-
154
- injectsigning: OptionItem.new(
155
- key: :injectsigning,
156
- name: '注入签名配置',
157
- description: '注入签名配置到 Android build.gradle(自动改写 signingConfigs)',
158
- type: OptionItem::Boolean,
159
- env_name: 'PINDO_INJECT_ANDROID_SIGNING_GRADLE',
160
- default_value: false,
161
- optional: true,
162
- example: 'pindo android autobuild --injectsigning'
163
130
  )
164
131
  }
165
132
  end
data/lib/pindo/version.rb CHANGED
@@ -6,7 +6,7 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.18.6"
9
+ VERSION = "5.18.9"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pindo
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.18.6
4
+ version: 5.18.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-04-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: claide
@@ -357,6 +357,7 @@ files:
357
357
  - lib/pindo/command/utils/device.rb
358
358
  - lib/pindo/command/utils/encrypt.rb
359
359
  - lib/pindo/command/utils/fabric.rb
360
+ - lib/pindo/command/utils/gitconfig.rb
360
361
  - lib/pindo/command/utils/icon.rb
361
362
  - lib/pindo/command/utils/installskills.rb
362
363
  - lib/pindo/command/utils/renewbundleid.rb
@@ -523,7 +524,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
523
524
  - !ruby/object:Gem::Version
524
525
  version: 3.0.0
525
526
  requirements: []
526
- rubygems_version: 4.0.3
527
+ rubygems_version: 3.6.3
527
528
  specification_version: 4
528
529
  summary: easy work
529
530
  test_files: []