最近团队内部的Gerrit服务器要做一次配置升级,顺带把Gitweb集成进去。这个需求很常见:Gerrit管代码审查很舒服,但要看仓库整体历史、分支结构,还是Gitweb效率更高。折腾了一下午,把配置链路理清楚了,也踩了几个不算坑的坑。这套思路适用于大多数自建代码托管环境的同学,尤其是那些已经用Gerrit、又想白嫖Gitweb浏览能力的团队。
先说结论:Gerrit和Gitweb集成,本质上就是两件事。一是让Gerrit知道你的Gitweb服务在哪,二是让Gitweb能正确读到Gerrit管理的仓库目录。这两点只要有一处含糊,出来的不是链接404,就是页面压根不显示入口。下面从选型、配置、联动到排查,一条条讲透。
1. 为什么要在Gerrit里集成Gitweb
1.1 代码审查和仓库浏览的互补
Gerrit的核心是代码审查,它的diff视图和inline comment做得确实好用,但你要在Gerrit里看一条分支的演化轨迹、比对两个tag之间的全部提交、或者翻某个文件的老旧历史,体验就比较难受。Gerrit的列表页偏重“变更”,而不是“仓库”。Gitweb恰恰补上这一环:它有summary、shortlog、log、commit、tree、blob、blame、tags、heads等一整套视图,而且不需要额外插件,Git源码里自带。
实际工作中最常见的场景是:评审一个改动时,评审者想知道这个commit在主干上有哪些前置提交;或者在回溯线上问题时,需要快速切到某个tag对比历史。典型做法是打开Gerrit的change页面,点一下旁边的Gitweb链接,整个仓库的全貌就出来了。省去切换系统、重新登录、手动拼URL的麻烦。对于非研发角色如测试、运维,Gitweb也特别友好,大家不熟悉Gerrit的操作逻辑,但Gitweb就是“点着看”的网页,学习成本极低。
1.2 两种接入模式选型:cgi与url
Gerrit集成Gitweb最核心的决策是选模式。gerrit.config里可以配置两类后端:一类是cgi,让Gerrit直接在自身Web容器里调用服务器上的gitweb.cgi;另一类是url,Gerrit只负责拼出外部Gitweb服务的链接,点击后跳转。两者没有绝对好坏,但适用场景差别很大。
cgi模式的优点是不用额外维护一个Apache或nginx服务,Gerrit自己包办了Gitweb的HTTP出口;缺点也明显,它依赖运行Gerrit的JVM进程外调Perl脚本,对Perl模块、CGI环境、文件权限都很挑剔。我见过不少同事配完之后页面白屏,最后发现是缺CGI.pm或者URI::Escape。如果只想在开发环境快速看一眼,cgi能跑;生产环境我还是更倾向url模式,让Apache和Gitweb各司其职,干净、稳定、好排查。
url模式的缺点是你要先有一个外部Gitweb站点。不过现在公司内部一般都有现成的Gitweb或是cgit服务,如果没有,用Apache挂一个Gitweb也就十分钟的事。所以我的建议很简单:只要是正式环境,就用url模式;实在不想搭站点,再考虑cgi。文章后面也主要按url模式展开,cgi模式会在调试章节单独讲。
2. Gerrit侧配置Gitweb的完整步骤
2.1 动手前先确认环境与配置文件
任何配置改动前,先确认Gerrit安装目录和配置文件位置。Gerrit站点目录是你初始化的时候用init指定的,通常里面有个etc/gerrit.config。你可以在服务器上执行:
ls $GERRIT_SITE/etc/gerrit.config没有这个文件的话,说明站点路径不对。如果你不确定站点目录,用ps aux | grep gerrit看进程参数,能找到-Dgerrit.site_path这类启动参数。
接着检查Gerrit版本。不同版本的[gitweb]配置项有小差异,但大方向一致。稳妥起见,先备份配置文件:
cp $GERRIT_SITE/etc/gerrit.config $GERRIT_SITE/etc/gerrit.config.bak.$(date +%Y%m%d)备份是好习惯,尤其在生产环境。之后可以用git config --file直接读取当前配置,确认还没有[gitweb]段落,避免重复配置:
git config --file $GERRIT_SITE/etc/gerrit.config --list | grep -i gitweb2.2 修改gerrit.config中的[gitweb]段
url模式的核心配置就三行。打开gerrit.config,加入如下内容:
[gitweb] url = http://git.example.com/gitweb type = gitweb注意几点:url写的是Gitweb站点的根地址,末尾不要带斜杠,Gerrit在拼链接时会在后面追加查询参数。以标准Gitweb为例,Gerrit会生成类似http://git.example.com/gitweb/?p=myproject.git;a=commit;h=abcdef的地址。type必须设为gitweb,不然Gerrit不知道用哪种链接生成器。
如果你的Gitweb是放在Apache的某个子路径下,比如http://git.internal/cgi-bin/gitweb.cgi,那url就写这个完整路径。我在实际配置里遇到过一种情况:Gitweb的URL前缀末尾带了/,Gerrit拼出来后变成了双斜杠,Apache开启了压缩或重写之后直接404。所以写URL时一定要统一不带尾部斜杠。
如果你实在想省掉外部Web服务,也可以用cgi模式。配置如下:
[gitweb] cgi = /usr/lib/cgi-bin/gitweb.cgi type = gitweb这里cgi必须是服务器上真实存在的脚本路径。配置完后需要重启Gerrit,并且保证Gerrit运行用户对该脚本有执行权限。cgi模式不推荐生产使用,但开发机应急确实方便。
2.3 重启Gerrit并验证生效
修改配置后,需要重启Gerrit才能让[gitweb]段生效。站点目录下一般有重启脚本:
$GERRIT_SITE/bin/gerrit.sh restart如果Gerrit是运行在容器或systemd里,就按对应的方式重启。重启后先看启动日志有没有异常:
tail -f $GERRIT_SITE/logs/error_log然后随便找一个项目,打开一个change页面,找到commit信息区域。正常情况会出现Gitweb字样的链接,点击后跳转到Gitweb的commit视图。
我验证时习惯直接在浏览器里手工拼一个URL,比如:
http://git.example.com/gitweb/?p=myproject.git;a=summary手工能打开,说明Gitweb本身没问题;Gerrit页面能点出链接,则说明解析和拼接都没问题。两条链路先分别验证,再合起来,排错会快很多。
3. Gitweb侧的环境准备与联动细节
3.1 用Apache把Gitweb服务架起来
url模式的前提是有一个正常运行的Gitweb。这里把标准搭建流程走一遍,以Debian/Ubuntu为例,先装包:
apt-get update apt-get install -y apache2 gitwebDebian系的gitweb包会自动在Apache下注册/gitweb路径。如果安装后访问http://服务器IP/gitweb能看到目录列表或gitweb页面,说明基础服务起来了。
接着要调整gitweb.conf,告诉Gitweb去哪里找仓库。这个文件通常在/etc/gitweb.conf,内容类似:
$projectroot = "/data/gerrit/git"; $git_dir_list = []; $project_list = []; $projects_max_depth = 1;$projectroot必须指向Gerrit实际存放仓库的目录。如果Gerrit和Gitweb在同一台机器,这个目录就是$GERRIT_SITE/git。如果不在同一台机器,就要考虑NFS同步或用远程仓库的镜像,但我会尽量避免这种部署,排查权限问题太痛苦。
修改完重启Apache:
systemctl restart apache2之后在浏览器里访问http://git.example.com/gitweb,能看到项目列表。如果列表是空的,多半是$projectroot设错了或目录权限不对。
3.2 仓库目录与权限设置
这是集成里最容易被忽略的坑。Gerrit的仓库目录通常是站点目录/git,里面每个项目是一个项目名.git文件夹。Gitweb默认会遍历$projectroot下所有符合规则的git目录来生成项目列表。如果你的Gerrit创建了新项目,Gitweb列表会延迟出现或压根不出现,先检查权限。
Apache运行用户通常是www-data,Gerrit运行用户一般是专有用户如gerrit。Gitweb要读取$projectroot下的目录和文件,但未必需要写权限。正确的权限设置是:
chmod o+x /data chmod o+x /data/gerrit chmod o+x /data/gerrit/git对git目录内部的objects、refs也要有读权限。如果你用了Git的updateServerInfo,还需要info/refs可读。很多团队图省事直接chmod -R 777,结果仓库裸奔,被扫描风险极高,别这么干。正确做法是把Gitweb运行用户加到一个组,然后把git目录按组授权。
补充一个细微点:Gerrit仓库文件夹名是myproject.git,但Gerrit的项目名是myproject。Gerrit拼接Gitweb链接时,会负责把项目名转成带.git后缀的路径。如果你在Gitweb端设置了$strict_export或用了$project_list白名单,请确保myproject.git在允许名单里,否则Gerrit能跳过去,Gitweb却拒绝显示。
3.3 链接拼接参数与Gerrit端模板
Gerrit的url模式并不是简单在root URL后面拼字符串。它内部有一套链接生成器,针对type = gitweb会采用默认的Gitweb查询格式,比如 commit 视图生成:
.../gitweb/?p=项目名.git;a=commit;h=commitIddiff视图会转成a=commitdiff;h=...;tag视图则转成a=tag;p=...。我们在配置时不需要手动写查询模板。不过有些新版本Gerrit支持自定义模板,可以在url里使用占位符,例如:
[gitweb] url = http://git.example.com/gitweb?p=${project}.git;a=commit;h=${commit} type = gitweb这个写法和默认生成效果类似,但给了你更多控制权。要注意的是a=参数必须和实际使用的Gitweb版本兼容。Gitweb这么多年核心参数没大改,但有些老版本对hash参数的支持有差异。如果你发现点击链接后进入了Gitweb的首页而不是commit页面,检查一下这个URL模板的a和h参数是否被Gitweb端接受。
另外,如果你想用一种更贴近现代代码托管平台的外观,其实不一定要绑死Gitweb。Gerrit还支持cgit、Bitbucket等类型,但那就偏离主题了。从运维角度讲,Gitweb最省心,因为它就是Git官方维护的Perl脚本,没有复杂的Node或Ruby依赖。
4. 实操中的常见问题与排查心得
4.1 点击Gitweb链接总是跳404
这个问题我遇到过不下三次。现象是Gerrit页面上的链接能点,但点过去要么Apache默认页,要么404。先别怀疑Gerrit,第一步在浏览器里直接访问http://git.example.com/gitweb/?p=myproject.git;a=summary,看能不能打开项目摘要。
手工能打开,说明Gitweb服务正常,问题出在Gerrit生成的链接和实际URL不一致。常见原因有三个:一是gerrit.config里的url带了尾部斜杠,拼接后出现双斜杠;二是项目名大小写不一致,Gerrit里叫MyProject,Gitweb端路径大小写敏感;三是Apache对查询字符串里的;做了拦截或重写,导致a=commit;h=...被截断。
如果手工也打不开,大概率是Gitweb的$projectroot没有指向Gerrit仓库目录,或者目录权限不够。这时候去查看Apache错误日志:
tail -f /var/log/apache2/error.log日志里能看到No such directory或Permission denied这类明确提示。按图索骥就能解决。
4.2 页面上压根不出现Gitweb链接
这种问题比404更隐蔽,因为配置改了、Gerrit也重启了,但change页面没有任何入口。先确认你是否有权限查看该项目。Gerrit的权限模型里,如果某条ref对匿名用户不可读,而登录用户也没有相关权限,页面上的Gitweb链接会被隐去。说白了,Gerrit不太愿意在评审页给一个大家都没权限的仓库暴露浏览入口。
再检查gerrit.config有没有写对段落名。注意是[gitweb],不是[git-web]也不是[gitweb]写错大小写。Git的配置项一般是大小写敏感的,Gitweb和gitweb会被视作两个不同段落。确认无误后重启。
还有一个细节:在老版本Gerrit里,type不写也能启动,但链接生成会走默认逻辑,可能生成的是cgit样式,导致跳转格式混乱。所以我建议无论如何都显式写type = gitweb。同时检查canonicalWebUrl,这是Gerrit生成自身页面URL的基础,如果它没配好,有些附属链接也会跟着错乱。
4.3 CGI模式的白屏与Perl环境问题
如果你坚持用cgi模式,白屏是最大的痛点。Gerrit内部通过CGI协议调用外部的gitweb.cgi,本质上是在Java容器里跑Perl,环境依赖非常苛刻。遇到白屏,先手工在命令行执行一次:
perl /usr/lib/cgi-bin/gitweb.cgi如果命令行报错,比如Can't locate CGI.pm,说明系统缺少Perl模块。Debian上可以装libcgi-pm-perl,另外Gitweb还依赖libgit-wrapper-perl、libcgi-fast-perl等,记得一起装上。
如果命令行执行能输出HTML,但Gerrit页面依然白屏,检查Gerrit运行用户的PATH和环境变量。JVM里调用外部CGI时,环境变量可能被清空,导致Perl找不到模块。可以在gerrit.config中为cgi模块设置环境变量,或者干脆在启动脚本里export完整的PATH。这个过程很折腾,我现在一般直接建议同事用url模式。
4.4 权限边界与安全提示
Gitweb直连Gerrit仓库目录,这意味着如果你不加保护,所有能访问Gitweb的人都能浏览全部仓库。这在内部环境可能无所谓,但如果Gerrit部分项目包含敏感代码,你需要在Gitweb端做访问控制。
最简单的做法是利用Apache的Require指令限制访问来源IP,比如只允许办公网段访问/gitweb:
<Location /gitweb> Require ip 10.0.0.0/8 </Location>如果你需要更细粒度的项目级权限,Gitweb本身不支持,建议用cgit或自研前端,甚至直接用Gerrit的REST API做一个网关。集成Gitweb本来是为了方便,千万别因权限开放引入安全风险。
5. 集成后的真实使用场景与经验建议
5.1 日常开发与排查中的高频玩法
配置好之后,最大的收益反而不是每天点链接,而是把Gitweb嵌入到工作流里。比如我可以从Gerrit的邮件通知里直接提取commit哈希,然后在Gitweb里快速查看这个提交的完整patch,比在Gerrit里翻diff更快预算。尤其是改动涉及几十个文件时,Gitweb的侧边栏和文件跳转比Gerrit的diff视图顺手很多。
另一个高频场景是分支和tag比对。Gerrit本身不擅长展示仓库级引用关系,但Gitweb的shortlog和tags视图一目了然。我习惯在发版前打开Gitweb,比较release/1.2和master之间的提交差异,确认合入范围没有漏。这个动作用Gerrit做要写查询,用Gitweb就是点两下的事。
所以这套配置对团队的实际影响不是多了一个链接,而是让“审查变更”和“理解仓库”解耦。Gerrit专注高强度的代码评审,Gitweb专注低门槛的浏览和追溯,各干各擅长的事。
5.2 配置维护的避坑清单
总结一下我自己的维护经验,写成一份清单供参考:
- 修改
gerrit.config之前一定备份,用git config --file校验格式,避免手工编辑时引入不可见字符。 - url模式时,
url结尾不要带斜杠;cgi模式时,确认Perl模块完整,生产环境慎用。 - 每次升级Gerrit版本后,重新检查
[gitweb]配置是否兼容,个别大版本升级会改配置结构。 - Gitweb的
$projectroot和Gerrit的仓库目录要严格一致,用软链接容易引发遍历问题。 - 权限遵循最小化原则,不随意
chmod -R 777,让Apache通过辅助组权限读仓库目录。 - 重启Gerrit后别急着完事,用
logs/error_log和Apache错误日志双端确认。
最后再分享一个小技巧:在Gerrit的change页面能看到Gitweb链接之后,我在浏览器书签里把Gitweb地址存了一份,但更多时候是直接在Gerrit页面点。集成这个事,最烦的不是配置本身,而是你根本不知道它有没有生效。用一个简单的shell脚本把gerrit.config里所有gitweb相关行、Gitweb站点响应码、仓库目录权限一起打出来,几分钟就能定位问题。遇到配置出错,记住“先分开验证,再合起来看”,大多数问题都能迎刃而解。