文章

Git Hooks 钩子

Git Hooks 钩子

Git Hooks 钩子

什么是 Git Hooks?

git hooks 是一些自定义的脚本,用于控制 git 工作的流程,分为客户端钩子和服务端钩子。

客户端钩子

客户端钩子分为很多种。 下面把它们分为:提交工作流钩子、电子邮件工作流钩子和其它钩子。主要介绍提交工作流钩子:pre-commitprepare-commit-msgcommit-msgpost-commit

pre-commit(常用)

git commit 执行前

如果该钩子以非零值退出,Git 将放弃此次提交,不过你可以用 git commit --no-verify 来绕过这个环节。

用途:

  • 检查是否有所遗漏,确保测试运行,以及核查代码。
  • 检查代码风格是否一致(运行类似 lint 的程序)、尾随空白字符是否存在(自带的钩子就是这么做的),或新方法的文档是否适当。

prepare-commit-msg

在启动提交信息编辑器之前,默认信息被创建之后运行。 它允许你编辑提交者所看到的默认信息。 该钩子接收一些选项:存有当前提交信息的文件的路径、提交类型和修补提交的提交的 SHA-1 校验。 它对一般的提交来说并没有什么用;然而对那些会自动产生默认信息的提交,如提交信息模板、合并提交、压缩提交和修订提交等非常实用。 你可以结合提交模板来使用它,动态地插入信息。

commit-msg(常用)

git commit 执行前;可以用 git commit –no-verify 绕过

接收一个参数,此参数即上文提到的,存有当前提交信息的临时文件的路径。 如果该钩子脚本以非零值退出,Git 将放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息

post-commit

在整个提交过程完成后运行。 它不接收任何参数,但你可以很容易地通过运行 git log -1 HEAD 来获得最后一次的提交信息。 该钩子一般用于通知之类的事情

服务端钩子

服务端钩子脚本在推送到服务器之前和之后运行。 推送到服务器前运行的钩子可以在任何时候以非零值退出,拒绝推送并给客户端返回错误消息,还可以依你所想设置足够复杂的推送策略。钩子包括:pre-receiveupdatepost-receive

pre-receive

处理来自客户端的推送操作时,最先被调用的脚本是 pre-receive。 它从标准输入获取一系列被推送的引用。如果它以非零值退出,所有的推送内容都不会被接受。 你可以用这个钩子阻止对引用进行非快进(non-fast-forward)的更新,或者对该推送所修改的所有引用和文件进行访问控制。

update

update 脚本和 pre-receive 脚本十分类似,不同之处在于它会为每一个准备更新的分支各运行一次。 假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个被推送的分支各运行一次。 它不会从标准输入读取内容,而是接受三个参数:引用的名字(分支),推送前的引用指向的内容的 SHA-1 值,以及用户准备推送的内容的 SHA-1 值。 如果 update 脚本以非零值退出,只有相应的那一个引用会被拒绝;其余的依然会被更新。

post-receive

post-receive 挂钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。 它接受与 pre-receive 相同的标准输入数据。 它的用途包括给某个邮件列表发信,通知持续集成(continuous integration)的服务器, 或者更新问题追踪系统(ticket-tracking system) —— 甚至可以通过分析提交信息来决定某个问题(ticket)是否应该被开启,修改或者关闭。 该脚本无法终止推送进程,不过客户端在它结束运行之前将保持连接状态, 所以如果你想做其他操作需谨慎使用它,因为它将耗费你很长的一段时间。

pre-push

git push 执行前

开启 Git Hooks

git hooks 存储位置

git hooks 被存储在 Git 目录下的.hooks 子目录中,即绝大部分项目中的 .git/hooks。%3Cbr%3E
注意这些以.sample 结尾的示例脚本默认是不会执行的,只有把一个正确命名(不带扩展名.sample)且可执行的文件放入 .git/hooks 目录中,才会激活该钩子脚本,生效且被 git 调用。

  • 把 sample 去掉
  • 名称必须是固定的,不能随意改名字

开启 git hooks 两种方式

1、单个项目

将脚本放到当前根目录的 .git/hooks 中,把 .sample 后缀去掉即可生效

2、全局所有项目

  • 配置 core.hooksPath,即配置全局的 hooks 路径,对所有项目生效,配置后会会在 ~/.gitconfig 生成一条配置
  • 然后将 hooks 脚本放到该目录即可,记得 chmod 改权限
  • 查看是否配置成功:git config core.hooksPath
1
2
3
4
5
6
7
8
#创建hooks文件夹
mkdir -p ~/.git-hooksPath/hooks
#配置全局git hooksPath
git config --global core.hooksPath ~/.git-hooksPath/hooks  
# 将commit-msg脚本放到~/.git-hooksPath/hooks目录下,chmod更改权限
chmod a+x ~/.git-hooksPath/hooks/commit-msg
# 查看git hook路径配置
git config core.hooksPath

配置完后,打开 ~/.gitconfig 看看是否配置成功:

示例

git commit msg 格式校验

