Lefthook 的 `script` 配置详解:在 Git 钩子中执行自定义脚本的完整指南
2026/9/16 20:33:48 网站建设 项目流程

Lefthook 的script配置详解:在 Git 钩子中执行自定义脚本的完整指南

【免费下载链接】lefthookFast and powerful Git hooks manager for any type of projects.项目地址: https://gitcode.com/GitHub_Trending/le/lefthook

script是 Lefthook 钩子配置中用来指定“要执行的自定义脚本文件名”的核心选项,与run(内联命令)并列。本文以 docs/configuration/script.md 为主体,结合 Lefthook 源码,系统讲解脚本的存放目录、runner执行器、args参数模板、可执行权限处理以及底层调用链,帮助你写出可直接落地的钩子脚本配置。

一、script是什么:与scriptsrun的关系

lefthook.yml中,每个 hook(如pre-commitcommit-msg)下既可以配置commands,也可以配置scripts,还可以通过jobs混用两者。script正是jobs中用来指向一个脚本文件的键:

# lefthook.yml pre-commit: jobs: - script: linter.sh # 指定要执行的脚本文件名 runner: bash # 指定执行器 args: "{staged_files}" # 追加参数

script的值是一个脚本文件名(而非内联命令),其规则与顶层scripts配置完全一致:脚本存放在<source_dir>/<hook-name>/目录下,由项目根目录执行。

从源码看,scriptrunJob结构中是互斥的“二选一”字段(见 internal/config/job.go):

Run string `json:"run,omitempty" jsonschema:"oneof_required=Run a command" ...` Script string `json:"script,omitempty" jsonschema:"oneof_required=Run a script" ...`

也就是说,一个 job 要么通过run执行内联命令,要么通过script执行外部脚本,二者不可同时作为主体。而scripts顶层键则是把“脚本名 → 配置”作为一个 map 批量声明的语法糖——Lefthook 内部会把它们统一转换成 Job 列表(见ScriptsToJobs,后文详述)。

二、脚本存放规则:<source_dir>/<hook-name>/

脚本必须放在与钩子同名的子目录中,目录根由source_dir决定,默认值为.lefthook/(见 internal/config/config.go 中SourceDir字段的默认值注释)。典型布局:

.lefthook/ ├── pre-commit/ │ ├── linter.sh │ └── check-python.py ├── commit-msg/ │ └── template_checker └── pre-push/ └── check-files.rb

对应的配置写法:

# lefthook.yml pre-commit: scripts: "linter.sh": runner: bash

lefthook add快速创建脚本骨架

官方推荐的实操流程(详见 Scripts.md):

  1. 运行lefthook add -d pre-commit-d(即CreateDirs)会在.lefthook/pre-commit/.lefthook-local/pre-commit/下创建目录(对应实现见 internal/command/add.go);
  2. 编辑.lefthook/pre-commit/my-script.sh,写入你的检查逻辑;
  3. lefthook.yml中注册脚本:
# lefthook.yml pre-commit: scripts: "my-script.sh": runner: bash

一个完整示例:校验 commit message

.lefthook/commit-msg/template_checker中放置以下 bash 脚本(不要求.sh后缀,文件名即标识):

INPUT_FILE=$1 START_LINE=`head -n1 $INPUT_FILE` PATTERN="^(TICKET)-[[:digit:]]+: " if ! [[ "$START_LINE" =~ $PATTERN ]]; then echo "Bad commit message, see example: TICKET-123: some text" exit 1 fi

然后在lefthook.yml中注册并保留 Git 传入的第一个参数(commit message 文件路径):

# lefthook.yml commit-msg: scripts: "template_checker": runner: bash args: "{0}"

此后执行git commit -m "bad commit text"时,template_checker会被运行;由于提交信息不匹配^(TICKET)-\d+:模式,脚本以非零状态退出,提交过程被中断。

三、args:向脚本追加参数(并“接管” Git 参数)

args(自 Lefthook 2.0.5 起引入,详见 args.md)用于向脚本追加参数,支持与run完全相同的模板语法:

模板含义
{files}自定义files命令的结果
{staged_files}暂存区中即将提交的文件
{push_files}已提交但尚未推送的文件
{all_files}Git 跟踪的所有文件
{cmd}配置文件中的命令简写(用于在 Docker 等 wrapper 中复用)
{0}Git 钩子全部参数拼接成的单字符串
{1}{2}第 1、2… 个 Git 钩子参数
{lefthook_job_name}当前 job/command/script 的名称

关键行为:一旦在配置中指定了args,Git 自动传入钩子的参数就会被省略;只有在args中显式包含{0}模板时,Git 参数才会被保留。换句话说:

  • 不配置args⇔ 配置args: "{0}":脚本能拿到 Git 传入的完整参数;
  • 配置args: "{staged_files}":脚本收到的是暂存文件列表,而不是 Git 参数。
