Git、Gerrit與Jenkins/Hudson CI服務器
本文講述了如何為基于團隊的代碼審查系統配置Git、Gerrit與Jenkins/Hudson,正如我在 《Git, Gerrit and Jenkins for iOS development》和 《Gerrit Git Reviewwith Jenkins CI Server》演講(以及第一次提出這種做法的 《Someday...》)中所倡導的那樣。文中的范例假定你所使用的操作系統是OS X或Linux,但是如果你愿意,也可以在Windows上運行它們。
配置Git
很多系統(例如Linux)已經默認提供了Git,在Git主頁也可以找到安裝程序。對于Windows用戶,最好的選擇是MsysGit。請注意,如果你安裝了Apple Developer Tools (for Xcode 4),那么其中已經自帶Git二進制包了。如果遇到了問題,help.github.com中可以找到很多非常出色的指南。
因為所有的Git提交都帶有作者和電子郵件地址,如果你還沒有設置過這些內容,請執行以下命令進行配置:
$ git config --global user.name "Alex Blewitt"$ git config --global user.email Alex.Blewitt@example.com
最好有一個Git代碼庫。Gerrit在初始化階段會自動掃描Git代碼庫,這樣做比在后期配置代碼庫容易一些,因此最好在初始化Gerrit前準備好Git代碼庫。
如果還沒有Git代碼庫,可以創建一個:
git init --bare /path/to/gits/example.git
Gerrit
可以從http://code.google.com/p/gerrit/下載到Gerrit,那是一個WAR文件。Gerrit有很好的文檔(目前的最新文檔是2. 2.1,但也可用于2.1.7)和安裝指南。
Gerrit在運行時需要用到數據庫(用于存儲代碼審查的信息)。目前支持的數據庫包括H2、PostgreSQL和MySQL。在沒有進行額外配置的情況下,它默認使用H2。
請注意,Gerrit 2.2.x正把項目配置、權限和其他元數據移到Git存儲中,這樣就可以通過Git進行訪問和版本控制。在2.2.x中,這個轉變會慢慢擴展到其他類型的元數據上,包含代碼評審內容。詳見2. 2.0版本的發布說明。
要初始化Gerrit,運行java -jar gerrit.war init -d /path/to/location會在指定路徑上安裝Gerrit運行時。
如果是在交互終端中運行的,安裝程序會提幾個問題,例如:
- Git代碼庫的位置 [git]
- 導入現有代碼庫 [Y/n]
- 數據庫服務器類型 [H2/?]
- 身份驗證方法 [OPENID/?]
- SMTP服務器主機名 [localhost]
- SMTP服務器端口 [(default)]
- SMTP加密 [NONE/?]
- SMTP用戶名
- 以何種身份運行 [you]
- Java運行時 [/path/to/jvm]
- 將gerrit.war復制到/path/to/location/bin/gerrit.war [Y/n]
- 監聽地址 [*]
- 監聽端口 [29418]
- 下載并安裝Bouncy Castle [Y/n]
- 位于HTTP反向代理之后 [y/N]
- 使用SSL [y/N]
- 監聽地址 [*]
- 監聽端口 [8080]
其中大部分可以保留默認值,但是有幾個需要重點關注。
- Git代碼庫的位置要指向正確的位置。默認值是位于安裝目錄中的'git'目錄,也可以是其他不同位置(例如/var/gits等等)。應該先配置好這個路徑,因為在Gerrit啟動時它會先掃描該目錄來添加新項目。
- 監聽地址對有多個地址(例如IPv4和IPv6)的主機很有用,它可以限定使用哪個地址。*表示本地主機上的任意地址。
- 監聽端口是一個端口號。29418是默認的Gerrit SSH端口,而8080是默認的Gerrit Web端口。但是如果8080已經被別的應用程序占用了,那么你可能會想修改第二個端口。
- 身份驗證方法確定了如何登錄Gerrit。如果你想掛入某個現有的身份驗證提供方(例如Google Accounts),那么可以使用OpenID。但對測試而言(還有上面提到的范例),可以使用development_become_any_account。鍵入?會顯示一個可用方法的列表。
Gerrit啟動后,會打開一個瀏覽器,顯示Gerrit主頁。登錄的第一個用戶將自動成為管理員;所有后續登錄的用戶都是無權限用戶。如果你選擇了development_become_any_account,在頁面頂端會有一個Become鏈接,通過它可以進入注冊/登錄頁面。
注冊用戶
為了使用Gerrit,你需要一個賬號,生成一個SSH密鑰對。在命令行中運行 ssh-keygen -t rsa -b 2048可以生成密鑰對,將其放到你的.ssh目錄中。如果你需要更多信息,可以訪問這篇博文,這是我六年前寫的SSH密鑰相關文章。此外,GitHub 幫助頁面中也有更多相關信息。
默認文件名為id_rsa(這是私鑰)和id_rsa.pub(這是公鑰)。你只能給別人公鑰,千萬別給別人私鑰。
有了密鑰,你就可以在Gerrit中注冊新賬戶了。點擊頂部右端的“Become”鏈接,然后再點擊“New account”按鈕,輸入Git知道的名稱和電子郵件(即上面用git config配置的),這些內容要完全匹配(包括大小寫)。保存變更,然后選擇一個唯一的用戶名(填入了名稱后點擊'select username',例如demo)。
頭痛的電子郵件就算是development_become_any_account模式,Gerrit也會給你發送電子郵件以驗證郵件地址。不通過驗證的話就無法使用該電子郵件地址,你也無法提交代碼。
我們可以很快處理掉這個問題,因此如果Gerrit目前不讓你注冊你的郵箱地址,請不用擔心。
在'add SSH public key'文本框中,添加.pub文件中的公鑰。如果你使用的是OS X,pbcopy< ~/.ssh/id_rsa.pub就可輕松搞定。記住要點擊“Add”保存公鑰。
點擊Continue后應該就能登錄到Gerrit的主窗口了。到目前為止一切還不錯,現在可以測試SSH的連通性了。
鍵入ssh -p 29418 demo@localhost會嘗試連接Gerrit服務器,會出現以下三者之一:
-
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
-
這并沒有看上去那么糟。它只是說你的~/.ssh/known_hosts文件中有舊的密鑰。最懶的一種修復方法是刪除這個文件(這是GitHub推薦的做法)。更好的方法是找到提示出錯的那一行,把它刪了:
Offending key in /Users/demo/.ssh/known_hosts:123
你可以用任何文本編輯器來刪除known_hosts文件中的第123行。在標準UNIX配置下,可以用以下命令自動刪除:
sed -i '''123d'~/.ssh/known_hosts
-
The authenticity of host '[localhost]:29418 ([::1]:29418)' can't be established.RSA key fingerprint is e8:e2:fe:19:6f:e2:db:c1:05:b5:bf:a6:ad:4b:04:33.Are you sure you want to continue connecting (yes/no)? yesWarning: Permanently added '[localhost]:29418'(RSA) to the list of known hosts.Permission denied (publickey).
如果看到這個消息,說明Gerrit沒有識別出你提交的任何密鑰。ssh默認會發送id_rsa,要確認它的確發送了該值,可以查看.ssh/config,文件頭上有一行IdentityFile ~/.ssh/id_rsa。可以運行ssh -v查看發送的內容:
debug1: Next authentication method: publickeydebug1: Trying private key: /Users/demo/.ssh/id_dsadebug1: Offering public key: /Users/demo/.ssh/id_rsa
假設已經發送了正確的密鑰,那么在settings - ssh public keys菜單項中檢查該密鑰是否關聯了Gerrit中的用戶。如果沒有的話點擊'Add Key'并和之前一樣粘貼公鑰。
如果Gerrit還是說你未經身份驗證,檢查用戶名和配置頁面里的用戶名是否一致。如果用戶名不同,試試ssh -p 29418 username@localhost。
最后,要驗證具體的密鑰,可以運行ssh -i ~/.ssh/id_rsa顯式地選擇要使用的密鑰,而不是讓它自動選擇密鑰。如果這樣可以工作,但不帶-i參數卻不行的話,那么問題出在你的~/.ssh/config文件里――你需要保證選擇了合適的IdentityFile。
-
**** Welcome to Gerrit Code Review **** Hi demo,you have successfully connected over SSH. Unfortunately, interactive shells are disabled. To clone a hosted Git repository, use: git clone ssh://demo@localhost:29418/REPOSITORY_NAME.gitConnection to localhost closed.
如果看到這個提示,說明Gerrit已經正常工作了。
修正電子郵件地址
如果之前你無法在Gerrit中注冊電子郵件地址,那么可以手工進行注冊。我們可以停止Gerrit,運行GSQL工具更新特
$ bin/gerrit.sh stop$ java -jar bin/gerrit.war gsqlWelcome to Gerrit Code Review 2.1.6.1(H2 1.2.134 (2010-04-23))Type '\h' for help. Type '\r' to clear the buffer.gerrit> select * from ACCOUNT_EXTERNAL_IDS; ACCOUNT_ID EMAIL_ADDRESS PASSWORD EXTERNAL_ID -----------+------------------------+----------+------------------------------------------ 1000000 NULL NULL uuid:ac1b8a08-2dd1-4aa1-8449-8b2994dffaed 1000000 NULL NULL username:demo(2 rows; 23 ms)gerrit> update ACCOUNT_EXTERNAL_IDS set EMAIL_ADDRESS='alex.blewitt@example.com'where ACCOUNT_ID=1000000;UPDATE 2; 5 msgerrit> select * from ACCOUNT_EXTERNAL_IDS; ACCOUNT_ID EMAIL_ADDRESS PASSWORD EXTERNAL_ID -----------+------------------------+----------+---------------- 1000000 alex.blewitt@example.com NULL uuid:ac1b8a08-2dd1-4aa1-8449-8b2994dffaed 1000000 alex.blewitt@example.com NULL username:demo(2 rows; 23 ms)gerrit> \qBye$ bin/gerrit.sh start
創建項目,克隆并推送代碼
開始前,我們需要先在Gerrit中創建一個項目。如果Gerrit沒有在你的范例目錄中檢測到項目,在它運行時,我們可以創建一個項目。
$ ssh -p 29418 demo@localhost gerrit create-project --name example.git
上述命令會創建一個名為example的項目,在之前指定的Git目錄里初始化一個空的代碼庫。如果已經有一個代碼庫了,Gerrit不允許創建同名代碼庫——但你可以先對它進行臨時重命名,隨后再把名字改回來。
隨后,可以創建一個克隆:
$ git clone ssh://demo@localhost:29418/example.gitCloning into example...warning: You appear to have cloned an empty repository.
我們可以向代碼庫進行提交和推送,就和其他Git系統一樣:
$ cd example$ echo hello > world$ git add world$ git commit -m "The World"[master (root-commit) 06bf85e] The World 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 world$ git pushNo refs in common and none specified; doing nothing.Perhaps you should specify a branch such as 'master'.$ git push origin masterCounting objects: 3, done.Writing objects: 100%(3/3), 217 bytes, done.Total 3 (delta 0), reused 0 (delta 0)To ssh://me@localhost:29418/example.git ! [remote rejected] master -> master (prohibited by Gerrit)error: failed to push some refs to 'ssh://demo@localhost:29418/example.git'
這是什么情況?Gerrit不希望我們直接覆寫Git代碼庫中的任何分支,而是將變更推送到另一個refspec中,這讓Gerrit有機會在代碼審查中修改代碼。最簡單的方法是為推送配置一個默認的refspec:
$ git config remote.origin.push refs/heads/*:refs/for/*$ git push originCounting objects: 3, done.Writing objects: 100%(3/3), 217 bytes, done.Total 3 (delta 0), reused 0 (delta 0)To ssh://demo@localhost:29418/example.git * [new branch] master -> refs/for/master
我們推送了一個分支,在Gerrit的change,1里可以發現,盡管實際的分支名稱是refs/changes/01/1/1,但看到的卻是refs/for/master。你會發現當前分支的哈希值和此處顯示的一樣(在我的例子里是06bf85e)。如果我們要再次推送,會創建一個新的分支refs/changes/02/2/1,而不是覆蓋refs/for/master。第一個數字是變更號的最后兩位,第二個數字是變更號,第三個數字是補丁集號。所以變更123的補丁集17就是refs/changes/23/123/17。
如果想要修正(amend)提交,Gerrit會創建一個新的代碼審查引用,這么做不太好。如果兩次變更之間有審查評語,你將丟失這些內容。
我們可以修改commit-msg,添加一個(Gerrit專門的)Change-Id。同一變更的所有后續提交都會關聯到一個補丁集上。Gerrit提供了一個實現,只要復制出來就行了。
$ cd .git/hooks$ scp -P 29418 demo@localhost:hooks/commit-msg .$ cd ../..
現在,當我們要提交修正時便會自動生成一個Change-Id。可以用這個值為變更集生成多個補丁。通過修正提交,并提供Gerrit變更的Change-Id,我們可以修改當前的提交。
$ git commit --amend -m "Hello World>> Change-Id: I06bf85ed12f370212ec22dbd76c115861b653cf2> "[master 86a7a39] Hello World 1 files changed, 1 insertions(+), 0 deletions(-)$ git pushCounting objects: 3, done.Writing objects: 100%(3/3), 260 bytes, done.Total 3 (delta 0), reused 0 (delta 0)remote: (W) 86a7a39: no files changed, message updatedTo ssh://me@localhost:29418/example.git * [new branch] master -> refs/for/master
現在再去看Gerrit中的change 1,你會發現出現了第二個與該變更相關聯的補丁集。遠端分支refs/changes/01/1/2包含了新的提交消息(盡管所有文件還是一樣的)。
通常情況下,你并不需要添加Change-Id,因為提交消息Hook會自動幫你添加的。
代碼審查與結果提交
我們要創建一個build.sh腳本來執行構建過程。Jenkins/Hudson會簽出并運行該腳本。一般而言,這是一個Maven的構建,也可能是xcodebuild或make——為了避免針對某種語言,我們就使用一個普通的腳本。
$ cat > build.sh#!/bin/shecho Pretending to build ...echo done^D$ chmod a+x build.sh$ git add build.sh$ git commit -m "Adding (dummy) build script"$ git push
問題是這些變更還在Gerrit的評審隊列里,我們要先批準這些變更。進入變更頁面,會看到這些文件,還有一個Review按鈕。在變更頁面有一個讓你做代碼審查的Review按鈕,但卻沒有提交按鈕……
這是因為變更在提交前默認要評審+2,每個人只能+1,也就是說只有一個人做代碼審查是無法提交代碼的。
我們可以修改Gerrit的規則,允許一個人投票+2,也可以準許+1提交。
最簡單的方法是進入項目管理頁面的All Projects Access標簽頁(在Gerrit 2.2.1和2.1.7.2里這個標簽從--All Projects--改成了All-Projects)。點擊“Code review”規則旁邊的復選框,修改“Permitted Range”為+2: Looks good to me, approved。Gerrit還需要一個+1校驗,隨后也會為Jenkins/Hudson配置的。按照下面內容添加一條新規則:
CategoryVerifiedGroup NameNon-Interactive UsersReference Namerefs/*Permitted Range-1: Fails to +1: Verified
點擊“Add Access Right”來配置該權限。
我們需要為Jenkins/Hudson創建一個新用戶并添加到這個分組里,因此進到登錄頁面注冊一個新賬戶buildbot。還需要一個新的SSH密鑰(名字是id_rsa.buildbot),和之前一樣粘貼在公鑰一欄里。
重新用管理員ID登錄(你早些時候創建的那個賬戶),進入Admin - Groups標簽。在Non-Interactive Users里加入buildbot和demo用戶(后者是臨時的)。
配置好了校驗規則,我們應該回到變更頁面,將這個變更標記為通過。
但我們還是缺少一個Submit按鈕,需要用它來合并分支。提交權限獨立于校驗和代碼審查權限之外,因此需要回到All Projects Access標簽頁,添加一個權限:
CategorySubmitGroup NameRegistered UsersReference Namerefs/*Permitted Range+1: Submit
點擊“Add Access Right”來配置該權限。
現在,回到變更頁面,我們就能看到Submit按鈕(如果變更已經通過了代碼審查)了,在評論/代碼審查頁面也有Publish and Submit按鈕。(請注意,如果代碼審查還沒到達提交所需的級別,在點擊Publish and Submit時會出現一個錯誤提示。)
最后,發布并提交所有未完成的代碼審查(保證構建腳本已經就緒),執行git pull來確保你的代碼庫是最新的。它們應該出現在已合并標簽頁中。
管理員注意事項:一般來說,不會普遍賦予'All Projects Access'權限,而是根據項目進行分配。這里使用'All Projects'是為了方便運行,也可以根據項目進行配置。
配置Jenkins/Hudson
現在要做的就是配置Jenkins(如果你喜歡也可以用Hudson)。它們和Gerrit一樣,默認運行在8080端口上,你需要為它們換個端口。修改Gerrit的端口要重新執行之前的配置過程,修改Jenkins/Hudson的端口只需命令行即可。
$#java -jar hudson-2.0.1.war --httpPort=1234$ java -jar jenkins.war -httpPort=1234...
Jenkins/Hudson可以直接簽出Git項目,也可以通過Gerrit簽出。但是簽出過程未必都能自定義SSH身份(不用.ssh/config中的默認身份集)。有時使用匿名Git協議來托管Git代碼庫會容易些,即無需身份驗證。可以運行如下命令:
$ git daemon --export-all --base-path=/path/to/gits
我們還需要安裝Git插件和Git/Gerrit觸發器。打開位于http://localhost:1234的Jenkins/Hudson,點擊左上方的Manage Jenkins/Hudson鏈接,再點擊Manage Plugins鏈接。切換到 Available標簽頁,安裝:
- Gerrit Trigger
Install按鈕在右下方,Jenkins/Hudson稍后會重啟。Gerrit Trigger會安裝Git插件。請注意,有一個Gerrit Plugin,那個不是我們要裝的。(如果你在使用Hudson,并且更新頁面里沒有任何內容的話,進入'Advanced'點擊下方的'Check now'。)
現在,我們可以開始配置Jenkins/Hudson了。先按以下步驟新建一個CI任務檢查Git代碼庫的變更(合并):
- 點擊左上方的'New Job'。這會要求你輸入一個名稱(例如Example)和類型;本例中選擇free-style項目。
- SCM類型選擇Git(如果沒有這個選項,說明需要安裝上面提到的Git插件)。URL是git://localhost/example.git
- 至于構建觸發器,選中Poll SCM并輸入*****。這里的格式與crontab類似,這里相當于每分鐘檢查一次代碼庫。
- 滾動到add a build step,選擇execute shell。輸入$WORKSPACE/build.sh(請注意:如果項目名中有空格,需要用引號把它括起來,例如"$WORKSPACE/build.sh")
- 最后,點擊Save,項目創建好了。
上面創建了一個項目,簽出master,如果有變化則執行build.sh。點擊build now鏈接會簽出代碼,運行腳本并報告執行成功與否。(如果失敗了,可以檢查構建日志了解詳細信息,在繼續后續任務前把問題解決掉。)
集成Gerrit
我們現在已經可以在master發生變更時通過Jenkins/Hudson自動構建了,但是我們還應該在提交代碼審查時進行構建。
下面要創建另一個任務:
NameExample-GerritTypeFree-styleSCMGitURLgit://localhost/example.gitAdvanced (below URL of repository) Refspec$GERRIT_REFSPECAdvanced (above repository browser) Choosing strategyGerrit-pluginBuild TriggersGerrit EventGerrit ProjectPath -**- Path -**
有新事件發生時,Gerrit會觸發項目。但是我們還差一步,要告訴Jenkins/Hudson監聽哪個Gerrit服務器。
在Manage Jenkins/Hudson標簽頁中,有一項Gerrit Trigger,添加如下信息:
HostnamelocalhostURLhttp://localhost:8080Port29418UsernamebuildbotKeyfile/path/to/.ssh/id_rsa.buildbotSSH Keyfile password...
首先,點擊下方的“Save”按鈕。然后,點擊Test Connection按鈕檢查配置是否成功。如果成功,點擊“Restart”,或者重啟Jenkins/Hudson。
如果一切順利,應該可以回到主頁,左邊有一個Query and Trigger Gerrit Patches鏈接。點擊該鏈接,輸入is:open查看打開的變更,is:merged查看合并的變更。完成后,選中復選框,點擊Trigger Selected觸發該變更的構建。
如果你使用的是Hudson 2.0.0和Gerrit 2.2.0,在Hudson控制臺里會看到java.lang.reflect.InvocationTargetException錯誤。這是新Gerrit Trigger的一個問題;升級到Hudson 2.0.1就好了。
整合到一起
你現在可以在代碼庫的本地克隆中創建一個變更,推送到Gerrit中,讓Jenkins/Hudson自動為你執行構建。
要測試失敗的情況,編輯build.sh腳本,在文件末尾輸入exit 1。提交文件并將其推送到Gerrit,你會看到Jenkins/Hudson的狀態會變為失敗,因為現在的構建腳本返回了一個非零碼。修復提交,將剛才那行改為exit 0,推送之后你會看到構建結果變回成功。
最后,如果要使用xcodebuild構建iOS應用程序,通過ocunit2junit.rb腳本來串聯整個構建,它會把SenTest的斷言轉為Jenkins/Hudson可以理解的形式,這樣失敗的斷言就能打印在輸出的構建日志里了。
關于作者
Alex Blewitt博士是Bandlem Limited的創始人,雖然就職于倫敦的一家投資銀行,但仍然花時間了解最新的OSGi和Eclipse動態。盡管他曾是EclipseZone的編輯,在2007年被提名為Eclipse Ambassador,可是他的日常工作與Eclipse和Java都沒有任何關系。在他那所剩無幾的空余時間里,他會和家人在一起,如果天氣好他們會出去玩。你可以在推ter上關注@alblue,或者去他的博客alblue.bandlem.com。
查看英文原文: Git, Gerrit Review and Jenkins or Hudson CI Servers
來自: InfoQ