
prek Hook 仓库编写指南从.pre-commit-hooks.yaml清单到发布、测试与 CI 全流程【免费下载链接】prek⚡ A fast Git hook manager written in Rust, designed as a drop-in alternative to pre-commit, reimagined.项目地址: https://gitcode.com/GitHub_Trending/pr/prek导读本文面向为 prek 编写并发布 Hook 仓库的开发者你将学习如何构建一个最小 Hook 仓库、编写被 prek 读取的.pre-commit-hooks.yaml清单文件、理解全部 manifest 字段包括shell、env、pass_filenames、glob 过滤器等 prek 扩展并掌握声明stages、传递参数、为prek update设计版本标签、用prek try-repo本地迭代以及接入 CI 验证的完整实战流程。读完本文你可以发布一个与 upstreampre-commit生态兼容、又充分发挥 prek 扩展能力的 Hook 仓库。本文仅讨论维护一个 Hook 仓库、供终端用户消费的场景。如果只是在自己的项目里配置已存在的 hooks请阅读 本地 Hook 配置指南如果你关心 prek 如何解析这些配置配置参考 提供了更完整的字段语义。一个最小 Hook 仓库的结构一个最小的 Hook 仓库 根目录下的 manifest 文件该语言要求的源码与打包文件。语言决定仓库如何被安装manifest 决定消费者该运行哪个已安装的命令。例如一个 Python Hook 的典型布局my-hook/ ├── .pre-commit-hooks.yaml ├── pyproject.toml └── src/ └── my_hook/ └── __init__.py确切的打包文件随语言而异Node Hook 使用package.jsonGo Hook 使用go.modRust Hook 使用Cargo.toml而language: system的 Hook 甚至不需要任何打包文件——prek 直接调用系统 PATH 中的可执行命令。各语言后端支持的完整列表与安装方式见 语言支持参考。仓库内一个真实的 manifest 示例来自本仓库测试夹具 uv-pre-commit-hooks.yaml展示了多语言/多命令 Hook 的写法- id: pip-compile name: pip-compile description: Automatically run uv pip compile on your requirements entry: uv pip compile language: python files: ^requirements\.(in|txt)$ args: [] pass_filenames: false additional_dependencies: [] minimum_pre_commit_version: 2.9.2 - id: uv-export name: uv-export description: Automatically run uv export on your project dependencies entry: uv export language: python files: ^uv\.lock$ args: [--frozen, --output-filerequirements.txt] pass_filenames: false additional_dependencies: []Manifest 文件.pre-commit-hooks.yamlHook 仓库必须在仓库根目录包含一个.pre-commit-hooks.yaml文件。prek 没有自己独立的 manifest 格式——它直接读取 upstreampre-commit定义的同一份.pre-commit-hooks.yamlmanifest。这一设计让 Hook 仓库天然兼容整个 pre-commit 生态prek也可以被当作pre-commit的直接替代品使用参见 项目 README 中对drop-in replacement的说明。manifest 是一个YAML 列表每一项是一个 hook 定义。在源码层prek 通过read_manifest解析该文件config/mod.rs 中的实现使用serde_saphyr将文件内容反序列化为Manifest结构Manifest即VecManifestHook其中ManifestHook要求id、name、entry、language四个字段必填见 config/hook.rs 中ManifestHook的定义。Hook 的约定失败时以非零退出码退出如果是修复型fixerHook则修改文件并以非零退出码退出好让 prek 报告修复内容。字段总览prek 支持以下 manifest 字段| 字段 | 必填 | prek 独有 | 类型 | 说明 | | -- | -- | -- | -- | -- | |id| 是 | 否 | string | 稳定标识符用于终端用户的配置引用与prek run选择器。 | |name| 是 | 否 | string | 人类可读的名称显示在输出中。 | |entry| 是 | 否 | string | 要执行的命令。 | |shell| 否 | 是 | string 枚举 | 通过预定义的 shell 适配器运行entrysh、bash、pwsh、powershell或cmd。 | |language| 是 | 否 | string | 执行环境例如python、node、system。 | |alias| 否 | 否 | string | 别名prek run也接受该标识符。 | |files| 否 | 否 | regex 字符串或 glob 映射 | 仅包含匹配的文件。 | |exclude| 否 | 否 | regex 字符串或 glob 映射 | 排除匹配的文件。 | |types| 否 | 否 | string 列表 | 要求文件具有列表中的全部文件类型标签。 | |types_or| 否 | 否 | string 列表 | 要求文件具有列表中的至少一个文件类型标签。 | |exclude_types| 否 | 否 | string 列表 | 排除具有任一列出标签的文件。 | |additional_dependencies| 否 | 否 | string 列表 | 安装进受管 Hook 环境的额外依赖。 | |args| 否 | 否 | string 列表 | 追加到entry之后的额外参数在文件名之前。 | |env| 否 | 是 | string 映射 | 创建 Hook 环境及执行时的环境变量。 | |always_run| 否 | 否 | boolean | 即使没有文件匹配也要运行。 | |fail_fast| 否 | 否 | boolean | 该 Hook 失败时立即停止运行。 | |pass_filenames| 否 | 否 | boolean 或正整数 | 控制是否传递或传递多少个匹配的文件名。 | |description| 否 | 否 | string | 自由格式元数据显示在列表输出中其第一行也会随运行详情一起显示。 | |language_version| 否 | 否 | string 或 map | 语言/工具链版本请求与来源偏好。 | |log_file| 否 | 否 | string 路径 | Hook 失败或 verbose 时把输出写入文件。 | |require_serial| 否 | 否 | boolean | 避免该 Hook 被并发调用。 | |stages| 否 | 否 | stage 名称列表 | 该 Hook 可运行的 Git hook 阶段。 | |verbose| 否 | 否 | boolean | 即使 Hook 成功也打印输出。 | |minimum_prek_version| 否 | 是 | 版本字符串 | 运行该 Hook 所需的最低 prek 版本。 |对于与 upstreampre-commit共有的字段prek 遵循其上游 manifest 语义。注意minimum_pre_commit_version上游字段会被 prek 有意忽略取而代之的是minimum_prek_version源码中deserialize_and_validate_minimum_versionconfig/mod.rs会把该值解析为semver::Version若当前 prek 版本低于要求则直接报错并提示升级方式。prek 独有字段详解1.shellprek 独有设置shell后entry会被当作该 shell 的源码而非直接解析成 argv 执行。prek 把源码写入临时脚本再用选定的 shell 适配器运行并将 Hook 的args与文件名作为脚本参数传入。因此 POSIX shell 的entry需要用$读取参数。各适配器的精确命令见 配置参考 ·shell|shell| 适配器命令 | 脚本参数 | | -- | -- | -- | |bash|bash --noprofile --norc -eo pipefail script|$| |sh|sh -e script|$| |pwsh|pwsh -NoProfile -NonInteractive -File script|$args| |powershell|powershell -NoProfile -NonInteractive -File script|$args| |cmd|cmd /D /E:ON /V:OFF /S /C CALL script|%*|shell仅在语言后端使用shell 感知的 entry 解析器时受支持对于docker/docker_image、dart、fail、julia/rust、pygrep、r等语言prek 会拒绝该字段原因各不相同例如fail的entry是失败消息正文、pygrep的entry是正则模式。2.envprek 独有为 Hook 环境创建与 Hook 进程设置环境变量值可覆盖已有进程环境包括PATH。注意两点一是当匹配的 Hook 环境已存在时prek 会直接复用而不会用新的env重装二是若多个 Hook 共享一个环境只有创建该环境那个 Hook 的env影响安装。语言后端也可能覆盖或移除它们管理的变量以保持 Hook 环境隔离。当 manifest 与终端用户配置都定义了env时两表会合并用户配置的重复键优先源码HookOptions::update中即通过extend实现合并见 config/hook.rs。3.pass_filenames: n正整数prek 扩展upstreampre-commit只接受布尔值prek 额外支持正整数当匹配文件多于n时prek 会把文件分批调用 Hook每批至多n个文件名。在源码中PassFilenames枚举对应三种形态Alltrue、Nonefalse、Limited(NonZeroUsize)正整数且 0 或负数会被拒绝解析见 config/hook.rs 中PassFilenames的反序列化实现。即使不设置该字段prek 也会自动限制文件数量避免命令行超过操作系统长度上限。4.{ glob: ... }形式的files/excludeprek 扩展默认与上游兼容情况下files/exclude是 regex 字符串prek 使用 Rustfancy-regex引擎以search语义匹配建议用^...$锚定。prek 还支持files: glob: src/**/*.rs # 单个 glob files: glob: # glob 列表任一匹配即可 - src/**/*.rs - crates/**/src/**/*.rs exclude: glob: - target/** - dist/**该 glob 形式由FilePattern枚举config/pattern.rs实现内部将 glob 编译为globset::GlobSet进行匹配。当 manifest 需要与 upstreampre-commit双兼容时应使用 regex 字符串形式。5.language_version的 map 形式prek 扩展prek 把language_version当作版本请求而非单个固定值字符串形式如3.12、^1.2、1.5, 2.0均可。map 形式prek 扩展支持两个字段request版本请求默认defaultpreference工具链来源偏好取only-managed/managed默认/system/only-system之一控制 prek 搜索受管工具链与系统工具链的顺序、是否允许下载。特殊值default表示使用该语言的默认解析逻辑system表示不下载新工具链。源码中的LanguageVersion结构config/hook.rs同时实现了request/preference的合并与默认值应用逻辑。终端用户的配置文档还提供default_language_version顶层键为每种语言设置默认值。若希望 manifest 与 upstream 兼容应使用字符串形式。只属于项目配置的字段priority和groups属于项目配置文件如.pre-commit-config.yaml/prek.toml中的字段不是manifest hook 字段。若在远程仓库的.pre-commit-hooks.yaml中出现groupsprek 会忽略它见 配置参考 · groups。一个完整的 manifest 示例# yaml-language-server: $schema... - id: format-json name: format json entry: python3 -m tools.format_json language: python files: \\.json$ - id: lint-shell name: shellcheck entry: shellcheck language: system types: [shell]编写entry的最佳实践尽量让entry直接调用可执行文件例如上面的shellcheck不要假定 shell 一定存在也不要在未显式声明平台要求的情况下假定 POSIX 路径在 Windows 上可用。语言支持与 Hook 入口解析的运行时、工作目录契约详见 语言支持参考 与 Hook 入口解析。编辑器补全与校验prek 在仓库中维护了一份针对.pre-commit-hooks.yaml的 prek-hooks.schema.json JSON Schema。该 schema留在 prek 仓库中而非注册到 SchemaStore因此编辑器需要显式接入它。使用 YAML Language Server 时在 manifest 顶部添加指令即可获得补全与校验# yaml-language-server: $schemahttps://raw.githubusercontent.com/j178/prek/master/prek-hooks.schema.json这会同时为 upstream 字段与 prek 扩展glob 过滤器、env、shell等提供补全和校验。本仓库中的 prek-hooks.schema.json 与 prek.schema.json项目配置 schema均由源码中带schemars的派生结构生成例如 config/hook.rs 中Manifest上的schemars(title .pre-commit-hooks.yaml)与$id声明。选择 Hook 阶段stagesHook 作者可以在.pre-commit-hooks.yaml中通过stages声明支持的 Git hook 阶段终端用户可以在自己的配置中覆盖该列表。如果两边都没有设置prek 回退到顶层default_stages其默认值为所有阶段。manual阶段是特殊的它永远不会自动运行只有用户显式执行prek run --hook-stage manual hook-id时才会运行。示例- id: lint name: lint entry: my-lint language: python stages: [pre-commit, pre-merge-commit, pre-push, manual]prek 支持的完整阶段名称包括manual、commit-msg、post-checkout、post-commit、post-merge、post-rewrite、pre-commit、pre-merge-commit、pre-push、pre-rebase、prepare-commit-msg源码枚举见 config/hook.rs 中的Stage其位掩码集合Stages用 16 位整数表示全部阶段。每个阶段在何时触发、是否基于仓库文件运行详见 支持的 Git Hook 阶段——注意commit-msg与prepare-commit-msg阶段 Hook 的输入是 Git 的提交信息文件而非仓库文件路径因此这类 Hook 通常需要配合always_run: true才会自动运行。向 Hook 传递参数当用户在配置中给 Hook 设置args时prek 会把这些参数放在文件路径列表之前传递如果args为空或被省略则只传文件路径。示例终端用户配置repos: - repo: https://github.com/example/hook-repo rev: v1.0.0 hooks: - id: my-hook args: [--max-line-length120]最终调用形态my-hook --max-line-length120 path/to/file1 path/to/file2如果配置了shell同样的args会作为脚本参数传入POSIX 用$读取如果设置了pass_filenames: false则文件路径不会追加。Hook 进程还会收到阶段相关的PRE_COMMIT_*环境变量。在pre-push、commit-message、rebase、checkout 与 rewrite 阶段可用的变量值清单见 暴露给 Hook 的变量。为prek update做版本管理终端用户通过配置中的rev字段锁定你的仓库版本。为了让prek update正常工作请为发布版本打 git tag优先使用语义化版本 tag如v1.2.3或1.2.3将 tag 推送到远端annotated 与 lightweight tag 均可不要移动 tag把 tag 当作不可变的发布引用。prek update默认选择最新的 tag使用--bleeding-edge时改用默认分支的 tip 而非 tag使用--freeze时则把 commit SHA 写入rev而不是 tag 名称。可选的发布治理手段还包括--cooldown-days新发布的版本可进入观察期、--include-tag/--exclude-tagglob 过滤以及update顶层键的项目级/全局配置见 配置参考 · update。此外prek 会检测配置中rev是否为可变引用分支或可移动的 tag并在运行时给出警告建议使用不可变的 tag 或 commit SHA见 config/mod.rs 中的相关警告逻辑。本地开发prek try-repoprek try-repo可以在不发布 release 的情况下直接运行某个仓库中的 hooks非常适合在迭代 Hook 时使用# 在另一个想测试该 hook 的仓库中执行 prek try-repo ../path/to/hook-repo my-hook-id --verbose注意事项prek try-repo接受任何git clone能理解的路径或 git URL测试prepare-commit-msg或commit-msg类 hooks 时需传入相应的--commit-msg-filename参数。从源码看try_repo.rs 的实现会做如下工作接受repo参数本地目录或远端 URL本地仓库有未提交改动时通过clone_and_commit创建一个影子仓库shadow repo临时提交改动以便干净测试随后 clone 仓库并读取其.pre-commit-hooks.yamlmanifest按选择器筛选 hook id动态生成一份临时的prek.toml配置render_repo_config_toml最后委托给crate::cli::run执行。因此你甚至可以在仓库还没发布时就用本地相对路径或任意远端 URL 做端到端验证。校验与 CI在发布 release tag 之前先在本机用prek validate-manifest校验 manifest 格式prek validate-manifest .pre-commit-hooks.yaml该命令读取并解析 manifest与运行时read_manifest走同一条解析路径确保其在发布前是良好成形的。源码实现位于 cli/validate.rs解析失败时会打印error及完整错误链caused by全部通过则输出success: All manifests are valid。同一文件还提供了validate-configs用于校验项目配置文件。推荐的 CI 流程在 CI 中运行prek validate-manifest .pre-commit-hooks.yaml用一个小的 fixture 仓库或直接通过prek try-repo hook-repo hook-id --all-files实际跑一遍你的 hook验证其行为与退出码语义配合通用 CI 框架使用——详见 持续集成 文档中关于 GitHub Actions 等场景的整体配置。总结Hook 作者的发布检查清单仓库根目录存在.pre-commit-hooks.yaml且每个 hook 具备id、name、entry、language用prek validate-manifest校验通过依据语言选择正确的打包文件与language值需要受管依赖时使用additional_dependencies恰当声明stages记住manual不会自动运行用prek try-repo在真实仓库上端到端验证包括args、pass_filenames与退出码行为用语义化版本 tag 发布并推送 tag确保prek update可正确选择版本在 CI 中固化校验与冒烟测试防止后续改动破坏 manifest。【免费下载链接】prek⚡ A fast Git hook manager written in Rust, designed as a drop-in alternative to pre-commit, reimagined.项目地址: https://gitcode.com/GitHub_Trending/pr/prek创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考