跳转至

Git Hooks

概述

linthis 与 Git 的 hook 系统集成,在提交(或推送)时自动运行 lint 检查和格式化。Hook 可以在两个作用域安装:

  • 项目级 — 写入单个仓库的 .git/hooks/<event>
  • 全局 — 写入 ~/.config/git/hooks/<event>,通过 git config --global core.hooksPath 对机器上所有仓库生效

全局 hook 采用 策略 B:本地项目 hook 优先。如果本地 .git/hooks/<event> 已调用 linthis,全局 hook 完全委托给它。如果本地 hook 存在但不调用 linthis,全局 hook 先运行 linthis,再链式调用本地 hook。如果没有本地 hook,全局 hook 直接运行 linthis。此设计保证对其他 hook 工具零干扰。

所有 hook 类型——gitprekgit-with-agentprek-with-agent——均支持在两个作用域安装。


快速开始

项目级 hook

# 默认:git pre-commit hook
linthis hook install

# git pre-push hook
linthis hook install --event pre-push

# commit message 格式检查 hook
linthis hook install --event commit-msg

# prek hook(适用于使用 prek 的项目)
linthis hook install --type prek

全局 hook

# 全局 git pre-commit hook(对所有仓库生效)
linthis hook install --global

# 全局 git pre-push hook
linthis hook install --global --event pre-push

# 全局 hook,非交互式
linthis hook install --global -y

运行 linthis hook install --global 后,该命令会:

  1. 将 hook 脚本写入 ~/.config/git/hooks/pre-commit
  2. 执行 git config --global core.hooksPath ~/.config/git/hooks

机器上的每个仓库都会立即使用该 hook,无需对现有仓库重新运行 git init


Hook 类型

类型 运行器 触发方式 说明
git Git 原生 .git/hooks/<event> 默认类型;无需额外工具
prek prek prek 运行器 需要安装 prek;配置文件可提交到仓库
git-with-agent Git 原生 .git/hooks/<event> git 相同,lint 失败时额外触发 AI 智能修复
prek-with-agent prek prek 运行器 prek 相同,lint 失败时额外触发 AI 智能修复

全局 Hook

安装

# 安装全局 pre-commit hook(git 类型)
linthis hook install --global

# 安装全局 pre-push hook
linthis hook install --global --event pre-push

# 安装带 agent 修复的全局 hook
linthis hook install --global --type git-with-agent --provider claude

# 非交互式(跳过确认提示)
linthis hook install --global -y

工作原理

--global 执行两个操作:

  1. 写入 ~/.config/git/hooks/<event> — hook 脚本文件
  2. 设置 git config --global core.hooksPath ~/.config/git/hooks

Git 的 core.hooksPath 使 Git 对所有仓库都从该目录查找 hook,立即生效,无需逐仓库配置。

目录结构

~/.config/git/hooks/
├── pre-commit     # 由 linthis hook install --global 安装
├── pre-push       # 由 linthis hook install --global --event pre-push 安装
└── ...

策略 B 详解

全局 hook 不会盲目运行,而是先检查当前仓库的本地 .git/hooks/<event>

本地 hook 状态 全局 hook 行为
无本地 hook 直接运行 linthis
本地 hook 存在,调用 linthis 先运行 linthis,再委托给本地 hook
本地 hook 存在,调用 linthis 完全委托(exec "$LOCAL_HOOK" "$@")——linthis 不会重复运行

检测使用 grep -qE '^[^#]*linthis'——匹配任何包含 linthis 的非注释行,注释行的修改不影响检测结果。

生成的全局 hook 脚本示例(commit-msg,git 类型)

#!/bin/sh
# linthis-hook

LINTHIS_CMD="linthis cmsg"

# Locate the local project hook (git-dir aware)
GIT_DIR="$(git rev-parse --git-dir 2>/dev/null)"
LOCAL_HOOK=""
if [ -n "$GIT_DIR" ]; then
  LOCAL_HOOK="$GIT_DIR/hooks/commit-msg"
