阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 使用Git钩子

使用Git钩子

作者:陈川 阅读数:63616人阅读 分类: 开发工具

Git钩子的基本概念

Git钩子是Git在特定事件发生时自动运行的脚本,它们存储在.git/hooks目录中。当执行某些Git操作时,如提交代码或推送更改,Git会检查这个目录中是否存在对应的钩子脚本,如果有就会执行它们。钩子分为客户端钩子和服务端钩子两类,客户端钩子由本地操作触发,服务端钩子则在服务器上响应网络操作时运行。

每个钩子脚本都需要是可执行文件,Git会根据文件名来识别它们。默认情况下,.git/hooks目录包含一些示例脚本,这些脚本以.sample结尾,要启用它们需要移除.sample后缀。

客户端钩子

客户端钩子影响的是开发者的本地工作流程,它们可以分为提交工作流钩子、电子邮件工作流钩子和其他客户端钩子。

预提交钩子(pre-commit)

pre-commit钩子在键入提交信息前运行,它用于检查即将提交的快照。例如,我们可以用它来运行代码风格检查:

#!/bin/sh

# 运行ESLint检查
npm run lint

# 如果lint检查失败,则终止提交
if [ $? -ne 0 ]; then
  echo "Lint检查失败,请修复错误后再提交"
  exit 1
fi

准备提交信息钩子(prepare-commit-msg)

这个钩子在启动提交信息编辑器之前运行,它允许我们自动生成提交信息模板或修改默认信息。例如:

#!/bin/sh

# 获取当前分支名
BRANCH_NAME=$(git symbolic-ref --short HEAD)

# 如果分支名包含JIRA编号,则添加到提交信息中
if [[ $BRANCH_NAME =~ (PROJ-[0-9]+) ]]; then
  echo "[${BASH_REMATCH[1]}] $(cat $1)" > $1
fi

提交信息钩子(commit-msg)

commit-msg钩子接收一个参数,此参数是包含提交信息的临时文件的路径。我们可以用它来验证提交信息格式:

#!/usr/bin/env node

const fs = require('fs');
const msg = fs.readFileSync(process.argv[2], 'utf8').trim();

const commitRegex = /^(revert: )?(feat|fix|docs|style|refactor|perf|test|chore)\(.+\): .{1,50}/;

if (!commitRegex.test(msg)) {
  console.error(`无效的提交信息格式:"${msg}"`);
  console.error('提交信息应遵循:类型(范围): 描述');
  console.error('例如:feat(login): 添加登录验证');
  process.exit(1);
}

服务端钩子

服务端钩子在推送到服务器时执行,它们可以用来实施项目策略。这些钩子运行在服务器上,而不是开发者的机器上。

预接收钩子(pre-receive)

pre-receive钩子在处理来自客户端的推送操作时最先运行,它可以用来拒绝不符合某些策略的推送。例如,检查提交者邮箱是否为公司邮箱:

#!/bin/sh

while read oldrev newrev refname; do
  # 获取推送的所有提交
  commits=$(git rev-list $oldrev..$newrev)
  
  for commit in $commits; do
    email=$(git show -s --format='%ae' $commit)
    
    if [[ ! "$email" =~ @company\.com$ ]]; then
      echo "错误:提交 ${commit} 使用了非公司邮箱 ${email}"
      exit 1
    fi
  done
done

更新钩子(update)

update钩子类似于pre-receive,但它为每个要更新的分支运行一次。我们可以用它来确保主分支只能通过合并请求更新:

#!/bin/sh

refname="$1"
oldrev="$2"
newrev="$3"

# 如果是主分支且不是快进更新
if [ "$refname" = "refs/heads/main" ] && [ "$(git merge-base $oldrev $newrev)" != "$oldrev" ]; then
  echo "错误:主分支只能通过合并请求更新"
  exit 1
fi

实用的钩子示例

自动运行测试

在每次提交前自动运行测试,确保不会提交失败的代码:

#!/bin/sh

# 运行测试
npm test

if [ $? -ne 0 ]; then
  echo "测试失败,请修复后再提交"
  exit 1
fi

防止提交大文件

防止意外提交大文件到仓库:

#!/usr/bin/env node

const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const execSync = require('child_process').execSync;

const files = execSync('git diff --cached --name-only --diff-filter=ACM')
  .toString()
  .split('\n')
  .filter(Boolean);

for (const file of files) {
  const size = parseInt(execSync(`git cat-file -s ":${file}"`).toString(), 10);
  if (size > MAX_FILE_SIZE) {
    console.error(`错误:文件 ${file} 太大 (${size} 字节)`);
    console.error('请使用Git LFS或从提交中移除该文件');
    process.exit(1);
  }
}

自动增加版本号

在每次提交到主分支后自动增加package.json中的版本号:

#!/bin/sh

branch=$(git symbolic-ref --short HEAD)

if [ "$branch" = "main" ]; then
  # 增加补丁版本号
  npm version patch --no-git-tag-version
  
  # 将版本变更添加到当前提交
  git add package.json
  git commit --amend --no-edit
fi

钩子的共享与管理

默认情况下,钩子存储在.git/hooks目录中,这不会被Git跟踪。要在团队中共享钩子,可以考虑以下方法:

  1. 将钩子存储在项目中:在项目中创建hooks目录,然后在README中说明如何手动复制它们到.git/hooks

  2. 使用husky等工具:husky可以让我们在package.json中定义Git钩子:

{
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
  1. 使用pre-commit框架:创建一个.pre-commit-config.yaml文件来定义钩子:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v3.2.0
  hooks:
    - id: trailing-whitespace
    - id: end-of-file-fixer
    - id: check-yaml

钩子的调试技巧

调试Git钩子可能会比较困难,因为它们是在Git命令执行过程中运行的。以下是一些调试技巧:

  1. 输出日志信息:在钩子脚本中添加日志输出:
#!/bin/sh

echo "pre-commit钩子开始运行" >> /tmp/git-hooks.log
date >> /tmp/git-hooks.log
  1. 手动运行钩子:可以直接执行钩子脚本来测试:
./.git/hooks/pre-commit
  1. 临时禁用钩子:在调试时可以暂时重命名钩子文件:
mv .git/hooks/pre-commit .git/hooks/pre-commit.disabled
  1. 使用set -x:在bash脚本中添加set -x来显示执行的命令:
#!/bin/sh

set -x  # 开始调试输出
npm run lint
set +x  # 结束调试输出

钩子的性能考虑

钩子脚本执行会增加Git操作的时间,特别是当它们运行复杂检查或测试时。为了减少对开发流程的影响:

  1. 只检查暂存的文件:而不是整个项目,使用git diff --cached获取暂存的文件。

  2. 并行运行任务:如果可能,使用工具并行运行检查。

  3. 缓存结果:对于耗时的操作,可以考虑缓存结果。

  4. 提供跳过选项:在紧急情况下允许跳过某些检查:

#!/bin/sh

if [ -n "$SKIP_HOOKS" ]; then
  echo "跳过钩子检查"
  exit 0
fi

使用时可以这样调用:

SKIP_HOOKS=1 git commit -m "紧急修复"

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