8.2.2. 钩子和模板
8.2.2.1. Git钩子
Git的钩子脚本位于版本库.git/hooks
目录下,当Git执行特定操作时会调用特定的钩子脚本。当版本库通过git init或者git clone创建时,会在.git/hooks
目录下创建示例脚本,用户可以参照示例脚本的写法开发适合的钩子脚本。
钩子脚本要设置为可运行,并使用特定的名称。Git提供的示例脚本都带有.sample
扩展名,是为了防止被意外运行。如果需要启用相应的钩子脚本,需要对其重命名(去掉.sample
扩展名)。下面分别对可用的钩子脚本逐一介绍。
8.2.2.1.1. applypatch-msg
该钩子脚本由git am命令调用。在调用时向该脚本传递一个参数,即保存有提交说明的文件的文件名。如果该脚本运行失败(返回非零值),则git am命令在应用该补丁之前终止。
这个钩子脚本可以修改文件中保存的提交说明,以便对提交说明进行规范化以符合项目的标准(如果有的话)。如果提交说明不符合项目标准,脚本直接以非零值退出,拒绝提交。
Git提供的示例脚本applypatch-msg.sample
只是简单的调用commit-msg
钩子脚本(如果存在的话)。这样通过git am命令应用补丁和执行git commit一样都会执行commit-msg
脚本,因此如须定制,请更改commit-msg
脚本。
8.2.2.1.2. pre-applypatch
该钩子脚本由git am命令调用。该脚本没有参数,在补丁应用后但尚未提交之前运行。如果该脚本运行失败(返回非零值),则已经应用补丁的工作区文件不会被提交。
这个脚本可以用于对应用补丁后的工作区进行测试,如果测试没有通过则拒绝提交。
Git提供的示例脚本pre-applypatch.sample
只是简单的调用pre-commit
钩子脚本(如果存在的话)。这样通过git am命令应用补丁和执行git commit一样都会执行pre-commit
脚本,因此如须定制,请更改pre-commit
脚本。
8.2.2.1.3. post-applypatch
该钩子脚本由git am命令调用。该脚本没有参数,在补丁应用并且提交之后运行,因此该钩子脚本不会影响git am的运行结果,可以用于发送通知。
8.2.2.1.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
,以及定义的分支依赖是否存在着重复依赖和循环依赖等。
8.2.2.1.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:”的行去掉。
8.2.2.1.6. commit-msg
该钩子脚本由git commit命令调用,可以通过传递--no-verify
参数而禁用。该脚本有一个参数,即包含有提交说明的文件的文件名。如果该脚本运行失败(返回非零值),Git提交被终止。
该脚本可以直接修改提交说明,可以用于对提交说明规范化以符合项目的标准(如果有的话)。如果提交说明不符合标准,可以拒绝提交。
Git提供的示例脚本commit-msg.sample
检查提交说明中出现的相同的Signed-off-by
行,如果发现重复签名即报错、终止提交。
Gerrit服务器需要每一个向其进行推送的Git版本库在本地使用Gerrit提供的commit-msg
钩子脚本,以便在创建的提交中包含形如“Change-Id: I...”的变更集标签。
8.2.2.1.7. post-commit
该钩子脚本由git commit命令调用,不带参数运行,在提交完成之后被触发执行。
该钩子脚本不会影响git commit的运行结果,可以用于发送通知。
8.2.2.1.8. pre-rebase
该钩子脚本由git rebase命令调用,用于防止某个分支参与变基。
Git提供的示例脚本pre-rebase.sample
是针对Git项目自身情况而开发的,当一个功能分支已经合并到next
分支后,禁止该分基进行变基操作。
8.2.2.1.9. post-checkout
该钩子脚本由git checkout命令调用,是在完成工作区更新之后触发执行。该钩子脚本有三个参数:前一个HEAD的引用,新HEAD的引用(可能和前一个一样也可能不一样),以及一个用于表示此次检出是否是分支检出的标识(分支检出为1,文件检出是0)。该钩子脚本不会影响git checkout命令的结果。
除了由git checkout命令调用外,该钩子脚本也在git clone命令执行后被触发执行,除非在克隆时使用了禁止检出的--no-checkout (-n)
参数。在由git clone调用时,第一个参数给出的引用是空引用,则第二个和第三个参数都为1。
这个钩子一般用于版本库的有效性检查,自动显示和前一个HEAD的差异,或者设置工作区属性。
8.2.2.1.10. post-merge
该钩子脚本由git merge命令调用,当在本地版本库完成git pull操作后触发执行。该钩子脚本有一个参数,标识合并是否是一个压缩合并。该钩子脚本不会影响git merge命令的结果。如果合并因为冲突而失败,该脚本不会执行。
该钩子脚本可以与pre-commit
钩子脚本一起实现对工作区目录树属性(如权限/属主/ACL等)的保存和恢复。参见Git源码文件contrib/hooks/setgitperms.perl
中的示例。
8.2.2.1.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命令向用户传递信息。
8.2.2.1.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服务架设”相关内容。
8.2.2.1.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
钩子脚本,以实现当版本库有变更后进行到各个镜像版本库的数据传输。
8.2.2.1.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命令,以更新哑协议需要的索引文件。如果通过哑协议共享版本库,应该启用该钩子脚本。
8.2.2.1.15. pre-auto-gc
该钩子脚本由git gc --auto命令调用,不带参数运行,如果以非零值退出会导致git gc --auto被中断。
8.2.2.1.16. post-rewrite
该钩子脚本由一些重写提交的命令调用,如git commit --amend、git rebase,而git-filter-branch当前尚未调用该钩子脚本。
该脚本的第一个参数用于判断调用来自哪个命令,当前有amend
和rebase
两个取值,也可能将来会有其他更多命令相关参数传递。
该脚本通过标准输入接收一个重写提交列表,每一行输入的格式如下:
<old-sha1> <new-sha1> [<extra-info>]
前两个是旧的和新的对象SHA1哈希值。而<extra-info>
参数是和调用命令相关的,而当前该参数为空。
8.2.2.2. Git模板
当执行git init或git clone创建版本库时,会自动在版本库中创建钩子脚本(.git/hooks/*
)、忽略文件(.git/info/exclude
)及其他文件,实际上这些文件均拷贝自模板目录。如果需要本地版本库使用定制的钩子脚本等文件,直接在模板目录内创建(文件或符号链接)会事半功倍。
Git按照以下列顺序第一个确认的路径即为模板目录。
如果执行git init或git clone命令时,提供
--template=<DIR>
参数,则使用指定的目录作为模板目录。由环境变量
$GIT_TEMPLATE_DIR
指定的模板目录。由Git配置变量
init.templatedir
指定的模板目录。缺省的模板目录,根据Git安装路径的不同可能位于不同的目录下。可以通过下面命令确认其实际位置:
$ ( cd $(git --html-path)/../../git-core/templates; pwd ) /usr/share/git-core/templates
如果在执行版本库初始化时传递了空的模板路径,则不会在版本库中创建钩子脚本 等文件。
$ git init --template= no-template Initialized empty Git repository in /path/to/my/workspace/no-template/.git/
执行下面的命令,查看新创建的版本库.git
目录下的文件。
$ ls -F no-template/.git/ HEAD config objects/ refs/
可以看到不使用模板目录创建的版本库下面的文件少的可怜。而通过对模板目录下的文件的定制,可以实现在建立的版本库中包含预先设置好的钩子脚本、忽略文件、属性文件等。这对于服务器或者对版本库操作有特殊要求的项目带来方便。