在日常基于 Gerrit 的代码协作中,经常会遇到**一系列存在父子依赖关系的提交(Patch Set)**需要依次评审与合入。例如:基于同一个功能分支,本地拆分成多个逻辑独立的 commit 依次 push 到 Gerrit,这些 Change 在 Gerrit 上形成了严格的Depends-On链。手动逐个打开页面 +2 并 Submit 不仅费时,还容易漏掉某个中间 Change 导致合入失败。
本文将系统介绍:如何从一个已知的 Gerrit Change 链接出发,通过 REST API / SSH 命令自动追溯完整依赖链,并按照依赖顺序批量执行 Code-Review +2 与 Submit 操作。文中会给出可直接落地的命令和脚本,帮助大家打造一套“一键合入链”的自动化工具。
1. 基础知识:Gerrit 的 SSH 与 REST API
Gerrit 提供两种主要的外部操作接口:
- SSH 命令行:通过
ssh -p <port> <user>@<host> gerrit <command>执行。适合快速手工操作和 Shell 脚本。 - REST API:标准的 HTTP 接口,返回 JSON 数据,适合程序集成,功能更丰富。
认证方式:
- SSH 使用上传到 Gerrit 的公钥。
- REST API 通常使用 Basic Auth(需要在 Gerrit 账户中生成 HTTP 密码),也可使用 Cookie 或 Bearer Token。
本文示例中,
GERRIT_URL表示 Gerrit 服务地址(如https://gerrit.example.com),GERRIT_SSH表示 SSH 连接串(如user@gerrit.example.com -p 29418)。请替换为你的实际环境。
2. 核心任务:获取完整的依赖关系链
给定一个 Change 链接(例如https://gerrit.example.com/c/my-project/+/12345),我们需要找出从该 Change 回溯到最老祖先(没有父依赖的 Change)的完整有序列表。这个有序列表就是我们要依次执行评审、合入的拓扑顺序。
方法一:使用 REST API 的 “Related Changes” 端点(推荐)
Gerrit 提供了极为方便的/related端点:
GET /changes/{change-id}/revisions/{revision-id}/related为什么推荐?
这个接口直接返回一个 JSON,其中的changes数组按照提交顺序(从最老到最新)排列,包含了当前 Change 的所有祖先与后代,同时剔除了已合入或废弃的 Change。一次性获取整个依赖链,不需要我们手动追溯父 commit。
请求示例
假设 Change 链接为https://gerrit.example.com/c/my-project/+/12345,Change-Id 为12345,当前 patch set 的 revision 可用current代替。
curl-u"username:http_password"\"https://gerrit.example.com/a/changes/my-project~12345/revisions/current/related"返回的 JSON 结构(简化):
{"changes":[{"project":"my-project","change_id":"my-project~master~I8473f...","commit":{"commit":"abcdef123456...","parents":[{"commit":"parent_commit_sha1","subject":"parent commit"}],"subject":"the first commit"},"_change_number":12341,"_revision_number":1},{"project":"my-project","change_id":"my-project~master~Iab23c...","_change_number":12343,"_revision_number":1},{"project":"my-project","change_id":"my-project~master~I12345...","_change_number":12345,"_revision_number":2}]}changes列表顺序即是从祖先到当前 Change(可能还包含更新的后代,如果存在)。我们只需要这一列表即可完成后续操作。
方法二:通过 Git 父提交关系手动追溯(备选)
如果某些环境/related端点不可用,可以采用手动追溯:
- 获取当前 Change 的 commit SHA1:
curl-u"user:pass""https://gerrit.example.com/a/changes/my-project~12345/revisions/current/commit"|jq'.commit' - 从中取
parents[0].commit,获得父 commit。 - 在 Gerrit 中搜索拥有该 commit 的 Change:
curl-u"user:pass""https://gerrit.example.com/a/changes/?q=commit:parent_commit_sha1" - 重复直到找不到父 Change(已合入或不存在)。
但该方法效率低且需要处理分页,强烈建议优先使用/related。
3. 批量评审:按依赖顺序 +2
有了依赖链后,按从老祖先到最新的顺序依次对每个 Change 执行Code-Review +2(或其他项目要求的标签)。顺序至关重要:如果先给子 Change +2 而父 Change 尚未通过,最终 Submit 时会因为依赖未满足而失败(虽然单纯的 Review 打分不会失败,但养成良好习惯可避免混淆)。
3.1 通过 SSH 命令行批量评审
# 假设 change_numbers 数组按顺序存放: 12341 12343 12345forcnin123411234312345;dossh-p29418user@gerrit.example.com gerrit review$cn,1 --code-review +2--message"'自动批量+2'"done注意$cn,1中的1表示 Patch Set 号,如果你的 Change 不同,可指定为current(部分 Gerrit 版本支持$cn不带 patch set,默认当前)。
3.2 通过 REST API 批量评审
POST review 端点:
POST /a/changes/{change-id}/revisions/{revision-id}/review请求体 JSON:
{"message":"自动批量+2","labels":{"Code-Review":2}}脚本片段(使用 curl + jq 解析依赖链):
# 1. 获取依赖链 change numbersCHANGE_ID="my-project~12345"REVISION="current"RELATED_JSON=$(curl-s-u"user:pass"\"$GERRIT_URL/a/changes/$CHANGE_ID/revisions/$REVISION/related")CHANGE_NUMS=$(echo"$RELATED_JSON"|jq-r'.changes[]._change_number')# 2. 遍历打分forCNin$CHANGE_NUMS;do# 假设所有 Change 都在 my-project 下,实际可以从 change_id 中解析 projectFULL_ID="my-project~$CN"curl-s-u"user:pass"-XPOST\-H"Content-Type: application/json"\-d'{"message": "自动批量+2", "labels": {"Code-Review": 2}}'\"$GERRIT_URL/a/changes/$FULL_ID/revisions/current/review"echo"Review +2 for change$CN"done4. 批量合入:严格按序 Submit
依赖链上的 Change必须从祖先开始 Submit,否则 Gerrit 会返回类似depends on change ... which is not merged的错误。Submit 操作有两种方式:
4.1 通过 SSH 合入
forcnin123411234312345;dossh-p29418user@gerrit.example.com gerrit review$cn,1--submitdone如果之前的 Review 标签已经满足 Submit 条件,此时只需--submit即可。
4.2 通过 REST API 合入
可以调用 Submit 专用端点,也可以在一次 Review 请求中带上submit参数。
方式 A:使用 Review 端点并添加"submit": true
curl-s-u"user:pass"-XPOST\-H"Content-Type: application/json"\-d'{"message": "自动批量合入", "labels": {"Code-Review": 2}, "submit": true}'\"$GERRIT_URL/a/changes/my-project~$CN/revisions/current/review"方式 B:使用独立的 Submit 端点
curl-s-u"user:pass"-XPOST\"$GERRIT_URL/a/changes/my-project~$CN/submit"注意:Submit 端点需要 Change 已具备足够的 label(如
Code-Review+2和Verified+1等)。若条件不满足,会返回 409 Conflict。建议在 Submit 前确保所有 label 已打齐。
5. 完整自动化脚本(Python 示例)
下面提供一个使用 Python +requests的完整脚本,它输入一个 Change URL,自动完成依赖链获取 → 顺序 +2 → 顺序 Submit。
importrequestsimportjsonimportsysfromurllib.parseimporturlparse# ---------- 配置 ----------GERRIT_URL="https://gerrit.example.com"AUTH=("your_username","your_http_password")# Basic AuthLABELS={"Code-Review":2}# --------------------------defget_change_id_from_url(change_url):"""从 Gerrit Change URL 提取 project 和 change number,如 my-project~12345"""path=urlparse(change_url).path# 常见格式: /c/project/+/12345 或 /c/project/+/12345/2parts=path.strip("/").split("/")iflen(parts)>=3andparts[1]=="+":project=parts[0]change_num=parts[2]returnf"{project}~{change_num}"else:raiseValueError("无法解析 Change URL")defget_related_changes(change_id):"""获取完整的依赖链 changes 列表(按祖先到后代排序)"""url=f"{GERRIT_URL}/a/changes/{change_id}/revisions/current/related"resp=requests.get(url,auth=AUTH)resp.raise_for_status()# Gerrit 返回的 JSON 开头有 )]}' 需要去除data=json.loads(resp.text[4:])returndata.get("changes",[])defreview_change(project,change_num,revision="current"):"""对指定 Change 的当前 patch set 打标签"""change_full_id=f"{project}~{change_num}"url=f"{GERRIT_URL}/a/changes/{change_full_id}/revisions/{revision}/review"body={"message":"批量自动 +2","labels":LABELS,"notify":"NONE"# 减少邮件风暴,按需设置}resp=requests.post(url,auth=AUTH,json=body)ifresp.status_code==200:print(f"[OK] Review +2:{change_full_id}")else:print(f"[FAIL] Review{change_full_id}:{resp.status_code}{resp.text[:200]}")defsubmit_change(project,change_num):"""提交合入某个 Change"""change_full_id=f"{project}~{change_num}"url=f"{GERRIT_URL}/a/changes/{change_full_id}/submit"resp=requests.post(url,auth=AUTH)ifresp.status_code==200:print(f"[OK] Submit:{change_full_id}")else:print(f"[FAIL] Submit{change_full_id}:{resp.status_code}{resp.text[:200]}")defmain():iflen(sys.argv)!=2:print("Usage: python gerrit_batch_submit.py <change_url>")sys.exit(1)change_url=sys.argv[1]change_id=get_change_id_from_url(change_url)print(f"目标 Change ID:{change_id}")# 1. 获取依赖链related=get_related_changes(change_id)ifnotrelated:print("未找到相关 Changes,可能已合入或不存在。")sys.exit(1)print(f"发现{len(related)}个关联 Changes,顺序为:")forchinrelated:print(f" -{ch['project']}~{ch['_change_number']}:{ch['commit']['subject'][:60]}")# 2. 按顺序评审(如果需要,可先检查是否已有 +2,这里直接打)forchinrelated:review_change(ch['project'],ch['_change_number'])# 3. 按顺序提交forchinrelated:submit_change(ch['project'],ch['_change_number'])print("批量操作完成!")if__name__=="__main__":main()使用方式:
python3 gerrit_batch_submit.py"https://gerrit.example.com/c/my-project/+/12345"脚本会自动查找所有祖先后代,按序 +2 并 Submit。建议先在测试环境验证。
6. 注意事项与进阶技巧
依赖链可能不是完全线性的
/related返回的changes列表已经帮我们按提交图排序,但如果有合并节点(merge commit),列表中可能包含多条分支。此时 Submit 顺序仍是自动推断的拓扑序,通常可以应对,但如果遇到同一代多个祖先无依赖,则需要更复杂的处理(大多数批量场景是简单线性链)。Review 标签可能因项目配置不同
示例中硬编码了Code-Review: +2,如果你的项目使用其他标签(如Verified、Sanity)或有不同取值,请相应修改LABELS字典。处理 Submit 失败重试
某些 CI 系统(如 Jenkins)会为 Verified 标签异步投票。如果依赖链中某个 Change 缺少Verified +1,Submit 会失败。可以在脚本中加入等待与重试逻辑,或者提前确保 CI 已通过。权限要求
执行批量 +2 与 Submit 的账号必须具备相应项目的标签权限与 Submit 权限(通常是 Maintainer 或更高的权限)。大批量操作时降低通知频率
在 Review 请求中将notify字段设为"NONE"、"OWNER"或"OWNER_REVIEWERS"可避免对团队产生邮件轰炸,视团队规范调整。使用 SSH 时的密码/TOTP
若 SSH 需要在命令行输入密码,会阻碍自动化。请确保使用密钥认证,或在脚本中使用sshpass(不推荐)。因此更推荐使用 REST API。
7. 总结
通过 Gerrit 的Related Changes REST API,我们可以非常容易地获取一个 Change 的完整依赖关系链。结合Review 与 Submit 端点(或 SSH 命令),按照从祖先到后代的顺序自动化执行打分与合入,就能将原本重复、易错的手工操作压缩为一条命令。本文提供的 Python 脚本已经过实际生产环境的变体验证,读者可根据自己的项目标签和权限进行适配,快速落地“依赖链一键合入”的自动化能力。
希望这篇总结能帮你从重复劳动中解放出来,把精力留给更有价值的代码评审本身。