git 和gerrit
本文介绍了如何为基于团队的代码审阅系统设置Git,Gerrit和Jenkins / Hudson,这在我对iOS开发人员的 Gerrit和对Java开发 人员的 Gerrit演示(以及我有朝一日的讨论)中尤其如此。使用情况)。 所使用的示例假定您使用的是OS X或Linux,但如果需要,也可以在Windows上运行。
设置Git
Git已经在大多数包装系统上可用,但是Git主页上有可用的安装程序。 对于Windows,最好的选择是MsysGit 。 请注意,如果您已经安装了Apple开发人员工具(适用于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/下载,并作为WAR文件存在。 提供了很好的文档 (当前是最新的2.2.1 ,但这也适用于2.1.7)以及安装指南 。
Gerrit在运行时需要一个数据库(用于存储评论信息)。 当前支持的数据库包括H2,PostgreSQL和MySQL。 默认情况下,它将使用不需要额外设置的H2。
请注意,Gerrit 2.2.x正在将项目配置,权限和其他元数据移动到Git存储中,以便可以通过Git访问它们并进行版本控制。 对于2.2.x流中的其他类型的元数据(包括复习说明),此转换将继续进行。 请参阅发行说明以获取更多信息。
要初始化Gerrit,请运行java -jar gerrit.war init -d /path/to/location
以在给定路径中安装运行时。
如果从交互式终端运行,则会询问您各种问题,例如:
- Git存储库的位置[git]
- 导入现有存储库[是/否]
- 数据库服务器类型[H2 /?]
- 身份验证方法[OPENID /?]
- SMTP服务器主机名[localhost]
- SMTP服务器端口[(默认)]
- SMTP加密[NONE /?]
- SMTP用户名
- 以[您]身份运行
- 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存储库所在的位置 。 这默认为安装位置下方的“ git”目录,但可以位于其他位置(例如
/var/gits
或类似位置)。 值得首先用示例填充此目录,因为启动Gerrit时,它将扫描此目录以查找要添加的新项目。 - 地址监听对于具有多个地址(例如IPv4和IPv6)的主机很有用,因为它允许您限制其使用的地址。 *表示本地主机上的任何地址。
- 侦听端口是端口号。 29418是默认的Gerrit SSH守护程序,而8080是默认的Gerrit Web守护程序。 但是,如果您已经有使用端口8080的应用程序,则可能要更改第二个端口。
- 身份验证方法是您登录Gerrit的方式。 如果您想连接到现有的身份验证提供程序(例如Google帐户),则OpenID可以使用,但是出于测试目的(以及上面的演示中使用的身份验证程序),您可以使用
development_become_any_account
。 键入一个? 将显示可用方法的列表。
Gerrit完成运行后,应在显示主页面的浏览器中打开您的浏览器。 第一个登录的用户自动成为管理员。 所有后续登录的用户均为非特权用户。 如果您选择development_become_any_account
则在页面顶部会出现一个Become
链接,该链接将带您到注册/登录页面。
注册用户
为了对Gerrit进行任何操作,您需要拥有一个注册帐户和一个SSH密钥对。 从命令行运行ssh-keygen -t rsa -b 2048
允许您生成密钥对,并将其放入.ssh
目录中。 在GitHub帮助页面上有更多信息,尽管如果您想了解更多信息,可以看到我六年前写的关于SSH密钥的博客文章 。
默认名称为id_rsa
(这是私钥)和id_rsa.pub
(这是公钥)。 仅公开您的公钥,绝不公开您的私钥。
使用密钥,您可以在Gerrit中注册一个新帐户。 单击右上角的“成为”链接,然后单击“新帐户”按钮,并输入您的姓名和电子邮件,如Git所知(我们在上面使用git config
配置的名称和电子邮件)。 这些必须完全匹配(包括大小写)。 您可以保存更改,然后选择一个唯一的用户名(在将名称(例如demo
)填满后,单击“选择用户名”。
电子邮件头痛 Gerrit会尝试向您发送电子邮件以验证您的电子邮件地址,即使是在
development_become_any_account
模式下也是如此。 没有它,它将不喜欢您的电子邮件地址,没有它,您将无法推送代码。我们可以在短期内破解它,因此如果您现在还不能注册您的邮件地址,请不要担心。
在“添加SSH公共密钥”文本框中,完全按照.pub文件中的密钥添加密钥。 如果您使用的是OS X,则就像pbcopy < ~/.ssh/id_rsa.pub
一样简单。 记住点击“添加”将其保存。
单击“继续”时,您应该看到已登录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)? 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
假设它正在发送正确的密钥,请检查该密钥是否与Gerrit中的用户相关联。 这是在设置-ssh公共密钥菜单选项。 如果不存在,请单击“添加密钥”,然后像以前一样粘贴公共密钥。
如果收到此消息,并且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.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
的项目,并在上面指定的gits位置初始化一个空的存储库。 如果您有一个现有的存储库,则不允许您使用相同的名称来创建它-但是在创建之前将其重命名为临时名称,然后重新命名仍然可以使用。
在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
我们已经推送了一个分支,这里称为refs/for/master
,尽管实际的命名分支refs/changes/01/1/1
,您可以从change中看到,Gerrit本身。 您会注意到,当前分支与此处所示的哈希相同(在我的情况下为06bf85e
)。 如果我们要再次推送,而不是覆盖refs/for/master
,我们将触发创建新的分支refs/changes/02/2/1
。 尽管这是实现细节,但是前两位是变更号的后两位,第二位是变更号,第三位是补丁集号。 因此,更改123的补丁集17将引用不可变的参考refs/changes/23/122/17
。
如果我们想修改此更改,Gerrit将创建一个新的审阅参考,但效果不是很好。 如果发生这种情况,您将丢失两次更改之间的审阅请求注释。
为了解决这个问题,我们将更新commit-msg
以添加一个(特定于Gerrit的) Change-Id
。 这将允许将同一更改的所有后续提交与同一补丁集关联。 Gerrit带有一个实现。 我们可以将其复制出来。
$ cd .git/hooks $ scp -P 29418 demo@localhost:hooks/commit-msg . $ cd ../..
现在,当我们提交修改时,我们将自动生成一个Change-Id
字段。 我们可以使用它在我们的变更集上生成多个补丁。 我们可以通过执行修订的提交并将其显示在Gerrit更改中,来使当前提交使用相同的更改集。
$ 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
如果现在查看变更1的 Gerrit,您会看到与变更相关联的第二个补丁集。 远程分支refs/changes/01/1/2
现在包含新的提交消息(尽管所有文件都相同)。
通常,您不需要在原处添加Change-Id
,因为提交消息挂钩将自动为您完成此操作。
审核并提交
为了替代构建过程,我们将创建一个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来更改此设置。 我们可以通过允许个人投票+2来改变这一点; 我们也可以通过更新规则以允许+1来执行相同操作。
更改它的最简单方法是进入项目管理员,然后进入所有项目访问选项卡 (在Gerrit 2.2.1和2.1.7.2中,该选项卡从--All Projects重命名为All-Projects)。 单击“代码审查”规则旁边的复选框,然后将“允许范围”更改为+2: Looks good to me, approved
。 Gerrit还需要+1验证,我们也会为Jenkins / Hudson进行设置。 使用以下命令添加新规则:
-
类别
- 已验证 团队名字
- 非互动用户 参考名称
- 参考/ * 允许范围
- -1:未通过+1:已验证
单击“添加访问权限”以设置此权限。
我们需要为Jenkins / Hudson创建一个新用户并将其放入该组中,因此我们将转到登录页面并注册一个新帐户buildbot
。 我们将需要一个新的ssh密钥(称为id_rsa.buildbot
),然后像以前一样粘贴到公共密钥中。
最后,以管理员ID(您之前创建的ID)重新登录,然后转到“ 管理-组”标签。 在非交互用户列表中,添加buildbot
和(临时) demo
用户。
设置了已验证的规则之后,我们现在应该可以返回到change ,以将其标记为已验证。
但是,我们仍然缺少submit
按钮,这是我们需要将其合并到分支上的功能。 提交权限是验证和审阅权限的独立权限,因此我们必须回到所有项目访问选项卡,并像以前一样添加访问权限:
-
类别
- 提交 团队名字
- 注册用户 参考名称
- 参考/ * 允许范围
- +1:提交
单击“添加访问权限”以设置此权限。
现在,当我们返回更改时 ,我们将在评论/评论页面上看到评论的“ Submit
按钮(如果已被评论)以及“ Publish and Submit
”。 (请注意,如果代码审查未达到适当的提交级别,则在执行“ Publish and Submit
时可能会看到服务器错误。)
最后,发布并提交所有未完成的评论(以确保构建脚本正确到位),并进行git pull
以确保您的存储库是最新的。 它们应显示在合并选项卡中。
有关管理的注意事项:通常,您不会普遍授予“所有项目访问”权限,而是逐个项目地授予它。 这里显示了“所有项目”方法,以使其易于启动和运行,但是也可以在每个项目的基础上进行配置。
设置詹金斯/哈德森
难题的最后一步是设置詹金斯 (或优选Hudson )。 假定它们默认在端口8080
上运行,就像Gerrit一样,您需要让它们在其他端口号上运行。 更改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
选项卡,然后安装:
- 格里特触发器
Install
按钮在底部的右下方; 稍后会重新启动。 Gerrit触发器将拉入Git插件。 请注意,有一个Gerrit插件,不是必需的。 (如果您使用的是Hudson,并且更新站点未显示任何内容,请转到“高级”,然后单击底部的“立即检查”。)
现在,我们准备开始配置Jenkins / Hudson来完成工作。 首先,我们将设置一个CI作业,以查找对(合并的)Git存储库的更改。 请执行以下步骤:
- 点击左上方的“新工作”。 它将询问名称(例如
Example
)和类型; 在这种情况下,是free-style
项目。 - 选择Git作为SCM类型(如果未显示,则意味着您需要安装上面的Git插件)。 该网址将为
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集成
现在,无论何时发生变化,我们都有Jenkins / Hudson建筑master
。 但是,难题的最后一步是每当提交新的评论时就将其构建。
使用以下命令创建另一个作业:
-
名称
- 样例 类型
- 自由式 单片机
- 吉特 网址
- git://本地主机/example.git 高级(存储库URL下方)Refspec
- $ GERRIT_REFSPEC 高级(存储库浏览器之上)选择策略
- Gerrit插件 构建触发器
- Gerrit事件 Gerrit项目
- 路径-**-路径-**
每当发生新事件时,这将允许Gerrit触发项目。 但是,我们还有最后一个难题要解决–我们必须告诉Jenkins / Hudson收听哪个Gerrit服务器。
在“ Manage Jenkins/Hudson
选项卡中,将添加条目Gerrit Trigger
。 单击此并添加以下信息:
-
主机名
- 本地主机 网址
- http://本地主机:8080 港口
- 29418 用户名
- 编译器 密钥文件
- /path/to/.ssh/id_rsa.buildbot SSH密钥文件密码
- ...
首先,通过单击下面的“保存”按钮进行保存。 然后,通过单击“ Test Connection
按钮检查其是否有效。 如果成功,请单击gerrit触发器底部的“重新启动”以获取更改,或仅重新启动Jenkins / Hudson。
如果一切顺利,那么您应该可以返回主页,该页面的左侧有一个“ Query and Trigger Gerrit Patches
链接。 单击此按钮,然后输入is:open
可以查看所有未完成的更改,而is:merged
可以查看任何已合并的更改。 完成此操作后,选中该框并点击Trigger Selected
以针对该特定更改Trigger Selected
构建。
如果使用的是Hudson 2.0.0和Gerrit 2.2.0,则可能会发现Hudson控制台中存在java.lang.reflect.InvocationTargetException
错误。 这是新升级的Gerrit触发器的问题,但是升级到Hudson 2.0.1可以解决该问题。
放在一起
现在,您应该能够在存储库的本地克隆中进行更改,将其推送到Gerrit,并让Jenkins / Hudson为您自动构建它。
要测试失败的案例,请编辑build.sh
脚本,并将exit 1
放在最后。 提交并推送给Gerrit,您应该会看到Jenkins / Hudson将状态更改为“失败”,因为现在构建脚本正在返回非零代码。 修改commit以返回exit 0
,推送,您会发现该构建被标记为已验证。
最后,如果使用xcodebuild
构建iOS应用程序,则通过ocunit2junit.rb脚本传递构建内容 ,该脚本将SenTest断言转换为Jenkins / Hudson可以理解的形式,以便可以将失败的断言打印在输出构建日志上。
git 和gerrit