跳至主要内容

Git Hooks 完全指南:安装、用法与技巧

了解如何使用 Git hooks 通过 pre-commit、pre-push 和服务端钩子自动化任务、执行代码标准并保障工作流程。
· 15分钟 阅读

使用 AI 探索

Git hooks 通过在提交或推送等 Git 操作之前或之后运行脚本,让您在工作流程中自动化任务。它们有助于及早发现问题、强制执行标准,甚至可以自动修复问题。

比如,您编写了一个脚本来强制执行代码风格标准。您可以将该脚本添加到在提交前运行的钩子中。此后,每当有人尝试提交时,钩子就会运行。如果代码通过了钩子中的检查,提交就会继续;如果没有,提交将被阻止。

而且这不仅仅是标记问题。您还可以自定义脚本,在提交发生之前修复这些问题。本文将介绍关于 Git hooks 您需要了解的一切:它们是什么、哪些重要、以及如何高效实施。

什么是 Git Hooks?

Git hooks 会在特定的 Git 操作发生时(例如提交、推送或合并前后)自动运行自定义脚本。它们内置于Git 中,因此无需安装任何外部库。

当您初始化一个 Git 仓库时,Git 会默认创建这些钩子。您可以在 .git/hooks 目录中找到它们。它们带有 .sample 扩展名,默认情况下不会运行。若要默认运行,只需移除 .sample 扩展名。

您可以使用任何可执行的脚本语言编写钩子。比如在 pre-commit 钩子文件中,您可以添加一个脚本来检查暂存文件中的 "To-do" 注释,一旦发现就阻止提交。这意味着每次您尝试提交时,Git 都会扫描以“To-do”开头的注释,如果存在未完成事项就使提交失败。类似地,您可以为不同的 Git 操作创建自定义钩子,如 post-commit、pre-merge、pre-push 等。

Git Hooks 的类型

在进入实践示例之前,先了解 Git hooks 的两种类型。

客户端钩子:类别与常见示例

客户端钩子在本地机器上于提交、变基、推送等操作时运行。下面是一些常见的客户端钩子及其具体用途。

  • pre-commit:在您输入提交消息之前运行。可用于强制代码风格、运行测试或在提交前检查语法错误。
  • prepare-commit-msg:在 Git 创建默认提交消息之后、提交消息编辑器弹出之前运行。该钩子可根据自定义脚本逻辑修改或替换默认消息。
  • commit-msg:在提交消息保存之后、Git 完成提交之前运行。这是强制提交消息规则(如格式、结构或标签)的最后检查点。
  • post-commit:在创建提交后运行。常用于通知(如提交后发送 Slack 消息)或日志记录(存储提交元数据)。
  • post-checkout:在成功执行 git checkout 命令后运行。当您切换分支或检出某个特定提交或路径时触发。可用于重建项目依赖、删除临时文件或提示进入了新分支。
  • post-merge:在成功合并后运行。常用于更新依赖、打印合并信息或清理缓存。
  • pre-rebase:在变基操作之前运行。可用于阻止对受保护分支进行变基、在允许变基前运行测试,或在存在未提交更改时阻止变基。
  • pre-push:在推送之前运行。可用于阻止推送到受保护分支或运行推送前测试。
  • post-rewrite:在 Git 成功重写提交之后运行。它会在 git commit --amend(重写最后一次提交)或 git rebase(重写一系列提交)等操作时触发。用于更新引用或在重写后触发测试与 CI 步骤。
  • pre-applypatch:由 git am 命令调用,在补丁应用到工作区之后、创建新提交之前运行。如果该钩子中的检查失败,它会以非零状态退出并阻止提交补丁。
  • post-applypatch:在应用补丁并创建提交之后运行。它无法中止提交,但适用于记录日志或发送通知。
  • applypatch-msg:在 git am 应用补丁之前运行。用于检查或编辑补丁的建议提交消息。

服务端钩子:类别与使用场景

