ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Git+Repo+Gerrit代码评审服务器搭建实战:多仓库协作与权限管理

Git+Repo+Gerrit代码评审服务器搭建实战:多仓库协作与权限管理 简介本资源是一份面向运维工程师、后端开发及代码管理初学者的代码评审服务器搭建文档围绕 Git、Repo 与 Gerrit 三大工具讲解如何从零构建一套功能完整的代码服务器解决团队代码托管、多仓库统一管理与代码评审流程落地的问题。资源包内含1个docx文档约294KB以图文步骤形式组织涵盖名词解释、必要软件安装、Gitosis 管理员创建与公钥配置、Repo 服务器搭建、Git 守护进程启动以及 Gerrit 评审环境配置等模块并附有大量可直接复用的命令与配置文件示例。目前已有3136人学习下载适合希望快速搭建内部代码托管与评审平台、对照实操并排查配置问题的读者参考。1. 从一台裸机到可评审的 Git 服务gitrepogerrit 到底在搭什么很多团队第一次听到「gitrepogerrit 代码服务器搭建」脑子里浮现的是三套互不相干的东西Git 是版本控制repo 是谷歌那套多仓库管理脚本Gerrit 是带代码评审的 Web 服务。真正落地时你会发现它们是一条流水线上的三个环节——Git 负责存储对象repo 负责把几十个仓库按清单一次性拉齐Gerrit 负责在 push 和 merge 之间插一道评审闸门。少了任何一环多仓库协作就会退化成手工 clone 的体力活。这套组合最适合的场景是项目被拆成多个独立仓库比如一个主工程加若干公共库需要统一版本基线同时要求每次提交都经过 Code Review 才能入库。它不适合单人小项目也不适合已经重度依赖某托管平台的团队。下面按「先跑通、再调参、最后避坑」的顺序把一台干净服务器变成可用的评审服务。2. 环境准备与 Gerrit 初始化把服务先立起来2.1 选型理由为什么用 Gerrit 而不是裸 Git裸 Git 服务器git init --bare 加 SSH只能解决「代码放哪」解决不了「谁改了什么、谁批准了」。Gerrit 在 Git 之上加了一层 refs/changes 引用空间每次 push 到 refs/for/分支 的提交不会直接进分支而是生成一个 Change 等待评审。评审通过后再由 Gerrit 合入目标分支。这个机制是多仓库协作里最省心的权限与审计方案。选 Gerrit 还要接受它的代价它自带一套账户体系、SSH 端口、Web 端口初始化时如果端口和系统已有服务冲突后面会反复翻车。所以第一步不是装 Gerrit而是把端口和目录规划清楚。2.2 依赖安装与专用账户Gerrit 是 Java 服务需要 JDK 和 Git。生产环境建议用独立系统账户运行避免权限混乱。# 以 root 执行安装基础依赖 apt-get update apt-get install -y openjdk-17-jre-headless git gitweb # 创建专用账户禁止登录 shell adduser --system --group --home /home/gerrit --shell /bin/bash gerrit # 准备三个目录安装目录、站点目录、Git 仓库根目录 mkdir -p /opt/gerrit /var/gerrit/site /var/gerrit/git chown -R gerrit:gerrit /opt/gerrit /var/gerrit逻辑说明JDK 版本要和 Gerrit 版本匹配常见做法是 Gerrit 3.x 配 JDK 11 或 17装之前先确认版本对应关系别装完才发现启动报 UnsupportedClassVersionError。专用账户用--system创建没有密码、不能交互登录降低被误操作的风险。三个目录分开是为了升级时只替换/opt/gerrit下的 war 包站点配置和仓库数据原地保留。参数说明--home指定家目录Gerrit 默认会在这里找.ssh授权文件--shell /bin/bash是为了后续能用su - gerrit进去执行初始化如果设成/usr/sbin/nologin就没法切换了。2.3 初始化站点与关键参数下载 Gerrit war 包后用init子命令生成站点。这一步会交互式问一堆问题提前想好答案能少走弯路。su - gerrit cd /var/gerrit/site # 初始化-d 表示使用默认值后续再改配置文件 java -jar /opt/gerrit/gerrit.war init -d # 初始化完成后检查关键配置 grep -E listenUrl|httpd|database|auth /var/gerrit/site/etc/gerrit.config逻辑说明init -d会生成etc/gerrit.config、etc/secure.config和 Git 仓库目录。默认数据库是 H2小团队够用认证默认是 OPENID_SSO内网环境通常要改成 HTTP 或 LDAP。初始化时如果提示下载插件失败多半是网络问题可以跳过后面手动放插件。参数说明listenUrl决定 Web 和 SSH 监听地址格式如proxy-http://*:8080/database段里type可选 h2、postgresql、mysql超过二十人建议换 PostgreSQLauth.type改成HTTP后要配合反向代理传REMOTE_USER头。2.4 启动与首次验证# 前台启动方便看日志 /var/gerrit/site/bin/gerrit.sh run # 另开终端验证端口 ss -lntp | grep -E 8080|29418逻辑说明gerrit.sh run前台运行日志直接打到终端排错时比start好用。29418 是默认 SSH 评审端口8080 是 Web 端口。两个端口都监听成功说明服务起来了。首次访问 Web 会用第一个登录账户作为管理员所以别急着把地址发给全组先自己登一次。3. repo 工具接入让多仓库按清单一次拉齐3.1 repo 是什么为什么 Gerrit 场景离不开它repo 本身不是 Git 的替代品它是一层 Python 封装读一个manifest.xml清单把清单里列出的几十个 Git 仓库批量 clone 到本地并统一管理分支切换。Gerrit 管的是单个仓库的评审repo 管的是「这一堆仓库在某个版本上应该是什么状态」。两者配合才能让一个多仓库项目有统一的版本基线。常见做法是在 Gerrit 上单独建一个 manifest 仓库里面放default.xml记录每个子仓库的远程地址和默认 revision。开发者只需repo init加repo sync就能拿到完整工程。3.2 安装 repo 客户端# 下载 repo 启动器到用户 bin 目录 mkdir -p ~/bin curl -o ~/bin/repo https://storage.example.com/git-repo/repo chmod ax ~/bin/repo # 加入 PATH echo export PATH$PATH:$HOME/bin ~/.bashrc source ~/.bashrc # 验证 repo --version逻辑说明repo 是一个可执行的 Python 脚本放在 PATH 里就能全局调用。下载地址按团队实际镜像替换不要照抄示例域名。repo --version能打印版本号说明脚本可执行、Python 环境正常。参数说明如果公司内网无法直连外部地址把 repo 脚本放到内部文件服务器用统一路径分发避免每个人去外网拉。3.3 编写 manifest 清单manifest 是 repo 的核心格式是 XML。下面是一个最小可用示例。!-- default.xml多仓库清单 -- manifest !-- 远程定义name 是别名fetch 是 Gerrit 上的仓库根路径 -- remote nameorigin fetchssh://gerritgerrit.example.com:29418 / !-- 默认 revision 指向具体分支 -- default remoteorigin revisionrefs/heads/main / !-- 每个 project 对应一个 Gerrit 仓库 -- project nameplatform/core pathcore / project nameplatform/common pathcommon / project nameplatform/manifest pathmanifest / /manifest逻辑说明remote的fetch是仓库根地址repo 会把project的name拼在后面形成完整地址。default的revision可以是分支名、tag 或 commit id生产环境建议用 tag 锁定基线避免每次 sync 都拉到最新提交导致构建不稳定。path是本地目录名不写则默认用name。参数说明revision写refs/heads/main比写main更明确避免歧义如果某个仓库需要单独指定分支在project标签里覆盖revision属性即可。3.4 用 repo 拉取并验证# 初始化-u 指定 manifest 仓库地址-b 指定分支 repo init -u ssh://gerritgerrit.example.com:29418/platform/manifest -b main # 同步所有仓库 repo sync -j4 # 查看当前清单覆盖的仓库 repo list逻辑说明repo init只拉取 manifest 仓库本身repo sync才按清单拉取全部子仓库。-j4表示四个并发网络好可以调大但别超过 Gerrit 服务端的连接承受能力。repo list列出所有已同步仓库及其路径用来确认清单是否生效。参数说明-b main指定 manifest 仓库的分支不是子仓库分支子仓库分支由清单里的revision决定。如果repo sync报某个仓库权限拒绝先检查 Gerrit 上该仓库的读权限是否对当前账户开放。4. 评审流程打通从 push 到 refs/for 的完整链路4.1 配置 SSH 与 Change-Id 钩子Gerrit 的评审入口是refs/for/分支但提交信息里必须带Change-Id否则 push 会被拒绝。这个 Id 由 commit-msg 钩子生成。# 从 Gerrit 拉取 commit-msg 钩子 scp -p -P 29418 gerritgerrit.example.com:hooks/commit-msg \ .git/hooks/ # 确认钩子可执行 chmod x .git/hooks/commit-msg # 配置用户信息 git config user.name 某开发者 git config user.email devexample.com逻辑说明钩子脚本会在每次git commit时自动在提交信息末尾追加Change-Id。如果仓库是 repo 管理的每个子仓库都要单独放一份钩子或者用repo forall批量执行。用户信息里的邮箱要和 Gerrit 账户绑定的一致否则评审页面显示的作者会错乱。参数说明scp -p保留文件权限-P是大写指定 SSH 端口别和-p混淆。4.2 提交并推送到评审队列# 在某个子仓库里修改后提交 cd core git checkout -b feature/login git add . git commit -m feat: 增加登录校验逻辑 # 推送到评审队列而不是直接推分支 git push origin HEAD:refs/for/main逻辑说明HEAD:refs/for/main表示把当前提交推到 main 分支的评审队列。Gerrit 收到后会生成一个 Change 页面等待评审人打分。如果直接推refs/heads/main有权限的账户会绕过评审直接入库这正是要避免的。参数说明refs/for/后面跟目标分支名不要写成refs/for/refs/heads/main。如果目标分支不存在push 会报错先在 Gerrit 上建好分支。4.3 评审、打分与合入评审人在 Web 页面查看 diff给出 Code-Review 2 和 Verified 1 后点 Submit 合入。命令行也能完成打分。# 查看待评审的 Change ssh -p 29418 gerritgerrit.example.com gerrit query \ --formatJSON status:open # 给某个 Change 打 Code-Review 2 ssh -p 29418 gerritgerrit.example.com gerrit review \ --code-review 2 Change-Id逻辑说明gerrit query支持按状态、项目、所有者过滤--formatJSON方便脚本解析。gerrit review的Change-Id是完整的 Change-Id 字符串不是提交哈希。打分后如果满足合入条件还需要单独执行 submit 或由页面操作。参数说明--code-review取值 -2 到 2-2 是强烈反对2 是批准--verified取值 -1 到 1通常由 CI 系统打。权限不够的账户执行 review 会报错需要在 Gerrit 的权限配置里给对应组授权。5. 避坑与排查那些让服务起不来的细节5.1 端口冲突导致启动即退出现象gerrit.sh start后进程立刻消失日志里出现Address already in use。 原因8080 或 29418 被其他服务占用常见的是系统里已有 Web 服务或另一个 Gerrit 实例。 解决ss -lntp找到占用进程改gerrit.config里的listenUrl和sshd.listenAddress换成空闲端口后重启。5.2 push 被拒missing Change-Id现象git push origin HEAD:refs/for/main报missing Change-Id in commit message footer。 原因commit-msg 钩子没装或没执行权限提交信息里没有 Change-Id 行。 解决把钩子放到对应仓库的.git/hooks/commit-msg并chmod x然后用git commit --amend重新生成提交信息。注意钩子是按仓库生效的repo 下每个子仓库都要放。5.3 repo sync 卡在某个仓库现象repo sync长时间停在某个 project 不动最后超时。 原因该仓库体积过大或 Gerrit 服务端并发连接数被占满。 解决先用repo sync project-name单独同步该仓库定位问题如果是体积问题在清单里给该仓库加clone-depth属性做浅克隆如果是并发问题把-j调小到 2 再试。5.4 评审页面作者显示为匿名现象提交成功但 Gerrit 页面上作者名是Anonymous Coward或邮箱不匹配。 原因Git 的user.email和 Gerrit 账户绑定的邮箱不一致Gerrit 无法关联。 解决在 Gerrit 账户设置里添加该邮箱并验证或把本地user.email改成已绑定的地址。改完后新提交才会生效旧提交需要 amend 后重新 push。5.5 升级后仓库数据丢失现象替换 war 包重启后所有项目不见了。 原因升级时误删了站点目录或gerrit.config里gerrit.basePath指向了错误路径。 解决升级只替换/opt/gerrit下的 war 包站点目录和 Git 根目录原地不动。升级前备份etc/和git/两个目录出问题能快速回滚。这是血泪经验别问怎么知道的。6. 进阶技巧用权限组和 CI 钩子把评审管住服务跑通只是开始真正让团队少扯皮的是权限和自动化。Gerrit 的权限模型围绕「组」展开常见做法是建三个组Administrators管全局配置Reviewers负责打分Submitters负责合入。把refs/heads/*的 Submit 权限只给Submitters把refs/for/*的 Push 权限给所有开发者这样谁都不能绕过评审直接改主干。配置权限时有个容易忽略的点refs/for/refs/heads/*和refs/heads/*是两套规则前者控制评审入口后者控制直接推送。只配前者不配后者有权限的账户仍可能直接推分支。稳妥做法是把refs/heads/*的 Push 权限设为空只保留 Submit。自动化方面可以在 Gerrit 上配hooks/目录下的patchset-created钩子每次有新补丁集就触发 CI 构建构建结果通过gerrit review --verified回写。这样评审人看到 Verified 1 才动手看代码省去大量「改了没编译过」的无效评审。# patchset-created 钩子示例触发构建并回写 Verified #!/bin/bash CHANGE_ID$1 PATCHSET$2 PROJECT$3 # 调用内部构建脚本返回 0 表示通过 if /opt/ci/build.sh $PROJECT $PATCHSET; then ssh -p 29418 gerritgerrit.example.com gerrit review \ --verified 1 $CHANGE_ID else ssh -p 29418 gerritgerrit.example.com gerrit review \ --verified -1 --message 构建失败请检查日志 $CHANGE_ID fi逻辑说明钩子脚本由 Gerrit 在事件发生时调用参数顺序按 Gerrit 文档约定。构建脚本返回非零时打 Verified -1 并附上提示信息评审人一眼能看到失败原因。注意钩子脚本要以 Gerrit 运行账户的身份执行SSH 密钥要提前配好免密。参数说明--message支持多行但命令行里换行要转义--verified只有 -1、0、1 三档别写成 2。最后说个习惯每次动 Gerrit 配置前先cp etc/gerrit.config etc/gerrit.config.bak改完重启验证不行就换回来。这套服务一旦跑起来最怕的不是搭不起来而是搭起来之后没人敢改。把备份和回滚路径留好比记住任何一条命令都管用。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进