跳轉到內容

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)
華夏公益教科書