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


目录

  1. 引言:为什么需要 Gerrit?
  2. Gerrit 核心概念速览
  3. 环境搭建实战(30 分钟快速上手)
  4. 账号配置与 SSH 密钥设置
  5. 代码提交实战:从 Git 到 Gerrit
  6. 代码评审全流程实战
  7. 高级功能:与 Jenkins 集成
  8. 常见问题与解决方案(FAQ)
  9. 最佳实践与团队协作建议
  10. 总结与进阶学习

一、引言篇:为什么需要 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 >= +2Verified >= +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

  1. 登录 http://114.116.234.168 → 右上角用户头像 → Settings
  2. 左侧菜单 → SSH Keys
  3. 点击 Add New SSH Key
  4. 粘贴 ~/.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 访问
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