在Git版本控制系统中,提交消息的规范化是一个常见需求。为了确保所有提交消息都遵循特定的规则(例如,行的最大长度等),需要实现一种机制来阻止不符合规则的提交被推送到服务器。本文将介绍如何通过Git钩子来实现这一目标。
Git钩子是Git仓库中可以触发特定脚本的机制。这些脚本在特定的动作发生时被调用,例如提交、推送等。Git钩子分为客户端钩子和服务器端钩子。在Git中,并没有严格区分服务器和客户端仓库,每个仓库都可以充当服务器和客户端的角色。所有的钩子脚本都位于仓库的.git/hooks
目录下。
对于需求来说,commit-msg
钩子正是需要的。当执行git commit
命令时,这个脚本会被调用,它接收提交消息作为输入参数。如果这个脚本返回非零值,那么提交将被丢弃。这听起来很好,但唯一的问题是这个脚本只在客户端仓库中运行。如果客户端(克隆服务器仓库的用户)从.git/hooks
目录中移除了这个钩子,他就可以提交任何他想要的内容。因此,虽然这是一个初步检查,但它并没有以一种安全的方式解决问题。
为了确保服务器仓库中不会出现无效的提交消息,需要在服务器端进行检查。一种可能的方法是使用服务器端的pre-receive
钩子。这个钩子在有人向服务器推送内容时被调用,如果它返回非零值,推送将被拒绝。
在Git中,当推送提交时,总是在推送到一个分支。默认情况下,在master分支上,但可以随时创建新的分支,这些分支是从已经存在的分支分叉出来的。如果执行git push
,它要么将分支推送到它的上游分支(如果存在的话),要么推送到从服务器获取的分支。上游分支总是在服务器上。如果没有从服务器获取分支,或者处于分离头模式(不在任何分支上,HEAD只是指向一个随机的提交),Git会要求指定想要推送到哪个分支(例如git push origin master
)。
Git引用就像是一个指向仓库中特定提交的命名指针。可以在.git/refs
目录下找到所有的引用。分支实际上只是特殊的引用,它们也是指向提交的指针,但如果提交了新的内容,分支会自动改变指向分支上的最新提交。但它们只是命名指针,没有其他含义。
提交本身并不知道太多信息。它们只知道自己的内容和父提交。在合并提交的情况下,提交有多个父提交,否则只有一个。仓库中的第一个“root”提交没有父提交。
在pre-receive
钩子中,目标是验证所有新推送的提交消息,但除此之外没有其他信息。首先,最简单的情况是有人推送了已经存在的分支的提交。在这种情况下,得到了引用的旧值和新值,通过git log old_hash..new_hash
,可以看到它们之间的提交。
这里有一个特殊情况,当这种方法显示了比必要的更多的提交:在合并提交的情况下,它显示了合并分支的整个内容,但可能那个分支至少部分已经被推送过了。
最后需要覆盖的情况是有人推送了一个新的分支。在这种情况下,引用的旧哈希是40个零,有引用的新哈希。这意味着只有分支上最新提交的哈希。想知道什么?经过一些调查,想法是做推送时同样的事情。检查最新的提交,然后跳到它的父提交,在合并提交的情况下,对所有父提交做同样的事情,一旦到达了一个已经被推送过的分支上的提交,就停止这个活动。