Gerrit 自己做代码审查已经够顺了,但每次想从某个 change 跳到仓库的整体目录结构、看看某一行代码是谁引入的、或者翻一下某次提交影响的全部文件,界面里总感觉少点什么。后来我把 Gitweb 接到 Gerrit 上,相当于给审查界面开了一个直接通往仓库浏览器的入口,体验一下就完整了。这篇文章就记录我实际配置 Gerrit 与 Gitweb 集成的全过程,包括配置项的选择、验证方法、踩过的坑,以及适合团队的接入姿势,适合那些已经能把 Gerrit 用起来、正准备提升代码浏览体验的团队参考。
1. 为什么需要给 Gerrit 接一个 Gitweb:审查场景下的真实痛点
1.1 两个工具的边界:Gerrit 管流程,Gitweb 管浏览
先理清这两个工具的定位。Gerrit 的核心对象是「change」和「patchset」,界面从设计之初就是围绕代码评审流程来的。你会关注某个提交改了哪些文件,会在 Diff 面板里逐行争论,会提交新的 patchset 然后再次 review。这些流程能力 Gitweb 完全没有,也不需要有。
Gitweb 不一样。它是一个纯仓库浏览器,看的是仓库本身:完整的目录树、里程碑、提交历史图、某条提交的完整 patch、某个文件每一行代码的 blame。它不管你的审核流程,也不管权限分级,就是老老实实地把 Git 仓库呈现给你。
所以两个工具不是替代关系,而是互补。Gerrit 负责「这个提交能不能合入」,Gitweb 负责「这个仓库里到底发生了什么」。把两者集成起来,最实在的收益就是:审查者看完 diff,点击一下就能跳到 Gitweb 里对应的文件、对应的提交,不需要复制 commit id 再去地址栏拼接,也不需要在两套系统之间反复切换上下文。
1.2 什么时机接入最合适
我见过不少团队刚搭好 Gerrit 就急着配 Gitweb,结果两边都还没稳定,最后出了问题根本分不清是 Gerrit 的问题还是 Gitweb 的问题。我的建议是等 Gerrit 已经度过最初的压力测试期,仓库也积累了一定数量之后再接入。原因很简单:排查链路越短,诊断越快。
接入前先在团队里对齐一个问题:你们是真的需要仓库浏览能力,还是仅仅想要一个能看 commit 列表的第三方页面?如果是后者,其实不接 Gitweb 也行,Gerrit 自带的提交历史足够用了。只有当你们频繁遇到「我要看这个文件的完整历史」「我要 blame 定位引入行」这类需求,Gitweb 才有刚需价值。
2. 独立把 Gitweb 跑通:整个集成的前提条件
2.1 安装 Gitweb 与 Apache 的基本配置
这个集成的前置条件不是 Gerrit 配置,而是 Gitweb 本身必须先能独立访问。我强烈建议先不要把 Gerrit 牵扯进来,单独把 Gitweb 部署好,浏览器打开能看到项目列表了,再谈下一步。这一步做干净了,后面所有排查都会轻松很多。
以常见的 Debian/Ubuntu 环境为例,安装命令是:
sudo apt install gitweb apache2装完之后,我习惯新建一个独立虚拟主机,把 Gitweb 挂成类似git.example.com的地址,而不是直接把 Gitweb 塞进 Gerrit 的域名下。单独域名的好处是后续要做访问控制、换证书、加反代,都不会和 Gerrit 的主入口打架。虚拟主机大概长这样:
<VirtualHost *:80> ServerName git.example.com Alias /gitweb /usr/share/gitweb <Directory /usr/share/gitweb> Options +ExecCGI +FollowSymLinks AddHandler cgi-script .cgi DirectoryIndex gitweb.cgi Require all granted </Directory> </VirtualHost>这里最关键的是Options +ExecCGI和AddHandler cgi-script .cgi,少了任何一个,Gitweb 的 CGI 脚本都不会被执行,访问得到的要么是源代码,要么直接 403。
2.2 确认 Gitweb 能查看到 Gerrit 的仓库根目录
光把页面跑起来还不够,Gitweb 得知道去哪里找仓库。它读取的配置是/etc/gitweb.conf,这个文件是 Perl 语法。你需要把$projectroot指向 Gerrit 实际存放仓库的目录。打个比方,如果 Gerrit 仓库放在/var/gerrit/git,配置就写成:
our $projectroot = "/var/gerrit/git"; our @git_base_url_list = ("http://git.example.com/gitweb"); $projects_list = $projectroot;这里有一个很隐蔽的坑:Gitweb 默认不会递归扫描深层子目录。如果你在 Gerrit 里创建了很多带嵌套路径的项目,比如apps/backend/core.git,Gitweb 很可能只把apps当作一个项目列出来,展开进去是空白。解决办法是让 Gitweb 使用$project_list文件,让 Gerrit 侧定期把完整项目列表写进这个文件,或者干脆调整仓库目录结构,保持一级扁平。
配置完 Gitweb 后,重载 Apache:
sudo systemctl reload apache2然后浏览器打开http://git.example.com/gitweb,如果能正常看到项目列表,而且点进某个项目的 summary、commit、blame 页面都没问题,这一步就算过了。
2.3 Nginx 场景下的注意事项
如果你用的是 Nginx,思路一样,但 CGI 处理方式不同。Nginx 本身不直接执行 CGI,需要借助fcgiwrap或spawn-fcgi来配合。我遇到过最常见的问题是:Nginx 的fastcgi_pass指向了 unix socket,但fcgiwrap服务没有启动,结果整个/gitweb路径刷出 502。所以用 Nginx 的话,先把fcgiwrap跑起来,再用curl -I验证一次再继续。
3. gerrit.config 里的 [gitweb] 配置段:每个选项背后的取舍
3.1 默认 gitweb 类型用到的字段
Gitweb 能独立访问之后,才轮到 Gerrit 侧配置。配置文件是$GERRIT_SITE/etc/gerrit.config,一般路径像/var/gerrit/etc/gerrit.config。打开后在合适位置加入[gitweb]配置段。
一个典型的外部 Gitweb 接入配置如下:
[gitweb] type = gitweb url = http://git.example.com/gitweb linkname = Gitweb blame = true highlight = true逐项解释一下,因为这些选项直接决定链接长什么样,也最容易配置错。
type指定浏览器类型,可选gitweb、cgit、custom。默认是gitweb,只要你的后端是标准 Gitweb,就用它;如果你架了 cgit,必须改成cgit,否则 Gerrit 会按 Gitweb 的链接规则生成 URL,点进去必然 404。
url是 Gitweb 对外的基础 URL。Gerrit 把这串字符串作为前缀拼接链接,所以这里不能写错。如果你只是把 Gerrit 本身当 Web 服务器,想用内置代理托管 Gitweb,那url可以写成相对路径/gitweb。如果用了外部 Apache,就写完整地址http://git.example.com/gitweb。
linkname是 Gerrit 界面上按钮显示的文字。很多人不设,默认会显示成 Gitweb。我习惯显式写一遍,改成Gitweb或者浏览仓库,方便团队里的人一眼看懂。
blame打开后,Gerrit 的 Diff 界面和文件信息里会多出跳转到 Gitweb blame 页的链接。这个功能对代码走查非常实用,推荐打开。highlight控制是否在 Gitweb 页面里启用语法高亮,但注意它依赖 Gitweb 自身的$highlight配置,那边没开,这边开了也不会生效。
3.2 cgit 与 custom 类型何时更划算
刚才说的是标准 Gitweb。如果你是想接入 cgit,配置差异不大,主要是改type和url:
[gitweb] type = cgit url = http://git.example.com/cgit linkname = Cgit blame = true highlight = truecgit 的优势是快,毕竟是 C 语言写的前端,仓库数量大、提交历史长的时候体验差距很明显。但它也有自己的 URL 结构,通常是路径参数,而不是 Gitweb 那种?p=project.git的 query string 形式。所以切到 cgit 时,Gerrit 会按 cgit 的规则拼接,不需要你手工改模板。
还有一种场景需要用到custom类型:你的 Gitweb 部署方式比较特殊,比如目录名带.git后缀、或者需要追加额外参数。这时候可以用自定义 URL 模板,Gerrit 支持在 URL 里使用${project}这样的占位符,实际请求时替换成真实项目名。例如:
[gitweb] type = custom url = http://git.example.com/gitweb?p=${project}.git linkname = Gitweb blame = true highlight = true这个灵活度很高,但也意味着 Gerrit 不会替你检查拼接出来的地址到底能不能访问,所以用custom时一定要手工验证每个链接。
3.3 reload 配置的具体操作
gerrit.config的改动不会热加载,需要重载 Gerrit 才会重新读取配置。最可靠的方式是通过 Gerrit 自带的 SSH 管理命令:
ssh -p 29418 <你的用户名>@<gerrit-host> gerrit reload执行完会提示 reload 完成。如果这台机器上没法用 SSH 登录,也可以重启 Gerrit 服务,但生产环境会有短暂的请求中断,所以优选reload。另外,很多时候配置改了不生效,不是命令的问题,而是配置文件格式错误,比如[gitweb]段之前不小心多了一个空格,或者url =后面多了一个不可见字符。先用git config --file gerrit.config --list检查一下键值是否正确,再做 reload。
4. 四种界面入口逐一验证:从项目页到 diff blame
4.1 项目页的 Gitweb 入口
配置完并重载之后,先不要急着宣布完成,因为 Gerrit 对集成是否生效的描述比较克制:配置不合法、项目不可见、URL 拼接不了的时候,它通常只是悄悄隐藏入口,而不是报错。所以我习惯按下面几个入口逐个验证。
先登录 Gerrit,打开一个具体项目,比如demo-project。项目页右侧一般会出现一个标着Gitweb的链接。点击后浏览器应该跳到类似http://git.example.com/gitweb/?p=demo-project.git这样的地址。如果 404 或者 403,大概率是 Gitweb 侧的问题,回头看第 2 节的独立部署;如果链接压根没出现,再看 Gerrit 配置。
4.2 change 页与 commit 页入口
然后随便打开一个 change。在 change 页面的顶部信息区或者补丁集描述附近,通常会出现带 Gitweb 文字的跳转链接。这个链接会直接指向 Gitweb 里对应 commit 的页面,方便你从评审页跳过去看这次提交的完整上下文。
这里要特别留意:Gerrit 的前端是典型单页应用,改完配置之后浏览器可能还缓存着旧的页面状态,验证时先强制刷新一下。我以前吃过这个亏,新配置明明已经生效,页面上就是看不到入口,最后只是缓存问题。
4.3 diff 里的 blame 链接
第三个入口在 Diff 页面。如果你开启了blame=true,在某个文件的 diff 面板里,通常会在文件工具条或者行号区域出现 Blame 相关链接。点进去后,Gitweb 会打开该文件的 blame 视图,逐行告诉你是哪次提交带来了当前这一行。
说实话,这个入口在代码走查里最常用。审查某个改动时,经常要判断「这一行引入的原作者是谁,他们当时的意图是什么」,不是从当前 diff 能直接看出来的。有了 blame 跳转,问题就变成了一次点击的事。
4.4 服务端 curl 验证
最后一个万能验证方法,是在服务器上直接 curl 一下 Gerrit 生成的 Gitweb 链接:
curl -I http://git.example.com/gitweb/?p=demo-project.git看 HTTP 状态码是 200 还是 302 即可。用 curl 的好处是绕开了浏览器缓存、前端 JS、反向代理筛选这些干扰因素,直接暴露问题在链路哪一端。如果 curl 正常但浏览器里打不开,再去查浏览器侧的安全策略。
5. 集成过程中的经典故障:根因分析与处理
5.1 项目入口出现但全站 404
这个场景我遇到过两次,症状是 Gerrit 上Gitweb链接能点,跳过去的地址却是 404。查到最后都指向同一个原因:url这个基础地址写的不对。特别是 Gitweb 部署路径是/gitweb/,但你在url里写成了http://git.example.com,少了一段路径,所有拼接出来的链接都会错位。这个靠肉眼很难看出来,最直接的办法是把拼接后的完整 URL 复制到浏览器地址栏,看真正丢失的是哪一段,再回头修正配置里的url。
5.2 内置代理时报 403 或 500
如果你选择让 Gerrit 内置代理 Gitweb,也就是配置了cgi选项,遇到 403 很常见。原因通常是 Gitweb 的 CGI 脚本没有执行权限,或者系统 SELinux 策略挡住了。先直接在服务器上执行:
/usr/share/gitweb/gitweb.cgi如果输出是 HTML 而不是 Permission denied,说明脚本本身没问题;如果报权限错误,去调目录执行权限和 SELinux 相关布尔值。500 则更可能是 Perl 模块缺失,或者$projectroot指向的目录在 Gitweb 运行用户下没有读权限。
5.3 跨域名与 hostname 不一致问题
生产环境里最容易忽略的是 Gerrit 自身的baseUrl。很多团队的反代架构下,Gerrit 监听在内网地址,外部通过公网域名访问。Gerrit 生成 Gitweb 链接时,某些拼接逻辑会和gerrit.baseUrl绑定。如果只改了[gitweb] url,忽略了这个基础配置,就会出现:公网上的 Gerrit 页面里生成的链接却指向内网地址,点了直接跨域失败。
我的做法是确保gerrit.baseUrl是完整的公网地址,[gitweb] url也统一用公网可访问的地址,前后保持一致。哪怕内网访问 Gerrit,访问 Gitweb 时也走公网域名,这样证书和跳转逻辑都简单很多。
5.4 页面入口显示不出来
页面入口缺失是另一种高频问题,配置说不上哪里错,但就是没出现链接。我把它归纳为三种原因。
第一,项目可见性问题。Gerrit 对特殊项目如All-Projects、私有项目有额外的可见性限制,它们默认不生成 Gitweb 链接。这不是配置错误,不要浪费时间排查。第二,配置文件格式问题。[gitweb]段内部不能嵌套其他未知字段,一旦格式里出现多余的空格、缩进、注释符号,Gerrit 解析时可能整体跳过这段配置。检查方式是用git config --file gerrit.config --list | grep -i gitweb看有没有输出。第三,缓存问题。前文已经提过,旧浏览器标签页里保留的 JS 状态可能不显示新链接,强制刷新或者无痕窗口验证即可。
5.5 别忽略日志
最后提醒一个排查习惯:排错时优先看日志。Gerrit 的日志一般在$GERRIT_SITE/logs/,比如httpd_log、error_log,Gitweb 的日志则在 Apache 的 access/error log。很多故障现场其实在日志里写得非常清楚,只是调试的人习惯性盯着界面看。我在处理 Gitweb 404 时,就是在 Apache error log 里看到 CGI 执行路径不对才定位到问题的。
6. 项目级覆盖与安全加固:上线前要做的两件事
6.1 project.config 覆盖 Gitweb 链接
基础集成跑通后,有一个进阶功能值得马上掌握:Gerrit 支持在项目自己的project.config里覆盖[gitweb]配置。这意味着你可以让大多数项目走统一的 Gitweb 地址,同时又让某一个特殊项目跳到独立镜像或者另一个浏览工具上。
比如某些外包协作项目,你不想把内部 Gitweb 的完整目录暴露给外部人员,就可以在它的project.config里单独设置:
[gitweb] url = http://mirror.example.com/cgit/${project} linkname = External Browse这样 Gerrit 为这个项目生成的链接就会指向新的地址。project.config的修改通常可以通过 project owner 在网页端 push 配置完成,也可以在管理界面修改。改完同样需要刷新项目配置缓存,例如:
ssh -p 29418 <你的用户名>@<gerrit-host> gerrit flush-caches --cache project_list这个覆盖能力在体量大一点、项目归属分散的团队里特别有用,相当于把浏览入口的选择权下放给了项目组,而不是全局一刀切。
6.2 访问控制与 HTTPS 建议
最后说两个容易被忽略的配套项。
第一是访问控制。Gerrit 有自己的一套权限体系,但 Gitweb 本身没有任何用户概念,它只是被动地读仓库文件目录。如果你把 Gitweb 暴露在公网,又没有在 Web 服务器侧加认证、IP 白名单或者流量限制,那等于是把仓库列表和源码目录直接对外开放。我见过不止一次因为 Gitweb 忘了加访问控制,被外部扫描工具扫出源码泄露的情况。所以无论你的 Gerrit 在内网还是公网,都建议给 Gitweb 单独加一层 HTTP Basic Auth 或者接入统一身份认证。
第二是 HTTPS。现在很多浏览器已经在逐步收紧对混合内容的限制,如果 Gerrit 是 HTTPS,Gitweb 却是 HTTP,页面跳转时会被浏览器拦截或者打上不安全警告。最好让 Gitweb 和 Gerrit 共用同一个证书体系,至少保证内网协议一致。如果是公网部署,直接两边都上 HTTPS,不要在这个问题上省事。
以上这些就是我实际配置 Gerrit 与 Gitweb 集成时的完整记录。过程不算复杂,但真正花时间的地方在于让两个独立系统在访问链路上无缝衔接。按第 2、3 节先把通路搭起来,再按第 5 节的坑位对照排查,基本上可以少走不少弯路。最后再分享一个小技巧:配置完成后的第一周,让一两个同事专门用「Gitweb 入口」走完整的审查流程,如果他们把每个入口都点了一遍,发现所有跳转都符合预期,那这个集成才算真正落地。