
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.gitGitweb 很可能只把apps当作一个项目列出来展开进去是空白。解决办法是让 Gitweb 使用$project_list文件让 Gerrit 侧定期把完整项目列表写进这个文件或者干脆调整仓库目录结构保持一级扁平。配置完 Gitweb 后重载 Apachesudo 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 那种?pproject.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 blame4.1 项目页的 Gitweb 入口配置完并重载之后先不要急着宣布完成因为 Gerrit 对集成是否生效的描述比较克制配置不合法、项目不可见、URL 拼接不了的时候它通常只是悄悄隐藏入口而不是报错。所以我习惯按下面几个入口逐个验证。先登录 Gerrit打开一个具体项目比如demo-project。项目页右侧一般会出现一个标着Gitweb的链接。点击后浏览器应该跳到类似http://git.example.com/gitweb/?pdemo-project.git这样的地址。如果 404 或者 403大概率是 Gitweb 侧的问题回头看第 2 节的独立部署如果链接压根没出现再看 Gerrit 配置。4.2 change 页与 commit 页入口然后随便打开一个 change。在 change 页面的顶部信息区或者补丁集描述附近通常会出现带 Gitweb 文字的跳转链接。这个链接会直接指向 Gitweb 里对应 commit 的页面方便你从评审页跳过去看这次提交的完整上下文。这里要特别留意Gerrit 的前端是典型单页应用改完配置之后浏览器可能还缓存着旧的页面状态验证时先强制刷新一下。我以前吃过这个亏新配置明明已经生效页面上就是看不到入口最后只是缓存问题。4.3 diff 里的 blame 链接第三个入口在 Diff 页面。如果你开启了blametrue在某个文件的 diff 面板里通常会在文件工具条或者行号区域出现 Blame 相关链接。点进去后Gitweb 会打开该文件的 blame 视图逐行告诉你是哪次提交带来了当前这一行。说实话这个入口在代码走查里最常用。审查某个改动时经常要判断「这一行引入的原作者是谁他们当时的意图是什么」不是从当前 diff 能直接看出来的。有了 blame 跳转问题就变成了一次点击的事。4.4 服务端 curl 验证最后一个万能验证方法是在服务器上直接 curl 一下 Gerrit 生成的 Gitweb 链接curl -I http://git.example.com/gitweb/?pdemo-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_logGitweb 的日志则在 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 是 HTTPSGitweb 却是 HTTP页面跳转时会被浏览器拦截或者打上不安全警告。最好让 Gitweb 和 Gerrit 共用同一个证书体系至少保证内网协议一致。如果是公网部署直接两边都上 HTTPS不要在这个问题上省事。以上这些就是我实际配置 Gerrit 与 Gitweb 集成时的完整记录。过程不算复杂但真正花时间的地方在于让两个独立系统在访问链路上无缝衔接。按第 2、3 节先把通路搭起来再按第 5 节的坑位对照排查基本上可以少走不少弯路。最后再分享一个小技巧配置完成后的第一周让一两个同事专门用「Gitweb 入口」走完整的审查流程如果他们把每个入口都点了一遍发现所有跳转都符合预期那这个集成才算真正落地。