Class: Pindo::GitHookHelper

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/pindo/module/utils/git_hook_helper.rb

Constant Summary collapse

HOOK_VERSION =

Hook 版本号,修改 hook 内容时递增此版本

'1.0.0'
HOOK_VERSION_PREFIX =

版本标记前缀(用于在 hook 文件中识别版本)

'# pindo_commit_stats_hook_version:'
COMMIT_STATS_MARKER =

变更统计关键字(用于旧版 hook 特征识别)

'变更统计'
GLOBAL_HOOKS_DIR =

全局 hook 目录(由 install_global_commit_stats_hook 使用)不放在 ~/.pindo 下,便于后续 shell 脚本入口以同一路径写入

File.expand_path('~/.git-hooks').freeze
GIT_HOOK_NAMES =

Git 官方支持的 hook 名称列表(来自 githooks(5))不含 prepare-commit-msg —— 该 hook 由 pindo 装完整统计逻辑,不做转发

%w[
  applypatch-msg         pre-applypatch         post-applypatch
  pre-commit             pre-merge-commit       commit-msg
  post-commit            pre-rebase             post-checkout
  post-merge             pre-push               pre-receive
  update                 proc-receive           post-receive
  post-update            reference-transaction  push-to-checkout
  pre-auto-gc            post-rewrite           sendemail-validate
  fsmonitor-watchman     p4-changelist          p4-prepare-changelist
  p4-post-changelist     p4-pre-submit          post-index-change
].freeze
LFS_AWARE_HOOKS =

git-lfs 相关的 4 个 hook —— 使用 LFS-aware 转发桩执行顺序: 1) 调 git lfs <name> 完成 LFS 工作 → 2) 无条件转发仓库本地同名 hook 标准 git-lfs 本地 hook 会让 LFS 被调用两次,但 LFS 是幂等的无副作用;换取“用户自定义逻辑(含 hybrid 脚本)完整执行”的保证。其余 23 个 hook 使用纯转发桩。

%w[pre-push post-checkout post-commit post-merge].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.share_instanceObject



10
11
12
# File 'lib/pindo/module/utils/git_hook_helper.rb', line 10

def share_instance
  instance
end

Instance Method Details

#install_commit_stats_hook(git_root_dir) ⇒ Object

为仓库及其子模块安装 prepare-commit-msg hook(自动追加变更统计)支持普通仓库、子模块、Husky 项目版本匹配则跳过,版本不匹配或无版本则覆盖

Parameters:

  • git_root_dir (String)

    Git 仓库根目录



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/pindo/module/utils/git_hook_helper.rb', line 53

def install_commit_stats_hook(git_root_dir)
  Funlog.instance.fancyinfo_start("安装 commit 变更统计 hook (v#{HOOK_VERSION})...")

  # 1. 主仓库
  install_hook_to_repo(git_root_dir)

  # 2. 子模块
  begin
    submodule_output = Pindo::GitHandler.git!(%W(-C #{git_root_dir} submodule status --recursive)).strip
    unless submodule_output.empty?
      submodule_output.each_line do |line|
        parts = line.strip.split(' ')
        next if parts.length < 2
        sub_path = File.join(git_root_dir, parts[1])
        if File.directory?(sub_path) && (File.directory?(File.join(sub_path, '.git')) || File.file?(File.join(sub_path, '.git')))
          install_hook_to_repo(sub_path)
        end
      end
    end
  rescue => e
    Funlog.instance.fancyinfo_warning("子模块检测跳过: #{e.message}") if ENV['PINDO_DEBUG']
  end

  Funlog.instance.fancyinfo_success("commit 变更统计 hook 安装完成!")
end

#install_global_commit_stats_hookString

全局安装 commit 变更统计 hook(通过 core.hooksPath 对所有 Git 仓库生效)

执行四件事:

1. 解析目标目录 —— 读 global + system 级 core.hooksPath:
   - 已指向某目录 → 沿用(adaptive,避免破坏已有全局 hook 体系)
   - 未设置 → 使用默认 GLOBAL_HOOKS_DIR (~/.git-hooks),并负责设置 core.hooksPath
2. 写入 prepare-commit-msg —— 主 hook,**完全接管**,不调用仓库本地同名 hook
3. 写入 27 个转发桩 —— 逐个独立判断:空位写入 / pindo 老版本升级 / foreign 保留
   其中 LFS_AWARE_HOOKS (4 个) 使用 LFS-aware 模板,其余 23 个使用纯转发桩
4. 若是默认路径 → 设置 git config --global core.hooksPath

Returns:

  • (String)

    实际使用的全局 hook 目录路径



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/pindo/module/utils/git_hook_helper.rb', line 101

def install_global_commit_stats_hook
  vstart("开始安装全局 Git hook (pindo v#{HOOK_VERSION})")
  vstart("  包含: 1 个 prepare-commit-msg 主 hook + #{GIT_HOOK_NAMES.size} 个转发桩 (#{LFS_AWARE_HOOKS.size} LFS-aware + #{GIT_HOOK_NAMES.size - LFS_AWARE_HOOKS.size} 纯转发)")

  vstart("[4.1] 解析目标 hooks 目录 (自适应 core.hooksPath)")
  target_dir, need_set_config = resolve_target_hooks_dir

  FileUtils.mkdir_p(target_dir)
  unless File.writable?(target_dir)
    Funlog.instance.fancyinfo_error("  目标目录不可写: #{target_dir}")
    raise Informative, "target hooks dir not writable: #{target_dir}"
  end

  vstart("[4.2] 安装 prepare-commit-msg 主 hook (完全接管,不调用仓库本地同名 hook)")
  install_main_global_hook(target_dir)

  vstart("[4.3] 安装 #{GIT_HOOK_NAMES.size} 个 hook 转发桩 (逐文件独立判断,foreign 保留不动)")
  install_forwarder_stubs(target_dir)

  vstart("[4.4] 配置 core.hooksPath")
  if need_set_config
    vstart("  执行: git config --global core.hooksPath #{target_dir}")
    Pindo::GitHandler.git!(%W(config --global core.hooksPath #{target_dir}))
    Funlog.instance.fancyinfo_success("  core.hooksPath 已设置: #{target_dir}")
  else
    vstart("  core.hooksPath 已指向 #{target_dir},跳过")
  end

  Funlog.instance.fancyinfo_success("全局 Git hook 安装完成! (target: #{target_dir})")
  target_dir
end

#verbose?Boolean

verbose 模式: PINDO_DEBUG 环境变量(由命令层 –verbose 透传)

Returns:

  • (Boolean)


80
81
82
# File 'lib/pindo/module/utils/git_hook_helper.rb', line 80

def verbose?
  !ENV['PINDO_DEBUG'].to_s.empty?
end

#vstart(msg) ⇒ Object

verbose 模式下才打印的 start 消息



85
86
87
# File 'lib/pindo/module/utils/git_hook_helper.rb', line 85

def vstart(msg)
  Funlog.instance.fancyinfo_start(msg) if verbose?
end