Git、Gerrit与Jenkins/Hudson CI服务器

本文讲述了如何为基于团队的代码审查系统配置Git、Gerrit与Jenkins/Hudson,正如我在《Git, Gerrit and Jenkins for iOS development》《Gerrit Git Review with 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服务器,会出现以下三者之一:

  1. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    @    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
  2. 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)? yes
    Warning: 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: publickey
    debug1: Trying private key: /Users/demo/.ssh/id_dsa
    debug1: 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。

  3.   ****    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.git
    
    Connection to localhost closed.

    如果看到这个提示,说明Gerrit已经正常工作了。

修正电子邮件地址

如果之前你无法在Gerrit中注册电子邮件地址,那么可以手工进行注册。我们可以停止Gerrit,运行GSQL工具更新特定数据字段。

$ bin/gerrit.sh stop
$ java -jar bin/gerrit.war gsql
Welcome 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 ms
gerrit> 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> \q
Bye
$ 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.git
Cloning 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 push
No refs in common and none specified; doing nothing.
Perhaps you should specify a branch such as 'master'.
$ git push origin master
Counting 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 origin
Counting 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 push
Counting 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 updated
To 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/sh
echo 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代码库的变更(合并):

  1. 点击左上方的’New Job’。这会要求你输入一个名称(例如Example)和类型;本例中选择free-style项目。
  2. SCM类型选择Git(如果没有这个选项,说明需要安装上面提到的Git插件)。URL是git://localhost/example.git
  3. 至于构建触发器,选中Poll SCM并输入* * * * *。这里的格式与crontab类似,这里相当于每分钟检查一次代码库。
  4. 滚动到add a build step,选择execute shell。输入$WORKSPACE/build.sh(请注意:如果项目名中有空格,需要用引号把它括起来,例如”$WORKSPACE/build.sh”)
  5. 最后,点击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都没有任何关系。在他那所剩无几的空余时间里,他会和家人在一起,如果天气好他们会出 去玩。你可以在Twitter上关注@alblue,或者去他的博客alblue.bandlem.com

 

查看英文原文: Git, Gerrit Review and Jenkins or Hudson CI Servers

Advertisements
This entry was posted in Agile. Bookmark the permalink.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s