# lefthook.yml pre-commit: jobs: - script: check-python-files.sh runner: bash args: "{staged_files}" glob: "*.py" - run: yarn lint args: "{staged_files}" glob: - "*.ts" - "*.js"

在源码层面,args的替换逻辑位于 internal/run/controller/command/replacer/replacer.go:Replacer.Discover会扫描args字符串中出现的模板并批量缓存结果;AddGitArgs(见 replacer.go)负责把{0}{1}… 映射为实际的 Git 参数。

四、源码级解析:脚本是如何被构建与执行的

1. 从scriptsmap 到 Job 的转换与排序

顶层scripts配置在加载时会被ScriptsToJobs转成 Job(见 internal/config/script.go):每个脚本的runnerargsfail_texttimeouttagsenvinteractiveuse_stdinstage_fixedskiponly都会被原样搬运到对应 Job 上。同时,脚本会按以下顺序排序执行:

  • 配置了priority的脚本按 priority 升序,未配置 priority 的脚本排最后
  • 都没有 priority 时,按脚本名的数字前缀升序(如1-lint.sh先于2-test.sh),否则按字典序(parseNum实现见 script.go)。

Script结构体支持的完整选项(见 internal/config/script.go):

选项说明
runner执行脚本的解释器/命令,如bashnodepython
args追加到脚本后的参数(支持模板)
skip/only条件跳过或仅在某些条件(git 状态/分支)下运行
tags标签,配合LEFTHOOK_EXCLUDE/exclude_tags过滤
env注入环境变量
priority执行顺序优先级
fail_text失败时输出的自定义提示
timeout超时时间(如15s
interactive是否以交互式终端运行
use_stdin是否把 stdin 传给脚本
stage_fixed是否在脚本修复文件后自动git add

2. 脚本执行的关键流程:查找、赋予可执行权限、拼接命令

当 job 的script生效时,实际构建命令的逻辑在 internal/run/controller/command/build_script.go 的buildScript中:

  1. 校验脚本路径:遍历所有SourceDirs(全局 + 本地),拼接<sourceDir>/<hookName>/<script名>并检查文件是否存在;
  2. 跳过空文件模板:如果args中包含{staged_files}等文件模板,但模板解析结果为空(如没有可检查的文件),默认会返回SkipError{"no files for inspection"}直接跳过该脚本(除非--force);
  3. 自动赋予可执行权限:若脚本文件缺少可执行位,Lefthook 会将其chmod0o751executableFileMode),见 build_script.go 与Chmod调用;
  4. 拼接命令:若配置了runner,先拼上 runner(如bash),随后用shellescape.Quote对脚本路径做 shell 转义(防止路径含空格/特殊字符时出错),最后追加args
  5. 未配置 args 时:直接追加 Git 钩子参数(b.opts.GitArgs);
  6. 处理命令行长度上限:当文件列表很长时,Replacer.ReplaceAndSplit会根据系统MaxCmdLen把命令拆分为多条顺序执行的命令(见 replacer.go 与run中的说明)。

3. 文件名的引号转义

ReplaceAndSplit在替换文件模板时会对每个文件名做 shell 转义(escapeFiles,见 replacer.go),并在命令行长度允许时对带引号的模板("{staged_files}"'{staged_files}')做对应引号包裹。这意味着文件名中的空格、引号等特殊字符都能被安全传递。

五、实战组合:把脚本与其它配置项配合使用

在 Docker 中运行脚本

利用{cmd}模板(见run),可以把本地脚本包进容器执行:

# lefthook-local.yml pre-commit: scripts: "good_job.js": runner: docker run -it --rm <container_id_or_name> {cmd}

多脚本 + 优先级 + 失败提示

# lefthook.yml pre-commit: scripts: "1-lint.sh": runner: bash priority: 1 glob: "*.{js,ts}" "2-test.sh": runner: bash priority: 2 fail_text: "测试未通过,请修复后再提交" "format-fixer.py": runner: python stage_fixed: true # 脚本修复文件后自动 git add

仓库中自带的可运行示例见 examples/with_scripts/lefthook.yml。

六、小结

script是 Lefthook 把“文件型检查器”接入 Git 钩子生命周期的主通道:脚本按<source_dir>/<hook-name>/规则存放,配合runner选择解释器,通过args模板精确控制传给脚本的参数(并决定是否保留 Git 参数)。底层实现上,Lefthook 会自动补齐可执行位、按 priority/数字前缀排序、对文件列表做 shell 转义并按命令行长度自动拆分,这些细节共同保证了脚本在各类系统上的稳定执行。掌握这些规则后,你可以把 lint、测试、格式修复等任意检查逻辑组织成独立脚本,交给 Lefthook 统一调度。

【免费下载链接】lefthookFast and powerful Git hooks manager for any type of projects.项目地址: https://gitcode.com/GitHub_Trending/le/lefthook

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询