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)