◆

Git commit 用 --no-verify 跳过 hooks 检测

2023-02-02 01:370

什么是 git hooks

git hooks 是 git 在执行特定操作时自动调用的脚本,存放在仓库的 .git/hooks 目录下。和提交流程相关的主要是这几个:

  • pre-commit:在提交信息编辑器打开之前触发。前端项目里通常挂着 eslint、prettier、stylelint,用来拦下不符合规范的代码。脚本以非 0 退出码结束时,提交被中止。
  • commit-msg:拿到提交信息之后触发,参数是存放提交信息的临时文件路径。常见用途是校验提交信息格式,比如 commitlint 检查是否符合 Conventional Commits 规范。
  • pre-push:在 git push 真正发送数据前触发,常用来跑单元测试或类型检查。

这套机制的价值在于把规范检查前移到本地,问题在提交那一刻就被拦住,不用等 CI 跑完再回头改。但它也有代价:钩子跑得慢的时候,一次提交要等十几秒甚至更久;而且有些场景下我们明确知道这次提交不需要走完整检查。这时候就需要一个跳过它的开关。

跳过 pre-commit 与 commit-msg

git commit 的 --no-verify 参数会同时跳过 pre-commit 和 commit-msg 两个钩子:

# --no-verify 可以省略成 -n
git commit --no-verify

配合提交信息一起写:

git commit -n -m "wip: 临时保存进度"

要留意 -n 这个简写在不同命令下含义不同。在 git commit 里它是 --no-verify,但在 git add -n、git rm -n 里它是 --dry-run。写脚本时建议用完整的 --no-verify,可读性更好也不容易搞错。

另外 --no-verify 不会跳过 prepare-commit-msg 钩子(那个用来预填提交信息模板的),也不影响 post-commit。它管的就是"校验类"的这两个。

跳过 pre-push

git push 也有同名参数,跳过的是 pre-push 钩子:

git push --no-verify

这里没有 -n 简写可用,git push -n 是 --dry-run(演练不实际推送),意思完全相反,别搞混。

husky 场景下的做法

现在前端项目的钩子基本都由 husky 管理,配合 lint-staged 只检查暂存区的文件。典型配置长这样:

{
  "lint-staged": {
    "*.{js,ts,vue}": ["eslint --fix", "prettier --write"]
  }
}

husky 的本质是把脚本写进 .husky/ 目录并让 git 指向它,所以 --no-verify 对它同样有效,因为跳过的是 git 调用钩子这一层,跟钩子内容是什么无关。

除此之外 husky 还提供了环境变量的方式,适合在 CI 或者批量操作里用。husky v8 及以上用 HUSKY=0:

HUSKY=0 git commit -m "chore: 批量格式化"

这个变量的作用范围比 --no-verify 更大,它会让 husky 管理的所有钩子都不执行,包括 pre-push。如果需要在一整段脚本里都禁用,可以先导出:

export HUSKY=0
# 这里的一系列 git 操作都不会触发 husky 钩子
git add .
git commit -m "chore: 迁移目录结构"
git push

用完记得 unset HUSKY 或者关掉当前终端,否则后续的提交也一直不走检查了。

顺带一提,CI 环境里通常不需要 husky,可以在安装依赖时就跳过它的安装步骤,比每次设变量更彻底。

什么时候该跳过

跳过检查本身不是坏事,判断标准是"这次提交的代码最终会不会以当前形态进入主干"。合理的场景:

  • 本地临时保存进度。写了一半的功能要切分支处理紧急问题,先 commit -n 存一下,回来接着写。这种提交后面会被 rebase 压掉,没必要过检查。
  • 提交非代码文件。只改了 README、图片、配置文件,但钩子配置得比较粗放,会连带跑一整套 lint,纯粹浪费时间。
  • 批量机械改动。比如全项目跑了一次格式化工具、或者做目录重命名,改动量上千个文件,lint-staged 会卡很久,而这次改动本身就是格式化产生的。
  • 钩子本身坏了。依赖没装全、Node 版本不对导致钩子直接崩,在修好之前需要一个逃生通道。
  • merge 冲突解决后的提交。有些钩子在 merge 场景下会误报。

什么时候不该跳过

  • 要推到共享分支的提交。master、develop 这类分支上的代码,跳过本地检查只是把问题推给 CI 或者同事,最终还是要改,而且改的成本更高。
  • 因为 lint 报错就跳过。这是最常见也最该避免的用法。报错说明代码确实有问题,正确做法是修掉,或者在确认规则不合理时去调整 eslint 配置、给具体行加 eslint-disable-next-line 注释并写明原因。用 --no-verify 掩盖报错,等于让规范配置逐渐失去意义。
  • 提交信息格式不合规就跳过。commitlint 拦下来通常就是漏了 type 前缀,改一下比跳过更快。而且提交信息格式往往和自动生成 changelog、语义化版本绑定,跳过会破坏下游流程。
  • 团队约定不允许时。有些团队会在 CI 上加一道"提交信息格式检查",本地跳过了 CI 照样会失败,白折腾一趟。

一个实用的心态是:把 --no-verify 当作调试和临时周转的工具,而不是日常提交的习惯。如果发现自己天天在用它,那说明真正该修的是钩子——要么是检查范围太大(该用 lint-staged 只查暂存文件),要么是把该放到 CI 的重活(完整测试套件、全量类型检查)塞进了 pre-commit。

检查当前仓库有哪些钩子

想知道自己提交时到底会触发什么,直接看目录就行。原生钩子在:

ls -l .git/hooks

.git/hooks 下带 .sample 后缀的都是 git 自带的示例,不会被执行,只有去掉后缀且有可执行权限的才会生效。

用 husky 的项目看这里:

ls -l .husky

还可以确认一下 git 有没有被配置成从别处找钩子,一些工具会改这个配置:

git config core.hooksPath

有输出说明钩子目录被改到了那个路径,.git/hooks 里的内容就不生效了,去输出的目录里看真正在跑的脚本。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEGit commit 用 --no-verify 跳过 hooks 检测加载中