ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Gerrit代码评审工具实战指南:从部署到CI集成

Gerrit代码评审工具实战指南:从部署到CI集成 Gerrit这个名字做代码托管和持续集成的同学应该都不陌生。我在团队里负责搭建和维护代码评审基础设施刚上手那阵子被Change、Patch Set、refs/for这一套概念绕得晕头转向踩了不少坑。后来把它真正接入日常开发流程从服务端部署、权限管控到和CI打通基本把常见场景都趟了一遍才算摸清楚这个工具的设计逻辑。这篇使用小结不是手册式的完整文档重点讲清楚Gerrit到底怎么用、为什么这么用以及实际运维中那些文档里不会写、但特别影响使用体验的细节适合刚接触Gerrit、打算在公司内部搭建代码评审平台的工程师参考。1. 先搞清楚Gerrit到底在管什么1.1 一次push背后的评审流程Gerrit本质上是一个基于Git的代码评审工具由Google开源最初是为了管理Android项目的庞大代码量而设计。它最大的特点是把“代码评审”强制塞进了Git工作流里而不是像很多团队那样靠开会、口头确认或者review完再合并。正常不用Gerrit的Git工作流是这样的你clone代码到本地改完commit直接git push origin master代码就进主干了之后想评审只能靠事后Pull Request。Gerrit把动作拆成了两步你push的地址不是refs/heads/master而是refs/for/master。Gerrit收到这个push之后不会直接把提交合并到目标分支而是为这次提交生成一条“待评审记录”也就是Change之后团队在这个Change下面做评论、打标签全部通过后才执行Submit操作代码才真正进入目标分支。这个设计很像我们工作中“先交草稿、审核通过再定稿”的流程refs/for就是“草稿投递入口”refs/heads才是“正式发布渠道”。理解了这一点后面很多操作就顺了。1.2 核心概念Change、Patch Set、Label刚开始用Gerrit的人经常被页面上几个英文词搞迷糊其实搞清楚三个概念就够用了。Change是一次完整的代码评审单元。一个Change对应一个Commit通过Change ID在Gerrit和Git提交之间建立关联。Patch Set是这同一个Change的不同版本。评审人给了修改意见你用git commit --amend改完再push一次Change号不会变但Patch Set会从1变成2、3评审记录会完整保留每一轮的改动这一点比在GitHub上反复开新PR要清爽得多。Label是评审维度。默认最重要的是两个Code-Review代码评审和Verified验证结果。Code-Review分-2、-1、1、2四个档位一般2代表“可以合入”-2代表“必须修改”。Verified是给CI机器人用的构建通过打1失败打-1如果开启了“一票否决”策略任何负分都能阻止代码合并。这套机制把“人的评审”和“机器的验证”分开不会出现“代码没跑通但人工review通过就合并”的情况。1.3 和GitHub/GitLab评审模式的根本差异很多团队是从GitLab或者GitHub迁移过来的习惯了一提评审就想到Merge Request。这里要特别说明Gerrit和它们的评审模型差异非常大。GitHub和GitLab的流程是“先推分支再开PR/MR”。开发者把代码push到自己的feature分支然后在页面上发起一个请求维护者审查通过后手动点合并这个过程中目标分支的代码没有变化属于“事后评审”。Gerrit则是“先评审再合入”代码根本进不了正式分支必须评审通过才合入属于“事前评审”。还有一个体验上的巨大差异GitHub的PR是“多次提交共用一个PR”新push会追加到同一个PR里Gerrit则是“一次提交对应一条Change”如果同一个分支连续push了多个commitGerrit会生成多条Change需要每个Change分别评审、分别Submit。刚切换过来的团队经常在这个地方栽跟头。对比维度GitHub/GitLabGerrit评审时机代码已推到远程分支事后开MR代码不进正式分支评审通过才合入提交与变更关系一个MR可包含多个Commit一条Change对应一个Commit评审维度评论、Approve多标签Code-Review/Verified等提交策略维护者手动合并权限内直接Submit策略可配置适合场景开源、中小团队、流程灵活严格质量管控、CI强集成、中大型团队如果你的团队追求“发布前必须有评审门槛且机器验证结果必须体现在合入依据里”Gerrit是很合适的选择如果团队更看重协作效率和流程灵活GitLab方案会更顺手。2. 服务端部署与项目初始化2.1 环境准备与版本选型Gerrit的服务端部署其实不复杂它是一个Java应用单机跑就行我们生产环境就是一台4C8G的虚拟机跑了三年很稳定。部署前需要准备Java环境Gerrit 3.x版本需要Java 11或Java 17建议直接用你的发行版默认OpenJDK避免兼容性问题。部署方式有两种一种是下载官方发布的WAR包用内嵌的Jetty容器直接跑另一种是用Docker镜像我生产用的是前一种因为在内网环境里离线安装更可控。到Gerrit官网下载对应版本的WAR包后放到一个固定目录比如/opt/gerrit/然后执行java -jar gerrit.war init -d /data/gerrit-site-d指定的是Gerrit的站点目录所有配置、数据库文件、日志都会放在里面。整个初始化过程是交互式的会一步步问你各种配置项如果是离线环境建议先用一台能联网的机器初始化好再迁移或者直接用--batch模式配合配置文件跳过交互。2.2 init过程中的关键决策点初始化过程中有几个选项会影响后续迁移和维护这里逐一说下我的选择思路。数据库。默认是H2嵌入式数据库适合测试环境省心但生产不建议数据量大了之后锁库、备份都是麻烦。生产环境建议选MySQL或者PostgreSQL初始化的时候会要求填数据库连接信息提前建好库和账号就行。我们用的是MySQL 8Gerrit跑得挺稳。认证方式。默认的OpenID在公网环境还行内网基本没法用。企业内部通常选两种LDAP认证或者HTTP认证。LDAP适合公司已经有统一账号体系的场景初始化时填LDAP服务器地址和搜索路径如果暂时没有LDAP可以用HTTP认证由nginx反代来做Basic AuthGerrit只认HTTP头里的用户信息。我们初期就是nginx做认证后来才切到统一认证切换成本不大。监听端口。默认HTTP监听8080SSH监听29418。SSH端口很重要因为后面所有Git操作都要走ssh://userhost:29418/project.git这种协议。如果服务器上已经有服务占用端口记得在init时改掉。反向代理。init到最后会问是否配置反向代理选是之后会要求填代理的主机名和端口。这个配置影响的是Gerrit生成的URL如果填错页面上的跳转链接会带错域名建议先规划好对外访问域名再初始化。2.3 反向代理与SSL配置Gerrit本身不推荐直接暴露8080端口对外规范做法是前面挂一个nginx做HTTPS终止和端口转发。我们在nginx上配置的server块大概是这样server { listen 443 ssl; server_name gerrit.example.com; ssl_certificate /etc/nginx/certs/gerrit.crt; ssl_certificate_key /etc/nginx/certs/gerrit.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; } }需要注意Gerrit会校验请求的Host头如果canonicalWebUrl和nginx的server_name不一致页面访问会报“Host/port not authorized”。我遇到过一次用户在办公室用IP访问正常但通过域名访问就报异常查下来就是etc/gerrit.config里的gerrit.canonicalWebUrl没改成域名。改配置文件后执行java -jar gerrit.war restart -d /data/gerrit-site重启就好。2.4 第一个项目与权限组初始化服务端起来之后用浏览器打开http://gerrit.example.com首次访问会让你创建管理员账号然后用管理员登录。接着要创建一个测试项目推荐在页面上直接操作或者用SSH命令ssh -p 29418 admingerrit.example.com gerrit create-project --name test-project --branch master创建完项目后还不能直接用因为Gerrit的权限体系是树状的所有项目都继承自All-Projects这个根项目默认情况下只有管理员有完整权限普通注册用户只能上传代码到refs/for/*不能直接push到refs/heads/*。这个默认逻辑其实已经帮你把“评审前置”的规则立住了后面要做的更多是细分授权。建议在初始化阶段就规划好组Developers普通开发、Reviewers评审人、CI-Bot机器人账号、Project-Owners项目负责人。Gerrit的组是全局概念可以在不同项目上对同一组授予不同权限先把组建好后面配权限会省很多事。3. 日常开发工作流从clone到submit3.1 SSH密钥配置与git clone服务端准备好了开发同学这边要做的第一件事是配置SSH公钥。在Gerrit页面右上角头像进入Settings找到SSH Keys栏把本地生成的id_rsa.pub粘贴进去。之后所有Git操作都用SSH协议不再需要反复输密码。本地操作其实和普通Git没区别clone命令格式是git clone ssh://用户名gerrit.example.com:29418/test-project.git cd test-project注意这里的用户名要和Gerrit里的用户名一致不是系统登录名。如果之前配过多个SSH key可以在~/.ssh/config里给Gerrit单独配一条记录避免key冲突问题。3.2 提交与推送refs/for才是评审入口clone下来之后Gerrit不会自动帮你装commit-msg hook而Commit-Id又必须通过hook生成。这一步非常关键很多新手第一次push被拒都是栽在这里。安装hook有两种方式一种是用scpscp -p -P 29418 用户名gerrit.example.com:hooks/commit-msg .git/hooks/另一种是直接curl服务端地址curl -Lo .git/hooks/commit-msg http://gerrit.example.com/tools/hooks/commit-msg chmod x .git/hooks/commit-msg装好之后本地正常提交代码。需要注意的是如果你想修改上一次的提交不要新起一个commit而是用git commit --amend这样提交记录里会保留同一个Change-Id后续push就会更新到同一个Change上。推送命令和我们平时直接推分支不一样git push origin HEAD:refs/for/master这个命令把本地当前分支的最新提交推送到Gerrit的评审队列目标主线是master。如果希望同一批改动归到同一个Topic下方便一起查找、一起Submit可以带上topic参数git push origin HEAD:refs/for/master%topicfix-login-timeoutpush成功之后页面上会出现一条新的Change记录状态是Needs Review这时候就可以把代码片段发给评审人去看了。3.3 根据评审意见修改并更新Patch Set评审人会在Change页面提交行上做inline评论这些都是基于具体代码行的标注比在聊天工具里贴代码片段高效多了。收到“请修改”的反馈后本地操作一般是这样# 切换到自己工作的分支 git checkout feature-login # 修改代码然后针对同一个提交做amend git add . git commit --amend --no-edit改完继续推同一个refs/for地址git push origin HEAD:refs/for/masterGerrit通过Change-Id识别出这是同一个Change会自动生成一个新的Patch Set。评审页面上可以选择查看Patch Set 1和Patch Set 2的diff能够清楚看到每一轮修改的变化。这点体验比GitHub反复开PR好很多评论和修改历史都在一条Change里串着。有一个操作禁忌要提一下如果改动提交时不用--amend而是重新git commit生成新提交那么push的时候Gerrit会为你创建一条新的Change团队就会看到两条内容差不多的Record评审意见也分散在两处非常容易漏掉。所以确认过“这是同一轮改动”的基础上务必amend。3.4 Submit合并与提交策略选择Change评审通过评审人给了2CI验证也打了1接下来就可以Submit了。但Submit的方式不是只有一种这里往往被团队忽略其实影响挺大的。Gerrit的Submit策略比较常用的有这几种提交策略行为说明适用场景Fast Forward Only只允许fast-forward提交目标分支不能做额外合并严格控制历史线性适合内部主干Merge If Necessary目标分支有更新时自动产生merge commit日常默认选择兼顾灵活Merge Always每次都生成merge commit即使可以fast-forward要求提交记录中有明确合并节点Cherry Pick把Change的补丁摘到目标分支头部原提交不保留希望目标分支历史干净、无merge节点Rebase If Necessary必要时自动rebase不给历史留merge commit干净历史比Cherry Pick更简单我们团队用的是默认的Merge If Necessary省心但提交历史里会偶尔出现merge记录。如果你们追求严格的线性历史建议改成Rebase If Necessary开发同学在push之前先手动git pull --rebase同步远端效果最好。还有一个和Submit相关的权限细节默认情况下拥有Submit权限的人才能点Submit。如果希望“评审人2后自动合入”需要在项目权限里额外配置规则一般不建议小团队这么干容易出线“没人看就自动合并”的情况。4. 权限模型与Access Control配置4.1 权限的核心refs/* 上的能力位Gerrit的权限控制粒度非常细它围绕Git引用refs展开常见的能力位包括Read、Push、Create Reference、Label Code-Review、Label Verified、Submit等。每个能力位都可以绑定到某个组并限定在特定的ref路径下。比如refs/heads/*代表所有正式分支refs/for/*代表评审队列分支。我们团队在All-Projects上的权限配置大致是这样的逻辑所有注册用户可以读取代码可以推送到refs/for/*但不能直接推送到refs/heads/*只有评审人组或者项目维护者组才能给refs/heads/*配置Submit和Push权限。4.2 参考实践普通开发者、评审人、CI机器人实际配置的时候不建议直接在Web界面上反复点更推荐直接改项目配置仓库。每个项目都可以推送一份refs/meta/config分支里面维护真实的权限配置。比如我的一个内部项目的project.config长这样[access refs/for/refs/*] push group Developers [access refs/heads/*] read group Developers read group CI-Bot label-Code-Review -1..1 group Developers label-Code-Review -2..2 group Reviewers label-Verified -1..1 group CI-Bot submit group Reviewers push group Project-Owners [submit] action rebase if necessary这段配置说明了几件事普通开发Developers可以把代码推到评审入口能在代码评审上打±1评审人Reviewers可以把Code-Review拉到±2并且拥有Submit权限CI机器人CI-Bot只负责打Verified标签。这样职责分离即使某个开发者的账号被攻破也无法绕过评审直接提交代码。这里有个容易踩的坑很多人会把push group Developers直接配到refs/heads/*下面结果开发者用git push origin HEAD:master就把代码绕过Gerrit评审推到正式分支了。要避免这个必须确保正式分支的Push权限只授权给极少数信任的人并且配合前端的“禁用直接push到refs/heads”约定一起做。4.3 常见权限误配置导致的问题我遇到过三次比较典型的权限问题都值得提一下。第一次是CI机器人的Verified权限没配结果构建脚本调用Gerrit SSH命令打Verified标签时报“not permitted”错误CI无法把失败结果反馈到Change页面等于评审流程里少了机器这一环。后来才发现label-Verified这个能力位没有配给CI组。这个权限位和label-Code-Review是分开的直接在refs/heads/*下补上即可。第二次是组继承关系理解错了。Gerrit的组可以嵌套子组默认继承父组权限。我当时把一个新成员加到了父组里以为他不会继承某个敏感权限实际他继承了并且有能力Submit别人的Change。排查半天最后把子组独立权限理顺才解决。第三次是权限改动没有生效。Gerrit的权限变更通过push到refs/meta/config分支之后需要等几秒到几分钟的缓存刷新急事可以用SSH命令强制刷新ssh -p 29418 admingerrit.example.com gerrit flush-caches --cache project不然就多刷新几遍浏览器别一直怀疑自己写错了配置。5. CI集成让机器人当第一个评审人5.1 Jenkins Gerrit Trigger插件配置要点Gerrit和Jenkins的集成是目前最主流的CI搭配。核心思路是当Gerrit收到新Patch Set时触发Jenkins执行构建构建结束后Jenkins把结果回写到Gerrit的Verified标签。这一步做到了代码质量门槛就不只是靠人眼看了。配置之前先安装Jenkins的Gerrit Trigger插件。插件装好后进入Manage Jenkins里的Gerrit Trigger配置页面添加一台Gerrit服务器填上主机名、SSH端口默认29418、用户名并且把jenkins用户对应的私钥贴上去同时保证公钥已经添加到Gerrit的账号里。关键点是这个jenkins账号要在Gerrit中属于CI-Bot组并且有label-Verified和read权限。我前面提过权限没配好后面全链路都会卡在“无法打标签”这一步。5.2 Verified标签策略一票否决的自动化管控在Jenkins Job里配置Gerrit Trigger的触发条件我们常用的组合是“Patchset Created”和“Change Merged”事件。前者是每次有新的Patch Set提交就构建后者是Change合入后再构建一次防止合并后分支又出现编译问题。构建完成后Jenkins Gerrit Trigger插件可以自动把结果回写到Gerrit。如果不依赖插件也可以自己在构建脚本里调SSH命令ssh -p 29418 jenkinsgerrit.example.com gerrit review --project test-project --verified 1 --message Build Success 12345,3如果构建失败ssh -p 29418 jenkinsgerrit.example.com gerrit review --project test-project --verified -1 --message Build Failed 12345,3注意末尾的12345,3格式是“Change号,Patch Set号”。只要CI机器人打了-1并且项目开启了“Verified -1阻止提交”的策略那么这个Change无论如何点Submit都会被拦下来。这个策略在project.config里的[capability]或者Web界面的“Submit Requirements”里配置比单纯依靠reviewer人眼判断更可靠。依赖这里我多说一句如果你用的是GitLab CI那边对分支后置校验很自然但Gerrit的前置模型就是把CI结果当作能否合入的硬指标这是它相比GitHub/GitLab在质量管控上的核心优势。5.3 结合Topic管理跨仓联调当一个功能涉及多个项目时比如前端仓库和后端仓库各有一个ChangeGerrit的Topic非常好用。你只需要在push的时候给这几个Change设置同一个Topicgit push origin HEAD:refs/for/master%topicuser-center-refactor然后在Gerrit的Query条件里用topic:user-center-refactor搜索就能把相关Change全部拉出来。在Web界面上这两个Change还会形成一个关联视图方便评审人一起看发布的时候也能统一跟踪。我们团队后期做多仓联调时Topic用得非常多标准动作就是先约定一个功能代号所有涉及仓库push都带上同一个topic评审状态一目了然。而且Jenkins Trigger还支持按Topic触发特定Topic的Change合入后可以联动触发下游依赖仓库的构建。6. 必须收藏的命令与SSH接口6.1 用SSH命令远程管理GerritGerrit提供了一套完整的SSH管理接口很多页面操作都能在命令行里完成脚本化运维非常方便。常用命令整理如下命令作用gerrit version查看服务端版本gerrit create-project --name xxx创建项目gerrit ls-projects列出所有项目gerrit review --project xxx --verified 1 12345,3给Change打标签gerrit set-reviewers添加/删除评审人gerrit flush-caches刷新缓存gerrit gc垃圾回收配合--all使用gerrit show-connections查看当前SSH连接情况需要注意这些命令是通过ssh协议调用的不是Gerrit服务器本地的shell命令。例如ssh -p 29418 admingerrit.example.com gerrit ls-projects --format json建议把常用命令做成脚本放到运维的公共环境变量里避免每次手敲。6.2 下载任意Patch Set的小技巧实际开发中经常会遇到这样的场景线上出现一个bug怀疑是某个Change的某个Patch Set导致的但想要快速拉取那版代码。Gerrit的Git引用命名规则刚好支持直接fetch任意Patch Set。规则是Change号取后两位作为中间目录前面的部分是refs/changes/后两位/完整Change号/Patch Set号。假设Change号是12345要拿第3个Patch Set引用路径就是git fetch ssh://用户名gerrit.example.com:29418/test-project.git refs/changes/45/12345/3 git checkout FETCH_HEAD这个技巧在定位问题时特别有用不需要先去Change页面下载Patch文件也不用重新构建整个分支直接checkout到那一版代码就能调试。如果嫌记规则麻烦页面左上角也有Download按钮会直接给出fetch命令行。6.3 高频操作速查表日常开发中我最常敲的几条Gerrit相关命令和操作整理成一个速查表给大家场景命令/操作提交到评审队列git push origin HEAD:refs/for/master带Topic提交git push origin HEAD:refs/for/master%topicxxx修改后更新Patch Setgit commit --amend --no-edit后重新push安装commit-msg hookscp -p -P 29418 用户host:hooks/commit-msg .git/hooks/查看项目的所有Change页面顶栏 Search输入project:xxx给Change打Verified标签gerrit review --project xxx --verified 1 123,1一键放弃ChangeChange页面点击Abandon按钮已合并后回滚Change页面点击Revert按钮这些命令不要求全部背下来但建议保存到团队Wiki里新同学入职时照着操作能少走很多弯路。7. 常见问题与排查实录7.1 push被拒的典型场景用Gerrit大半年团队成员反馈最多的就是push被远端拒绝错误信息往往很抽象这里把常见的场景整理出来报错信息原因解决方案! [remote rejected] HEAD - refs/for/master (no new changes)当前提交和上一个Change内容一致Gerrit认为没有新Patch Set用git commit --amend改提交信息后重新push或者先确认是不是真的改动过了! [remote rejected] ... (change 123 closed)Change已经被Abandon或Submit不能再推重新创建分支再提交或复制原有改动开新Change! [remote rejected] ... (missing Change-Id)没有安装commit-msg hook重新安装hook后git commit --amend补Change-Id! [remote rejected] ... (not permitted: update)当前用户没有对应refs的push权限检查项目权限配置! [remote rejected] ... (commit message content check failed)提交信息不符合规范通常是subject为空或格式错误检查提交信息的首行subject不能为空最常见的还是前两条。“no new changes”这个报错经常发生在git commit --amend --no-edit但代码没有任何实际改动的情况下Gerrit会认为这次push是无意义的所以没生成新的Patch Set。这种情况只需要在amend之前保证至少有一个文件内容变动。7.2 Change-Id丢失的根治方法Change-Id弄丢是另一个高频问题尤其出现在第一次配置环境的时候。Change-Id是Gerrit判断同一个Change的唯一标识一般格式是Change-Id: Ixxxxxxxxxxxxxxxxxxxx放在commit message的末尾。根治办法是在clone之后立刻安装hook并且要特别注意如果已经commit过但还没有安装hook即使后来装了hook历史提交里也不会有Change-Id。这时候需要执行git commit --amend一次让hook补上Change-Idgit commit --amend --no-edit如果分支上有多个旧提交都缺Change-Id可以用git rebase交互模式逐个处理或者借用filter-branch批量补脚本。我建议团队在每个新仓库的README里写清楚这两个步骤能省掉大量支援时间。还有一个细节不管开发同学是在哪一台机器上clone仓库.git/hooks/commit-msg都是本地文件不会随项目提交被同步。换电脑、重新clone后都需要再执行一次安装hook的命令。7.3 如何在合并前撤销或放弃一个ChangeChange提交到了评审队列但发现方案完全不可行或者评审意见要求大改而你不打算继续了这时候有两种操作Abandon和Revert。Abandon是“放弃未合并的Change”适合Change还没Submit的场景。在Change页面右上角点击Abandon这条Change会从活跃列表消失状态变为Abandoned。如果后续想重新启用管理员可以把状态改回来。Revert是“对已合并的Change做反向回滚”适合Change已经Submit、合入正式分支之后发现需要撤回的场景。点击Revert后Gerrit会自动生成一个反向提交的新Change同样要走评审流程评审通过后合入从而达到回滚效果。这两个操作最大的区别是Abandon不会修改任何代码分支只是关闭这条评审记录Revert会真的生成一次新的代码改动并需要再次经过评审才能合入。7.4 性能与数据库偶发问题Gerrit单机部署一般问题不大但长期运行下来会遇到一些小毛病。最常见的是缓存膨胀和数据库锁等待表现为页面响应变慢、SSH连接卡住。处理手段其实很直接定期执行垃圾回收压缩Git对象库ssh -p 29418 admingerrit.example.com gerrit flush-caches --all ssh -p 29418 admingerrit.example.com gerrit gc --all还有个问题是H2嵌入式数据库在高并发下容易锁库。我们生产切到MySQL之后稳定很多如果项目数量大、评审频率高建议直接使用外置数据库。最后提醒一下备份策略。Gerrit的核心数据除了Git仓库本身还有/data/gerrit-site/etc里的配置文件、/data/gerrit-site/db里的数据库以及/data/gerrit-site/git下的裸仓库建议每晚打包迁移到备份机。恢复过程就是把目录拷回去再执行java -jar gerrit.war init --batch -d /data/gerrit-site重新引导一次实测恢复时间基本在10分钟以内。我个人在实际操作中还发现一个容易被忽视的细节Gerrit的日志文件默认没有按天切割时间长了一个httpd_log能膨胀到好几个GB建议在log4j.properties里配置好滚动策略不然某天磁盘突然满了排查起来非常被动。这个坑我在线上环境遇到过两次后来改成每天归档一次问题才彻底解决。
返回列表