fi

if [ -f "$LOCAL_HOOK" ] && [ -x "$LOCAL_HOOK" ]; then
  if grep -qE '^[^#]*linthis' "$LOCAL_HOOK" 2>/dev/null; then
    # Local hook already calls linthis — delegate entirely
    exec "$LOCAL_HOOK" "$@"
  else
    # Local hook exists but has no linthis — run linthis first, then delegate
    $LINTHIS_CMD "$@"
    LINTHIS_EXIT=$?
    "$LOCAL_HOOK" "$@"
    LOCAL_EXIT=$?
    [ $LINTHIS_EXIT -ne 0 ] && exit $LINTHIS_EXIT
    exit $LOCAL_EXIT
  fi
else
  # No local hook — run linthis directly
  $LINTHIS_CMD "$@"
  LINTHIS_EXIT=$?
  exit $LINTHIS_EXIT
fi

注意:$@ 将 git 的 $1(消息文件路径)安全传递,即使路径包含空格也能正确处理。

生成的全局 hook 脚本示例(pre-commit,git 类型)

#!/bin/sh
# linthis-hook

LINTHIS_CMD="linthis -s -c -f --hook-event=pre-commit"

# Locate the local project hook (git-dir aware)
GIT_DIR="$(git rev-parse --git-dir 2>/dev/null)"
LOCAL_HOOK=""
if [ -n "$GIT_DIR" ]; then
  LOCAL_HOOK="$GIT_DIR/hooks/pre-commit"
fi

if [ -f "$LOCAL_HOOK" ] && [ -x "$LOCAL_HOOK" ]; then
  if grep -qE '^[^#]*linthis' "$LOCAL_HOOK" 2>/dev/null; then
    # Local hook already calls linthis — delegate entirely
    exec "$LOCAL_HOOK" "$@"
  else
    # Local hook exists but has no linthis — run linthis first, then delegate
    $LINTHIS_CMD
    LINTHIS_EXIT=$?
    "$LOCAL_HOOK" "$@"
    LOCAL_EXIT=$?
    [ $LINTHIS_EXIT -ne 0 ] && exit $LINTHIS_EXIT
    exit $LOCAL_EXIT
  fi
else
  # No local hook — run linthis directly
  $LINTHIS_CMD
  LINTHIS_EXIT=$?
  exit $LINTHIS_EXIT
fi

三层 Hook 解析机制

linthis hook install 运行时,按以下三层优先级(由高到低)解析 hook 脚本:

层级 来源 使用方式
第 1 层 固定路径自动发现 在项目根目录的 hooks/git/<event> 放置脚本
第 2 层 TOML 来源映射 .linthis/config.toml 中设置 [hook.git] 条目
第 3 层 内置生成器 默认——内置生成的脚本

第 1 层:固定路径自动发现

在项目根目录的约定路径创建可执行文件:

hooks/git/pre-commit
hooks/git/pre-push
hooks/git/commit-msg

若该文件存在,linthis 直接使用,无需生成脚本,无需额外配置。

第 2 层:TOML 来源映射

.linthis/config.toml 中通过 source 条目覆盖 hook 来源。插件通过 linthis plugin add 添加时通常会自动注入这些条目。

[hook.git]
pre-commit = { source = { plugin = "my-plugin", file = "hooks/git/pre-commit" } }

支持五种来源变体:

# 本地文件(相对于项目根目录)
pre-commit = { source = { file = "hooks/git/pre-commit" } }

# 已安装插件中的文件
pre-commit = { source = { plugin = "my-plugin", file = "hooks/git/pre-commit" } }

# 来自命名市场的插件文件
pre-commit = { source = { marketplace = "corp", plugin = "linthis-official", file = "hooks/git/pre-commit" } }

# 直接 URL 下载
pre-commit = { source = { url = "https://example.com/hooks/pre-commit" } }

# 克隆 git 仓库
pre-commit = { source = { git = "https://github.com/org/hooks.git", ref = "main", path = "pre-commit" } }

