Gerrit 的代码审查流程在业界是出了名的严谨,但真要论"逛代码"的体验,它其实有点偏科——项目里的文件树就是一个最朴素的列表,想看分支对比、翻历史、找 blame、查 tag,全靠人肉在终端里敲命令。Gitweb 恰好就是来补这块短板的:把两者接起来之后,Gerrit 的项目页、变更页、提交列表上都会多出一个跳转链接,点过去就是 Gitweb 那个清爽的仓库浏览界面。这篇文章记录的就是一次完整的 gerrit 配置过程——从安装 Gitweb、配 Apache、打通仓库读取权限,到最后在 gerrit.config 里写 [gitweb] 段并验证生效,顺手把踩过的坑也一起整理出来。如果你正在自建 Gerrit,或者想把团队的代码浏览入口统一起来,这篇可以直接照着抄。
1. 为什么非要把 Gitweb 集成进 Gerrit
1.1 Gerrit 的短板:能审代码,但不方便"逛"代码
Gerrit 的核心定位是代码审查,它把"提交-评审-合入"这条链路做得极其扎实,但代价就是它对仓库本身的浏览能力非常克制。你在 Gerrit 的 Web 界面里可以看 diff、评 comment、追踪 review 状态,可一旦想看某个分支的整体结构、某次提交改了哪些文件之外的内容、两个 tag 之间差了多少提交,就只能回到命令行去git log、git branch、git show。对纯粹用 Gerrit 做 code review 的人来说问题不大,但对新人熟悉项目、管理层快速了解代码库、或者排查问题时,这种体验就很憋屈。
Gitweb 是 Git 官方仓库里自带的一个 Web 界面,它的定位就是"把裸仓库变成可浏览的网站"。树形目录、blame、历史提交、diff、tag/branch 列表、搜索,这些功能它全都覆盖。它和 Gerrit 并不冲突,反而是互补关系:Gerrit 管流程,Gitweb 管浏览。
1.2 集成之后的实际效果
所谓"集成",核心就是让 Gerrit 的界面里出现指向 Gitweb 的入口,并且 Gerrit 能根据当前上下文生成对应 Gitweb 的链接。配置完成之后你会看到:
- 项目页上多出一个名为 gitweb(名字可自定义)的链接,点击直接进入该仓库在 Gitweb 中的 summary 页;
- 变更详情页里,点击某次提交的 SHA-1 时,可以通过 gitweb 链接跳到 Gitweb 中这次提交的完整视图;
- 一些版本里文件 diff 界面也会有跳转到 Gitweb blob 页的入口。
这个过程不需要改 Gerrit 源码,也不需要装插件,纯靠gerrit.config里的一个配置段就能完成。Gerrit 原生支持对接 gitweb、cgit、gitiles、bitbucket 几种知名仓库浏览器,也支持自定义 URL 模板,灵活性很大。
1.3 适合哪种团队和场景
这套组合最适合的是:内网自建 Gerrit 做代码托管和审查的小团队。尤其是那种"Gerrit 里仓库不少、但一直缺一个统一浏览入口"的情况,装一个 Gitweb 再配置一下,成本极低,收益却很直接。如果你们已经有 Gitiles 或其他浏览器,思路完全一样,只是配置项不同。下面我按最常见的环境——Linux 服务器 + Debian/Ubuntu 系 + Apache,走一遍完整流程。
2. 动手前先选型:gitweb、cgit、gitiles 还是 custom
2.1 四种接入方式的对比
Gerrit 的[gitweb]配置段里,type字段决定了链接的生成方式。常见几个取值如下:
| type 取值 | 适用场景 | 优点 | 注意点 |
|---|---|---|---|
gitweb | 系统里装了 Gitweb CGI | 与 Git 同源,安装简单,链接可深入到 commit/file 层级 | 需要单独配 Apache 和仓库读取权限 |
cgit | 已部署 cgit 实例 | 页面更轻量、速度快 | 需要额外安装 cgit 并维护其配置 |
gitiles | 已有 Gitiles 服务 | 和 Gerrit 配合度最高,Google 同款 | Gitiles 本身部署成本偏高,适合大团队 |
custom | 只想给每个项目加一个外部链接 | 配置最省,一个 url 搞定 | 通常只能生成项目主页链接,无法精细到具体 commit |
如果你的 Gitweb 实例是独立部署在外网或另一台机器上,那么custom是最直接的方案——你只要告诉 Gerrit"每个项目对应的 Gitweb 链接长什么样"就行了。
2.2 我的选型结论
对大多数中小团队,我推荐直接用type = gitweb,配合系统自带 Gitweb CGI。理由有三点:
第一,Gitweb 是 Git 官方仓库里就带的东西,Debian/Ubuntu 一个 apt 包就能装好,不引入额外维护成本;第二,gitweb类型是 Gerrit 支持得最久、文档最全的对接方式,URL 生成规则稳定;第三,它能生成到具体提交、具体文件的深层链接,而不只是仓库主页,实用性高出custom一个档次。
如果你们团队已经有人在维护 Gitiles,那直接配type = gitiles更顺手。custom则适合那些"Gitweb 部署在别处、图省事先接个入口"的过渡场景。选型没有绝对正确答案,但先想清楚"我要不要点进具体 commit",就能很快定下来。
3. Gitweb 集成配置完整实操
3.1 第一步:安装 Gitweb 并让 Apache 跑起来
这里以 Debian/Ubuntu 为例。先安装 Apache 和 Gitweb:
sudo apt update sudo apt install -y apache2 gitweb装完之后,Debian 系会自动在/usr/share/gitweb下放置 Gitweb 的界面文件,并把gitweb.cgi放在/usr/lib/cgi-bin/。同时它还会生成一个 Apache 配置文件/etc/apache2/conf-available/gitweb.conf,内容大致是:
Alias /gitweb /usr/share/gitweb <Directory /usr/share/gitweb> Options +ExecCGI AddHandler cgi-script .cgi DirectoryIndex gitweb.cgi </Directory>这个配置意味着访问http://你的服务器/gitweb/时会执行 Gitweb 的 CGI 脚本。要让配置真正生效,还需要启用 Apache 的 cgi 模块并加载这份配置:
sudo a2enmod cgi sudo a2enconf gitweb sudo systemctl reload apache2然后打开浏览器访问http://服务器IP/gitweb/,如果能看到一个仓库列表页面——哪怕现在是空的——就说明 Gitweb 本身已经工作了。这一步卡住的话先别往下走,Gitweb 没跑通,后面 Gerrit 怎么配都是白搭。
3.2 第二步:让 Gitweb 能读到 Gerrit 的裸仓库
Gitweb 本身并不知道 Gerrit 的存在,它只是按自己的配置去某个目录下扫描裸仓库。所以关键是把 Gitweb 的$projectroot指向 Gerrit 实际存储仓库的目录。
Gerrit 初始化之后,所有仓库都放在站点目录下的git目录里,比如典型路径是/home/gerrit/review_site/git。每个项目对应一个裸仓库目录,命名是"项目名.git",子目录项目会按层级再建子目录。
打开 Gitweb 的配置文件/etc/gitweb.conf,改成如下内容:
our $projectroot = "/home/gerrit/review_site/git"; our $git_dir = "$projectroot/$project"; our $projects_list = $projectroot; our $export_ok = "";这里$export_ok = ""很重要,它表示"目录下所有 Git 仓库全部对外展示",不要额外要求git-daemon-export-ok标记文件。如果你不把这一项置空,Gitweb 默认行为是只导出带有那个标记文件的仓库,而 Gerrit 创建的仓库默认没有,结果就是你看到的仓库列表永远是空的。
接着是权限问题。Gerrit 的进程通常以独立用户(比如gerrit)运行,仓库文件归gerrit用户所有;而 Apache 的 CGI 进程跑在www-data用户下。如果www-data读不了仓库目录,Gitweb 列表照样是空的。最简单的处理办法是把www-data加入gerrit用户组,并补齐组读取权限:
sudo usermod -a -G gerrit www-data sudo chmod g+X /home/gerrit sudo chmod -R g+rX /home/gerrit/review_site/git改完之后刷新 Gitweb 页面,应该能看到 Gerrit 里的全部项目了。如果依然空白,优先检查路径拼写和权限,最直接的办法是用www-data身份去目录里试读取:
sudo -u www-data ls /home/gerrit/review_site/git3.3 第三步:修改 gerrit.config 完成接入
Gitweb 自己已经能跑、能列出仓库之后,接下来就是在 Gerrit 这边"开开关"。编辑 Gerrit 站点配置文件$site/etc/gerrit.config(比如/home/gerrit/review_site/etc/gerrit.config),找到或新建[gitweb]段:
[gitweb] type = gitweb cgiPath = /usr/lib/cgi-bin/gitweb.cgi root = /home/gerrit/review_site/git linkName = gitweb各字段的含义和选值逻辑:
type = gitweb:告诉 Gerrit 按 Gitweb 的标准 URL 格式生成链接;cgiPath:Gitweb CGI 在服务器上的实际路径。Gerrit 会以它为基础拼接查询参数,形如cgiPath?p=项目名.git;a=summary;root:仓库根目录,必须和 Gitweb 的$projectroot保持一致。这个值用来把磁盘路径映射成 URL 里的项目名,填错会导致 Gerrit 生成的链接跳过去 404;linkName:这个链接在 Gerrit 界面上显示的文字。默认就叫 gitweb,你要是想显示成"浏览仓库"或 "Browse",改这里就行。
保存后重启 Gerrit 让配置生效:
cd /home/gerrit/review_site/bin ./gerrit.sh restart重启完成后进入 Gerrit 的 Web 界面,打开任意一个项目页面,就能看到 gitweb 链接了。点进去如果跳到了 Gitweb 里对应仓库的 summary 页,说明整条链路已经打通。
3.4 备选:用 custom 类型接外部 Gitweb
如果你手头已经有一套部署在其他域名或端口上的 Gitweb,不想再动本机 Apache,可以用custom类型。它的原理很简单:Gerrit 把 URL 模板里的${project}替换成当前项目名,然后生成一个链接。例如:
[gitweb] type = custom url = http://git.example.com/gitweb/?p=${project}.git;a=summary linkName = 浏览仓库这种方式的优点是几乎零成本,缺点是 Gerrit 无法确认你这套 Gitweb 的 URL 规则,所以通常只会生成到仓库主页的链接,不会深入到具体 commit 或文件。如果你只是想让同事"有个地方能点开看代码",这个方案完全够用;但如果你希望从 Gerrit 的提交详情直接跳到 Gitweb 的对应 commit 页,还是老老实实用标准的gitweb类型更稳妥。
4. 常见问题与排查记录
配置过程看着不长,但每个人环境不一样,踩坑的位置也不一样。我把自己实际遇到过、以及身边同事问过的问题整理成了一张速查表,再逐个展开说细节。
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 访问 /gitweb/ 报 403 | Apache 没启用 cgi 模块 | 执行a2enmod cgi并 reload |
| 访问 gitweb.cgi 显示源码 | 缺AddHandler cgi-script .cgi | 检查 conf 文件里的 handler 配置 |
| 仓库列表为空 | $projectroot路径不对或权限不足 | 核对路径,检查www-data读取权限 |
| 仓库列表不全 | $export_ok设置了标记文件限制 | 把$export_ok置空 |
| Gerrit 界面看不到链接 | [gitweb]没生效或 type 为 disabled | 修改后重启 Gerrit,确认 type 值 |
| 从 Gerrit 点过去 404 | cgiPath或root与 Gitweb 实际配置不一致 | 手动在 Gitweb 里打开该项目路径做对比 |
4.1 Apache 层面:403 和源码乱码
403 一般就是 cgi 模块没启用。确认顺序是:a2enmod cgi、a2enconf gitweb、systemctl reload apache2,三步缺一不可。另外注意有些环境下 Gitweb 包安装的 conf 文件里没有AddHandler cgi-script .cgi,这种情况浏览器会把 CGI 脚本当纯文本吐出来,页面不是乱码就是下载一个 .cgi 文件。手动把上面那段 Apache 配置补全再 reload 就行。
4.2 仓库列表为空或不全
这是 Gitweb 侧最典型的故障。第一查路径:$projectroot和 Gerrit 实际的git目录是否一致,别有符号链接绕来绕去;第二查权限:sudo -u www-data ls $projectroot能不能看到目录内容;第三查$export_ok:如果你没有把它显式置空,而系统的默认又恰好配置成git-daemon-export-ok,那仓库永远不会出现。我的建议是配置里明确写好our $export_ok = "";,避免依赖发行版默认值。
4.3 Gerrit 界面上没有链接
先确认配置文件写对了位置,[gitweb]段必须放在gerrit.config根级别,别不小心写进[gerrit]段里面了。然后确认type不是disabled。最后也是最容易忘的一步:必须重启 Gerrit。Gerrit 对这份配置不是热加载的,不重启就等于没改。重启后如果还是没有,打开浏览器开发者工具看项目页 HTML 里有没有包含 "gitweb" 字样,能帮你判断是生成端问题还是前端渲染问题。
4.4 从 Gerrit 跳转后 404
这类问题集中在 URL 拼接结果和 Gitweb 路由不匹配。比如 Gitweb 实际的项目名是infra/scripts.git,Gerrit 生成的却是infra%2Fscripts.git,或者反过来多了一个.git后缀。排查时不要猜,直接把 Gerrit 生成的链接复制出来,手动改到 Gitweb 能访问的形式,比对差异就清楚了。root参数在这里面扮演的角色很关键,它决定了 Gerrit 如何把项目名映射到磁盘路径,所以务必和 Gitweb 的$projectroot严格保持一致。
5. 踩坑之后的一些心得
整套流程走下来,我对"配置"这件事最大的体会是:三分靠配置,七分靠权限,剩下九十分靠路径对齐。无论是root、cgiPath、$projectroot,本质上都是同一件事——让 Gerrit 认识的仓库、和 Gitweb 能读到的仓库,是同一个仓库。
还有一点必须提醒:Gitweb 集成之后,它会独立于 Gerrit 的权限体系存在。如果你的 Gerrit 里有私有项目,而 Gitweb 的仓库目录对全员可读,那就等于把私有代码暴露了。Gerrit 的访问控制和 Gitweb 的访问控制是两套系统,这一点很容易被忽略。我个人的做法是:要么在 Gitweb 侧用$strict_export配合标记文件精确控制导出范围,要么把 Gitweb 部署在只有内网可访问的网段,同时在重复一遍——私有仓库绝对不要随便全局列出来。
最后分享一个小技巧:linkName不只是改个文字而已。如果团队里有人不熟悉 Gitweb,把链接名字改成"浏览仓库"或"代码浏览",能显著减少"这个 gitweb 是干嘛的"这种问题。多花十秒钟改一行配置,省掉以后无数次的解释成本,这笔账非常划算。