跳转到内容

Git/Gerrit 代码审查

来自维基教科书,开放世界中的开放书籍
< Git

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;

将项目导入 Gerrit

[编辑 | 编辑源代码]

您是否有权执行此操作取决于您所在的访问组,以及您尝试执行此操作的位置。它对于将预先存在的仓库推送到服务器最为有用,但理论上可以在您想要推送不需审查的更改时使用。

使用

$ 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

审查和合并更改

[编辑 | 编辑源代码]

另请参阅

[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
  1. Re: 由于路径冲突而无法提交,但没有真正的冲突。 (Shawn Pearce)
华夏公益教科书