资讯动态

Gerrit commit信息获取实战:SSH与REST API查询语法、字段解析及避坑指南

发布时间:2026/10/21 6:04:50 来源:尧图企业网站定制
1. 从一次代码评审的“考古”需求说起如果你在团队里做过代码评审、版本追溯或者需要把某次提交的来龙去脉整理成报告大概率绕不开一个场景怎么把 Gerrit 上的 commit 信息批量、准确地取出来。Gerrit 作为一套基于 Git 的代码评审系统它的数据模型和原生 Git 有重叠但又不完全一样——很多刚接触的人会下意识地直接git log结果发现拿到的东西要么缺字段要么和评审页面上看到的对不上。我自己第一次遇到这个需求是要给一个迭代周期内的所有变更做一份汇总需要拿到每个 commit 的提交者、评审人、变更编号、当前状态、所属分支这些信息。当时想当然地写了个脚本去解析git log跑出来才发现评审状态、Code-Review 打分、Verified 标记这些东西本地仓库里根本没有它们只存在于 Gerrit 的服务端。这就是第一个关键认知——Gerrit 的 commit 信息和 Git 的 commit 信息不是一回事前者是后者加上一整套评审元数据。所以这篇内容想聊的就是围绕“从 Gerrit 获取 commit 信息”这件事把几条主流路径讲透什么时候该用 SSH 命令、什么时候该走 REST API、查询语法怎么写、返回的 JSON 里哪些字段是真正有用的、分页和性能怎么处理、以及那些文档里不写但实际会踩的坑。不管你是要写自动化脚本、做数据看板还是单纯想手动查一次变更都能在这里找到能直接抄的写法。需要先说明一点下面涉及的所有命令和接口都基于 Gerrit 公开的通用能力具体版本之间可能有细微差异实际使用时以你所在环境的接口文档为准。另外文中出现的项目名、分支名、人员标识一律用占位符代替你替换成自己的即可。2. 先搞清楚 Gerrit 到底把 commit 信息存在哪2.1 Git 仓库层与服务端元数据层的分工要取对数据先得明白数据在哪。Gerrit 的架构可以粗略分成两层底层是标准的 Git 仓库每个变更最终都会以 commit 的形式落到某个仓库里上层是 Gerrit 自己的元数据库常见的是关系型数据库比如 PostgreSQL 或 MySQL也有用 H2 的轻量部署存的是评审相关的信息。这两层各管什么直接决定了你该从哪里取数数据类别存放位置典型字段获取方式提交内容Git 仓库commit hash、作者、提交时间、diff、文件列表git log、git show评审元数据Gerrit 元数据库Change-Id、评审状态、打分、评审人、主题分支SSH 命令、REST API两者关联通过 commit hash / Change-Id 关联—查询时做 join很多人卡住的点就在这张表的第三行Git 层和服务端层是靠Change-Id这个特殊 trailer 关联起来的。Change-Id是 Gerrit 在 commit message 末尾自动加的一行形如Change-Id: I加一串 40 位十六进制。同一个逻辑变更经过多次 amend、rebase 之后commit hash 会变但Change-Id不变——这正是 Gerrit 能追踪“同一个变更的多个版本”的基础。2.2 为什么直接 git log 拿不到评审状态理解了上面的分层就明白为什么git log拿不到评审状态了评审状态是服务端元数据它压根不在 Git 对象里。你在本地仓库执行git log看到的只是 commit 对象自带的字段Gerrit 的“当前是 Open 还是 Merged”“有几个 Code-Review 2”这些信息本地无从得知。有一种例外情况如果你拉取的是已经合并的变更commit message 里通常会带上Reviewed-on、Reviewed-by这类 trailer能间接反映一部分评审信息。但这是“事后快照”对于还在评审中的变更、或者需要精确打分的场景完全不够用。所以结论很明确要完整的 commit 信息必须走 Gerrit 的服务端接口。2.3 两条主流取数通道的定位差异Gerrit 对外提供两条取数通道选哪条取决于你的使用场景SSH 命令通道通过ssh -p port host gerrit command调用比如gerrit query、gerrit ls-projects。优点是轻量、无需处理认证 token、适合 shell 脚本和人工快速查询缺点是返回格式相对固定虽然支持--formatJSON复杂过滤能力不如 REST。REST API 通道通过 HTTP 访问/changes/、/projects/等端点返回标准 JSON。优点是字段全、过滤灵活、支持分页和字段裁剪适合程序化集成缺点是要处理认证HTTP 密码或 OAuth token且响应体前面有个防 XSS 的前缀需要剥掉。我的一般原则是临时查一次、写个几行的 shell 循环用 SSH要做成服务、要接前端、要复杂查询用 REST。下面两章分别展开。3. SSH 通道gerrit query 的实战用法与查询语法3.1 最小可用命令与返回结构最基础的取数命令长这样ssh -p 29418 gerrit.example.com gerrit query --formatJSON --current-patch-set status:merged project:my-project limit:5拆开看几个关键参数-p 29418Gerrit 的 SSH 端口默认是 29418但很多环境会改先确认。gerrit query查询子命令。--formatJSON输出 JSON每行一个对象。不加这个参数默认是给人看的文本格式脚本解析很痛苦。--current-patch-set附带当前 patch set 的详细信息包括文件列表、approvals 等。这个参数很关键不加的话拿不到评审打分。status:merged查询条件这里查已合并的。project:my-project限定项目。limit:5限制返回条数。返回的 JSON 每行一个 change 对象字段相当多。我挑几个最常用的列一下字段含义备注project项目名branch目标分支topic主题可选用于分组变更idChange-Id形如my-project~master~Ixxxxnumber变更编号数字页面上 URL 里的那个subject提交标题owner提交者含 name、email、usernameurl评审页面链接status状态NEW / MERGED / ABANDONEDcreatedOn/lastUpdated时间戳Unix 秒currentPatchSet当前 patch set需--current-patch-set3.2 查询语法把过滤条件写对gerrit query的查询语法和网页搜索框里那套是一致的掌握几个高频操作符能省很多事status:open/status:merged/status:abandoned按状态过滤。project:xxx按项目。branch:xxx按分支。owner:xxx或owner:self按提交者self指当前登录用户。reviewer:xxx按评审人。label:Code-Review2按打分过滤这个在统计“谁给了通过”时特别有用。message:xxx按 commit message 内容搜。age:1d、before:2024-01-01、after:2024-01-01按时间。limit:N限制条数配合--start做分页。多个条件之间是 AND 关系直接空格分隔即可。比如“查某项目 master 分支上、由某人提交、已合并、最近 7 天的变更”ssh -p 29418 gerrit.example.com gerrit query \ --formatJSON --current-patch-set \ project:my-project branch:master owner:someuser status:merged age:7d注意age的单位是 d天、h小时、m分钟、s秒写age:7d表示最近 7 天。这个和before/after的绝对日期是两套写法别混用。3.3 分页与批量拉取的正确姿势gerrit query默认返回条数有限通常是 500 左右取决于配置要拉全量必须自己分页。它的分页靠--start参数配合limit#!/bin/bash HOSTgerrit.example.com PORT29418 QUERYproject:my-project status:merged LIMIT100 START0 while true; do RESULT$(ssh -p $PORT $HOST gerrit query --formatJSON --current-patch-set \ --start $START limit:$LIMIT $QUERY) # 去掉最后一行统计信息gerrit query 会在末尾多输出一行 {type:stats,...} echo $RESULT | grep -v type:stats all_changes.jsonl COUNT$(echo $RESULT | grep -c type:stats) # 判断是否还有更多看返回的 change 条数是否达到 limit CHANGES$(echo $RESULT | grep -v type:stats | wc -l) if [ $CHANGES -lt $LIMIT ]; then break fi START$((START LIMIT)) done这里有个坑必须点出来gerrit query的输出最后一行永远是{type:stats,rowCount:N,...}它不是 change 对象。如果你直接jq整个输出会报错或者多出一条脏数据。所以脚本里一定要把这行过滤掉我上面用grep -v处理了。另一个坑是分页的终止条件。不能简单看“返回条数是否为 0”因为最后一页可能刚好返回 limit 条但已经没有更多了。稳妥的做法是当返回的 change 条数小于 limit 时停止。如果恰好整除会多查一次空页无害。4. REST API 通道字段更全、更适合程序化集成4.1 认证方式与那个必须剥掉的前缀REST API 的入口是https://host/changes/。认证有两种常见方式HTTP 密码在 Gerrit 个人设置里生成一个 HTTP password然后用 Basic Auth。适合脚本。OAuth / 会话 cookie适合有前端交互的场景。用 curl 调用的典型写法curl -s --user username:http_password \ https://gerrit.example.com/changes/?qproject:my-projectstatus:mergedn5注意这里有个 Gerrit 特有的“怪癖”所有 REST 响应体前面都会加一行)]}前缀目的是防止 JSON 被当成脚本执行防 XSS。你解析之前必须把这行剥掉curl -s --user username:http_password \ https://gerrit.example.com/changes/?qproject:my-projectstatus:mergedn5 \ | tail -n 2 | jq .tail -n 2就是从第二行开始取把第一行前缀丢掉。这一步忘了jq会直接报 parse error是新手最常见的翻车点。4.2 查询参数q、n、o 三件套REST 的查询参数核心就三个q查询表达式语法和 SSH 那套完全一致只是空格要 URL 编码成或%20。n返回条数默认 25最大一般 500。o附加字段option可以重复出现决定返回体里带哪些额外信息。o参数是 REST 比 SSH 强的地方常用的有option作用oCURRENT_REVISION带当前 revision 的 commit 信息oCURRENT_COMMIT带当前 revision 的完整 commit messageoCURRENT_FILES带当前 revision 的文件列表oDETAILED_LABELS带详细的 label 打分和评审人oDETAILED_ACCOUNTS把 owner/reviewer 展开成完整账号信息oMESSAGES带所有评论消息oALL_REVISIONS带所有 revision一个信息比较全的查询curl -s --user username:http_password \ https://gerrit.example.com/changes/?qproject:my-projectstatus:mergedn10oCURRENT_REVISIONoCURRENT_COMMIToDETAILED_LABELSoDETAILED_ACCOUNTS \ | tail -n 2 | jq .[] | {number, subject, owner: .owner.name, status, labels: .labels}4.3 单条变更的详情接口如果你已经知道变更编号直接取单条更省事curl -s --user username:http_password \ https://gerrit.example.com/changes/my-project~master~I1234567890abcdef?oCURRENT_REVISIONoCURRENT_COMMIT \ | tail -n 2 | jq .这里的变更 ID 有三种写法都等价数字编号12345项目~分支~Change-Idmy-project~master~I1234...纯 Change-IdI1234...如果全局唯一我一般用数字编号因为短、好记、URL 里也是它。但要注意数字编号在不同 Gerrit 实例间不通用跨环境迁移脚本时用 Change-Id 更稳。4.4 分页_more_changes 与 S 参数REST 的分页和 SSH 不同它靠响应头或响应体里的_more_changes标记配合Sstart参数START0 while true; do RESP$(curl -s --user username:http_password \ https://gerrit.example.com/changes/?qproject:my-projectstatus:mergedn100S$STARToCURRENT_REVISION) BODY$(echo $RESP | tail -n 2) echo $BODY | jq -c .[] all_changes.jsonl MORE$(echo $BODY | jq if typearray then (.[-1]._more_changes // false) else false end) if [ $MORE ! true ]; then break fi START$((START 100)) done_more_changes是挂在最后一条记录上的布尔字段为 true 表示还有下一页。这个设计比 SSH 的“数条数”要明确不容易出错。5. 拿到数据之后字段解析与常见数据清洗5.1 时间戳、账号、label 的规范化原始返回里有几类字段需要二次处理才能用时间戳createdOn、lastUpdated都是 Unix 秒直接看没法读。转成可读时间jq .createdOn | strftime(%Y-%m-%d %H:%M:%S)注意strftime在 jq 1.6 才支持老版本得用todate或外部date命令。账号信息owner字段默认可能只有_account_id要拿到名字和邮箱得加oDETAILED_ACCOUNTS。而且不同 Gerrit 版本对匿名用户的处理不一样有的会返回name: Anonymous Coward做统计时要过滤掉。label 打分labels字段的结构是{Code-Review: {approved: {...}, value: 2, all: [...]}}。要统计“谁给了 2”得遍历all数组找value 2的项。这里有个细节all里同一个人的多次打分可能都在取最新的一条要看date字段排序。5.2 用 jq 做字段裁剪与聚合原始 JSON 字段太多直接存下来又大又乱。我习惯用 jq 先裁成自己需要的结构jq -c .[] | { number: .number, subject: .subject, owner: .owner.name, status: .status, branch: .branch, created: (.createdOn | strftime(%Y-%m-%d)), url: .url } all_changes.json如果要按提交者聚合统计jq -s group_by(.owner) | map({owner: .[0].owner, count: length}) | sort_by(-.count) all_changes.jsonl-s是 slurp把多行 JSON 合成一个数组再处理。这个在处理 SSH 输出的 JSONL 时特别有用。5.3 关联 Git 层数据做交叉验证有时候你需要把 Gerrit 的元数据和 Git 的提交内容对起来。做法是从 Gerrit 拿到currentPatchSet.revision也就是 commit hash然后去本地仓库git show hash取 diff 和文件列表。REVISION$(jq -r .currentPatchSet.revision change.json) git -C /path/to/repo show --stat $REVISION这里有个前提本地仓库得 fetch 过对应的 ref。Gerrit 的变更 ref 格式是refs/changes/后两位/编号/patchset比如变更 12345 的第 3 个 patch set 是refs/changes/45/12345/3。要取某个变更的内容git fetch origin refs/changes/45/12345/3 git show FETCH_HEAD这个 ref 格式是 Gerrit 的约定记住“编号后两位 / 编号 / patchset 号”这个结构就不会错。6. 那些文档不写但一定会踩的坑6.1 权限不是所有字段你都能看到Gerrit 有细粒度的权限控制。同一个查询管理员能看到全部字段普通用户可能看不到某些 label 的明细甚至看不到某些项目。表现是字段直接缺失而不是报错。所以脚本里对字段的访问要做空值保护别假设owner.name一定存在。我踩过一次统计脚本在测试环境跑得好好的上生产后owner.name全是 null排查半天才发现是权限差异——测试环境用的是管理员账号生产用的是只读账号。建议脚本里所有字段访问都用// unknown兜底jq .owner.name // unknown6.2 大结果集别一次性拉全量如果项目很大status:merged可能有几万条。一次性拉全量既慢又容易超时。正确做法是用时间窗口切片比如按月拉after:2024-01-01 before:2024-02-01。配合limit和分页别指望单次请求拿完。如果只是要统计数量用--formatJSON加limit:0或者 REST 的n0只拿 stats 行快得多。6.3 特殊字符查询表达式里的转义查询表达式里如果包含空格、冒号、引号需要小心处理。比如搜 commit message 里带空格的短语gerrit query message:fix bug在 shell 里要用单引号包住整个表达式避免被 shell 拆词。REST 里则要 URL 编码空格变引号变%22。我一般用--data-urlencode让 curl 自动处理curl -s --user u:p -G \ --data-urlencode qproject:my-project message:fix bug \ https://gerrit.example.com/changes/-G表示把 data 拼到 URL 上做 GET 请求--data-urlencode自动编码省心。6.4 版本差异字段名和接口路径会变Gerrit 从 2.x 到 3.xREST 接口有不少变化。比如早期版本/changes/返回的字段结构和现在不完全一样某些 option 名称也调整过。跨版本迁移脚本时最稳的办法是先打一次样本请求把返回结构 dump 出来对比而不是照搬网上的老教程。提示不确定当前实例支持哪些 option 时可以先发一个不带 option 的请求看基础字段再逐个加 option 验证。Gerrit 对不认识的 option 通常是忽略而非报错所以“加了没效果”往往意味着拼写或版本不支持。7. 我个人的几条实操建议第一优先用 RESTSSH 只做临时查询。REST 的字段完整度、分页机制、错误处理都更适合工程化SSH 的优势只在“不用配认证、一行命令搞定”。我现在的习惯是调试阶段用 SSH 快速验证查询语法确认无误后翻译成 REST 写进脚本。第二把查询表达式抽成变量。不管是 shell 还是 Python别把project:xxx status:merged age:7d这种字符串硬编码在命令里抽出来单独维护。查询条件一变改一处就行也方便做参数化。第三落盘用 JSONL 而不是 JSON 数组。JSONL每行一个 JSON 对象的好处是可以流式追加、可以grep、可以wc -l数条数处理大结果集时内存友好。我上面所有脚本都是往.jsonl里追加最后需要数组时再用jq -s合成。第四给脚本加个 dry-run 开关。第一次跑某个查询时先只打印条数和前几条样本确认数据符合预期再全量拉。这个习惯帮我避免过好几次“拉了几万条发现查询条件写错”的尴尬。第五注意时区。Gerrit 返回的时间戳是 UTC 的 Unix 秒但你在页面上看到的时间可能是本地时区。做日报、周报统计时时区差一天会导致数据对不上。我的做法是统一转成 UTC 再按 UTC 切窗口展示时再转本地。这套东西用熟了之后从 Gerrit 取 commit 信息基本就是十几行脚本的事。真正花时间的从来不是命令本身而是搞清楚“数据在哪一层”“权限够不够”“字段叫什么”这三个问题。把这三件事想明白剩下的就是拼积木了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