同样的覆盖结构适用于所有 hook 类型([hook.git-with-agent][hook.prek][hook.prek-with-agent] 等)。

插件捆绑 Hook

插件可以在插件根目录的 linthis-hook.toml 中捆绑 hook 覆盖配置。当用户运行 linthis plugin add <alias> <url> 时,linthis 自动:

  1. plugin = "self" 替换为 plugin = "<alias>"(用户指定的别名)
  2. [hook.*] 条目以非覆盖方式合并到用户的 .linthis/config.toml

这意味着添加团队插件即可让所有成员自动获得团队定制的 pre-commit 脚本。


*-with-agent Hook 类型

git-with-agentprek-with-agent 类型添加了 AI agent 修复兜底机制。当 linthis 以非零状态码退出(lint 失败)时,hook 会以无头模式调用指定的 agent CLI 尝试自动修复,然后重新运行 linthis 验证结果。

支持的 provider

--provider Agent CLI 无头模式命令
claude Claude Code CLI claude -p '<prompt>'
codex OpenAI Codex CLI codex exec '<prompt>'
gemini Google Gemini CLI gemini -p '<prompt>'
cursor Cursor agent cursor-agent chat '<prompt>'
droid Droid droid exec --auto low '<prompt>'
auggie Auggie auggie --print '<prompt>'

--provider 支持 provider/model 语法(如 claude/opus),等同于 --provider claude --provider-args "--model opus"。使用 --provider-args 可向 AI agent CLI 传递额外参数。

示例

# 项目级:git hook,使用 Claude 修复兜底
linthis hook install --type git-with-agent --provider claude

# 项目级:prek hook,使用 Gemini 修复兜底
linthis hook install --type prek-with-agent --provider gemini

# 使用 provider/model 语法(向 agent CLI 传递 --model)
linthis hook install --type git-with-agent --provider claude/opus

# 使用显式 provider-args
linthis hook install --type git-with-agent --provider claude --provider-args "--model opus"

# 全局:git hook,使用 Claude 修复兜底
linthis hook install --global --type git-with-agent --provider claude

hook status

查看所有已安装 hook 的当前状态:

linthis hook status

输出示例:

Git Hook Status
Repository: /path/to/repo

Project Hooks (.git/hooks/):
✓ /path/.git/hooks/pre-commit [project]
    pre-commit (runs before commit)
    ✓ linthis

Global Hooks (~/.config/git/hooks/):
  core.hooksPath = /Users/username/.config/git/hooks
  ✓ /Users/username/.config/git/hooks/pre-commit [global]
      ℹ Strategy: local hook takes priority

状态输出包含:

  • 已安装的项目级 hook 及其是否包含 linthis 调用
  • 已安装的全局 hook
  • 当前生效的 core.hooksPath 设置
  • 正在使用的委托策略

全局 vs 项目级对比

