41.2 钩子和模板

    41.2.1 Git钩子

    Git的钩子脚本位于版本库的.git/hooks目录下,当Git执行特定操作时会调用特定的钩子脚本。当版本库通过git init或git clone创建时,会在.git/hooks目录下创建示例脚本,用户可以参照示例脚本的写法开发适合的钩子脚本。

    钩子脚本要设置为可运行,并使用特定的名称。Git提供的示例脚本都带有.sample扩展名,是为了防止被意外运行。如果需要启用相应的钩子脚本,需要对其重命名(去掉.sample扩展名)。下面分别对可用的钩子脚本逐一进行介绍。

    1.applypatch-msg

    该钩子脚本由git am命令调用。在调用时向该脚本传递一个参数,即保存有提交说明的文件的文件名。如果该脚本运行失败(返回非零值),则git am命令在应用该补丁之前终止。

    这个钩子脚本可以修改文件中保存的提交说明,以便规范提交说明以符合项目的标准(如果有的话)。如果提交说明不符合项目标准,脚本直接以非零值退出,则拒绝提交。

    Git提供的示例脚本applypatch-msg.sample只是简单地调用commit-msg钩子脚本(如果存在的话)。这样通过git am命令应用补丁和执行git commit一样,都会执行commit-msg脚本,因此如需定制,请更改commit-msg脚本。

    2.pre-applypatch

    该钩子脚本由git am命令调用。该脚本没有参数,在补丁应用后但尚未提交前运行。如果该脚本运行失败(返回非零值),则不会提交已经应用了补丁的工作区文件。

    这个脚本可以用于对应用补丁后的工作区进行测试,如果测试没有通过则拒绝提交。

    Git提供的示例脚本pre-applypatch.sample只是简单地调用pre-commit钩子脚本(如果存在的话)。这样通过git am命令应用补丁和执行git commit一样都会执行pre-commit脚本,因此如需定制,请更改pre-commit脚本。

    3.post-applypatch

    该钩子脚本由git am命令调用。该脚本没有参数,在补丁应用并且提交之后运行,因此该钩子脚本不会影响git am的运行结果,可以用于发送通知。

    4.pre-commit

    该钩子脚本由git commit命令调用。可以通过传递—no-verify参数而禁用。该脚本在获取提交说明之前运行。如果该脚本运行失败(返回非零值),Git提交就被终止。

    该脚本主要用于对提交数据的检查,例如对文件名进行检查(是否使用了中文文件名),或者对文件内容进行检查(是否使用了不规范的空白字符)。

    Git提供的示例脚本pre-commit.sample禁止提交在路径中使用非ASCII字符(如中文字符)的文件。如果确有使用的必要,可以在Git配置文件中设置配置变量hooks.allownonascii为true以允许在文件名中使用非ASCII字符。Git提供的该示例脚本也对不规范的空白字符进行检查,如果发现则终止提交。

    Topgit为所管理的版本库设置了自己的pre-commit脚本,检查工作的Topgit特性分支是否正确地设置了Topgit的两个管理文件.topdeps和.topmsg,以及定义的分支依赖是否存在着重复依赖和循环依赖等。

    5.prepare-commit-msg

    该钩子脚本由git commit命令调用,在默认的提交信息准备完成后但编辑器尚未启动之前运行。

    该脚本有1~3个参数。第一个参数是包含提交说明的文件的文件名。第二个参数是提交说明的来源,可以是message(由-m或-F参数提供),可以是template(如果使用了-t参数或由commit.template配置变量提供),或者是merge(如果提交是一个合并或存在.git/MERGE_MSG文件),或者是squash(如果存在.git/SQUASH_MSG文件),或者是commit并跟着一个提交SHA1哈希值(如果使用-c、-C或—amend参数)。

    如果该脚本运行失败(返回非零值),Git提交就被终止。

    该脚本用于对提交说明进行编辑,并且该脚本不会因为—no-verify参数被禁用。

    Git提供的示例脚本prepare-commit-msg.sample可以用于向提交说明中嵌入提交者的签名,或者将来自merge的提交说明中含有"Conflicts:"的行去掉。

    6.commit-msg

    该钩子脚本由git commit命令调用,可以通过传递—no-verify参数而禁用。该脚本有一个参数,即包含有提交说明的文件的文件名。如果该脚本运行失败(返回非零值),Git提交被终止。

    该脚本可以直接修改提交说明,可以用于规范提交说明以符合项目的标准(如果有的话)。如果提交说明不符合标准,可以拒绝提交。

    Git提供的示例脚本commit-msg.sample检查提交说明中出现的相同的含"Signed-off-by"的行,如果发现重复签名即报错并终止提交。

    Gerrit服务器需要每一个向其进行推送的Git版本库在本地使用Gerrit提供的commit-msg钩子脚本,以便在创建的提交中包含形如"Change-Id:I……"的变更集标签。

    7.post-commit

    该钩子脚本由git commit命令调用,不带参数运行,在提交完成之后被触发执行。

    该钩子脚本不会影响git commit的运行结果,可以用于发送通知。

    8.pre-rebase

    该钩子脚本由git rebase命令调用,用于防止某个分支参与变基。

    Git提供的示例脚本pre-rebase.sample是针对Git项目自身的情况而开发的,当一个功能分支已经合并到next分支后,禁止该分支进行变基操作。

    9.post-checkout

    该钩子脚本由git checkout命令调用,在完成工作区更新之后触发执行。该钩子脚本有三个参数,分别是:之前的HEAD所指向的引用、新的HEAD所指向的引用(可能和前一个一样也可能不一样),以及一个用于表示此次检出是否是分支检出的标识(分支检出为1,文件检出是0)。该钩子脚本不会影响git checkout命令的运行结果。

    除了由git checkout命令调用外,该钩子脚本也在git clone命令执行后被触发执行,除非在克隆时使用了禁止检出的—no-checkout(-n)参数。在由git clone调用时,第一个参数给出的引用是空引用,则第二个和第三个参数都为1。

    这个钩子一般用于版本库的有效性检查,自动显示和前一个HEAD的差异,或者设置工作区属性。

    10.post-merge

    该钩子脚本由git merge命令调用,当在本地版本库完成git pull操作后触发执行。该钩子脚本有一个参数,标识合并是否是一个压缩合并。该钩子脚本不会影响git merge命令的运行结果。如果合并因为冲突而失败,则该脚本不会执行。

    该钩子脚本可以与pre-commit钩子脚本一起实现对工作区目录树属性(如权限/属主/ACL等)的保存和恢复。参见Git源码文件contrib/hooks/setgitperms.perl中的示例。

    11.pre-receive

    该钩子脚本由远程版本库的git receive-pack命令调用。当从本地版本库完成一个推送之后,在远程服务器上开始批量更新引用之前,该钩子脚本被触发执行。该钩子脚本的退出状态决定了更新引用的成功与否。

    该钩子脚本在接收(receive)操作中只执行一次。传递参数不通过命令行,而是通过标准输入进行传递。通过标准输入传递的每一行的语法格式为:


    <old-value><new-value><ref-name>

    <old-value>是引用更新前的老的对象ID,<new-value>是引用即将更新到的新的对象ID,<ref-name>是引用的全名。当创建一个新引用时,<old-value>是40个0。

    如果该钩子脚本以非零值退出,一个引用也不会更新。如果该脚本正常退出,每一个单独的引用的更新仍有可能被update钩子所阻止。

    标准输出和标准错误都重定向到在另外一端执行的git send-pack上,所以可以直接通过echo命令向用户传递信息。

    12.update

    该钩子脚本由远程版本库的git receive-pack命令调用。当从本地版本库完成一个推送之后,在远程服务器上更新引用时,该钩子脚本被触发执行。该钩子脚本的退出状态决定了更新引用的成功与否。

    该钩子脚本在每一个引用更新的时候都会执行一次。该脚本有三个参数。

    参数1:要更新的引用的名称。

    参数2:引用中保存的旧对象名称。

    参数3:将要保存到引用中的新对象名称。

    正常退出(返回0)则允许更新该引用,而以非零值退出则禁止git-receive-pack更新该引用。

    该钩子脚本可以用于防止某些引用被强制更新,因为该脚本可以通过检查新旧引用对象是否存在继承关系,从而提供更为细致的“非快进式推送”的授权。

    该钩子脚本也可以用于记录(如用邮件)引用变更历史old..new。然而因为该脚本不知道完整的引用更新,所以可能会导致每一个引用发送一封邮件。因此如果要发送通知邮件,可能post-receive钩子脚本更为适合。

    另外,该脚本可以实现基于路径的授权。

    标准输出和标准错误都重定向到在另外一端执行的git send-pack上,所以可以直接通过echo命令向用户传递信息。

    Git提供的示例脚本update.sample展示了对多种危险的Git操作行为进行控制的可行性。

    只有将配置变量hooks.allowunannotated设置为true才允许推送轻量级里程碑(不带说明的里程碑)。

    只有将配置变量hooks.allowdeletebranch设置为true才允许删除分支。

    如果将配置变量hooks.denycreatebranch设置为true则不允许创建新分支。

    只有将配置变量hooks.allowdeletetag设置为true才允许删除里程碑。

    只有将配置变量hooks.allowmodifytag设置为true才允许修改里程碑。

    相比Git的示例脚本,Gitolite服务器为其管理的版本库设置的update钩子脚本更实用也更强大。Gitolite实现了用户认证,并通过检查授权文件,实现基于分支和路径的写操作授权,等等。具体内容请参见本书第5篇“第30章Gitolite服务架设”的相关内容。

    13.post-receive

    该钩子脚本由远程版本库的git receive-pack命令调用。当从本地版本库完成一个推送,并且在远程服务器上的所有引用都更新完毕后,该钩子脚本被触发执行。

    该钩子脚本在接收(receive)操作中只执行一次。该脚本不通过命令行传递参数,但是像pre-receive钩子脚本那样,通过标准输入以相同格式获取信息。

    该钩子脚本不会影响git-receive-pack的结果,因为调用该脚本时工作已经完成。

    该钩子脚本胜过post-update脚本之处在于:它可以获得所有引用的老的和新的值,以及引用的名称。

    标准输出和标准错误都重定向到在另外一端执行的git send-pack上,所以可以直接通过echo命令向用户传递信息。

    Git提供的示例脚本post-receive.sample引入了contrib/hooks目录下的名为post-receive-email的示例脚本(默认被注释),以实现发送通知邮件的功能。

    Gitolite服务器要对其管理的Git版本库设置post-receive钩子脚本,以实现当版本库有变更后将数据传输到各个镜像版本库。

    14.post-update

    该钩子脚本由远程版本库的git receive-pack命令调用。当从本地版本库完成一个推送之后,即当所有引用都更新完毕后,在远程服务器上该钩子脚本被触发执行。

    该脚本接收不定长的参数,每个参数实际上就是已经成功更新的引用名。

    该钩子脚本不会影响git-receive-pack的结果,因此主要用于通知。

    钩子脚本post-update虽然能够提供哪些引用被更新了,但是该脚本不知道引用更新前后的对象SHA1哈希值,所以在这个脚本中不能记录形如old..new的引用变更范围。而钩子脚本post-receive知道引用更新前后的对象ID,因此更适合于此种场合。

    标准输出和标准错误都重定向到在另外一端执行的git send-pack上,所以可以直接通过echo命令向用户传递信息。

    Git提供的示例脚本post-update.sample会运行git update-server-info命令,以更新哑协议需要的索引文件。如果通过哑协议共享版本库,应该启用该钩子脚本。

    15.pre-auto-gc

    该钩子脚本由git gc—auto命令调用,不带参数运行,如果以非零值退出会导致git gc—auto被中断。

    16.post-rewrite

    该钩子脚本由一些重写提交的命令调用,如git commit—amend、git rebase,而git-filter-branch当前尚未调用该钩子脚本。

    该脚本的第一个参数用于判断调用来自哪个命令,当前有amend和rebase两个取值,也可能将来会传递其他的更多命令相关的参数。

    该脚本通过标准输入接收一个重写提交列表,每一行输入的格式如下:


    <old-sha1><new-sha1>[<extra-info>]

    前两个是旧的和新的对象SHA1哈希值。而<extra-info>参数是和调用命令相关的,当前该参数为空。