Gerrit 代码审查工具实战指南:从零到精通
Gerrit 代码审查工具实战指南:从零到精通
作者:资深 DevOps 工程师
实验环境:ecs-6515 × 4台 | Ubuntu 24.04.4 LTS | Java 21.0.11 | Gerrit 3.9.3
关键词:Gerrit、代码审查、Code Review、Git、Change-Id、refs/for
发布日期:2026-06-12
目录
- 引言:为什么需要 Gerrit?
- Gerrit 核心概念速览
- 环境搭建实战(30 分钟快速上手)
- 账号配置与 SSH 密钥设置
- 代码提交实战:从 Git 到 Gerrit
- 代码评审全流程实战
- 高级功能:与 Jenkins 集成
- 常见问题与解决方案(FAQ)
- 最佳实践与团队协作建议
- 总结与进阶学习
一、引言篇:为什么需要 Gerrit?
1.1 代码审查的价值:一道绕不过去的关卡
任何见过生产事故的工程师都有过这样的经历:一个看似无害的 if 条件写反了,一个未处理的 null 指针,一次粗心的并发竞态——代码上线,告警炸响,凌晨三点紧急回滚。
事后复盘,几乎所有人的结论都是同一句话:“这段代码当时要是被 Review 一下就好了。”
代码审查(Code Review)不是走过场的形式主义,它实实在在地提供三重价值:
| 价值维度 | 具体体现 |
|---|---|
| 质量把关 | 在缺陷进入主干之前拦截 Bug、安全漏洞、性能隐患 |
| 知识共享 | 高级工程师通过 Review 传递设计思路,新人快速上手 |
| 规范统一 | 命名约定、错误处理、日志风格趋于一致,减少技术债 |
Google 内部研究表明,代码审查能在 CI/CD 之前发现约 60% 的 Bug,而修复成本仅为生产修复的 1/30。
1.2 传统代码审查的痛点
"传统方式"通常是这样的:
- 开发者在群里发一句"帮我看看这个 MR",链接丢在消息流里,三天后无人响应
- 评审意见散落在邮件、IM、会议记录里,追踪困难
- "走读式"Code Review:大家轮流看屏幕,效率极低
- 合并代码全靠人工判断"这个可以了",没有强制门禁
这些痛点的根源在于:流程没有工具约束,质量靠个人自觉。
1.3 为什么是 Gerrit?
Gerrit 是 Google 开源的基于 Git 的 Web 代码审查平台,诞生于 Android 项目,如今广泛应用于 AOSP、Eclipse、Qt 等顶级开源项目及众多企业内部。
选择 Gerrit 的核心理由:
- 强制门禁:代码必须经过审批才能合并,无人能绕过
- 精细权限:
+2才能合并,-2可一票否决,权限粒度极细 - Patch Set 机制:每次修改形成新版本,完整保留评审历史
- 与 Jenkins 深度集成:构建结果自动写回 Gerrit,
Verified+1后才能合并 - 自托管:数据完全自主,适合企业安全合规要求
1.4 实验架构总览
本文基于以下 4 台华为云 ECS 进行全程实操:
┌─────────────────────────────────────────────────────────────────┐
│ ecs-6515 实验集群架构 │
│ │
│ gerrit-01 (114.116.234.168) gerrit-02 (119.3.183.130) │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ Gerrit 3.9.3 │ │ Jenkins CI │ │
│ │ Port: :8080 (Web) │ │ Port: :8080 │ │
│ │ :29418 (SSH) │ │ Gerrit Trigger Plugin │ │
│ │ Nginx :80 (反向代理) │ └─────────────────────────┘ │
│ └───────────┬─────────────┘ │
│ │ SSH Clone/Push │
│ gerrit-03 (119.3.238.229) gerrit-04 (120.46.82.41) │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 开发机(代码提交演示) │ │ 备用 / SonarQube │ │
│ │ git 2.43.0 │ │ │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ 规格:2vCPU / 3.3GiB / 40GB Ubuntu 24.04.4 LTS │
└─────────────────────────────────────────────────────────────────┘
二、Gerrit 核心概念速览
在动手之前,先把几个核心概念搞清楚——很多人踩坑,就是因为概念没理清。
2.1 Gerrit 是什么?
用一句话描述:Gerrit = Git 服务器 + 代码审查 Web 界面 + 权限控制系统。
开发者本地 Git ──push──▶ Gerrit(审查中转站) ──merge──▶ 主干分支
▲
评审者在此评分
Code-Review: +2
Verified: +1
它不是 GitHub/GitLab 的替代品,而是一个更专注于"代码合并前强制审查"的工具。
2.2 Change-Id:变更的身份证
每个提交到 Gerrit 的代码变更都有一个 Change-Id,格式如下:
Change-Id: Ic0d4dbc52b6354b654b58dd3da1681218dab503b
类比:就像身份证号码,无论你换了多少张照片(Patch Set),身份证号始终不变。Change-Id 唯一标识一个"待审查的变更",不随代码修改而改变。
Change-Id 由 commit-msg Git Hook 自动生成,安装后每次 git commit 都会自动追加到 Commit Message 末尾。
2.3 Patch Set:同一变更的不同版本
当评审者说"这里有问题,改一下",开发者修改后重新 push,Gerrit 会在同一个 Change 下创建新的 Patch Set:
Change #1: "feat: add hello module with greet function"
Change-Id: Ic0d4dbc52b6354b654b58dd3da1681218dab503b
├── Patch Set 1 (初始提交,2026-06-12 11:50)
├── Patch Set 2 (修复空指针检查,根据评审意见修改)
└── Patch Set 3 (优化注释,最终版本) ← 当前最新
评审者可以查看任意两个 Patch Set 之间的差异,清晰掌握修改历史。
2.4 refs/for/*:神奇的推送目标
正常推送到 Git:
git push origin master # 直接提交到主干
推送到 Gerrit 审查队列:
git push origin HEAD:refs/for/master # 进入审查,等待批准
refs/for/master 是 Gerrit 的虚拟引用:它不是真实分支,而是 Gerrit 的"收件箱"。push 到这里的代码不会立即合并,而是生成一个等待审查的 Change。
类比:就像给领导发邮件申请签字,不是直接把合同盖章——得等领导看完才能盖。
2.5 Code Review 评分体系
Gerrit 内置了两个核心标签:
| 标签 | 分值 | 含义 |
|---|---|---|
| Code-Review | +2 | 代码没问题,可以合并(只有高级权限才能打) |
| Code-Review | +1 | 看起来不错,但需要其他人也确认 |
| Code-Review | 0 | 无意见 |
| Code-Review | -1 | 不建议合并(软拒绝,可被覆盖) |
| Code-Review | -2 | 一票否决,必须有此权限的人改为 +2 才能合并 |
| Verified | +1 | 构建/测试通过(通常由 Jenkins 自动打) |
| Verified | -1 | 构建/测试失败,禁止合并 |
关键规则:只有同时满足
Code-Review >= +2且Verified >= +1时,Change 才能被 Submit(合并)。任何一个-2一票否决。
2.6 Gerrit 工作流总览
开发者 Gerrit 评审者/Jenkins
│ │ │
│── git commit ─────────────│ │
│ (commit-msg hook 生成 │ │
│ Change-Id) │ │
│ │ │
│── git push refs/for/master▶│ │
│ │── 通知评审者 ─────────────▶│
│ │ │
│ │◀── Code-Review +2 ────────│
│ │◀── Verified +1 (Jenkins) ─│
│ │ │
│ │── Submit(自动合并到master)│
│ │ │
│◀── 合并成功通知 ───────────│ │
三、环境搭建实战
3.1 前置条件
| 组件 | 版本要求 | 实际版本 |
|---|---|---|
| Java (JDK) | ≥ 11,推荐 17/21 | OpenJDK 21.0.11 |
| Git | ≥ 2.x | 2.43.0 |
| OS | Linux (推荐 Ubuntu 20.04+) | Ubuntu 24.04.4 LTS |
| 内存 | ≥ 2GiB(推荐 4GiB+) | 3.3GiB |
| 端口 | 8080(Web)、29418(SSH) | 已开放 |
3.2 安装 OpenJDK 21
# gerrit-01: 114.116.234.168
root@ecs-6515-0001:~# apt-get update -qq
root@ecs-6515-0001:~# DEBIAN_FRONTEND=noninteractive apt-get install -y openjdk-21-jdk-headless
# 验证安装
root@ecs-6515-0001:~# java -version
实机输出:
openjdk version "21.0.11" 2026-04-21
OpenJDK Runtime Environment (build 21.0.11+10-1-24.04.2-Ubuntu)
OpenJDK 64-Bit Server VM (build 21.0.11+10-1-24.04.2-Ubuntu, mixed mode, sharing)
3.3 创建专用用户并下载 Gerrit WAR
# 创建 gerrit 系统用户
root@ecs-6515-0001:~# useradd -m -s /bin/bash gerrit
# 输出: gerrit user created
# 创建安装目录并下载 Gerrit 3.9.3 WAR 包
root@ecs-6515-0001:~# mkdir -p /opt/gerrit
root@ecs-6515-0001:~# wget -q \
https://gerrit-releases.storage.googleapis.com/gerrit-3.9.3.war \
-O /opt/gerrit/gerrit.war
# 验证下载
root@ecs-6515-0001:~# ls -lh /opt/gerrit/gerrit.war
实机输出:
-rw-r--r-- 1 root root 84M Apr 12 2024 /opt/gerrit/gerrit.war
💡 提示:如果直连 Google 下载慢,可以先下载到本地再 scp 上传。
3.4 初始化 Gerrit Site
# 授权目录给 gerrit 用户
root@ecs-6515-0001:~# chown -R gerrit:gerrit /opt/gerrit
# 以 gerrit 用户身份初始化(--batch 跳过交互,--dev 启用开发模式)
root@ecs-6515-0001:~# su - gerrit -c \
'java -jar /opt/gerrit/gerrit.war init --batch --dev -d /opt/gerrit/review_site 2>&1'
实机输出(关键片段):
[2026-06-12 11:35:41,501] [main] INFO com.google.gerrit.server.config.GerritServerConfigProvider :
No /opt/gerrit/review_site/etc/gerrit.config; assuming defaults
Auto-configured "receive.autogc = false" to disable auto-gc after git-receive-pack.
Generating SSH host key ... rsa... ed25519... ecdsa 256... ecdsa 384... ecdsa 521... done
Initialized /opt/gerrit/review_site
Reindexing accounts: 100% (1/1) done
Index accounts in version 13 is ready
Reindexing groups: 100% (2/2) done
Index groups in version 10 is ready
Reindexing changes: done
Index changes in version 84 is ready
Reindexing projects: 100% (2/2) done
Index projects in version 8 is ready
Executing /opt/gerrit/review_site/bin/gerrit.sh start
Starting Gerrit Code Review: OK
初始化完成后,Gerrit 目录结构如下:
/opt/gerrit/review_site/
├── bin/
│ └── gerrit.sh # 启动/停止脚本
├── etc/
│ └── gerrit.config # 核心配置文件
├── git/ # 仓库存储目录(bare git repos)
│ ├── All-Projects.git # 全局权限配置仓库
│ └── All-Users.git # 用户数据仓库
├── index/ # Lucene 索引
├── cache/ # 缓存目录
└── logs/ # 日志目录
├── error_log # 错误日志
└── httpd_log # HTTP 访问日志
3.5 核心配置文件解析
root@ecs-6515-0001:~# cat /opt/gerrit/review_site/etc/gerrit.config
实机输出:
[gerrit]
basePath = git # 仓库根目录(相对于 review_site)
canonicalWebUrl = http://114.116.234.168/ # 对外访问的 Web 地址
serverId = c0828ead-9210-4414-b5c8-0b7ca10db172 # 实例唯一 ID
[container]
javaOptions = "-Dflogger.backend_factory=..." # JVM 日志配置
user = gerrit # 运行用户
javaHome = /usr/lib/jvm/java-21-openjdk-amd64 # Java 路径
[index]
type = lucene # 搜索索引引擎(默认 Lucene)
[auth]
type = DEVELOPMENT_BECOME_ANY_ACCOUNT # 认证模式(开发/测试用)
userNameCaseInsensitive = true
[receive]
enableSignedPush = false # 是否启用 GPG 签名推送
[sendemail]
smtpServer = localhost # 邮件服务器
[sshd]
listenAddress = *:29418 # SSH 监听地址和端口
[httpd]
listenUrl = http://*:8080/ # HTTP 监听地址
[cache]
directory = cache # 缓存目录
关键配置参数说明:
| 参数 | 含义 | 生产建议 |
|---|---|---|
canonicalWebUrl |
对外访问地址,影响所有链接生成 | 设置为真实域名或 IP |
auth.type |
认证方式 | 生产用 HTTP(nginx htpasswd)或 LDAP |
sshd.listenAddress |
Gerrit SSH 端口,用于 git push | 必须开放防火墙 |
httpd.listenUrl |
proxy-http 表示 nginx 代理模式 |
nginx 反代时改为 proxy-http://*:8080/ |
3.6 配置 systemd 服务
root@ecs-6515-0001:~# cat > /etc/systemd/system/gerrit.service << 'EOF'
[Unit]
Description=Gerrit Code Review Service
After=network.target
[Service]
Type=forking
User=gerrit
Group=gerrit
ExecStart=/opt/gerrit/review_site/bin/gerrit.sh start
ExecStop=/opt/gerrit/review_site/bin/gerrit.sh stop
ExecReload=/opt/gerrit/review_site/bin/gerrit.sh restart
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
root@ecs-6515-0001:~# systemctl daemon-reload
root@ecs-6515-0001:~# systemctl enable gerrit
root@ecs-6515-0001:~# systemctl start gerrit
验证服务状态:
root@ecs-6515-0001:~# systemctl status gerrit --no-pager
实机输出:
● gerrit.service - Gerrit Code Review Service
Loaded: loaded (/etc/systemd/system/gerrit.service; enabled; preset: enabled)
Active: active (running) since Fri 2026-06-12 11:36:59 CST; 5s ago
Process: 9347 ExecStart=/opt/gerrit/review_site/bin/gerrit.sh start (code=exited, status=0/SUCCESS)
Main PID: 9413 (java)
Tasks: 81 (limit: 3994)
Memory: 345.2M (peak: 345.7M)
CPU: 9.564s
CGroup: /system.slice/gerrit.service
└─9413 GerritCodeReview -jar /opt/gerrit/review_site/bin/gerrit.war daemon ...
Jun 12 11:36:59 ecs-6515-0001 gerrit.sh[9347]: OK
Jun 12 11:36:59 ecs-6515-0001 systemd[1]: Started gerrit.service - Gerrit Code Review Service.
验证端口监听:
root@ecs-6515-0001:~# ss -tlnp | grep -E "8080|29418"
实机输出:
LISTEN 0 50 *:29418 *:* users:(("java",pid=9413,fd=180))
LISTEN 0 50 *:8080 *:* users:(("java",pid=9413,fd=183))
至此,Gerrit 已成功启动,Web 界面访问地址:http://114.116.234.168:8080
四、账号配置与 SSH 密钥设置
4.1 首次登录(开发模式)
在开发/测试模式(DEVELOPMENT_BECOME_ANY_ACCOUNT)下,Gerrit 初始化时会自动创建 admin 账号(account_id=1000000)。
通过 REST API 验证 admin 账号:
# 先建立 session(DEVELOPMENT 模式特有)
curl -s -c /tmp/gc.txt -b /tmp/gc.txt -L \
"http://localhost:8080/login/%2F?account_id=1000000" \
-o /dev/null -w "http_code=%{http_code}\n"
# 验证当前用户
curl -s -b /tmp/gc.txt "http://localhost:8080/a/accounts/self"
实机输出:
{"_account_id":1000000,"name":"Administrator","email":"admin@example.com","username":"admin"}
4.2 在开发机(gerrit-03)生成 SSH 密钥
# 在 gerrit-03 (119.3.238.229) 上操作
root@ecs-6515-0003:~# ssh-keygen -t rsa -b 4096 \
-C "developer@gerrit-lab" \
-f /root/.ssh/id_rsa -N ""
实机输出:
Generating public/private rsa key pair.
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:Ov3h7sZNIAUkkM+T6DNuh5DmrEzvEhm0o1EBhnup1Uw developer@gerrit-lab
The key's randomart image is:
+---[RSA 4096]----+
|oo...o..o. |
|o.. E . . |
|.o.= + . . |
|o++ + = . . |
|.=+o .S. . |
|oo+ + o . |
| =.o +o ...o |
|o.+ + .. oo.. |
|.oo+ . += |
+----[SHA256]-----+
查看公钥:
root@ecs-6515-0003:~# cat /root/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDIj8//lCn2PFQ... developer@gerrit-lab
4.3 将 SSH 公钥添加到 Gerrit
方式一:通过 REST API(脚本化)
# 在 gerrit-01 上执行,通过 session cookie 添加公钥
curl -s -b /tmp/gc.txt \
-H "X-Gerrit-Auth: ${XSRF_TOKEN}" \
-H "Content-Type: text/plain" -X POST \
--data-binary @/tmp/gerrit_pubkey.txt \
"http://localhost:8080/a/accounts/self/sshkeys"
实机输出:
{"seq":1,
"ssh_public_key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB... developer@gerrit-lab",
"encoded_key":"AAAAB3NzaC1yc2EAAAADAQAB...",
"algorithm":"ssh-rsa",
"comment":"developer@gerrit-lab",
"valid":true}
方式二:通过 Web UI
- 登录
http://114.116.234.168→ 右上角用户头像 → Settings - 左侧菜单 → SSH Keys
- 点击 Add New SSH Key
- 粘贴
~/.ssh/id_rsa.pub内容 → Save
4.4 测试 SSH 连接
# 在 gerrit-03 开发机上测试
root@ecs-6515-0003:~# ssh -o StrictHostKeyChecking=no -p 29418 \
admin@114.116.234.168 gerrit version
实机输出:
Warning: Permanently added '[114.116.234.168]:29418' (ED25519) to the list of known hosts.
gerrit version 3.9.3
SSH 连接成功!
4.5 生成 HTTP Password(REST API 调用必需)
# 生成 Gerrit HTTP Password(用于 REST API 认证)
curl -s -b /tmp/gc.txt \
-H "X-Gerrit-Auth: ${XSRF_TOKEN}" \
-H "Content-Type: application/json" -X PUT \
--data '{"generate":true}' \
"http://localhost:8080/a/accounts/self/password.http"
实机输出:
"SqKrCQPrj0sc86zSqEfcsRUSSSBYmc6aZ2wimB8hsg"
说明:HTTP Password 是独立于登录密码的令牌,专用于 git clone/push 的 HTTP 认证和 REST API 调用。类似 GitHub 的 Personal Access Token。
五、代码提交实战
5.1 创建演示项目
# 通过 REST API 创建项目(gerrit-01 上执行)
curl -s -b /tmp/gc.txt \
-H "X-Gerrit-Auth: ${XSRF_TOKEN}" \
-H "Content-Type: application/json" -X PUT \
--data '{"description":"Demo project for Gerrit blog","create_empty_commit":true}' \
"http://localhost:8080/a/projects/demo-project"
实机输出:
{
"id": "demo-project",
"name": "demo-project",
"parent": "All-Projects",
"description": "Demo project for Gerrit blog",
"state": "ACTIVE",
"labels": {
"Code-Review": {
"values": {
" 0": "No score",
"-1": "I would prefer this is not submitted as is",
"-2": "This shall not be submitted",
"+1": "Looks good to me, but someone else must approve",
"+2": "Looks good to me, approved"
},
"default_value": 0
}
}
}
5.2 克隆项目到开发机
# 在 gerrit-03 (119.3.238.229) 上操作
root@ecs-6515-0003:~# git clone \
ssh://admin@114.116.234.168:29418/demo-project \
/root/demo-project
实机输出:
Cloning into '/root/demo-project'...
Total 2 (delta 0), reused 0 (delta 0)
5.3 安装 commit-msg Hook(关键步骤!)
这是 Gerrit 工作流最重要的一步——没有这个 Hook,提交时不会自动生成 Change-Id,push 会失败。
root@ecs-6515-0003:~# mkdir -p /root/demo-project/.git/hooks
root@ecs-6515-0003:~# curl -Lo /root/demo-project/.git/hooks/commit-msg \
http://114.116.234.168:8080/tools/hooks/commit-msg
root@ecs-6515-0003:~# chmod +x /root/demo-project/.git/hooks/commit-msg
# 验证
root@ecs-6515-0003:~# wc -c /root/demo-project/.git/hooks/commit-msg
实机输出:
3012 /root/demo-project/.git/hooks/commit-msg
Hook 文件来自 Gerrit 服务器,确保与当前版本匹配。路径固定为 /tools/hooks/commit-msg。
5.4 配置 Git 用户信息
root@ecs-6515-0003:~# cd /root/demo-project
root@ecs-6515-0003:/root/demo-project# git config user.email "developer@gerrit-lab"
root@ecs-6515-0003:/root/demo-project# git config user.name "Zhang San"
5.5 创建代码并提交
root@ecs-6515-0003:/root/demo-project# mkdir -p src
root@ecs-6515-0003:/root/demo-project# cat > src/hello.py << 'EOF'
#!/usr/bin/env python3
"""
Hello World module
Author: Zhang San
Date: 2026-06-12
"""
def greet(name: str) -> str:
"""Return a greeting message."""
if not name:
raise ValueError("name cannot be empty")
return f"Hello, {name}! Welcome to Gerrit Code Review."
if __name__ == "__main__":
print(greet("Gerrit"))
EOF
root@ecs-6515-0003:/root/demo-project# git add src/hello.py
root@ecs-6515-0003:/root/demo-project# git commit -m "feat: add hello module with greet function
Add a simple greeting module as demo for Gerrit code review workflow.
The greet() function accepts a name parameter and returns a formatted message."
实机输出:
[master f38200b] feat: add hello module with greet function
1 file changed, 15 insertions(+)
create mode 100644 src/hello.py
查看 commit-msg Hook 自动生成的 Change-Id:
root@ecs-6515-0003:/root/demo-project# git log -1 --format="%B" | tail -3
实机输出:
The greet() function accepts a name parameter and returns a formatted message.
Change-Id: Ic0d4dbc52b6354b654b58dd3da1681218dab503b
✅ Change-Id 自动生成,这就是 Gerrit 追踪变更的关键标识。
5.6 Push 到 Gerrit 审查队列
# 核心命令:推送到 refs/for/master,而不是 master
root@ecs-6515-0003:/root/demo-project# git push \
ssh://admin@114.116.234.168:29418/demo-project \
HEAD:refs/for/master
实机输出:
remote: Processing changes: refs: 1, new: 1, done
remote:
remote: SUCCESS
remote:
remote: http://114.116.234.168/c/demo-project/+/1 feat: add hello module with greet function [NEW]
remote:
To ssh://114.116.234.168:29418/demo-project
* [new reference] HEAD -> refs/for/master
关键信息解读:
new: 1→ 创建了 1 个新 Change[NEW]→ Change 状态为新建,等待评审http://114.116.234.168/c/demo-project/+/1→ Change 的 Web 访问地址,Change #1
六、代码评审全流程实战
6.1 查看 Change 详情
评审者收到通知后,通过 REST API 或 Web UI 查看 Change:
# REST API 查看 Change 详情
curl -s -b /tmp/gc.txt \
"http://localhost:8080/a/changes/1/detail" | python3 -c "
import sys, json
d = sys.stdin.read()[5:] # 跳过 XSSI 前缀 )]}'\n
obj = json.loads(d)
print('Change ID: ', obj['id'])
print('Subject: ', obj['subject'])
print('Status: ', obj['status'])
print('Owner: ', obj['owner']['name'])
print('Created: ', obj['created'])
"
实机输出:
Change ID: demo-project~1
Subject: feat: add hello module with greet function
Status: NEW
Owner: Administrator
Created: 2026-06-12 03:50:53.000000000
6.2 代码审查评分
评审者查看代码差异后,进行评分:
# Code-Review +2(批准)并添加评论
curl -s -b /tmp/gc.txt \
-H "X-Gerrit-Auth: ${XSRF_TOKEN}" \
-H "Content-Type: application/json" -X POST \
--data '{
"labels": {"Code-Review": 2},
"message": "LGTM! Code looks clean and well-documented. The error handling is proper."
}' \
"http://localhost:8080/a/changes/1/revisions/current/review"
实机输出:
{"labels": {"Code-Review": 2}}
评分流程说明:
评审者操作 含义
─────────────────────────────────────────────────────
Code-Review: +2 "我批准,可以合并"
Code-Review: +1 "我看了没问题,但需要其他人也批"
Code-Review: -1 "有小问题,建议修改但不强制"
Code-Review: -2 "有严重问题,必须修改后才能合并"
添加行级评论 "第42行,这里应该加异常处理"
6.3 Submit(合并到主干)
当 Change 满足合并条件(Code-Review >= +2)时,可以 Submit:
curl -s -b /tmp/gc.txt \
-H "X-Gerrit-Auth: ${XSRF_TOKEN}" \
-H "Content-Type: application/json" -X POST \
--data '{}' \
"http://localhost:8080/a/changes/1/submit"
实机输出(关键字段):
{
"id": "demo-project~1",
"change_id": "Ic0d4dbc52b6354b654b58dd3da1681218dab503b",
"subject": "feat: add hello module with greet function",
"status": "MERGED",
"submitted": "2026-06-12 03:51:21.000000000",
"submitter": {"_account_id": 1000000}
}
✅ Change 状态变为 MERGED,代码已合并到 master 分支!
6.4 Patch Set 更新流程(评审后修改)
当评审者说"有问题,改一下",开发者修改代码后不是发新的 Change,而是在同一个 Change 下追加 Patch Set:
# 修改代码
root@ecs-6515-0003:/root/demo-project# vim src/hello.py
# ... 根据评审意见修改 ...
# 重新提交(注意:不要 git commit --amend,要保持 Change-Id 一致)
root@ecs-6515-0003:/root/demo-project# git add src/hello.py
root@ecs-6515-0003:/root/demo-project# git commit --amend --no-edit
# 再次 push(Change-Id 不变,Gerrit 识别为同一 Change 的新 Patch Set)
root@ecs-6515-0003:/root/demo-project# git push \
ssh://admin@114.116.234.168:29418/demo-project \
HEAD:refs/for/master
预期输出:
remote: Processing changes: refs: 1, updated: 1, done
remote:
remote: SUCCESS
remote:
remote: http://114.116.234.168/c/demo-project/+/1 feat: add hello module [PS2]
注意 updated: 1 和 [PS2]——这是第 2 个 Patch Set,同一个 Change #1 下。
七、高级功能:与 Jenkins 集成
7.1 集成架构
触发构建
开发者 push refs/for/master ──────────▶ Jenkins
│
Gerrit ◀── Verified+1/Verified-1 ──────────┘
│ 构建结果回写
│
(Verified+1 后才可 Submit)
7.2 安装 Gerrit Trigger 插件
在 Jenkins(gerrit-02: 119.3.183.130)上操作:
Jenkins → 系统管理 → 插件管理 → 搜索 "Gerrit Trigger"
→ 安装 → 重启 Jenkins
7.3 配置 Jenkins 连接 Gerrit
步骤一:在 Gerrit 上创建 Jenkins 专用账号
# 通过 Gerrit REST API 创建 jenkins 账号
curl -s -b /tmp/gc.txt \
-H "X-Gerrit-Auth: ${XSRF_TOKEN}" \
-H "Content-Type: application/json" -X PUT \
--data '{"name":"Jenkins CI","email":"jenkins@gerrit-lab"}' \
"http://localhost:8080/a/accounts/jenkins"
步骤二:Jenkins 系统配置
Jenkins → 系统配置 → Gerrit Trigger → Add New Server
Server Name: gerrit-01
Hostname: 114.116.234.168
Frontend URL: http://114.116.234.168/
SSH Port: 29418
Username: jenkins
SSH Key File: /var/lib/jenkins/.ssh/id_rsa
步骤三:配置 Job 触发规则
在 Jenkins Job 的配置中:
Build Triggers → Gerrit event
→ Trigger on: Patchset Created
→ Gerrit Project: demo-project
→ Branches: master
步骤四:构建后写回 Verified
// Jenkinsfile 示例
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'python3 -m pytest src/'
}
}
}
post {
success {
gerritReview(
labels: [verified: 1],
message: "Build PASSED: ${env.BUILD_URL}"
)
}
failure {
gerritReview(
labels: [verified: -1],
message: "Build FAILED: ${env.BUILD_URL}"
)
}
}
}
7.4 完整 CI/CD 流程
开发者 push refs/for/master
│
▼
Gerrit Change 创建
│
├──── 通知 Jenkins(Gerrit Trigger)
│ │
│ Jenkins 拉取代码并构建
│ │
│ 测试通过? ──YES──▶ Verified +1
│ │
│ 测试失败? ──YES──▶ Verified -1(Change 无法合并)
│
├──── 等待人工 Code-Review +2
│
└──── Code-Review ≥+2 AND Verified +1
│
Submit(合并到 master)
│
触发生产构建/部署
八、常见问题与解决方案(FAQ)
Q1:push 时报 “missing Change-Id in commit message”
错误信息:
remote: ERROR: commit f38200b: missing Change-Id in commit message
原因:没有安装或激活 commit-msg Hook。
解决方案:
# 1. 下载 Hook
curl -Lo .git/hooks/commit-msg \
http://<gerrit-host>:8080/tools/hooks/commit-msg
chmod +x .git/hooks/commit-msg
# 2. 为已有的 commit 补充 Change-Id(amend 触发 Hook)
git commit --amend --no-edit
Q2:SSH 连接报 “Permission denied (publickey)”
错误信息:
admin@114.116.234.168: Permission denied (publickey).
排查步骤:
# 1. 确认公钥已添加到 Gerrit
curl -s -b /tmp/gc.txt \
"http://localhost:8080/a/accounts/self/sshkeys"
# 2. 确认私钥路径正确
ssh -i ~/.ssh/id_rsa -p 29418 admin@114.116.234.168 gerrit version
# 3. 检查 Gerrit SSH 日志
tail -20 /opt/gerrit/review_site/logs/sshd_log
# 4. 确认 Gerrit 账号用户名与 SSH 连接用户名一致
ssh -o StrictHostKeyChecking=no -p 29418 你的用户名@gerrit-host gerrit version
Q3:如何修改已提交的 Change?
# 方案一:amend 最后一次提交(保持 Change-Id 不变)
git add 修改的文件
git commit --amend --no-edit # --no-edit 保留原有提交信息
git push ssh://admin@gerrit-host:29418/项目 HEAD:refs/for/master
# 方案二:rebase 修改历史中的某次提交
git rebase -i HEAD~3
# 在编辑器中将目标 commit 标记为 edit
# 修改文件 → git add → git commit --amend → git rebase --continue
Q4:如何处理代码冲突?
# 1. 拉取最新代码
git fetch origin master
# 2. rebase 到最新
git rebase origin/master
# 3. 解决冲突
vim 冲突文件
git add 冲突文件
git rebase --continue
# 4. 重新 push(Change-Id 不变,Gerrit 识别为新 Patch Set)
git push ssh://admin@gerrit-host:29418/项目 HEAD:refs/for/master
Q5:如何放弃(Abandon)一个 Change?
# 通过 REST API
curl -s -b /tmp/gc.txt \
-H "X-Gerrit-Auth: ${XSRF_TOKEN}" \
-H "Content-Type: application/json" -X POST \
--data '{"message":"Not needed anymore"}' \
"http://localhost:8080/a/changes/1/abandon"
# 通过 Gerrit SSH 命令
ssh -p 29418 admin@gerrit-host gerrit review --abandon \
--message "Not needed anymore" \
<change-id>,<patch-set>
Q6:OOM Killer 警告如何处理?
警告信息:
WARNING: Could not adjust Gerrit's process for the kernel's out-of-memory killer.
解决方案:
# 临时处理
PID=$(pgrep -f GerritCodeReview)
echo '-1000' | tee /proc/$PID/oom_score_adj
# 永久处理:在 systemd 服务中添加
[Service]
OOMScoreAdjust=-1000
Q7:Gerrit 启动慢,reindex 要多久?
首次启动或升级版本后,Gerrit 需要重建索引。对于大型仓库,可以后台进行:
# 后台重建所有索引
ssh -p 29418 admin@gerrit-host gerrit index start \
--force --background
# 查看索引进度
ssh -p 29418 admin@gerrit-host gerrit index activate changes
九、最佳实践与团队协作建议
9.1 Commit Message 规范
Gerrit 对 Commit Message 格式有要求,建议遵循 Conventional Commits 规范:
<类型>(<范围>): <简短描述>
<详细说明(可选)>
<Breaking Change(可选)>
Change-Id: Ixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
类型参考:
| 类型 | 含义 | 示例 |
|---|---|---|
feat |
新功能 | feat(auth): add OAuth2 login |
fix |
Bug 修复 | fix(parser): handle empty input |
docs |
文档 | docs: update API reference |
refactor |
重构 | refactor: extract common utils |
test |
测试 | test: add unit tests for parser |
chore |
构建/配置 | chore: update CI config |
9.2 Change 粒度建议
❌ 错误做法:一个 Change 包含 5 个功能
✅ 正确做法:每个逻辑功能一个 Change
Change 大小建议:
- 小:< 100 行,易于快速审查
- 中:100-400 行,需要分模块讲解
- 大:> 400 行,考虑拆分
9.3 评审效率技巧
评审者:
- 先看整体架构,再看细节
- 给出具体的改进建议,不要只说"不对"
- 使用 Gerrit 行级评论,定位到具体代码行
- Code-Review +1 表示"我看了",+2 才是正式批准
开发者:
- 提交前自己先 Review 一遍(
git diff --cached) - 在 Change 描述里说明设计决策和背景
- 评审意见全部处理后,Reply 并 @评审者确认
- 回复评论时标记 “Done” 或解释为什么不改
9.4 团队角色分工
Administrator(管理员)
├── 管理用户权限和项目配置
├── 维护 All-Projects 全局设置
└── 处理紧急情况(如 revert)
Tech Lead(技术负责人)
├── 有 Code-Review +2 权限
├── 负责架构层面的审查
└── 把控技术方向
Reviewer(普通评审者)
├── 有 Code-Review +1 权限
├── 关注业务逻辑和代码质量
└── 提出改进建议
Developer(开发者)
├── 提交 Change 请求评审
└── 根据评审意见修改代码
9.5 Gerrit 与 GitFlow 结合
GitFlow 分支模型 + Gerrit 审查:
main ◀── (只有 Gerrit Submit 能写入)
↑
develop ◀── feature/* 合并,需通过 Code-Review +2
↑
feature/* ── push refs/for/develop ──▶ Gerrit 审查
↓
Verified+1 (Jenkins)
↓
Code-Review +2
↓
Submit → develop
十、总结与进阶学习
10.1 本文核心要点回顾
通过本文的实战演练,我们完整走完了 Gerrit 的核心流程:
| 步骤 | 关键操作 | 验证点 |
|---|---|---|
| 安装 | Java 21 + gerrit.war init | systemctl status gerrit → active |
| 连接 | SSH 公钥 + gerrit version | gerrit version 3.9.3 |
| 克隆 | git clone ssh://gerrit:29418/project | 仓库拉取成功 |
| 提交 | commit-msg hook → Change-Id | Change-Id: I... 自动生成 |
| 审查 | push refs/for/master | [NEW] Change 创建 |
| 评分 | Code-Review +2 | {"labels":{"Code-Review":2}} |
| 合并 | Submit → MERGED | Change 状态 = MERGED |
10.2 Gerrit vs GitHub PR vs GitLab MR 对比
| 对比维度 | Gerrit | GitHub PR | GitLab MR |
|---|---|---|---|
| 强制审查 | ✅ 无法绕过 | ❌ 可以关闭保护 | 部分支持 |
| Patch Set | ✅ 完整历史 | ❌ 强制追加 | 部分支持 |
| 权限粒度 | ✅ 极细(行级) | ❌ 较粗 | 中等 |
| 自托管 | ✅ 完全自主 | 部分(企业版) | ✅ 支持 |
| 学习成本 | ❌ 较高 | ✅ 低 | ✅ 低 |
| CI 集成 | ✅ 深度(Verified) | ✅ Actions | ✅ Pipeline |
| 适用场景 | 大型团队/开源项目 | 中小团队/开源 | 企业/全功能 |
选型建议:
- 追求最严格审查门禁 → Gerrit
- 团队规模 < 50人,快速迭代 → GitHub PR / GitLab MR
- 已有 GitLab 基础设施 → GitLab MR
10.3 进阶学习资源
| 资源 | 地址 |
|---|---|
| Gerrit 官方文档 | https://gerrit-review.googlesource.com/Documentation/ |
| Gerrit REST API | https://gerrit-review.googlesource.com/Documentation/rest-api.html |
| AOSP 代码提交指南 | https://source.android.com/docs/setup/contribute |
| Gerrit GitHub 仓库 | https://github.com/GerritCodeReview/gerrit |
10.4 踩坑总结
| 坑 | 原因 | 解决方案 |
|---|---|---|
missing Change-Id |
未安装 commit-msg Hook | 下载 Hook 并 git commit --amend |
| SSH 认证失败 | 公钥未添加/用户名不匹配 | 检查 Gerrit 账号设置 |
| HTTP API 401 | 未使用 HTTP Password / 需要 X-Gerrit-Auth | 生成 HTTP Password,POST/PUT 带 XSRF token |
proxy-http 模式 401 |
nginx 没正确传 Authorization 头 |
proxy_pass_header Authorization |
| OOM Killer 警告 | Gerrit 进程优先级低 | /proc/<pid>/oom_score_adj = -1000 |
| 合并后 git log 为空 | bare repository 需用 --work-tree |
git --git-dir=xxx.git log |
附录:快速参考卡
┌────────────────────────────────────────────────────────────────┐
│ Gerrit 日常操作速查 │
├─────────────────────────────┬──────────────────────────────────┤
│ 操作 │ 命令 │
├─────────────────────────────┼──────────────────────────────────┤
│ 提交到审查队列 │ git push origin HEAD:refs/for/master │
│ 提交到指定分支 │ git push origin HEAD:refs/for/feature/xxx │
│ 绕过审查直接推(需权限) │ git push origin HEAD:master │
│ 查看 Gerrit 版本 │ ssh -p 29418 user@host gerrit version │
│ 列出项目 │ ssh -p 29418 user@host gerrit ls-projects │
│ 批准并合并 │ ssh -p 29418 user@host gerrit review --code-review 2 --submit <id> │
│ 放弃 Change │ ssh -p 29418 user@host gerrit review --abandon <id> │
└─────────────────────────────┴──────────────────────────────────┘
实验环境信息(2026-06-12)
- gerrit-01: 114.116.234.168 → Gerrit 3.9.3 主节点(:8080/:29418)
- gerrit-03: 119.3.238.229 → 开发机演示
- Gerrit Web UI: http://114.116.234.168:8080
- admin 账号:通过 DEVELOPMENT 模式 account_id=1000000 访问
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)