Git/Gerrit 代码审查
Gerrit 是一个基于 Web 的代码审查工具,用于使用 Git VCS 的项目。它允许代码审查流程变得更精简,并为项目成员提供高度可配置的层次结构。通常,任何用户都可以将补丁(“变更集”)提交到服务器以供审查。一旦有人对更改进行了充分审查,他们就会将这些更改合并到开发的主线上,然后可以拉取这些更改。
Gerrit 使用专用的 Jetty 服务器,通常通过反向代理访问。以下是 Apache 的示例配置
<VirtualHost *> ProxyRequests Off ProxyVia Off ProxyPreserveHost On <Proxy *> Order deny,allow{{typo help inline|reason=similar to deny, allow|date=August 2022}} Allow from all </Proxy> # Reverse-proxy these requests to the Gerrit Jetty server RedirectMatch ^/gerrit$ /gerrit/ ProxyPass /gerrit/ http://127.0.0.1:8081/gerrit/ </VirtualHost>
Gerrit 使用 JGit,它是 git 的 Java 实现。这有一些限制:速度上的牺牲,以及一些未实现的功能[1]。例如,JGit 不支持内容级合并 - 用户必须拉取和合并更改,然后在需要内容级合并的情况下将它们重新上传到 Gerrit 服务器。
Gerrit 的权限模型允许高度可配置的层次结构,规定谁可以提交补丁,以及谁可以审查补丁。这可以根据每个项目的开发模型,按需进行扁平化或扩展。
服务器允许脚本在响应某些事件时运行。钩子脚本位于 $GIT_INSTALL_BASE/hooks 中,并且必须在 Unix 系统上设置为可执行文件。例如,钩子可以允许项目安装一个自动守门员,当收到足够的 +1 票“看起来不错”时,自动投票 +2 “提交批准”。
#!/usr/bin/perl
#
# comment-added: hook for a +2 approval from a simple quorum of +1 votes.
#
# (c) 2012 Tim Baverstock
# Licence: Public domain. All risk is yours; if it breaks, you get to keep both pieces.
$QUORUM = 2; # Total number of +1 votes causing a +2
$PLEBIANS = 'abs(value) < 2'; # or 'value = 1' to ignore -1 unvotes
$AUTO_SUBMIT_ON_QUORACY = '--submit'; # or '' for none
$AND_IGNORE_UPLOADER = 'and uploader_account_id != account_id'; # or '' to let uploaders votes count
$GERRIT_SSH_PORT = 29418;
$SSH_PRIVATE_KEY = '/home/gerrit2/.ssh/id_rsa';
$SSH_USER_IN_ADMIN_GROUP = 'devuser';
# Hopefully you shouldn't need to venture past here.
$SSH = "ssh -i $SSH_PRIVATE_KEY -p $GERRIT_SSH_PORT $SSH_USER_IN_ADMIN_GROUP\@localhost";
$LOG = "/home/gerrit2/hooks/log.comment-added";
open LOG, ">>$LOG" or die;
sub count_of_relevant_votes {
# Total selected code review votes for this commit
my $relevance = shift;
$query = "
select sum(value) from patch_sets, patch_set_approvals
where patch_sets.change_id = patch_set_approvals.change_id
and patch_sets.patch_set_id = patch_set_approvals.patch_set_id
and revision = '$V{commit}'
and category_id = 'CRVW'
and $relevance
$AND_IGNORE_UPLOADER
;";
$command = "$SSH \"gerrit gsql -c \\\"$query\\\"\"";
#print LOG "FOR... $command\n";
@lines = qx($command);
chomp @lines;
#print LOG "GOT... ", join("//", @lines), "\n";
# 0=headers 1=separators 2=data 3=count and timing.
return $lines[2];
}
sub response {
my $review = shift;
return "$SSH 'gerrit review --project=\"$V{project}\" $review $V{commit}'";
}
# ######################
# Parse options
$key='';
while ( $_ = shift @ARGV ) {
if (/^--(.*)/) {
$key = $1;
}
else {
$V{$key} .= " " if exists $V{$key};
$V{$key} .= $_;
}
}
#print LOG join("\n", map { "$_ = '$V{$_}'" } keys %V), "\n";
# ######################
# Ignore my own comments
$GATEKEEPER="::GATEKEEPER::";
if ($V{comment} =~ /$GATEKEEPER/) {
print LOG localtime() . "$V{commit}: Ignore $GATEKEEPER comments\n";
exit 0;
}
# ######################
# Forbear to analyse anything already +2'd
$submittable = count_of_relevant_votes('value = 2');
if ($submittable > 0) {
print LOG localtime() . "$V{commit} Already +2'd by someone or something.\n";
exit 0;
}
# ######################
# Look for a consensus amongst qualified voters.
$plebicite = count_of_relevant_votes($PLEBIANS);
#if ($V{comment} =~ /TEST:(\d)/) {
# $plebicite=$1;
#}
# ######################
# If there's a quorum, approve and submit.
if ( $plebicite >= $QUORUM ) {
$and_submitting = ($AUTO_SUBMIT_ON_QUORACY ? " and submitting" : "");
$review = " --code-review=+2 --message=\"$GATEKEEPER approving$and_submitting due to $plebicite total eligible votes\" $AUTO_SUBMIT_ON_QUORACY";
}
else {
$review = " --code-review=0 --message=\"$GATEKEEPER ignoring $plebicite total eligible votes\"";
print LOG localtime() . "$V{commit}: $review\n";
exit 0; # Perhaps don't exit here: allow a subsequent -1 to remove the +2.
}
$response = response($review);
print LOG localtime() . "RUNNING: $response\n";
$output = qx( $response 2>&1 );
if ($output =~ /\S/) {
print LOG localtime() . "$V{commit}: output from commenting: $output";
$response = response(" --message=\"During \Q$review\E: \Q$output\E\"");
print LOG localtime() . "WARNING: $response\n";
$output = qx( $response 2>&1 );
print LOG localtime() . "ERROR: $output\n";
}
exit 0;
您是否有权执行此操作取决于您所在的访问组,以及您尝试执行此操作的位置。它对于将预先存在的仓库推送到服务器最为有用,但理论上可以在您想要推送不需审查的更改时使用。
使用
$ git push gerrit:project HEAD:refs/heads/master
因为您希望直接推送到分支,而不是创建代码审查。推送到 refs/for/*
会创建必须经过批准然后提交的代码审查。推送到 refs/heads/*
会完全绕过审查,并将提交直接进入分支。后者不会检查提交者身份,使其适合导入过去的项目历史记录。
正确的权限设置可以在此处找到:http://stackoverflow.com/questions/8353988/how-to-upload-a-git-repo-to-gerrit。此外,对于某些仓库,可能需要“推送合并提交”用于“refs/*”(有关详细信息,请参阅 http://code.google.com/p/gerrit/issues/detail?id=1072)。
只需使用任何 Git 客户端工具推送到项目的魔术 refs/for/$branch
(通常为 master)引用即可
$ git push ssh://user@host:29418/project HEAD:refs/for/master
git push 客户端上传的每个新提交都将被转换为服务器上的更改记录。远程引用 refs/for/$branch
实际上不是由 Gerrit 创建的,即使客户端的状态消息可能显示并非如此。推送到这个魔术分支会提交更改以供审查。推送更改后,必须对这些更改进行审查,然后才能将它们提交到它们适用的任何分支。您可以像使用任何其他 git 仓库一样从 Gerrit 克隆/拉取(没有 refs/for/branch,只需使用分支名称)
$ git clone ssh://user@host:29418/project $ git pull ssh://user@host:29418/project master:testbranch
Gerrit 目前没有 git-daemon
,因此拉取是通过 ssh 进行的,因此速度相对较慢(但安全)。您可以为仓库运行 git-daemon
以使其通过 git://
可用,或者配置 Gerrit 将更改复制到另一个 git 仓库,您可以在该仓库中拉取。
由于您将经常使用同一个 Gerrit 服务器,请在 ~/.ssh/config 中添加一个 SSH 主机块以记住您的用户名、主机名和端口号。这允许在命令行上使用更短的 URL,如所示,例如
$ tail -n 4 ~/.ssh/config Host gerrit Hostname host.com Port 29418 User john.doe $ git push gerrit:project HEAD:refs/for/master
或者,您也可以通过发出以下命令在 git 的配置文件中配置您的远程仓库
$ git config remote.remote_name.fetch +refs/heads/*:refs/remotes/origin/* $ git config remote.remote_name.url ssh://user@host:29418/project_name.git
如果您使用以下命令从 Gerrit 的项目仓库开始您的本地仓库,则应该自动为您完成此操作
$ git clone ssh://user@host:29418/project_name.git
请注意,Gerrit 服务器有自己的 sshd,具有不同的主机密钥。某些 ssh 客户端会对此强烈抱怨。
当您提交的提交存在问题时,这很有用。也许您发现了问题,也许是审阅者发现了问题——无论哪种方式,您都希望提交更改以供审查,替换您先前提交的错误更改。这将代码审查集中在一个地方,简化流程。首先,使用 git rebase -i 将您的更改压缩成一个提交——您可能希望更改提交消息。
这实际上并没有替换之前的推送,它只是将您的更新的变更集添加为一个较新的版本。
您可以在提交消息中提供一个 Change-Id 行:它必须位于提交消息的底部部分(最后一段),并且可以与 Signed-off-by、Acked-by 或其他此类脚注混合在一起。Change-Id 在您最初推送提交的元数据表中可用。在这种情况下,Gerrit 会自动将此变更集与之前的变更集匹配。
或者,您可以推送到一个特殊的位置:refs/changes/*
分支。要替换变更集 12345,请推送到 refs/changes/12345
。此编号可以在查看您想要替换的变更集时在 URL 中找到:#change,12345
。您也可以在变更集的“下载”部分中找到它:...refs/changes/45/12345/1
(选择中间的编号:忽略 /45/
并省略尾部的 /1
)。在这种情况下,您的推送变为
$ git rebase -i HEAD~2 # squash the relevant commits, probably altering the commit message $ git push gerrit:project a95cc0dcd7a8fd3e70b1243aa466a96de08ae731:refs/changes/12345
- ↑ Re: 由于路径冲突而无法提交,但没有真正的冲突。 (Shawn Pearce)