功能 全局(--global 项目级
作用域 机器上的所有仓库 仅当前仓库
位置 ~/.config/git/hooks/ .git/hooks/
修改的 Git 配置 core.hooksPath(全局)
对现有仓库立即生效
可提交到仓库 否(.git/ 不被追踪)
团队共享 需要 prek 或 pre-commit 类型
Hook 共存 策略 B(自动委托) 手动链式调用
支持的类型 所有类型 所有类型

卸载

删除指定的全局 hook

# 删除全局 pre-commit hook
linthis hook uninstall --global

# 删除全局 pre-push hook
linthis hook uninstall --global --event pre-push

# 非交互式
linthis hook uninstall --global -y

删除所有全局 hook

linthis hook uninstall --global --all

# 非交互式
linthis hook uninstall --global --all -y

--all 会删除 ~/.config/git/hooks/ 中的所有 hook 脚本,如果没有其他 hook 剩余,还会取消 core.hooksPath 设置。

删除项目级 hook

# 删除项目 pre-commit hook
linthis hook uninstall

# 删除项目 pre-push hook
linthis hook uninstall --event pre-push

命令参考

# 项目级安装
linthis hook install                                               # git pre-commit
linthis hook install --event pre-push                             # git pre-push
linthis hook install --type prek                                   # prek
linthis hook install --type git-with-agent --provider claude       # git + agent 修复
linthis hook install --type git-with-agent --provider claude/opus  # git + agent 修复(指定 model)
linthis hook install --type prek-with-agent --provider gemini      # prek + agent 修复

# 全局安装
linthis hook install --global                                      # 全局 git pre-commit
linthis hook install --global --event pre-push                     # 全局 git pre-push
linthis hook install --global --type git-with-agent --provider claude  # 全局 + agent 修复
linthis hook install --global -y                                   # 非交互式

# 卸载
linthis hook uninstall                                             # 删除项目 pre-commit
linthis hook uninstall --global                                    # 删除全局 pre-commit
linthis hook uninstall --global --all                              # 删除所有全局 hook
linthis hook uninstall --global -y                                 # 非交互式

# 状态
linthis hook status

常见问题

Q1:全局 hook 和项目级 hook 可以共存吗?

可以。这正是策略 B 的主要使用场景。如果项目有调用 linthis.git/hooks/pre-commit,全局 hook 会检测到并完全委托——linthis 只运行一次,不会重复。如果项目 hook 不调用 linthis,全局 hook 会先运行 linthis,再调用项目 hook。

Q2:策略 B 如何检测本地 hook 是否调用 linthis?

运行 grep -qE '^[^#]*linthis' "$LOCAL_HOOK"。该模式匹配任何包含字符串 linthis 的非注释行(^[^#]*)。以 # 开头的注释行会被忽略。因此修改注释(例如添加 # previously used linthis)不会影响检测结果——只有可执行行才算数。

Q3:如何针对特定仓库禁用全局 hook?

安装一个调用 linthis 的项目级 hook。全局 hook 会检测到并委托,项目 hook 成为唯一入口,你可以完全控制该仓库中 linthis 的调用方式。

如果你希望某个仓库完全不运行 linthis,最简洁的方式是卸载全局 hook,改用项目级 hook 管理。

Q4:全局 hook 会影响不使用 linthis 的仓库吗?

hook 会尝试运行 linthis -s -c -f --hook-event=pre-commit。如果仓库没有 linthis 配置文件(.linthis/config.toml.linthis.tomllinthis.toml),linthis 会立即退出且不报错,提交正常进行。

Q5:我使用了 --type git-with-agent,但 agent CLI 未安装会怎样?

hook 首先运行 linthis。如果 linthis 成功退出,agent 永远不会被调用。如果 linthis 失败且 agent CLI 二进制文件不存在,hook 会打印警告并以原始 linthis 退出码退出,提交依然被阻断。

Q6:--type prek--type pre-commit 可以与 --global 一起使用吗?

可以。所有 hook 类型都支持 --global。写入 ~/.config/git/hooks/<event> 的 hook 脚本会调用相应的运行器(prekpre-commit)而非直接调用 linthis。策略 B 委托逻辑同样适用。

Q7:如何查看当前生效的 core.hooksPath

git config --global --get core.hooksPath
# 输出:/Users/username/.config/git/hooks

如果没有输出,说明未设置全局 core.hooksPath,Git 按常规使用各仓库的 .git/hooks/ 目录。


修复提交模式(Fix Commit Mode)

控制自动格式化和 agent 修复的提交方式。按事件配置:

[hook.pre_commit]
fix_commit_mode = "squash"    # squash | dirty | fixup

[hook.pre_push]
fix_commit_mode = "dirty"     # squash | dirty | fixup

或通过 CLI 设置:linthis hook install --fix-commit-mode <mode>

模式 行为
squash 修复 → 创建 fixup commit → 压入原始 commit。保留 stash 快照。
dirty 修复 → 留在工作区 → 阻止提交/推送。用户先审查。
fixup 原始 commit 直接通过。post-commit 创建单独的 fixup commit。

详见 修复提交模式 完整行为矩阵。

参考资料