搭建代码审查系统Gerrit
简介
谷歌的 Android 开源项目在 Git 的使用上有两个重要的创新,一个是为多版本库协同而引入的 repo,另外一个重要的创新就是 Gerrit —— 代码审核服务器。Gerrit 为 Git 引入的代码审核是强制性的,就是说除非特别的授权设置,向 Git 版本库的推送(Push)必须要经过 Gerrit 服务器,修订必须经过代码审核的一套工作流之后,才可能经批准并纳入正式代码库中。gerrit目前也被用于Openstack中,用于审核Openstack项目下的各子项目。
Gerrit安装与配置
安装Gerrit需要装有最低1.6版本的JDK:
apt-getinstalldefault-jre
安装git: apt-get install git
wgethttp://gerrit.googlecode.com/files/gerrit-full-2.5.1.war
mysqlCREATEUSER'gerrit2'@'localhost'IDENTIFIEDBY'secret';CREATEDATABASEreviewdb;ALTERDATABASEreviewdbcharset=latin1;GRANTALLONreviewdb.TO'gerrit2'@'localhost';FLUSHPRIVILEGES;
addusergerrit2sugerrit
- 执行交互式配置
java-jargerrit-full-2.5.1.warinit-d~
接下来进行交互式的配置后,就完成了全部的设置。
- 启动gerrit
sugerrit2&&cd~&&./bin/gerrit.shstart
Gerrit工作原理和流程
首先贡献者的代码通过 git 命令(或git review封装)推送到 Gerrit 管理下的 Git 版本库,推送的提交转化为一个一个的代码审核任务,审核任务可以通过 refs/changes/<change-id>下的引用访问到。代码审核者可以通过 Web 界面查看审核任务、代码变更,通过 Web 界面做出通过代码审核或者打回等决定。测试者也可以通过 refs/changes/<change-id>引用获取(fetch)修订对其进行测试,如果测试通过就可以将该评审任务设置为校验通过(verified)。最后经过了审核和校验的修订可以通过 Gerrit 界面中提交动作合并到版本库对应的分支中。更详细的流程描述见下图所示:
Gerrit的实现原理
SSH 协议的 Git 服务器
Gerrit 本身基于 SSH 协议实现了一套 Git 服务器,这样就可以对 Git 数据推送进行更为精确的控制,为强制审核的实现建立了基础。 Gerrit 提供的 Git 服务的端口并非标准的 22 端口,缺省是 29418 端口。可以访问 Gerrit 的 Web 界面得到这个端口。对 Android 项目的代码审核服务器,访问 https://review.source.android.com/ssh_info 就可以查看到 Git 服务的服务器域名和开放的端口。下面我们用 curl 命令查看网页的输出。
root@lx1:~# curl -L -k http://gerrit.newptone.com/ssh_info
gerrit.newptone.com 29418
特殊引用 refs/for/<branch-name> 和 refs/changes/nn/<task-id>/m
Gerrit 的 Git 服务器,禁止用户向 refs/heads 命名空间下的引用执行推送(除非特别的授权),即不允许用户直接向分支进行提交。为了开发者能够向 Git 服务器提交修订,Gerrit 的 Git 服务器只允许用户向特殊的引用refs/for/<branch-name> 下执行推送,其中 <branch-name> 即为开发者的工作分支。向refs/for/<branch-name> 命名空间下推送并不会在其中创建引用,而是为新的提交分配一个 ID,称为 task-id ,并为该 task-id 的访问建立如下格式的引用 refs/changes/nn/<task-id>/m ,其中:
- task-id 为 Gerrit 为评审任务顺序分配的全局唯一的号码。
- nn 为 task-id 的后两位数,位数不足用零补齐。即 nn 为 task-id 除以 100 的余数。
- m 为修订号,该 task-id 的首次提交修订号为 1,如果该修订被打回,重新提交修订号会自增。
Git 库的钩子脚本 hooks/commit-msg
为了保证已经提交审核的修订通过审核入库后,被别的分支 cherry-pick 后再推送至服务器时不会产生新的重复的评审任务,Gerrit 设计了一套方法,即要求每个提交包含唯一的 Change-Id,这个 Change-Id 因为出现在日志中,当执行 cherry-pick 时也会保持,Gerrit 一旦发现新的提交包含了已经处理过的 Change-Id ,就不再为该修订创建新的评审任务和 task-id,而直接将提交入库。为了实现 Git 提交中包含唯一的 Change-Id,Gerrit 提供了一个钩子脚本,放在开发者本地 Git 库中(hooks/commit-msg)。这个钩子脚本在用户提交时自动在提交说明中创建以 "Change-Id: " 及包含 git hash-object 命令产生的哈希值的唯一标识。当 Gerrit 获取到用户向 refs/for/<branch-name> 推送的提交中包含 "Change-Id: I..." 的变更 ID,如果该 Change-Id 之前没有见过,会创建一个新的评审任务并分配新的 task-id,并在 Gerrit 的数据库中保存 Change-Id 和 Task-Id 的关联。如果当用户的提交因为某种原因被要求打回重做,开发者修改之后重新推送到 Gerrit 时就要注意在提交说明中使用相同的 “Change-Id” (使用 --amend 提交即可保持提交说明),以免创建新的评审任务,还要在推送时将当前分支推送到refs/changes/nn/task-id/m 中。其中 nn 和 task-id 和之前提交的评审任务的修订相同,m 则要人工选择一个新的修订号。以上说起来很复杂,但是在实际操作中只要使用 repo 这一工具,就相对容易多了。 其余一切交给Web Gerrit 另外一个重要的组件就是 Web 服务器,通过 Web 服务器实现对整个评审工作流的控制。
下面是一个review.openstack.org上的一个提交修改https://review.openstack.org/#/c/22035/:
- URL 中显示的评审任务编号为 22035。
- 该评审任务的 Change-Id 以字母 I 开头,包含了一个唯一的 40 位 SHA1 哈希。
- 整个评审任务有三个人以及自动测试工具Jenkins参与,一个人进行了检查(verify),两个人进行了代码审核,并有一人最终批准了该提交(approve),Jenkins进行了verify。
- 该评审任务的状态为打开状态:“open”。
- 该评审任务总共包含一个补丁集: Patch set 1。