服务端钩子在远程 Git 服务器上运行,并在远程事件(如向仓库推送前后)发生时触发。

  • pre-receive:在远程接受推送之前运行。可用于在用户间强制统一代码风格,或在接受来自客户端的更改之前进行安全检查。
  • update:与每次推送只运行一次的 pre-receive 不同,update 钩子会针对单次推送中每个被更改的引用(分支或标签)各运行一次。
  • post-receive:在 git push 操作完成后运行。常用于发送通知、全面日志记录,或触发CI/CD 流水线。
  • post-update:在 Git 更新推送中的所有引用后运行。适合用于简单通知,因为与 post-receive 不同,它无法访问每个引用更新的旧日志和新日志。
  • reference-transaction:在引用事务被准备、提交或中止时触发,因此可能运行多次。用于在单个事务中验证或拒绝跨多个引用的更新,或记录与审计引用变更。
  • push-to-checkout:当您向远程当前已检出的分支执行 git push 时触发。通过此钩子,您可以在推送后更新服务器上的文件、记录变更,或阻止直接推送到工作目录的不安全操作。
  • pre-auto-gc:在 Git 触发自动垃圾回收之前运行。可用于记录垃圾回收的开始、在资源密集型操作期间阻止其运行,或处理垃圾回收前所需的任务。
  • proc-receive:在 git-receive-pack 命令处理传入推送时运行。它会更新远程仓库中的引用(分支和标签),并将结果报告回客户端。

设置 Git Hooks

本节将介绍安装和设置 Git hooks 的步骤,并演示如何从零创建自定义钩子。

安装与配置机制

当您初始化一个 Git 仓库时,Git 会自动在 .git/hooks 文件夹内创建一组钩子。要查看它们,请打开终端并进入您运行过 git init 的仓库根目录。从那里打开隐藏的 .git 目录,再进入 hooks 子目录。一个简单的 cd 命令即可完成这些并进入 hooks 子目录。

cd .git/hooks

进入后,您会看到带有 .sample 扩展名的默认钩子。要启用默认钩子,请移除对应钩子的 .sample 扩展名,并将其设为可执行。

例如,使用下面的命令移除 pre-commit 钩子的 .sample 扩展名。

mv .git/hooks/pre-commit.sample .git/hooks/pre-commit

接着,使用以下命令将其设为可执行:

chmod +x .git/hooks/pre-commit

以上涵盖了编辑和启用默认钩子。若要从零创建自定义钩子,请按以下步骤操作。

要在 .git/hooks/ 中打开新文件,运行:

nano .git/hooks/commit-msg

在该文件中编写脚本。例如:

 #!/bin/sh
echo "✅ Commit message hook triggered"

保存退出后,将其设为可执行:

chmod +x .git/hooks/commit-msg 

在上述示例中,每当提交通过时,钩子都会打印一条消息 " Commit message hook triggered"

还有另一种设置自定义钩子的方法。当您初始化仓库时,Git 会包含默认钩子。您可以使用模板目录来编辑这些默认项。

工作原理如下:当您运行 git init 时,Git 会将全局模板目录的内容复制到新仓库的 .git/ 目录中(包括钩子)。如果您将自定义钩子脚本添加到全局模板目录,那么您创建的每个新仓库都会自动继承这些自定义钩子。

创建与自定义钩子脚本

首先,明确您的目标。是要拦截含糊的提交信息、防止意外推送,还是清理缓存?预先定义目标很重要,因为不同的 Git 钩子用途不同,您需要知道该用哪一个。

例如,如果要检查提交消息风格,就使用 commit-msg 钩子。如果想避免对 main 分支的某些推送,应使用 pre-push 钩子。

选定钩子后,下一步是选择语言。在 macOS 和 Linux 上,一个简短的 Bash(或纯 sh)脚本通常就够了。在 Windows 上,一些团队会使用 PowerShell。

