做代码评审最烦什么?界面上一排 change 翻来翻去,先给张三发评审邀请,再等李四打完+2,最后还得手动点一下 Submit。一个人同时盯好几个 change 的时候,光是在 Gerrit Web UI 上点点点就能耗掉半小时。Gerrit 本身是个好用的代码审查平台,但在批量操作、脚本集成和自动化流程面前,图形界面天生吃亏。这篇博文专门聊怎么用命令行把“添加 Reviewer、打 Review 分数、Submit 合入”这一整套动作跑起来。核心工具就是 Gerrit 自带的 SSH 命令和 REST API,不需要装任何第三方插件,装好 Gerrit 就可以直接用。适合开发者、测试、DevOps,还有需要把评审流程接进 CI/CD 流水线的同学。
1. 准备工作:先把 SS H通道打通再谈效率
1.1 生成并配置SSH密钥
Gerrit 支持原生 SSH 服务,默认监听29418端口,这个端口通常和常见的22不一样,第一次连接很容易踩坑。要让命令行工具能识别你的身份,第一步是把你的 SSH 公钥加到 Gerrit 账号里。
先在本地生成一对密钥,这里建议用 ed25519 算法,兼容性和安全性都够:
ssh-keygen -t ed25519 -C "你的邮箱或备注"生成完成后,公钥默认在~/.ssh/id_ed25519.pub,把里面的内容复制出来。然后登录 Gerrit,进入个人设置Settings -> SSH Keys,粘贴保存。这一步做完,先不要急着敲完整命令,用一条最简单的命令验证一下链路是否通:
ssh -p 29418 your_gerrit_username@gerrit.example.com如果你的用户名和公钥都配置正确,SSH 会直接登录到 Gerrit 服务端,并显示类似Welcome to Gerrit Code Review的欢迎信息。看到这个,说明 SSH 通道已经通了。
有个容易忽略的细节:这里登录的用户名不是服务器系统账号,而是你在 Gerrit 里注册的用户名。头几次用很容易下意识填系统账号,结果一直提示认证失败,反复折腾半天才发现是用户名的问题。如果确认用户名没问题还是连不上,优先检查防火墙是否放行了29418/TCP。
1.2 Gerrit SSH命令的通用格式
SSH 通道通了之后,所有 Gerrit 管理指令都遵循同一个壳子:
ssh -p 29418 your_gerrit_username@gerrit.example.com gerrit <子命令> <参数>也就是说,你在本地机器通过 SSH 执行远程的gerrit命令。后面接的是什么子命令,Gerrit 就会执行对应的操作。常用的子命令包括:
gerrit set-reviewers:添加或移除 Reviewergerrit review:对某个 revision 打分数、写评论、甚至直接提交gerrit query:按条件查询 change 的状态和信息gerrit set-project、gerrit create-project等项目管理和权限管理命令
如果对参数不熟,随时可以加--help查看版本对应的帮助信息:
ssh -p 29418 your_gerrit_username@gerrit.example.com gerrit review --help不同版本的 Gerrit 在小参数上会有细微差别,官方文档反而不如命令自带帮助来得直接。建议实际操作之前先瞄一眼帮助,确认参数名没记错。
除了 SSH,Gerrit 的另一个可选方案是 REST API,适合在脚本和 CI 工具里调用。使用 REST API 需要在Settings -> HTTP Password里生成一个密码,这个密码配合用户名走 HTTP Basic Auth。两者各有用处:SSH 适合人肉敲命令时用,敲起来顺手;REST API 适合程序调用,输出是 JSON,方便解析。后面的部分我会把两种方式都覆盖到。
2. 添加Reviewer:set-reviewers指令详解
2.1 基础用法与change定位方式
给一个 change 添加 Reviewer,最常用的 SSH 指令是gerrit set-reviewers。举个例子:
ssh -p 29418 your_gerrit_username@gerrit.example.com \ gerrit set-reviewers I1234567890abcdef --add alice@example.com指令本身不复杂,难点在于怎么准确告诉 Gerrit 你要操作的是哪个 change。这里提供三种定位方式,按推荐程度排:
Change-Id:形如
I1234567890abcdef的 40 位字符串。这是 Gerrit 跨分支识别 change 的唯一标识,推荐优先使用。Change-Id 在 Gerrit UI 的 change 详情页顶部能看到,不依赖分支和项目上下文。数字 change 号:比如
12345。这个数字虽然好记,但在不同项目里是递增分配的,如果同时管着多个仓库,只给数字容易混淆。项目~分支~Change-Id组合:例如myproject~main~I1234567890abcdef。当 Change-Id 在多个分支或同项目多个 change 里存在歧义时,这个写法最精准。shell 命令里建议给~打引号或用转义,否则某些环境会把它解释成特殊符号。
我自己的习惯是:手敲的时候用数字 change 号,脚本和 CI 里一律拼接成项目~分支~Change-Id的完整三元组,避免任何歧义。
2.2 批量增删reviewer
set-reviewers支持在一条命令里同时增删多个评审人。--add和--remove可以混着用,比如:
ssh -p 29418 your_gerrit_username@gerrit.example.com \ gerrit set-reviewers myproject~main~I1234567890abcdef \ --add alice@example.com \ --add bob@example.com \ --remove carol@example.com这条命令会一次性添加 Alice、Bob,同时移除 Carol。需要注意,--add指定的用户必须是 Gerrit 里已经存在的用户。如果对方从来没登录过 Gerrit,系统会直接报错,提示fatal: user "xxx" not found。想让同事被添加成功,得先让他们至少登录一次 Gerrit 完成账号激活。
另一个常被忽视的点是权限。给 change 添加评审人需要项目上授予Add Reviewer权限。大多数团队给注册用户默认开了这个权限,但如果你发现命令执行后提示fatal: not permitted,不要怀疑命令写错了,先去看项目Access配置里有没有授予相应权限。尤其是从零搭建 Gerrit 的团队,默认权限模板可能没完全配好,这里卡住非常正常。
2.3 用REST API添加Reviewer
脚本场景我更推荐 REST API,因为返回结构化 JSON,方便在 CI 日志里排查。添加评审人的接口是POST /changes/{change-id}/reviewers:
curl -u your_gerrit_username:your_http_password \ -X POST \ -H "Content-Type: application/json" \ --data '{"reviewer": "alice@example.com", "state": "REVIEWER"}' \ https://gerrit.example.com/a/changes/myproject~main~I1234567890abcdef/reviewers这里有三个细节必须注意:
第一,Gerrit 的认证请求 URL 要在/changes前加/a/前缀,也就是/a/changes/...。不加会收到401 Unauthorized或者被当作匿名请求处理。
第二,state字段可以设成REVIEWER,也可以设成CC。CC表示把用户加入抄送列表,对方能看到 change 但不会出现在 Reviewer 名单里。如果只是想让某个人“知道有这个变更”,用 CC 比 Reviewer 更合适。
第三,Gerrit REST API 的响应为了防 XSS,会在最前面加一段)]}'前缀。用 curl 在终端里看问题不大,但如果用jq解析 JSON,要先去掉第一行。常见做法是:
curl -s -u user:password ... | sed '1d' | jq .3. 执行Review:打分、评论与机器人评审
3.1 先搞懂Gerrit的评分机制
Gerrit 里最核心的两种标签是Code-Review和Verified。Code-Review 表示代码审查意见,取值从-2到+2共五档;Verified 表示验证结果,一般只有-1、0、+1三档。
具体含义可以这样理解:Code-Review +1表示“我看了,没问题”;+2表示“我同意合入,责任我扛”;-1是“我不太满意但不想一票否决”;-2是“我强烈反对,这个改动不能合”。Verified 则是 CI 或者测试人员的管辖范围,+1代表编译、单测、集成测试全部通过,-1代表有任何一个环节挂了。
按默认配置,一个 change 要能被 submit,至少需要一个Code-Review +2和至少一个Verified +1。很多团队还加了自定义标签,比如QA-Verified、CI-Verified,原理完全一样。搞清楚这套评分语义,你就明白了为什么 Gerrit 的审阅不是“点一下按钮”那么简单——它把每个环节的“同意权”和“验证权”拆开了。
如果你是在写自动化脚本,记得新 patchset 推送后,旧 label 通常会被重置或重新验证。也就是说,CI 必须对最新的 revision 重新打Verified +1,而不是只关心最初那版。这点在搭建流水线时非常容易漏,漏掉的结果就是 change 永远无法 submit。
3.2 用gerrit review指令打分
gerrit review是给某个具体 revision(也就是某个 patchset 对应的 commit)打分的指令。基本用法:
ssh -p 29418 your_gerrit_username@gerrit.example.com \ gerrit review \ --project myproject \ --branch main \ --verified +1 \ --code-review +2 \ --message "CI与人工评审均通过,可以合入" \ 6a5f0d3e8b7c1a2b3c4d5e6f7a8b9c0d1e2f3a4b最后一个参数是完整的 commit SHA-1,也就是你要评审的那个 patchset。实际开发中,可以从本地仓库用git rev-parse HEAD拿到最新提交的 SHA,也可以从 Gerrit UI 的 patchset 列表里复制。
--message是留评论,多条评论可以重复传--message,或者用多行字符串。如果评论内容包含特殊字符,比如引号、$、反斜杠,建议用双引号把整个 message 包起来。内容特别长的时候,可以先写到文件里再读进来:
MESSAGE=$(cat review_comment.txt) ssh -p 29418 user@host gerrit review --project myproject --branch main \ --message "$MESSAGE" <revision-sha>这个技巧在自动化场景里很实用,因为 AI 生成的评审意见通常很长,而且充满换行和特殊符号,直接往命令行里拼字符串很容易出错。
gerrit review还可以追加--submit参数,在打分的同时直接提交,省一条命令。不过我不建议把打分和提交混在一条命令里写,因为 submit 能否成功取决于 label 是否满足,如果分数还没生效,命令会先报错,排查时反而不如分开执行来得直观。
3.3 通过REST API完成Review
REST API 的 review 接口是POST /changes/{change-id}/revisions/{revision-id}/review。revision-id在自动化流程里可以固定写成current,表示请求自动解析到最新 patchset,不用每次拼 SHA:
curl -u your_gerrit_username:your_http_password \ -X POST \ -H "Content-Type: application/json" \ --data '{ "labels": { "Code-Review": "+2", "Verified": "+1" }, "message": "CI编译通过,审查意见合理,同意合入" }' \ https://gerrit.example.com/a/changes/myproject~main~I1234567890abcdef/revisions/current/review如果请求成功,Gerrit 通常会返回202 Accepted。这里不要惊讶,它不是200 OK,代表 Gerrit 接收了请求并异步处理。只要 HTTP 状态码是 2xx,就说明请求已经被接受。
REST API 的优势在于响应体是 JSON,可以很轻松地在脚本里判断是否成功。比如用curl -s -w "%{http_code}"拿到状态码,结合sed '1d'清理响应头,一套组合拳下来,CI 日志能做得相当友好。
4. Submit:合入代码的最后一公里
4.1 命令行提交change的两种方式
当 change 上的所有 label 都满足条件后,就到了最后一步:Submit。用 SSH 可以这样提交:
ssh -p 29418 your_gerrit_username@gerrit.example.com \ gerrit review <revision-sha> --submit这条命令会把该 change 提交合入,前提是权限足够、label 满足、且没有 merge conflict。
用 REST API 同样可以完成:
curl -u your_gerrit_username:your_http_password \ -X POST \ -H "Content-Type: application/json" \ --data '{"wait_for_merge": true}' \ https://gerrit.example.com/a/changes/myproject~main~I1234567890abcdef/submitwait_for_merge建议设成true,这样接口会等待合并真正落库后才返回,CI 下一阶段拿到证据才安心。如果设成false,请求只是触发了 submit 动作,后续的状态需要额外轮询才能确定。
4.2 submit的权限与策略细节
Submit 操作需要Submit权限。在项目配置里,这个权限经常只开放给集成负责人或代码维护者,普通开发者不一定有。所以如果你的账号被提示没权限,别硬刚,找维护者在 Project Access 里把Submit授予给对应用户组就行。
还要注意 Gerrit 的提交策略,常见的有:
Fast Forward Only:只允许快进式合并,不允许产生 merge commit,历史保持线性。Merge If Necessary:必要时自动生成 merge commit,允许分支合入。Merge Always:总是生成 merge commit。Cherry Pick:将当前 change 以 cherry-pick 方式提到目标分支,同时保留 Change-Id。
提交策略通常在项目配置里设置,不影响命令行本身,但它会影响合入后的分支形态。比如团队想严格保持线性历史,就选Fast Forward Only,否则遇到并行开发时,一个简单的 submit 可能因为非快进而被拒。搞清楚策略,再看 submit 失败原因,很多疑惑就解开了。
4.3 submit被拒的排查思路
submit 被拒是家常便饭,集中注意这四类原因。
第一类是 label 不满足。最常见是还差一个Code-Review +2,或者 CI 的Verified +1还没打上。查看方式很简单,通过 Gerrit UI 打开 change 详情,或者用 REST API:
curl -u user:password \ https://gerrit.example.com/a/changes/myproject~main~I1234567890abcdef/detail看返回 JSON 里的labels字段,能直观看到每个 label 的当前状态,以及还差什么条件。
第二类是 change 不是 open 状态。如果 change 已经被 abandoned 或 merged,submit 会直接失败。这类 change 需要先restore恢复,再用gerrit review --restore命令可以解决。
第三类是 merge conflict。如果目标分支在你的 patchset 之后又有了新提交,或者与你改的文件产生冲突,submit 会被拦截。需要先git rebase到最新目标分支,重新推一个 patchset。
第四类是权限不足。检查账号是否有Submit权限,以及项目是不是被refs/heads/*之类的规则限制了合入权限。
5. 自动化场景:把整套流程写进脚本
5.1 一个可直接复制的完整示例
命令行最大的价值在于可以编排成脚本。下面是一个比较完整的 Bash 示例,输入项目名、Change-Id、revision SHA 和评审人列表,脚本自动完成“加评审人、打分评论、提交合入”三步操作:
#!/bin/bash set -euo pipefail GERRIT_SSH_PORT=29418 GERRIT_HOST="gerrit.example.com" GERRIT_USER="ci-bot" BRANCH="${BRANCH:-main}" PROJECT="${1:?请输入project名}" CHANGE_ID="${2:?请输入Change-Id}" REVISION_SHA="${3:?请输入revision SHA}" REVIEWERS="${4:-alice@example.com,bob@example.com}" IFS=',' read -ra REVIEWER_LIST <<< "$REVIEWERS" for reviewer in "${REVIEWER_LIST[@]}"; do echo ">>> 添加评审人: $reviewer" ssh -p "$GERRIT_SSH_PORT" "$GERRIT_USER@$GERRIT_HOST" \ gerrit set-reviewers "$PROJECT~$BRANCH~$CHANGE_ID" --add "$reviewer" done MESSAGE="CI 编译通过,测试用例全部通过,评审意见已复核。" echo ">>> 打 Review 分数" ssh -p "$GERRIT_SSH_PORT" "$GERRIT_USER@$GERRIT_HOST" \ gerrit review --project "$PROJECT" --branch "$BRANCH" \ --verified +1 --code-review +2 --message "$MESSAGE" \ "$REVISION_SHA" echo ">>> 提交合入" ssh -p "$GERRIT_SSH_PORT" "$GERRIT_USER@$GERRIT_HOST" \ gerrit review "$REVISION_SHA" --submit echo ">>> 已完成: $CHANGE_ID"脚本的关键在于set -euo pipefail,任何一步失败都会立即退出,避免 CI 出现“假装成功”的情况。尤其多人协作时,reviewer 临时写错、revision SHA 过期、label 未满足——任何一步失败都应该暴露出来,而不是静默通过。
5.2 参数化、错误处理与AI评论接入
上面的脚本能用,但真实场景往往更复杂。比如 reviewer 列表由外部参数传入,项目分支不固定,甚至要对多个 change 批量执行。这时候建议把 SSH 命令封装成函数,再包一层 for 循环:
gerrit_add_reviewers() { local project="$1" change_id="$2" shift 2 for reviewer in "$@"; do ssh -p "$GERRIT_SSH_PORT" "$GERRIT_USER@$GERRIT_HOST" \ gerrit set-reviewers "$project~$BRANCH~$change_id" --add "$reviewer" done } gerrit_review_and_submit() { local project="$1" branch="$2" change_id="$3" sha="$4" ssh -p "$GERRIT_SSH_PORT" "$GERRIT_USER@$GERRIT_HOST" \ gerrit review --project "$project" --branch "$branch" \ --verified +1 --code-review +2 --message "$MESSAGE" "$sha" ssh -p "$GERRIT_SSH_PORT" "$GERRIT_USER@$GERRIT_HOST" \ gerrit review "$sha" --submit }把命令封装成函数的好处是,CI 脚本里出现的是语义明确的调用,而不是一长串难维护的 SSH 拼接。团队成员接手也容易。
最近很多人问我 AI 生成代码怎么接入 Gerrit 评审流程。我的做法很简单:让 LLM 对 diff 输出结构化审查意见,然后把意见写入临时文件,再用--message "$(cat ai_review.txt)"的方式挂到 change 上。这样 AI 的意见就能完整呈现在评审记录里,人工 Reviewer 无需重复发现低级问题,只用聚焦 AI 判断不准的领域。本质上就是把 AI 当成一个自动评审器,打Code-Review +1而不是+2,最终合入权仍然保留给人。
6. 实战问题排查与经验笔记
6.1 高频报错速查表
把命令行操作 Gerrit 高频报错整理成了一份速查表,都是我实际踩过或者帮同事排查过的:
| 报错信息 | 可能原因 | 处理方式 |
|---|---|---|
fatal: user "xxx" not found | 指定用户不存在或未激活账号 | 让对方至少登录一次 Gerrit 并设置用户名 |
fatal: not permitted | 账号缺少 Add Reviewer 或 Code-Review 权限 | 检查 Project Access 并对应授权 |
label "Verified" is not set | 没打 Verified 分就尝试 submit | 先执行--verified +1或 REST 设置 label |
change is not open | change 已被 merged 或 abandoned | 确认当前 change 状态,必要时 restore |
409 Conflict | label 不满足、merge conflict、提交策略拒绝 | 查看labels和 merge 状态,按原因处理 |
401 Unauthorized | REST 请求没带认证或漏掉/a/前缀 | 加上/a/并确认 HTTP Password 正确 |
Connection refused | 端口错误或防火墙拦截 | 确认29418端口开放且 SSH 端口监听正常 |
这张表前三种情况占了日常问题的八成。发现问题先看状态码和报错文本,再对照表查,基本都能定位。
6.2 这几个坑我替你踩过了
先说 message 里的引号问题。Gerrit 的--message参数对引号非常敏感。我吃过一次大亏:CI 脚本里双引号包裹 message 内部变量又出现双引号,结果整个命令被 shell 拆得四分五裂。后来强制规范是:message 一律先写入临时文件,再用$(cat file)读取,杜绝一切内联引号问题。
其次是 revision SHA 容易过期。Gerrit 的gerrit review针对的是某个具体 revision,不是 change。如果你的 CI 拿的是旧 patchset 的 SHA,打分和评论会落到旧版本上,新 patchset 的 label 还是空的,提交自然失败。规范做法是每次取最新 patchset 的 SHA,REST 场景就直接用revisions/current代替手工维护 SHA。
还有一点想特别提醒:不要拿 root 或者共享账号去连 Gerrit SSH。Gerrit 会把操作记录归属到登录用户名,如果所有人用一个 CI 账号,出了问题你根本不知道是谁评的、谁批的。规范做法是每个服务建专用技术账号,比如ci-bot、deploy-bot,并按最小权限原则只授予必要标签。
最后一个容易被忽略的是 REST API 响应里的)]}'前缀。很多人在脚本里用jq解析 Gerrit 返回结果,莫名其妙报 JSON parse error,其实就是忘了先用sed '1d'把首行剥离。这个问题很小,但几乎每个刚开始用 Gerrit REST API 的人都会遇到一次,记牢能省不少排查时间。
我个人在实际操作中的体会是,把 Gerrit 命令行用顺之后,再也不想回到纯 Web 界面去手动操作重复性流程了。尤其是接 CI 和写自动化评审,SSH 指令和 REST API 就像给 Gerrit 开了个后门,把原本需要人为反复确认的步骤一次性跑完。如果你的团队每天有大量 change 在流转,建议从中挑一个高频动作开始,比如只做自动添加 Reviewer 或自动 Verified,跑顺了再逐步扩展到 review 和 submit。一次只推进一个小环节,自动化流程才不会一上来就复杂到没人维护。