在提交时在客户端进行判断,不符合提交规范的不让提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/sh
# COMMIT_EDITMSG_FILE=$1
# 读取commit message的第一行
# START_LINE=$(head -n1 $COMMIT_EDITMSG_FILE)

# 匹配代码仓库
#  1. https://github.com/hacket/king-assist.git
#REG='^https:\/\/github\.com\/(king-assist\)(.{1,})(\.git)$'
#
#temp=$(git config --get remote.origin.url)
#if [[ "$temp" =~ $REG ]];
# if [ true ];
  # then
    # 正则匹配 格式:
    # 场景                    commit message
    #
    # 01. 需求Jira单号          "feat(XXX-123456): 需求描述"
    # 02. bug修复单号           "fix(XXX-123456): bug描述"
    # 03. 更新Jira号            "update(XXX-123456): 需求描述"
    # 04. 修改版本号             "feat(version): 版本描述"
    # 05. 修改打包脚本           "feat(ci/cd): 改动描述"
    # 06. 修改Debug工具          "feat(debug): 改动描述"
    # 07. 预研或Demo代码         "feat(new): 功能描述"
    # 08. CI打包集成             "[CI]:8.4.6/release integration success"
    # 09. 合并代码(git自动产生)   "Merge branch ..."
    # 10. 解决冲突               "Resolve conflicts: 冲突描述"

    # REG='^((feat)|(fix)|(update))[(]([A-Za-z]{3,})-([0-9]{5,})[)][::]'
    # REG='^((((fix)|(feat)|(update))[(](([A-Za-z]{1,})-([0-9]{1,})|(version)|(ci\/cd)|(debug)|(new))[)]|(Resolve conflicts)|(\[CI\]))[::]|(Merge branch))'
    # if ! [[ "$START_LINE" =~ $REG ]];
      # then
        # echo "hey, guy,your commit message doesn't meet specifications! check it"
        # exit 1
      # else
        # exit 0
    # fi
  # else
    # exit 0
# fi

commit 前关键字绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/bin/sh
echo "Debug: pre-commit hook running..."  # 提交时观察是否打印

# 定义敏感关键字列表(按需修改)
KEYWORDS="pwd password secret token api_key app_secret credentials access_token neo"

# ANSI 颜色代码
RED='\033[1;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # 恢复默认颜色

# 初始化错误标记
HAS_ERROR=0
ERROR_MESSAGES=""

# 将关键字列表转换为正则表达式(全词匹配 + 忽略大小写)
REGEX_PATTERN="\\b($(echo "$KEYWORDS" | sed 's/ /|/g'))\\b"

# 处理带空格/特殊字符的文件路径
while IFS= read -r -d '' FILE; do
    # 跳过不存在的文件
    [ ! -f "$FILE" ] && continue

    # 快速跳过二进制文件(性能优化)
    if ! grep -Iq "^" "$FILE" 2>/dev/null; then
        echo -e "[INFO] 跳过二进制文件: ${YELLOW}$FILE${NC}"
        continue
    fi

    # 单次扫描文件内容,匹配所有关键字(使用正则表达式合并)
    MATCH_LINES=$(grep -inwE "$REGEX_PATTERN" "$FILE")
    if [ -n "$MATCH_LINES" ]; then
        HAS_ERROR=1
        # 处理匹配行
        while IFS= read -r LINE; do
            LINE_NUMBER=$(echo "$LINE" | cut -d: -f1)
            LINE_CONTENT=$(echo "$LINE" | cut -d: -f2-)
            # 提取具体的敏感词(避免误判)
            MATCHED_KEYWORD=$(echo "$LINE_CONTENT" | grep -oiwE "$REGEX_PATTERN" | head -n1)
            # 高亮敏感词
            HIGHLIGHT_CONTENT=$(echo "$LINE_CONTENT" | sed "s/${MATCHED_KEYWORD}/${RED}${MATCHED_KEYWORD}${NC}/gI")
            ERROR_MSG="[ERROR] 文件 ${YELLOW}'$FILE'${NC}${RED}${LINE_NUMBER}${NC} 行: $HIGHLIGHT_CONTENT"
            ERROR_MESSAGES="$ERROR_MESSAGES$ERROR_MSG\n"
        done <<< "$MATCH_LINES"
    fi
done < <(git diff --cached --name-only --diff-filter=ACM -z)

# 输出结果
if [ $HAS_ERROR -ne 0 ]; then
    echo -e "\n${RED}⚠️ 检测到敏感词!${NC}"
    echo -e "$ERROR_MESSAGES"
    echo -e "${RED}--------------------------------"
    echo -e "请修改上述文件后再提交!${NC}\n"
    exit 1
else
    echo -e "\n${GREEN}✅ 所有文件校验通过,允许提交!${NC}\n"
fi

exit 0

绕过钩子

1
git commit -m 'xxx' --no-verify

本地.git/hooks 的缺陷

不能共享,由于 .git 文件夹是不会被 git 跟踪的,所以 .git/hooks 目录下的 hooks 钩子无法提交,就不能和他人共享钩子脚本。
解决:用husky)

本文由作者按照 CC BY 4.0 进行授权