第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。
1 source.android/source/life-of-a-patch.html
图32-1:Gerrit 代码审核工作流
32.1Gerrit 的实现原理
Gerrit 更准确地说应该称为 Gerrit2。因为 Android 项目最早使用的评审服务器 Gerrit 不是今
天这个样子的。最早版本的 Gerrit 是用 Python 开发运行于 Google App Engine 上的,从 Python 之父 Guido van Rossum 开发的 Rietveld 分支而来。在这里要讨论的 Gerrit 实为 Gerrit2,是用Java 语言实现的1。
1.SSH 协议的 Git 服务器
Gerrit 本身基于 SSH 协议实现了一套 Git 服务器,这样就可以对 Git 数据推送进行更为精确的控制,为强制审核的实现建立了基础。
Gerrit 提供的 Git 服务的端口并非标准的 22 端口,默认是 29418 端口。这个端口是可以被发现的,当访问 Gerrit 的 Web 界面时可以得到这个端口。对 Android 项目的代码审核服务器,访问review.source.android/ssh_info就可以查看到 Git 服务的服务器域名和开放的端口。下面用 curl 命令查看网页的输出。
$ curl -L -k review.source.android/ssh_info
review.source.android 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,如果该修订被打回,重新提交修订号会自增。
1 le/p/gerrit/wiki/Background
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 工作流程图。
想要感受一下 Gerrit 的魅力?请直接访问 Android 项目的 Gerrit 网站:
review.source.android/,您会看到如图32-2的界面。
图32-2:Android 项目代码审核网站
点击菜单中的“Merged”即可显示已经通过评审且合并到代码库中的审核任务。图32-3中
显示的就是 Andorid 一个已经合并到代码库中的历史评审任务。
git使用详解
图32-3:Android 项目的 16993 号评审
从图32-3中可以看出:
URL 中显示的评审任务编号为 16993。