你的团队在 .git/hooks/pre-commit 里配置了 ESLint 检查,commit-msg hook 验证 commit 消息格式,但新同事 clone 仓库后提交代码,这些检查完全没有触发。ESLint 错误的代码被直接提交进了主分支。原因其实很简单:.git/ 目录不被 Git 追踪,不会随 clone 或 push 传播,每个人 clone 后都有一个全新的空 hooks 目录。这是 Git 的设计,安全考量是避免克隆操作自动执行不受信任的代码。
常见原因
1. .git/hooks/ 不在版本控制中
根本原因。Git 故意不追踪 .git/ 目录,包括 hooks。就算你把 hook 脚本放进 .git/hooks/,推送后其他人 clone 也不会得到这些文件。
怎么判断:ls .git/hooks/ 查看本机 hooks,再让新同事执行同样命令,若结果完全不同(或新同事只有 .sample 文件),说明 hooks 没有被分发。
2. hooks 目录已配置但脚本没有可执行权限
即使通过某种方式把 hook 脚本分发到了 .git/hooks/,文件权限不是可执行(chmod +x)的话 Git 也不会执行,静默跳过。
怎么判断:ls -la .git/hooks/pre-commit,若权限是 -rw-r--r-- 而非 -rwxr-xr-x,缺少执行权限。
3. core.hooksPath 未配置或配置路径不存在
Git 2.9+ 支持 core.hooksPath 把 hooks 目录指向仓库内的某个路径(如 .githooks/),该目录可以被版本控制。但配置需要每个开发者在本地执行 git config core.hooksPath .githooks,若 setup 步骤被遗漏,配置就没有生效。
怎么判断:git config core.hooksPath 若无输出,说明 hooksPath 没有被设置,Git 仍然使用默认的 .git/hooks/。
4. 使用了 Husky 但 prepare 脚本没有自动运行
Husky 是流行的 hook 管理工具,在 package.json 里配置 "prepare": "husky install" 后,执行 npm install 时会自动安装 hooks。但若新同事直接 clone 后没有执行 npm install(比如只用 yarn,或者项目用 pnpm),prepare 脚本不会运行,hooks 就不存在。
怎么判断:cat package.json | grep prepare,若有 "prepare": "husky install" 但 .husky/ 目录里的 hook 没有被执行,检查是否运行过 npm install。
5. Windows 与 Unix 换行符导致 hook 脚本无法执行
团队在 macOS/Linux 上编写的 hook 脚本被 Windows 开发者 clone 后,CRLF 自动转换把脚本第一行的 #!/bin/bash 变成了 #!/bin/bash\r,shell 无法识别这个 shebang,脚本报错后 Git 跳过 hook 执行。
怎么判断:file .git/hooks/pre-commit 若输出包含「CRLF line terminators」,就是这个问题。
6. --no-verify 被写进了团队脚本或 IDE 配置
某个开发者为了绕过一次慢速 hook,在 IDE 的 Git commit 设置里勾选了「Skip pre-commit hooks」或者在 commit 命令里加了 --no-verify,并且这个设置被同步到了共享配置,其他人也受影响。
怎么判断:检查 VS Code 的 settings.json(搜索 git.enableCommitSigning)或 IntelliJ 的 VCS 设置,是否有跳过 hooks 的选项被开启。
最短修复路径
Step 1:将 hook 脚本移到版本控制目录
mkdir -p .githooks
cp .git/hooks/pre-commit .githooks/pre-commit
chmod +x .githooks/pre-commit
git add .githooks/
git commit -m "chore: add team git hooks to .githooks/"
Step 2:在仓库里统一配置 hooksPath
git config --local core.hooksPath .githooks
但这只对你本机生效。需要让所有人在 clone 后也执行这个命令。
Step 3:通过 setup 脚本自动化
在项目根目录创建 scripts/setup.sh:
#!/bin/sh
git config core.hooksPath .githooks
echo "Git hooks configured."
并在 README.md 或 CONTRIBUTING.md 里注明 clone 后必须执行 ./scripts/setup.sh。
Step 4:若使用 npm 生态,改用 Husky 自动化
npm install --save-dev husky
npx husky init
# 这会在 package.json 里添加 "prepare": "husky"
# 并创建 .husky/ 目录
之后 npm install 会自动安装 hooks,无需手动步骤。
Step 5:修复 Windows CRLF 问题
在 .gitattributes 里为 hook 脚本强制 LF:
.githooks/* text eol=lf
然后重新 checkout:
git rm --cached .githooks/*
git checkout .githooks/
预防建议
- 采用 Husky +
prepare脚本的方案,是目前最可靠的 hook 自动分发方式,npm/yarn/pnpm install 后自动生效,无需记住手动步骤。 - 在
README.md顶部显著位置写明 setup 步骤,并在 CI 里增加 hook 配置检查,若core.hooksPath未设置则发出警告(不阻断,只告知)。 - hook 脚本保持轻量,pre-commit 检查控制在 3 秒内,避免开发者因为慢而用
--no-verify绕过。 - 在
.gitattributes里为所有 shell 脚本设置text eol=lf,防止 Windows 上的 CRLF 问题:*.sh text eol=lf。 - 团队的 onboarding checklist 里加一条「执行
./scripts/setup.sh或npm install」,并由导师在入职第一天检查确认。 - 定期在 CI 里跑 hook 脚本做同等检查(如 ESLint、单元测试),即使本地 hook 被绕过,CI 也能兜底。
常见问答 (FAQ)
Q: core.hooksPath 可以配置在全局 git config 里,让所有仓库都用吗?
A: 可以,git config --global core.hooksPath ~/.githooks,但这会把所有仓库的 hooks 都指向同一个目录,可能产生混乱。推荐每个仓库独立配置,或者通过 includeIf 按目录区分配置。
Q: 仓库用了 Git LFS,会影响 hooks 的执行吗?
A: 不会直接影响,但 git lfs install 会在 .git/hooks/ 里安装自己的 LFS 钩子(post-checkout、pre-push 等)。若使用 core.hooksPath 把 hooks 目录移走,LFS 的钩子也需要手动迁移到新目录,否则 LFS 功能会失效。
Q: 我能在 hook 里调用 Docker 运行检查,不依赖本地环境安装吗?
A: 可以,pre-commit: docker run --rm -v $(pwd):/app myteam/linter:latest 在容器里运行检查,不依赖本地工具版本。代价是首次启动需要拉取镜像,速度较慢;可以在 CI 里用同一个镜像保证一致性。
Q: commit-msg hook 报错但提交还是成功了,是 hook 没运行吗?
A: 若 hook 脚本的退出码是 0,Git 认为检查通过,不管脚本里是否打印了错误信息。确保 hook 脚本在失败时返回非零退出码:exit 1。可以用 bash -x .git/hooks/commit-msg "test-message" 调试。
相关阅读
- 凭证 helper 锁住,pull / push 全失败
- 分支保护规则让我的 PR 合不了
- AI 把 secret 推到公开仓库了
- CRLF 转换让单次 commit 产生几千行 diff
- Submodule 怎么都拉不到最新 commit
- Claude Code commit 了 secret
标签: #git #version-control #排查