lefthook 配置指南:glob文件过滤规则与glob_matcher匹配引擎详解
【免费下载链接】lefthookFast and powerful Git hooks manager for any type of projects.项目地址: https://gitcode.com/GitHub_Trending/le/lefthook
glob是 lefthook 中用于按文件名模式过滤文件的配置项,它决定一条 command/job 究竟作用于哪些文件,是编写精准、高效的 Git hooks 的核心工具。本篇指南将完整讲解glob的配置语法、与run模板及files命令的配合方式、**通配符的特殊语义,并结合源码剖析 gobwas 与 doublestar 两种匹配引擎的底层差异,帮助你写出可复现、可维护的文件过滤规则。
一、glob是什么:为命令划定作用文件范围
在 lefthook 中,glob用于为某条命令设置一个(或多个)文件匹配模式,从而把命令的执行范围限制在符合模式的文件上。需要特别注意的是:glob只有在两种场景下才会生效,glob 官方文档 明确说明:
- 你在
run选项中使用了文件模板(如{staged_files}、{files}、{push_files}、{all_files}); - 你为命令提供了自定义的
files命令。
换言之,glob是一个"过滤器",它本身不产生文件列表,只负责从已有文件集合中筛出符合条件的子集。
基础用法示例
# lefthook.yml pre-commit: jobs: - name: lint run: yarn eslint {staged_files} glob: "*.{js,ts,jsx,tsx}"当pre-commit钩子触发时,lefthook 收集暂存区文件(即{staged_files}),再通过glob: "*.{js,ts,jsx,tsx}"只保留.js、.ts、.jsx、.tsx结尾的文件,最后把过滤后的文件列表替换进run命令执行。
二、多个 glob 模式:从 1.10.10 起支持列表
从 lefthook 版本1.10.10开始,glob除了接受单个字符串,还可以接收一个模式列表:
# lefthook.yml pre-commit: jobs: - run: yarn lint {staged_files} glob: - "*.ts" - "*.js"列表中的多个模式是并集关系:只要文件匹配其中任意一个模式就会被保留。这在需要覆盖多种扩展名而每种扩展名又有独立模式时非常实用。
从源码结构看,这一能力在配置层已经全面支持:无论是Job.Glob还是Command.Glob,其类型都是[]string,并且带jsonschema:"oneof_type=string;array"注解,说明 YAML/TOML/JSON 中既可以写单字符串也可以写数组。过滤时,filter.go的byGlob函数 会遍历所有模式,把每个模式命中的文件累积合并。
三、模式语法:基于 gobwas/glob 库
lefthook 的默认 glob 匹配引擎是 gobwas/glob 库(该链接为文档原文所引,供参考)。这意味着默认情况下你可以使用以下语法:
*— 匹配路径片段内任意数量的字符(不跨目录分隔符);?— 匹配单个任意字符;[...]— 字符集匹配,如[abc];{a,b,c}— 花括号分组匹配,如*.{js,ts}等价于*.js或*.ts;**— 匹配一层或多层目录(详见下文"**的特殊行为");!— 取反,排除匹配的模式。
对应到源码,matchFilesGobwas通过glob.MustCompile(lowerMatcher)编译模式,并逐一对文件路径做Match判断。值得注意的实现细节是:匹配前会把模式和文件路径都转为小写(strings.ToLower),因此默认情况下 glob 匹配是不区分大小写的。
四、**的特殊行为:与大多数工具不同
glob文档中特别强调了一个容易踩坑的差异:
**模式匹配1 层或更多层目录(而不是像大多数工具那样匹配 0 层或更多层)。
也就是说,默认(gobwas)引擎下:
glob: "src/**/*.js" # 不会匹配 src/file.js glob: "src/*.js" # 只会匹配 src/file.jssrc/**/*.js:要求**至少匹配一层目录,因此src/app/util.js能命中,但src/file.js(**匹配了 0 层)不会命中;- 若需要同时覆盖顶层和嵌套层文件,需要分别写两个模式,或者切换匹配引擎(见下文
glob_matcher)。
这一行为对应源码中的 doublestar 与 gobwas 的分支逻辑,同时也被 filter_test.go 的测试用例直接验证(例如**/*.rb在 gobwas 与 doublestar 两种引擎下的命中结果对比)。
五、与root的交互:glob 永远基于 Git 仓库根计算
如果你在命令上配置了root来指定命令执行的工作目录,请注意:
glob 始终基于 Git 仓库的实际根目录计算,
root会被忽略。
这意味着即使你把命令的root指定为某个子目录,glob 模式仍然要按仓库根目录下的相对路径来书写。例如仓库根目录下有一个src/目录,那么匹配其中所有 JS 文件应写成glob: "src/**/*.js",而不是glob: "**/*.js"。这条规则对exclude等同样基于文件路径的过滤项也适用。
六、没有文件模板时:glob仍可触发过滤与跳过
一个经常被忽略但非常实用的行为是:即使你的run中没有使用任何文件模板,glob依然生效。
此时 lefthook 会自动检查pre-commit钩子的{staged_files}和pre-push钩子的{push_files},并对其应用 glob 过滤;如果过滤后没有任何文件剩余,该命令会被直接跳过。
# lefthook.yml pre-commit: jobs: - name: lint run: npm run lint # 如果没有暂存任何 .js 文件,这条命令会被跳过 glob: "*.js"这是一个非常实用的"按需执行"手段:当你不想让某些 lint 工具在无关变更时白白空跑时,用glob即可实现"没有匹配文件就不执行"。同样的逻辑也适用于exclude:当指定了exclude而没有文件模板时,lefthook 会检查暂存/推送文件并应用排除过滤,若无文件剩余则跳过命令。
七、glob_matcher:切换标准**语义的全局开关
如果你从其他工具(如 bash、gitignore、pre-commit 生态)迁移而来,习惯了**匹配 0 层或更多层的标准语义,可以通过顶层配置glob_matcher切换匹配引擎。
可选值:
| 值 | 说明 |
|---|---|
gobwas(默认) | 使用 gobwas/glob 库,**匹配 1 层或更多层目录 |
doublestar | 使用 bmatcuk/doublestar 库,**为标准的 Bash 式行为,匹配 0 层或更多层 |
配置示例
# lefthook.yml glob_matcher: doublestar pre-commit: jobs: - name: lint run: yarn eslint {staged_files} glob: "**/*.{js,ts}"两种引擎的行为对比
# gobwas(默认):**/*.js 匹配 src/app.js,但不匹配 app.js # doublestar: **/*.js 匹配 app.js、src/app.js、a/b/c/app.js也就是说,切到doublestar后,**/*.js会同时命中根目录下的app.js和任意深度的嵌套文件,这更符合大多数人的直觉。
适用范围与兼容性
从配置结构与源码可以确认以下几点:
- 全局生效:
glob_matcher是Config的顶层字段,即仓库级配置,它同时影响所有glob和exclude模式; - 向后兼容:默认值仍是
gobwas,不显式配置时行为与旧版本完全一致; - 源码实现:过滤链
Filter.Apply中,byGlob与byExclude都会接收GlobMatcher参数并据此选择matchFilesDoublestar/matchFilesGobwas(以及对应的 exclude 分支); - 对 exclude 同样生效:
exclude文档 明确指出,其模式同样受glob_matcher影响,因此切换引擎后要同时复核glob与exclude两处模式的写法。
八、源码视角:一条命令的完整过滤链路
了解底层实现有助于你预测glob在复杂配置下的实际行为。核心代码位于 internal/run/controller/filter/filter.go,一次文件过滤按以下顺序执行:
files = byGlob(files, f.Glob, f.GlobMatcher) // 1. glob 正向匹配 files = byExclude(files, f.ExcludeFiles, f.GlobMatcher) // 2. exclude 反向排除 files = byRoot(files, f.Root) // 3. root 路径裁剪 files = f.byType(f.fs, files, f.FileTypes) // 4. file_types 类型过滤几点值得注意:
byGlob的语义是"并集":传入多个模式时,任一模式命中即保留(见byGlob),不会做交集;- 空模式会被跳过:如果模式列表为空字符串会被忽略,全部为空时视为未设置,返回原文件列表;
exclude是第二步:先正向匹配再反向排除,两者使用同一个glob_matcher引擎;- 过滤顺序可组合:
glob之后还有root裁剪和file_types类型过滤,因此你可以把"扩展名过滤 + 目录裁剪 + 文件类型过滤"组合成精确的执行范围控制; - 大小写不敏感:无论是 gobwas 还是 doublestar 分支,匹配前都会把路径和模式转小写;
- 调试输出:
Apply会以 debug 级别日志打印过滤前后的文件列表(filtered [ ]→filtered [x]),运行lefthook时开启 verbose 即可观察每个 job 实际接收了哪些文件。
九、实战组合:glob+files+ 模板
glob最常见的完整用法,是配合自定义files命令与{files}模板,把"自定义文件来源 + 扩展名过滤 + 批量执行"串成一条流水线。
场景一:对自定义命令输出的文件做过滤
# lefthook.yml pre-push: commands: stylelint: tags: - frontend - style files: git diff --name-only master glob: "*.js" run: yarn stylelint {files}流程:git diff --name-only master输出变更文件 →glob: "*.js"只保留 JS 文件 → 替换进{files}执行 stylelint。
场景二:多语言仓库分别过滤
# lefthook.yml pre-commit: jobs: - name: rubocop glob: "*.rb" exclude: - config/application.rb - config/routes.rb run: bundle exec rubocop --force-exclusion -- {staged_files} - name: eslint glob: - "*.js" - "*.jsx" - "*.ts" - "*.tsx" run: yarn eslint {staged_files}这里同时演示了glob的单字符串与列表写法、exclude排除特定文件,以及run文档 中推荐的 RuboCop--force-exclusion配合方式(避免 RuboCop 自身的 Exclude 配置被忽略)。
场景三:没有匹配文件时自动跳过
# lefthook.yml pre-commit: jobs: - name: lint run: npm run lint # 只有暂存了 .js 文件才会真正执行 glob: "*.js"利用第六节所述的"无模板时自动过滤并跳过"行为,让命令在无相关文件变更时零开销退出。
十、常见误区与建议
**在默认引擎下不匹配根目录文件:glob: "**/*.js"不会命中app.js。要么分别写"*.js"与"**/*.js"两个模式,要么全局切换到glob_matcher: doublestar;glob与root的基准不同:glob 始终基于 Git 仓库根计算,不要因为设置了root就改写相对路径;glob列表是并集而非交集:写多模式时不要误以为文件必须同时满足所有模式;- 切换
glob_matcher后检查exclude:两者共用引擎,引擎切换可能让原先的排除模式失效或意外扩大范围; - 大小写不敏感:默认匹配会忽略大小写,若你需要大小写敏感匹配,需要额外手段(如文件命名规范约束)。
结语
glob是 lefthook 文件过滤体系的基石,配合run模板、自定义files命令与exclude排除规则,可以精确控制每条钩子命令的作用范围;而glob_matcher则为迁移用户提供了标准的**语义开关。理解**在不同引擎下的差异、glob 与root的计算基准、以及无模板时的自动跳过行为,是写出既不误伤也不漏跑的高质量 lefthook 配置的关键。
【免费下载链接】lefthookFast and powerful Git hooks manager for any type of projects.项目地址: https://gitcode.com/GitHub_Trending/le/lefthook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考