第32章 Gerrit代码审核服务器
谷歌Android开源项目在Git的使用上有两个重要的创新,一个是为多版本库协同而引入的repo,这在前面第25章已经详细讨论过。另外一个重要的创新就是Gerrit——代码审核服务器。Gerrit为Git引入的代码审核是强制性的,也就是说除非特别的授权设置,向Git版本库的推送必须要经过Gerrit服务器,修订必须经过代码审核的一套工作流之后,才可能经批准并纳入正式代码库中。
首先贡献者的代码通过git命令(或repo封装)推送到Gerrit管理下的Git版本库,推送的提交转化为一个一个的代码审核任务,审核任务可以通过refs/changes/下的引用访问到。代码审核者可以通过Web界面查看审核任务、代码变更,通过Web界面做出通过代码审核或打回等决定。测试者也可以通过refs/changes/之下的引用获取修订然后对其进行测试,如果测试通过就可以将该评审任务设置为校验通过(verified)。最后经过了审核和校验的修订可以通过Gerrit界面中的提交动作合并到版本库对应的分支中。
Android项目网站上有一个代码贡献流程图[1],详细地介绍了Gerrit代码审核服务器的工作流程。翻译后的工作流程图如图32-1。
图 32-1 Gerrit代码审核工作流
32.1 Gerrit的实现原理
Gerrit更准确地说应该称为Gerrit2。因为Android项目最早使用的评审服务器Gerrit不是今天这个样子的。最早版本的Gerrit是用Python开发运行于Google App Engine上的,从Python之父Guido van Rossum开发的Rietveld分支而来。在这里要讨论的Gerrit实为Gerrit2,是用Java语言实现的[2]。
1.SSH协议的Git服务器
Gerrit本身基于SSH协议实现了一套Git服务器,这样就可以对Git数据推送进行更为精确的控制,为强制审核的实现建立了基础。
Gerrit提供的Git服务的端口并非标准的22端口,默认是29418端口。这个端口是可以被发现的,当访问Gerrit的Web界面时可以得到这个端口。对Android项目的代码审核服务器,访问https://review.source.android.com/ssh_info就可以查看到Git服务的服务器域名和开放的端口。下面用curl命令查看网页的输出。
$curl-L-k http://review.source.android.com/ssh_info
review.source.android.com 29418
2.特殊引用refs/for和refs/changes
Gerrit的Git服务器,禁止用户向refs/heads命名空间下的引用执行推送(除非特别的授权),即不允许用户直接向分支进行提交。为了让开发者能够向Git服务器提交修订,Gerrit的Git服务器只允许用户向特殊的引用refs/for/<branch-name>下执行推送,其中<branch-name>即为开发者的工作分支。向refs/for/<branch-name>命名空间下推送并不会在其中创建引用,而是为新的提交分配一个ID,称为review-id,并为该review-id的访问建立如下格式的引用refs/changes/nn/<review-id>/m,其中:
review-id是Gerrit为评审任务顺序而分配的全局唯一的号码。
nn为review-id的后两位数,位数不足用零补齐。即nn为review-id除以100的余数。
m为修订号,该review-id的首次提交修订号为1,如果该修订被打回,重新提交修订号会自增。
3.Git库的钩子脚本hooks/commit-msg
为了保证已经提交审核的修订通过审核入库后,如果被别的分支拣选(cherry-pick)后再推送至服务器时不会产生新的重复的评审任务,Gerrit设计了一套特殊的方法,即要求每个提交在提交说明中包含Change-Id键值对作为标签,该标签在首次生成时使用特殊的哈希算法以保障其唯一性。执行拣选操作时,提交说明会被保持,即来自原始提交说明中的Change-Id键值对也会保持不变,这样当新提交推送到Gerrit服务器时,Gerrit会发现新的提交包含了已经处理过的Change-Id,就不再为该修订创建新的评审任务和review-id,而直接将提交入库。
为了使得Git提交中包含唯一的Change-Id,Gerrit提供了一个钩子脚本,将该脚本拷贝到开发者的本地Git库的钩子脚本目录中,即脚本文件.git/hooks/commit-msg。这个钩子脚本在用户提交时,自动在提交说明中创建Change-Id键值对。至于如何实现Change-Id值的唯一性,可以参考该脚本。
当Gerrit获取到用户向refs/for/<branch-name>推送的提交中包含"Change-Id:I……"的格式时,如果该Change-Id之前没有见过,会创建一个新的评审任务并分配新的review-id,并在Gerrit的数据库中保存Change-Id和review-id的关联。
如果用户的提交因为某种原因被打回重做,开发者修改之后重新推送到Gerrit时就要注意在提交说明中使用相同的Change-Id(使用—amend提交即可保持提交说明),以免创建新的评审任务。还要在推送时将当前分支推送到refs/changes/<nn>/<review-id>/<m>中,是为该评审任务的一个新的修订,其中<nn>和<review-id>和之前提交的评审任务的修订号相同,<m>则要人工选择一个新的修订号。
以上说起来很复杂,但是在实际操作中只要使用repo这一工具,就相对容易多了。
4.其余一切交给Web
Gerrit另外一个重要的组件就是Web服务器,通过Web服务器实现对整个评审工作流的控制。关于Gerrit工作流,请参见本章开头出现的Gerrit工作流程图。
从图32-3中可以看出:
URL中显示的评审任务编号为16993。
该评审任务的Change-Id以字母I开头,包含了一个唯一的40位SHA1哈希值。
整个评审任务有三个人参与,一个人进行了检查(verify),两个人进行了代码审核。
该评审任务的状态为已合并:"merged"。
该评审任务总共包含两个补丁集:Patch set 1和Patch set 2。
补丁集的下载方法是:repo download platform/sdk 16993/2。
如果使用repo命令获取补丁集是非常方便的,因为封装后的repo屏蔽掉了Gerrit的一些实现细节,例如补丁集在Git库中的存在位置。如前所述,补丁集实际保存在refs/changes命名空间下。使用git ls-remote命令,从Gerrit维护的代码库中可以看到补丁集对应的引用名称。
$git ls-remote\
ssh://review.source.android.com:29418/platform/sdk\
refs/changes/93/16993*
5fb1e79b01166f5192f11c5f509cf51f06ab023d refs/changes/93/16993/1
d342ef5b41f07c0202bc26e2bfff745b7c86d5a7 refs/changes/93/16993/2
接下来就来介绍一下Gerrit服务器的部署和使用方法。