如果您的仓库是多语言的,您可能会偏向使用 Python 或 Node.js 等通用解释器,以便所有人都能运行相同的逻辑。因此,关键是在顶部包含 shebang(#!/bin/sh、#!/usr/bin/env python3 等),以便 Git 知道如何执行。

一个简单的 pre-commit 钩子示例,用于阻止提交 .env 文件。这可以避免敏感 API 密钥被意外推送。

#!/bin/sh

# Block committing secrets
if git diff --cached --name-only | grep -q ".env"; then
  echo "❌ .env file detected in commit! Remove it before committing."
  exit 1
fi

exit 0

当您想禁用钩子时,最简单的方法是暂时重命名它们(将 pre-commit 改为 pre-commit.off),或移除其可执行权限。

不过,如果您只想跳过一次检查,可使用 git commit --no-verify 跳过提交钩子,使用 git push --no-verify 跳过推送钩子。您甚至可以通过将 Git 指向一个空文件夹来全局禁用钩子:git config core.hooksPath /dev/null。

Git Hooks 的常见用例与示例

Git hooks 尤其适用于在工作流程中自动化测试、安全检查,以及强制执行质量标准。

实用自动化场景

强制代码风格或 Lint:pre-commit 钩子通常用于自动进行代码格式化,并在提交前捕获潜在错误。

例如,在下面的 pre-commit 钩子中,prettier 会自动处理格式化并在提交前暂存更改。随后运行 linter,且仅当 lint 通过时提交才会成功;如果失败,提交会被阻止。

#!/bin/sh

# Collect staged JS/TS files
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
[ -z "$FILES" ] && exit 0

echo "Formatting with Prettier..."
npx --yes prettier --write $FILES

# Re-stage files that Prettier changed
echo "$FILES" | xargs git add

echo "Linting with ESLint..."
npx --yes eslint $FILES
STATUS=$?

if [ $STATUS -ne 0 ]; then
  echo "ESLint found issues. Fix them or commit with --no-verify if needed."
  exit $STATUS
fi

echo "Pre-commit checks passed."
exit 0

运行自动化测试: 您可以设置在推送任何更改之前运行自动化测试。例如,下面的 pre-push 钩子会在推送之前自动发现并运行仓库中的 pytest。

#!/bin/sh
echo "Running tests before push..."
pytest -q
STATUS=$?
[ $STATUS -eq 0 ] || { echo "Tests failed. Push aborted."; exit $STATUS;

自动阻止提交敏感数据: 像 .env、.pem 或 .key 这样的敏感文件通常包含密钥。以下 pre-commit 钩子示例会检查这些模式,并在包含时阻止提交。

#!/bin/sh
set -e

STAGED=$(git diff --cached --name-only --diff-filter=ACM)

# Block dotenv and private keys
echo "$STAGED" | grep -E '\.env(\.|$)|(^|/)\.env$|\.pem$|\.key$|id_rsa$' >/dev/null 2>&1 && {
  echo "Sensitive file detected in staged changes. Remove it from the commit."
  exit 1
}

编写高效 Git Hooks 的技巧

这里,我汇总了一些在编写 Git hooks 时非常实用的建议。

保持钩子足够快

钩子会在您触发某些 Git 操作时每次运行,因此应保持轻量。如果测试套件很大,请将其放在 CI 中运行,而不是在本地钩子中,以获得更好的性能。每次提交都在整个仓库上运行 linter、格式化或检查会拖慢速度,因此请将检查限制在实际变更的文件上。

保持跨平台兼容性

确保您的钩子在不同操作系统上都能顺畅运行。使用可移植的 shebang,统一处理行尾(使用 LF 而非 CRLF),不硬编码绝对路径。

请依赖 PATH 和本地项目脚本,如 npm run lint 或 python -m,从而在多种环境中运行。

调试策略

添加详细日志或调试开关(例如 HOOK_DEBUG=1)以追踪脚本的运行方式。打印当前目录、分支或被暂存文件等细节。这样更容易跟随执行流程并定位问题。

在本地用不同输入测试钩子并模拟失败场景后再推送更改。如果钩子意外阻塞进度,您可以临时通过 Git 的 --no-verify 标志或使用用于跳过钩子的环境变量来绕过它。

为在不同操作系统间保持一致行为,请避免使用特定于操作系统的命令,并优先选择可移植的脚本语言。

清晰记录钩子

良好的文档可以提升团队理解并简化新成员的入职。维护一个集中式 README,详细说明每个钩子的目的、行为和用法。包括如何运行、前置条件、故障排除和绕过技巧,并提供安装/设置说明。

Git Hooks 的高级实现技术与最佳实践

如果您是进阶用户,这些建议会有所帮助:

性能优化

为优化复杂工作流中的性能,尽量减少外部依赖,避免使用重量级库。这可以降低启动时间并减少故障点。

缓存昂贵流程(如 lint)的结果,以便在文件未变化时复用。

如果钩子包含多个检查,可并行化以同时运行。此外,将钩子脚本模块化。也就是说,将钩子结构化为可复用的小组件,并基于上下文仅执行相关部分。

钩子链与编排

使用钩子链在单个 Git 事件中运行多个钩子。例如,在 pre-commit 钩子中,您可能希望先运行 lint,再运行测试,然后格式化代码,依次串联。各命令依次执行,若其中任何一个失败(以非零状态退出),后续命令可被跳过以防止不良提交。

当钩子中的操作彼此独立时,您也可以并行运行它们以节省时间。这时就需要通过编排来管理任务按正确顺序执行。

配置管理

每个仓库都有自己的 .git/hooks 目录。这意味着如果您想在多个项目中使用相同的检查(比如 lint),就必须到处复制粘贴。更好的方式是集中管理钩子,使所有仓库都能访问并运行它们。

您可以用一条命令实现:git config --global core.hooksPath ~/.githooks。这会告知 Git 到 ~/.githooks 文件夹中查找钩子。您在此处创建的任何钩子都会自动在所有仓库中共享。

您还可以使用配置文件(JSON 或 YAML)让钩子逻辑更具动态性。与其为每个仓库硬编码不同逻辑,不如让钩子脚本读取该仓库的配置文件并自动应用正确的规则。

条件执行

在钩子脚本中,您可以添加简单的条件,使其仅在相关时运行。这可能包括检查当前分支名称、查看实际变更的文件,或读取指示跳过的环境变量。

如果需要更多控制,可以使用 pre-commit 之类的框架,您可以在配置文件中声明哪些钩子在何时运行。您还可以添加条件逻辑,比如仅在特定分支上运行钩子、通过变量跳过钩子,甚至添加基于时间的条件。这会使您的钩子脚本更加动态,贴合真实工作流。

协作与共享策略

集中化并在仓库与团队间共享 Git hooks 有两种方式:

Git 模板

当您初始化仓库时,Git 会在 git/hooks 目录下添加一些默认钩子。这些来自 Git 的默认模板。

若要集中创建自定义钩子,您可以创建一个 Git 模板目录,将钩子写在其中,并全局设置 Git 使用这个新目录作为默认模板。

git config --global init.templateDir /path/to/hooks-template

但这种方法缺乏对钩子的版本控制。如果您的钩子很少变更,这种方式尤其合适。如果您经常更新钩子并需要版本控制,则应使用下一种方法。

集中式 Git 仓库

创建一个专用的、受版本控制的集中仓库,将所有自定义钩子脚本存储在该仓库中,与项目仓库分离。这样您就可以独立于项目代码库来更新钩子。

这些钩子应是标准化的,也就是说避免硬编码仅适用于某个仓库的路径或值。

这种设置对团队尤其有用,因为每个人都可以定期拉取更新并将其同步到各自的项目中。

更高效的做法是使用一个设置脚本,它会克隆集中钩子仓库并将钩子脚本复制到各项目的 .git/hooks 目录中。示例设置脚本:

#!/usr/bin/env bash
set -euo pipefail

# Location of the shared hooks template repo
HOOKS_TEMPLATE_REPO="[email protected]:your-org/git-hooks-template.git"
HOOKS_TEMPLATE_DIR="$HOME/.git-hooks-template"

# Clone or update the template repo
 echo "Cloning hooks template repo..."
 git clone "$HOOKS_TEMPLATE_REPO" "$HOOKS_TEMPLATE_DIR"

# The repo where you want hooks installed
TARGET_REPO="${1:-$(pwd)}"
TARGET_HOOKS_DIR="$TARGET_REPO/.git/hooks"

# --- Copy strategy ---
echo "Copying hooks into $TARGET_HOOKS_DIR..."
cp -r "$HOOKS_TEMPLATE_DIR/"* "$TARGET_HOOKS_DIR/"

# 4. Make sure hooks are executable
chmod +x "$TARGET_HOOKS_DIR"/* || true

echo "✅ Hooks installed successfully into $TARGET_REPO"

另一种选择是使用符号链接。您可以将每个项目的 .git/hooks 目录链接到版本控制的钩子仓库中的脚本。这样每当您更新集中仓库,钩子会因为符号链接的指向而自动更新。

为保证该体系可靠,请建立清晰的钩子维护流程。团队成员应通过拉取请求提出钩子变更,评审通过后再合并,并在钩子演进时同步更新文档。

将 Git Hooks 融入开发工作流

Git hooks 在持续集成(CI)中发挥重要作用。背景是,CI 指的是频繁合并更改,然后运行构建与测试以尽早发现问题。通过将钩子与 Jenkins、GitHub Actions 或 GitLab CI 等 CI/CD 工具相结合,您可以基于 Git 事件触发构建、运行测试,甚至自动部署应用。

例如,服务端 pre-receive 钩子会在 Git 服务器接受推送之前运行自定义脚本。如果脚本失败,服务器会拒绝推送。您可以用它在允许推送之前检查该分支最近一次 CI 构建是否通过。该 CI 运行可能包含代码覆盖率检查、单元或集成测试,甚至安全扫描。

钩子还可以自动化部署任务。post-receive 钩子可以触发一个 webhook 来启动 CI/CD 流水线。当代码合并到 main 时,它会自动开启 构建 → 部署 → 发布 的流程。

安全注意事项与最佳实践

如果您维护集中式钩子以便更轻松地协作并共享标准,请确保钩子目录具有安全的文件权限,防止未授权访问。仅可信用户应有写权限。

始终需要验证钩子脚本接收的输入或参数,以避免代码注入攻击。例如,按严格模式验证输入:仅允许包含字母数字与短横线的分支名,或匹配 vMAJOR.MINOR.PATCH 的标签。

切勿在钩子脚本中硬编码敏感数据。应使用环境变量或专用的机密管理工具,在运行时安全地提供凭据。您可以将 Gitleaks 等工具与 pre-commit 钩子集成,针对即将提交的更改运行敏感信息安全扫描。

有人可能会将您的钩子重定向到其他服务器以发送代码或信息。因此请限制钩子可访问的域名。使用代理或防火墙阻止未知目标服务器。同时,确保日志不会在显示消息中泄露机密信息。

结论

Git hooks 是在开发工作流中直接自动化任务并强制执行标准的强大方式。它们可帮助处理从代码风格、测试到部署与安全检查的各类事务。

合理使用它们,可以减少错误、节省时间,并让跨团队协作更加顺畅。

从简单的 pre-commit 检查开始,再尝试更高级的设置,如编排、集中管理或 CI/CD 集成。如果您是 Git 用户并希望提升技能,欢迎查看这门高级 Git 课程。

Git Hooks 常见问题

我在哪里可以找到 Git 钩子?

Git 钩子的默认路径为 . git/hooks,该文件夹通常是隐藏的,因此您只能在 Bash 或终端中访问。

设置 pre-commit 钩子的最佳实践有哪些?

让钩子尽可能快,并在暂存文件上运行以获得更高性能。不要在 pre-commit 钩子中运行耗时任务(如整套测试);这类任务应放入 CI/CD 流水线。同时,请清晰记录钩子的目的。

服务端 Git 钩子与客户端钩子有何不同?

客户端钩子在开发者机器上运行,用于反馈与自动修复。例如:pre-commit、pre-push。

服务端钩子在 Git 远程服务器上运行,用于关键检查,以确保生产流水线的顺畅和高效的团队协作。例如:pre-receive、update。如果服务端钩子拒绝推送,变更将无法落地。

我可以使用 Git 钩子来强制执行编码标准吗?

使用 pre-commit 进行格式化与 lint,使用 commit-msg 强制消息规范,使用 pre-push 进行快速测试,使用服务端 pre-receive 阻止机密信息被提交到远端。

Srujana Maddula

作者

Srujana Maddula

Srujana 是一名自由职业技术写作者,拥有计算机科学四年制学位。她擅长撰写各类主题的内容,包括数据科学、云计算、开发、编程、安全等众多领域。她热爱经典文学,也喜欢探索新的旅行目的地。

主题