lefthook add 命令详解:快速安装 Git Hooks 并初始化脚本目录
【免费下载链接】lefthookFast and powerful Git hooks manager for any type of projects.项目地址: https://gitcode.com/GitHub_Trending/le/lefthook
lefthook add是 lefthook 提供的一条专用子命令,用于把指定的 Git Hook(如pre-commit、pre-push)直接安装到.git/hooks/目录,并可选地(--dirs)为项目级与个人级脚本目录创建对应的 hook 子目录。读完本文,你将掌握lefthook add的全部参数用法、底层执行流程(从 CLI 定义到internal/command/add.go的具体实现)、与已有 Hook 冲突时的处理策略,以及如何配合lefthook.yml中source_dir/source_dir_local配置灵活管理脚本存放位置。
一、命令概览:lefthook add做了什么
根据 官方命令文档,lefthook add的职责是:
Installs the given hook to Git hook.
即:把给定名称的 Hook 安装到 Git 的 Hook 目录中。它完成的核心动作包括:
- 校验传入的 Hook 名称是否为 lefthook 支持的 Git Hook(见 internal/config/available_hooks.go);
- 清理
.git/hooks/下可能已存在的同名文件(详见下文“与已有 Hook 的冲突处理”); - 使用内置模板生成并写入对应的 Hook 文件(权限为
0o755,即-rwxr-xr-x,见 internal/command/lefthook.go); - 若指定了
--dirs参数,则创建脚本目录:.lefthook/<hook>/与.lefthook-local/<hook>/(目录权限0o755,见 internal/command/add.go)。
其中第 4 步的价值在于:lefthook 约定“按 Hook 名组织脚本”,每个 Hook 对应的脚本存放在source_dir/<hook>/下。提前用--dirs把目录建好,随后把脚本文件放进去、在lefthook.yml中声明即可,无需手工mkdir。
注意:原命令帮助文档(cmd/add-usage.txt)中明确指出,
-d(即--dirs)选项负责创建这些脚本目录,而.git/hooks/下的 Hook 可执行文件本身则始终会被写入,与是否加--dirs无关。
二、命令行参数
lefthook add的完整参数定义位于 cmd/add.go,基于urfave/cli/v3实现:
| 参数 | 别名 | 类型 | 说明 |
|---|---|---|---|
--force | -f | bool | 当同名.old备份文件已存在时强制覆盖 |
--create-dirs | --dirs | bool | 为脚本创建.lefthook/<hook>与.lefthook-local/<hook>目录 |
--verbose | -v | bool | 开启调试级别日志(等价于设置环境变量LEFTHOOK_VERBOSE=1) |
位置参数为 Hook 名称,通过cmd.Args().Get(0)取得并赋给AddArgs.Hook(见 cmd/add.go)。
2.1 参数生效路径
这些参数最终会组成command.AddArgs结构体(见 internal/command/add.go):
type AddArgs struct { Hook string CreateDirs, Force bool }--verbose单独处理:它被传入command.NewLefthook(verbose, "auto"),用于把日志级别提升到LevelDebug(见 internal/command/lefthook.go)。
2.2 Shell 补全支持
lefthook add注册了ShellComplete回调,支持对参数(--force、--dirs等)以及当前配置文件中已声明的 Hook 名称进行自动补全(见 cmd/add.go 与 internal/command/lefthook.go)。Shell 补全实现从加载的配置cfg.Hooks中枚举 Hook 名输出,因此配置文件中声明过的 Hook 会出现在补全候选里。
三、完整使用示例(以 pre-push 为例)
3.1 安装 Hook 并创建脚本目录
$ lefthook add pre-push --dirs执行后仓库中将出现:
.git/hooks/pre-push # 由 lefthook 生成的 Hook 可执行文件 .lefthook/pre-push/ # 项目级脚本目录(提交到版本控制) .lefthook-local/pre-push/ # 个人级脚本目录(建议加入 .gitignore)这正是 cmd/add-usage.txt 中描述的目标结构:
├───.git │ └───hooks │ └───pre-commit // this executable will be added. Existing file with │ // same name will be renamed to pre-commit.old (lefthook adds these dirs if you run the command with the -d option) │ ├───.lefthook // directory for project level hooks │ └───pre-commit // directory with hook executables └───.lefthook-local // directory for personal hooks; add it in .gitignore └───pre-commit3.2 在 lefthook.yml 中描述 Hook
在项目根目录的lefthook.yml中声明pre-push要执行的脚本:
# lefthook.yml pre-push: jobs: - script: "audit.sh" runner: bashscript字段的取值规则与scripts目录约定一致,即脚本文件需要放在脚本目录中与 Hook 同名的子目录下(可参考 docs/configuration/script.md)。
3.3 编写脚本
$ vim .lefthook/pre-push/audit.sh # 示例内容: #!/usr/bin/env bash echo "Running dependency audit before push..." # ... 审计逻辑3.4 触发验证
运行git push,lefthook 便会以pre-pushHook 的身份执行bash audit.sh。
整个过程可归纳为一条闭环:lefthook add负责“装钩子 + 建目录” → 配置声明脚本 → 编写脚本 → Git 事件触发执行。
四、源码级原理剖析
4.1 入口与执行流程
lefthook add的 Action 先构造Lefthook实例,再调用l.Add(ctx, args)(见 cmd/add.go)。核心实现在 internal/command/add.go,流程如下:
Hook 名校验:
config.KnownHook(args.Hook)检查 Hook 是否在 internal/config/available_hooks.go 的AvailableHooks白名单中。该名单收录了来自 Git 官方文档(git-scm.com/docs/githooks)的全部标准 Hook,共 28 个,涵盖applypatch-msg、pre-commit、pre-merge-commit、commit-msg、pre-push、pre-receive、post-receive、reference-transaction、fsmonitor-watchman等。若传入非法名称(如super-star),命令会返回错误skip adding, hook is unavailable: <hook>。清理旧 Hook:
l.cleanHook(args.Hook, args.Force)(见 internal/command/lefthook.go)——具体策略见下文。确保 hooks 目录存在:
l.ensureHooksDirExists()保证.git/hooks/可写。写入 Hook 文件:
l.addHook(args.Hook, templates.Args{})通过templates.Hook()渲染 internal/templates/hook.tmpl,以0o755权限写入.git/hooks/<hook>(见 internal/command/lefthook.go)。按需创建脚本目录:若
--dirs为真,调用getSourceDirs()取得实际的全局/本地脚本目录(见 internal/command/add.go),再MkdirAll分别创建<root>/<global>/<hook>与<root>/<local>/<hook>。
4.2 生成的 Hook 文件长什么样
写入.git/hooks/<hook>的内容由 internal/templates/hook.tmpl 渲染,它是一个 POSIXsh脚本,具备以下能力:
- 支持
LEFTHOOK_VERBOSE=1时开启set -x调试输出; - 支持
LEFTHOOK=0环境变量一键跳过执行; - 支持通过
LEFTHOOK_BIN指定 lefthook 二进制路径,并依次回退到 PATH 中的lefthook、当前可执行文件路径,再到node_modules(含各平台二进制包、@evilmartians/lefthook、lefthook-installer)、go tool lefthook、bundle exec、yarn、pnpm、swift package、mint、uv run、mise exec、devbox run等多种发现途径; - 最终执行
call_lefthook run "<hook_name>" "$@",即把 Git 传入的参数原样转交给lefthook run。
在 Windows 上,生成的 Hook 文件扩展名会变为.exe(见 internal/templates/templates.go)。
4.3 与已有 Hook 的冲突处理(cleanHook 策略)
当.git/hooks/<hook>已存在同名文件时,cleanHook按以下规则处理(见 internal/command/lefthook.go):
- 该文件本身就是 lefthook 生成的:通过扫描文件内容是否包含指纹字符串
LEFTHOOK判断(isLefthookFile,见 internal/command/lefthook.go)。若是,则直接删除后重新写入,不会产生.old备份。 - 该文件是用户自定义的 Hook:将其重命名为
<hook>.old(后缀常量oldHookPostfix = ".old"),然后写入 lefthook 的 Hook。日志会输出Renamed <path> to <path>.old。 <hook>.old已存在:默认报错can't rename <hook> to <hook>.old - file already exists;若加--force,则覆盖旧的.old文件并提示File <hook>.old already exists, overwriting。
这套策略确保:不会静默覆盖用户已有的自定义 Hook,原逻辑以.old形式保留,便于回滚。
4.4 测试佐证
internal/command/add_test.go 用 8 个表格驱动用例覆盖了上述行为:
- 空仓库中
add pre-commit:只生成.git/hooks/pre-commit,不创建.lefthook/.lefthook-local(与文档“不加--dirs不建脚本目录”的说明一致); - 非法 Hook 名(
super-star):返回错误,不产生任何文件; CreateDirs: true:额外创建.lefthook、.lefthook-local及对应 hook 子目录;- 配置了自定义
source_dir: .source_dir/source_dir_local: .source_dir_local:脚本目录改为.source_dir/post-commit与.source_dir_local/post-commit; - 已存在用户自定义 Hook:生成
.git/hooks/post-commit与post-commit.old; - 已存在 lefthook 生成的 Hook:直接覆盖,不产生
.old; - 已存在
.old且未加--force:返回错误; - 已存在
.old且加--force:成功覆盖。
此外,端到端集成测试 tests/integration/add.txt 验证了真实 Git 仓库中的行为:lefthook add pre-commit只生成.git/hooks/pre-commit,而lefthook add pre-push --dirs会同时生成.lefthook/pre-push与.lefthook-local/pre-push(该测试在 Windows 上跳过)。
五、脚本目录的定制:source_dir 与 source_dir_local
默认情况下,--dirs创建的目录是:
- 项目级(提交进版本库):
.lefthook/<hook>/ - 个人级(不提交,建议加入
.gitignore):.lefthook-local/<hook>/
这两个默认值定义在 internal/config/loader.go:
DefaultSourceDir = ".lefthook" DefaultSourceDirLocal = ".lefthook-local"add命令在创建目录前会先尝试加载配置,若lefthook.yml中配置了source_dir/source_dir_local,则使用配置值替换默认值(见 internal/command/add.go):
# lefthook.yml source_dir: .githooks # 项目级脚本根目录 source_dir_local: .githooks-local # 个人级脚本根目录配置项的完整语义可参考 docs/configuration/source_dir.md 与 docs/configuration/source_dir_local.md。例如配置source_dir: .githooks后,lefthook add pre-commit --dirs会创建.githooks/pre-commit/而不是.lefthook/pre-commit/,并且lefthook run执行脚本时也会从该目录寻找。这里需要特别留意:先写配置、后执行add --dirs,目录才会建在正确的位置。
六、与其他 lefthook 命令的分工
lefthook add与安装类命令存在明确分工:
lefthook install:根据配置文件一次性安装所有声明过的 Hook(详见 docs/usage/commands/install.md),适合把 Hook 声明进lefthook.yml后统一落地;lefthook add <hook> [--dirs]:单独安装某一个 Hook,并可选地初始化脚本目录,适合在编写某个 Hook 的脚本之前快速搭建骨架。
实际工作流通常是:先用lefthook add pre-push --dirs建目录 → 编写脚本 → 在lefthook.yml声明 → 之后交给lefthook install统一管理所有 Hook。
七、小结
lefthook add是一条小而美的脚手架命令:它把“装 Hook、建脚本目录”两个高频操作压缩成一条命令,同时通过.old备份机制保证用户自定义 Hook 不被误伤。理解它的参数(--dirs/--force)、底层流程(校验 → 清理 → 模板渲染 → 建目录)以及source_dir/source_dir_local的定制方式,可以让你在搭建 Git Hooks 工作流时更加顺手。相关源码入口:cmd/add.go、internal/command/add.go、internal/command/lefthook.go、internal/templates/hook.tmpl,测试参考 internal/command/add_test.go 与 tests/integration/add.txt。
【免费下载链接】lefthookFast and powerful Git hooks manager for any type of projects.项目地址: https://gitcode.com/GitHub_Trending/le/lefthook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考