1. 从 Gerrit 拉取 commit 信息,到底在解决什么问题
做过协同开发的人大概率都碰过这种场景:代码托管在 Gerrit 上,每次提交都要走 review 流程,而你想把某段时间内的提交记录批量导出来做分析、做周报、做代码审计,或者单纯想统计一下某个同事最近改了多少文件。这时候你会发现,Gerrit 的 Web 界面虽然能看,但一条条点开复制粘贴,效率低到让人想砸键盘。
Gerrit 本质上是一个基于 Git 的代码评审系统,它在标准 Git 仓库之上加了一层 review 逻辑。每个 commit 在 Gerrit 里都有一个对应的 Change-Id,这个 ID 是 Gerrit 用来追踪评审状态的核心标识。普通 Git 仓库你直接git log就能拿到提交历史,但 Gerrit 上的 commit 信息往往还附带评审状态、投票结果、提交者、评审者等额外元数据,这些才是真正有价值的部分。
这个内容适合谁看?三类人最需要:一是做研发效能或代码质量分析的工程师,需要批量拉取 commit 数据做统计;二是项目管理者,想定期汇总团队提交情况;三是刚接触 Gerrit 的开发者,搞不清楚怎么用命令行高效获取提交信息。不管你属于哪一类,核心诉求都是一样的——用最省事的方式,把 Gerrit 上的 commit 信息完整、准确地拿到手。
我试过好几种方案,从最笨的手工复制到写脚本调 API,踩过的坑不算少。下面把我实际用下来最稳的几条路子拆开讲,包括每种方案的适用场景、具体操作步骤、参数怎么配、遇到问题怎么排查,尽量让你看完就能直接上手。
2. 先搞清楚 Gerrit 的 commit 信息长什么样
2.1 Gerrit 与普通 Git 仓库的核心差异
普通 Git 仓库里,一个 commit 的信息无非就是 hash、author、date、message 这几项。但 Gerrit 不一样,它在 commit message 的末尾强制追加了一个 Change-Id 字段,格式大概是这样:
commit abc123def456... Author: 某开发者 <dev@example.com> Date: Mon Jan 15 10:30:00 2024 +0800 修复登录页面的表单校验逻辑 Change-Id: I1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t这个 Change-Id 是 Gerrit 在代码被 push 到 refs/for/ 分支时自动生成的,它跟 commit hash 不是一回事。同一个 Change 经过多次修改后,commit hash 会变,但 Change-Id 保持不变。这就是为什么你想追踪一个评审的完整生命周期,光看 commit hash 是不够的,必须拿到 Change-Id。
除了 Change-Id,Gerrit 还维护了一套完整的评审元数据,包括:当前评审状态(New、Merged、Abandoned)、Code-Review 投票分数、Verified 投票分数、提交者、评审者列表、创建时间、最后更新时间等。这些信息在 Web 界面上看得到,但要从命令行或脚本里拿到,就得走 Gerrit 提供的接口。
2.2 你需要拉取哪些字段
在动手之前,先想清楚你到底要哪些信息。不同需求对应的拉取方式差别很大。我列一个常见的字段清单,你可以对照自己的场景勾选:
| 字段 | 说明 | 获取难度 |
|---|---|---|
| commit hash | 提交的唯一标识 | 低,git log 直接拿 |
| Change-Id | Gerrit 评审追踪 ID | 低,从 commit message 解析 |
| 提交者 | author 姓名和邮箱 | 低,git log 直接拿 |
| 提交时间 | commit date | 低,git log 直接拿 |
| 提交信息 | commit message 全文 | 低,git log 直接拿 |
| 评审状态 | New/Merged/Abandoned | 中,需要调 Gerrit API |
| 投票分数 | Code-Review 和 Verified | 中,需要调 Gerrit API |
| 评审者列表 | 谁投了什么票 | 中,需要调 Gerrit API |
| 文件变更列表 | 改了哪些文件 | 低,git show 可拿 |
| 行变更统计 | 增删了多少行 | 低,git log --stat 可拿 |
如果你只是要 commit hash、提交者、时间、message 这些基础信息,那根本不需要碰 Gerrit 的 API,直接用 Git 命令就够了。但如果你要评审状态、投票分数这些,就必须走 Gerrit 的 REST API 或者 SSH 接口。
提示:很多人一上来就想调 API,其实先确认一下自己的需求是不是真的需要 API。我见过不少场景,用
git log加几个参数就能搞定,结果绕了一大圈去写 API 调用脚本,纯属给自己找麻烦。
2.3 两种主流获取方式的取舍
从 Gerrit 拿 commit 信息,主流就两条路:一是走 Git 命令行,二是走 Gerrit 的接口。这两条路各有各的适用场景,我做个对比:
Git 命令行方式的优势是简单直接,不需要额外配置认证,只要你能 clone 仓库就能用。缺点是拿不到评审相关的元数据,因为那些数据根本不在 Git 仓库里,而是存在 Gerrit 的数据库中。
Gerrit REST API 方式的优势是能拿到完整的评审信息,包括状态、投票、评审者等。缺点是需要处理认证,而且 API 的返回格式是 JSON,需要额外解析。Gerrit 的 REST API 有个反人类的设定——返回的 JSON 前面会加一个)]}'前缀,这是为了防止 JSON 劫持攻击,解析的时候必须先把这个前缀去掉。
SSH 接口方式算是第三条路,Gerrit 提供了一套 SSH 命令,比如gerrit query,可以直接查询 change 信息。这种方式的好处是输出格式比较友好,支持多种格式(JSON、text 等),而且认证走 SSH key,不需要额外配 token。缺点是可移植性差一些,换台机器可能得重新配 SSH。
我个人的选择逻辑是:如果只要基础 commit 信息,用 Git 命令行;如果要评审元数据且环境固定,用 SSH 接口;如果要集成到自动化系统里,用 REST API。
3. 用 Git 命令行拉取基础 commit 信息
3.1 克隆仓库与指定分支
第一步没什么好说的,把仓库 clone 下来:
git clone ssh://user@gerrit.example.com:29418/project-name cd project-name注意 Gerrit 的 SSH 端口通常不是默认的 22,而是 29418,这个在 clone 的时候要显式指定。如果你用的是 HTTP 协议,那就走 80 或 443,但 HTTP 方式在 Gerrit 上通常需要额外配置认证信息。
clone 下来之后,默认是在 master 分支。如果你想拉取特定分支的 commit,先切过去:
git checkout -b release-1.0 origin/release-1.0或者你不想切换工作区,只想看某个分支的 log,可以直接指定分支名:
git log origin/release-1.0 --oneline3.2 按时间范围过滤 commit
实际工作中最常见的需求就是“拉取某段时间内的提交”。Git 的--since和--until参数就是干这个的:
git log --since="2024-01-01" --until="2024-03-31" --pretty=format:"%H|%an|%ae|%ad|%s" --date=short这条命令会输出 2024 年第一季度所有 commit 的 hash、作者名、作者邮箱、提交日期和标题,用竖线分隔。你可以把输出重定向到文件里,后续用脚本处理:
git log --since="2024-01-01" --until="2024-03-31" --pretty=format:"%H|%an|%ae|%ad|%s" --date=short > commits.txt这里有几个参数值得说明。%H是完整 commit hash,%h是短 hash。%an是作者名,%ae是作者邮箱。%ad是作者日期,配合--date=short输出 YYYY-MM-DD 格式。%s是 commit message 的第一行,也就是标题。
如果你想要完整的 commit message 而不只是标题,把%s换成%B:
git log --since="2024-01-01" --pretty=format:"%H|%an|%B" --date=short但要注意,%B包含换行符,输出到文件后解析起来会麻烦一些。我一般建议用%s拿标题就够了,真要完整 message 再单独处理。
3.3 提取 Change-Id 的实用技巧
前面说了,Change-Id 藏在 commit message 的末尾。要从git log的输出里提取它,可以用--grep参数过滤:
git log --grep="Change-Id" --pretty=format:"%H|%s|%b" --since="2024-01-01"但这样拿到的%b是完整 body,里面包含 Change-Id 那一行以及其他内容。要精确提取 Change-Id 的值,得用正则处理:
git log --since="2024-01-01" --pretty=format:"%H|%s|%b" | grep -oP 'Change-Id: \K[A-Za-z0-9]+'这条命令用grep -oP配合\K断言,只输出 Change-Id 冒号后面的值。如果你想把 commit hash 和 Change-Id 对应起来,可以这样:
git log --since="2024-01-01" --pretty=format:"%H" -1 | while read hash; do change_id=$(git log -1 --pretty=format:"%b" $hash | grep -oP 'Change-Id: \K[A-Za-z0-9]+') echo "$hash|$change_id" done不过这种逐条处理的方式在 commit 数量多的时候会很慢。更高效的做法是用git log一次性输出,然后用 awk 或 Python 脚本解析。我实测下来,几千条 commit 用 Python 脚本处理也就几秒钟的事。
注意:有些 commit 可能没有 Change-Id,比如直接 push 到分支的 merge commit,或者仓库没有配置 commit-msg hook。处理的时候要做好空值判断,别让脚本因为一条异常数据就崩了。
3.4 按作者或文件路径筛选
除了时间范围,按作者筛选也很常用:
git log --author="某开发者" --since="2024-01-01" --pretty=format:"%H|%ad|%s" --date=short--author支持正则匹配,所以你可以用--author="@example.com"来匹配某个邮箱域名的所有提交。
按文件路径筛选则是这样的:
git log --since="2024-01-01" -- src/main/java/ --pretty=format:"%H|%s"注意路径前面要加--,否则 Git 会把路径当成分支名。这个用法在你想看某个模块的变更历史时特别有用。
还有一个很实用的参数是--no-merges,可以过滤掉 merge commit,只保留实际的代码变更:
git log --no-merges --since="2024-01-01" --pretty=format:"%H|%an|%s"merge commit 通常没有实际的代码改动,统计代码量的时候把它们算进去会虚高,所以做分析时建议加上这个参数。
4. 通过 Gerrit REST API 获取完整评审信息
4.1 API 认证方式的选择与配置
Gerrit 的 REST API 走 HTTP 协议,认证方式主要有两种:HTTP Password 和 OAuth。对于脚本自动化场景,HTTP Password 是最简单的方式。
获取 HTTP Password 的步骤:登录 Gerrit Web 界面,进入 Settings -> HTTP Credentials,点击 Generate New Password。Gerrit 会生成一串随机字符,这就是你的 HTTP Password。注意这串密码只显示一次,关掉页面就看不到了,所以要当场保存好。
拿到密码后,调用 API 时用 Basic Auth 方式带上用户名和密码:
curl -u "username:http_password" \ "https://gerrit.example.com/a/changes/?q=status:merged+after:2024-01-01"注意 URL 里的/a/前缀,这是告诉 Gerrit 要走认证通道。如果你不加/a/,Gerrit 会当成匿名请求处理,很多接口会返回 403。
提示:HTTP Password 跟你登录 Web 界面的密码不是一回事。有些人拿登录密码去调 API,结果一直 401,排查半天才发现用错了。记住,API 认证用的是 HTTP Password,在 Settings 里单独生成的。
4.2 查询参数与过滤条件详解
Gerrit 的 changes 查询接口支持丰富的过滤条件,常用的有这些:
| 参数 | 说明 | 示例 |
|---|---|---|
| q | 查询条件 | q=status:merged |
| n | 返回数量限制 | n=100 |
| S | 起始偏移量 | S=100 |
| o | 附加字段 | o=CURRENT_REVISION |
| O | 附加字段(数字形式) | O=1 |
q参数是最核心的,它支持 Gerrit 的查询语法。几个常用的查询表达式:
status:merged已合并的 changestatus:open待评审的 changestatus:abandoned已放弃的 changeafter:2024-01-01某日期之后创建的before:2024-03-31某日期之前创建的owner:username指定提交者project:project-name指定项目branch:master指定分支message:"fix bug"commit message 包含特定文字
这些条件可以用+连接,表示 AND 关系:
curl -u "username:http_password" \ "https://gerrit.example.com/a/changes/?q=status:merged+after:2024-01-01+before:2024-03-31+project:my-project&n=100"返回的结果是一个 JSON 数组,但前面有个)]}'前缀。解析的时候要先去掉这一行:
curl -s -u "username:http_password" \ "https://gerrit.example.com/a/changes/?q=status:merged+after:2024-01-01" \ | tail -n +2 | jq '.'tail -n +2的作用就是从第二行开始输出,跳过第一行的)]}'。然后用jq解析 JSON,就能拿到结构化的数据了。
4.3 分页拉取与增量同步策略
Gerrit 的 API 默认返回数量有限制,通常是 25 或 50 条。如果你要拉取的数据量很大,必须处理分页。
分页有两种方式:一是用n参数指定每页数量,用S参数指定偏移量;二是利用返回结果中的_more_changes字段判断是否还有更多数据。
offset=0 limit=100 while true; do result=$(curl -s -u "username:http_password" \ "https://gerrit.example.com/a/changes/?q=status:merged+after:2024-01-01&n=$limit&S=$offset" \ | tail -n +2) count=$(echo "$result" | jq 'length') if [ "$count" -eq 0 ]; then break fi echo "$result" >> all_changes.json offset=$((offset + limit)) sleep 1 done注意那个sleep 1,这是为了避免请求过于频繁被 Gerrit 限流。我实测下来,不加延迟连续请求,大概几十次之后就会开始返回 429 错误。加个 1 秒延迟基本就稳了。
增量同步的策略是记录上次拉取的最后一条 change 的更新时间,下次只拉取这个时间之后的:
last_sync="2024-03-31 23:59:59" curl -s -u "username:http_password" \ "https://gerrit.example.com/a/changes/?q=status:merged+after:\"$last_sync\"" \ | tail -n +2 | jq '.'Gerrit 的after参数支持完整的时间戳格式,用引号包起来就行。
4.4 解析返回的 JSON 数据结构
Gerrit API 返回的 JSON 结构比较复杂,我挑几个关键字段说明:
[ { "id": "my-project~master~I1a2b3c4d5e6f7g8h9i0j", "project": "my-project", "branch": "master", "change_id": "I1a2b3c4d5e6f7g8h9i0j", "subject": "修复登录页面的表单校验逻辑", "status": "MERGED", "created": "2024-01-15 10:30:00.000000000", "updated": "2024-01-16 14:20:00.000000000", "submitted": "2024-01-16 14:20:00.000000000", "submitter": { "name": "某开发者", "email": "dev@example.com" }, "owner": { "name": "某开发者", "email": "dev@example.com" }, "current_revision": "abc123def456..." } ]id是 change 的完整标识,格式是project~branch~Change-Id。change_id就是那个 Change-Id。status是评审状态,可能的值有 NEW、MERGED、ABANDONED。submitted是合并时间,只有已合并的 change 才有这个字段。
如果你想拿到投票信息,需要在查询时加上o=DETAILED_LABELS参数:
curl -s -u "username:http_password" \ "https://gerrit.example.com/a/changes/?q=status:merged+after:2024-01-01&o=DETAILED_LABELS" \ | tail -n +2 | jq '.[0].labels'返回的 labels 结构里包含每个投票项的详情,包括谁投了什么分。这个在做代码评审质量分析时特别有用。
5. 用 SSH 接口查询 change 信息
5.1 配置 SSH 访问与测试连接
Gerrit 的 SSH 接口走 29418 端口,认证用 SSH key。配置步骤:
首先确认你本地有 SSH key,没有的话生成一个:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"然后把公钥添加到 Gerrit 的 Settings -> SSH Public Keys 里。添加完成后测试连接:
ssh -p 29418 username@gerrit.example.com gerrit version如果返回 Gerrit 的版本号,说明配置成功。如果报权限错误,检查一下用户名是否正确,以及公钥是否真的添加成功了。
注意:Gerrit 的 SSH 用户名跟你 Web 界面的登录名可能不一样,具体以 Settings -> Profile 里显示的 Username 为准。我见过有人拿邮箱当用户名去连,结果一直认证失败。
5.2 gerrit query 命令的常用参数
gerrit query是 SSH 接口里最常用的命令,基本用法:
ssh -p 29418 username@gerrit.example.com gerrit query --format=JSON status:merged project:my-project支持的查询条件跟 REST API 的q参数基本一致,但写法上有些差异。比如多个条件直接用空格分隔,不需要+号。
常用参数:
| 参数 | 说明 |
|---|---|
| --format | 输出格式,可选 JSON、TEXT、XML |
| --current-patch-set | 包含当前 patch set 信息 |
| --all-approvals | 包含所有投票信息 |
| --files | 包含文件变更列表 |
| --comments | 包含评论 |
| --limit | 返回数量限制 |
一个比较完整的查询示例:
ssh -p 29418 username@gerrit.example.com gerrit query \ --format=JSON \ --current-patch-set \ --all-approvals \ --files \ --limit 100 \ status:merged \ project:my-project \ after:2024-01-01这个命令会返回 JSON 格式的结果,每行一个 JSON 对象。注意是每行一个,不是整个结果是一个 JSON 数组。解析的时候要逐行处理。
5.3 输出格式选择与结果解析
--format=JSON输出的每行是一个独立的 JSON 对象,最后一行是一个统计信息对象,包含rowCount和runTime等字段。解析的时候要跳过最后一行:
ssh -p 29418 username@gerrit.example.com gerrit query \ --format=JSON \ status:merged \ project:my-project \ | head -n -1 \ | jq -s '.'head -n -1去掉最后一行,jq -s把多行 JSON 合并成一个数组。
如果你选--format=TEXT,输出是人类可读的格式,适合直接在终端看,但不适合脚本处理。--format=XML用得比较少,除非你的下游系统需要 XML。
我个人的习惯是:调试的时候用 TEXT 格式快速看结果,写脚本的时候用 JSON 格式方便解析。
5.4 SSH 与 REST API 的适用场景对比
这两种方式我都在实际项目里用过,总结一下各自的适用场景:
SSH 接口更适合:环境相对固定、已经配好了 SSH key、需要快速查询少量数据、不想处理 HTTP 认证的场景。它的输出格式比较干净,解析起来省事。
REST API 更适合:需要集成到 Web 服务或自动化平台、需要跨机器调用、需要更细粒度的权限控制的场景。它的可移植性更好,不依赖 SSH 配置。
还有一个实际考虑:有些公司的网络环境对 SSH 端口有限制,但对 HTTPS 是放行的。这种情况下 REST API 是唯一选择。反过来,如果 Gerrit 的 HTTP 接口没开或者认证配置有问题,SSH 就是备选方案。
6. 实操中踩过的坑与排查经验
6.1 认证失败的五种常见原因
认证问题是调 Gerrit API 时最高频的故障。我整理了一个排查表:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 401 Unauthorized | 用户名或密码错误 | 确认用的是 HTTP Password 而非登录密码 |
| 403 Forbidden | 没有访问该项目的权限 | 联系管理员确认权限配置 |
| 404 Not Found | URL 路径错误 | 检查是否漏了 /a/ 前缀 |
| 返回匿名数据 | 没走认证通道 | URL 里加上 /a/ |
| SSH 连接被拒 | 端口或 key 配置错误 | 确认端口 29418 和公钥已添加 |
其中最容易搞错的就是 HTTP Password 和登录密码的区别。Gerrit 的 HTTP Password 是在 Settings 里单独生成的,跟登录密码完全无关。我见过不止一个人在这上面卡了半天。
还有一个坑是 URL 里的/a/前缀。Gerrit 的 API 路径设计是这样的:/changes/是匿名接口,/a/changes/是认证接口。如果你用匿名接口去请求需要权限的数据,Gerrit 不会报错,而是返回空结果或者部分数据,让人误以为查询条件写错了。
6.2 JSON 解析报错的排查思路
Gerrit API 返回的 JSON 前面有)]}'前缀,这是最常见的解析报错原因。如果你用jq直接解析原始返回,会报parse error。
解决方法前面提过了,用tail -n +2跳过第一行。但还有一种情况:如果返回结果为空,Gerrit 可能返回一个空数组[],这时候tail -n +2会把整个内容都跳过,导致后续处理出错。稳妥的做法是先判断返回内容:
response=$(curl -s -u "username:http_password" "https://gerrit.example.com/a/changes/?q=status:merged") if echo "$response" | head -1 | grep -q ")]}'"; then json=$(echo "$response" | tail -n +2) else json="$response" fi echo "$json" | jq '.'另外,Gerrit 返回的 JSON 里有些字段可能包含特殊字符,比如 commit message 里有引号或换行符。用jq处理一般没问题,但如果你用 sed 或 awk 手动解析,就要特别小心转义问题。
6.3 大数据量拉取时的性能优化
当你要拉取的 change 数量达到几千甚至几万条时,性能就成了问题。我总结几个优化点:
第一,合理设置分页大小。Gerrit 默认每页返回 25 或 50 条,你可以通过n参数调大,但不要超过 500。我实测下来,n=100是个比较平衡的值,再大容易超时。
第二,使用增量拉取。不要每次都全量拉取,记录上次同步的时间点,只拉取新增的部分。这样每次的数据量小,速度快。
第三,并行拉取。如果确实需要全量拉取,可以把时间范围拆成多个小段,用多个进程并行请求。但要注意控制并发数,一般 3 到 5 个并发就够了,太多会被限流。
第四,只请求需要的字段。Gerrit 的o参数可以控制返回哪些附加信息。如果你不需要投票详情,就别加o=DETAILED_LABELS,能省不少传输量。
第五,本地缓存。拉取下来的数据存到本地数据库或文件里,后续分析直接读本地,不要每次都重新拉。
6.4 权限不足与项目可见性处理
Gerrit 的权限模型比较细,不同项目、不同分支的可见性可能不一样。如果你发现某些 change 拉不到,先确认几件事:
一是你的账号是否有该项目的 Read 权限。这个在 Gerrit 的 Access 配置里设置,普通用户看不到,得找管理员确认。
二是项目是否设置了is:visible之类的过滤条件。有些 Gerrit 实例会默认隐藏某些状态的 change,需要在查询时显式指定。
三是分支权限。有些项目对特定分支的访问有额外限制,比如只允许特定团队查看。
如果确认权限没问题但还是拉不到数据,可以试试用管理员账号或者换一个查询条件验证一下。有时候是查询语法的问题,不是权限的问题。
提示:遇到权限问题时,先别急着找管理员。用
ssh -p 29418 username@gerrit.example.com gerrit ls-projects看看你能列出哪些项目,这能快速判断是全局权限问题还是单个项目的权限问题。
7. 把拉取结果用起来:几个实用场景
7.1 生成团队提交周报
拉取一周内的 commit 信息,按作者分组统计:
git log --since="2024-01-15" --until="2024-01-21" --no-merges \ --pretty=format:"%an|%s" | sort | awk -F'|' '{count[$1]++} END {for (a in count) print a, count[a]}'这个输出会列出每个作者的提交数量。如果你想更详细一点,把提交标题也带上:
git log --since="2024-01-15" --until="2024-01-21" --no-merges \ --pretty=format:"%an|%s" | sort按作者排序后,一眼就能看出谁这周干了什么。这个用在周会上特别方便,不用再手工整理了。
7.2 统计代码变更量
用--numstat参数可以拿到每个文件的增删行数:
git log --since="2024-01-01" --until="2024-03-31" --no-merges \ --pretty=format:"COMMIT|%H|%an" --numstat输出格式是每个 commit 一行 COMMIT 标记,后面跟若干行文件变更统计。解析的时候按 COMMIT 行分割,就能算出每个 commit 的总增删行数。
如果要按作者汇总,可以用 awk 脚本处理:
git log --since="2024-01-01" --no-merges --pretty=format:"AUTHOR|%an" --numstat | \ awk -F'|' ' /^AUTHOR/ { author=$2; next } /^[0-9]/ { added[author]+=$1; deleted[author]+=$2 } END { for (a in added) print a, added[a], deleted[a] } '这个脚本会输出每个作者的累计新增和删除行数。注意--numstat输出的二进制文件变更会显示为-,处理的时候要过滤掉。
7.3 追踪特定 Change 的评审历史
如果你要追踪某个 Change-Id 的完整评审过程,用 REST API 的 detail 接口:
curl -s -u "username:http_password" \ "https://gerrit.example.com/a/changes/my-project~master~I1a2b3c4d5e6f7g8h9i0j/detail?o=ALL_REVISIONS&o=ALL_COMMITS" \ | tail -n +2 | jq '.revisions'返回的 revisions 字段包含这个 Change 的所有 patch set,每个 patch set 的 commit hash、创建时间、上传者等信息都在里面。配合o=ALL_COMMITS还能拿到每个 patch set 对应的 commit 详情。
这个在做代码评审回溯分析时特别有用,能清楚地看到一个 change 从提交到合并经历了多少次修改、每次修改改了什么。
7.4 集成到自动化流水线
把拉取脚本集成到 CI/CD 流水线里,可以实现自动化的代码质量报告。基本思路是:
在流水线的定时任务里调用拉取脚本,把数据存到数据库,然后跑分析脚本生成报告。关键是要处理好增量同步和错误重试。
#!/bin/bash LAST_SYNC_FILE="/var/lib/gerrit-sync/last_sync.txt" if [ -f "$LAST_SYNC_FILE" ]; then LAST_SYNC=$(cat "$LAST_SYNC_FILE") else LAST_SYNC="2024-01-01 00:00:00" fi CURRENT_TIME=$(date "+%Y-%m-%d %H:%M:%S") curl -s -u "username:http_password" \ "https://gerrit.example.com/a/changes/?q=status:merged+after:\"$LAST_SYNC\"&n=500" \ | tail -n +2 > /tmp/gerrit_changes.json if [ -s /tmp/gerrit_changes.json ]; then python3 /opt/scripts/process_changes.py /tmp/gerrit_changes.json echo "$CURRENT_TIME" > "$LAST_SYNC_FILE" fi这个脚本每次只拉取上次同步之后的新数据,处理完更新同步时间戳。配合 crontab 定时执行,就能实现自动化的数据同步。
注意:时间戳的格式要跟 Gerrit 的查询语法匹配。Gerrit 的
after参数支持YYYY-MM-DD HH:MM:SS格式,但时区默认是 Gerrit 服务器所在的时区。如果你的服务器和 Gerrit 不在同一时区,要做相应的转换,否则可能漏掉或重复拉取数据。
8. 几个容易被忽略的细节
8.1 Change-Id 的生成机制
Change-Id 不是 Gerrit 服务器生成的,而是本地 commit-msg hook 生成的。这个 hook 在git commit时自动运行,往 commit message 末尾追加一个 Change-Id。如果你 clone 仓库后没有安装这个 hook,提交的 commit 就不会有 Change-Id,push 到 Gerrit 时会被拒绝。
安装 hook 的命令:
curl -Lo .git/hooks/commit-msg \ "https://gerrit.example.com/tools/hooks/commit-msg" chmod +x .git/hooks/commit-msg这个细节在做自动化提交时特别重要。如果你的脚本自动生成 commit 并 push,必须确保 hook 已经安装,否则 push 会失败。
8.2 时区问题对时间过滤的影响
Gerrit 存储的时间戳是 UTC 时间,但查询时用的after和before参数默认按服务器本地时区解释。如果你在中国(UTC+8)查询after:2024-01-01,实际拉取的是 UTC 时间 2023-12-31 16:00 之后的数据。
这个差异在跨天查询时会导致数据偏差。解决方法是在查询时显式指定时区,或者在心里做好换算。我一般建议在脚本里统一用 UTC 时间,避免混淆。
8.3 空结果与异常返回的区分
调 API 时,空结果和异常返回是两回事。空结果是查询条件没匹配到数据,返回[];异常返回是请求本身出了问题,返回错误码或错误信息。
处理的时候要区分对待:空结果可以正常继续,异常返回需要重试或报警。我见过有脚本把 403 错误当成空结果处理,结果数据一直拉不到也不报警,排查了好久才发现是权限问题。
8.4 数据去重与增量更新的边界
增量拉取时,边界条件的处理很容易出问题。比如你用after参数拉取,如果两条 change 的更新时间完全相同,可能会漏掉其中一条。稳妥的做法是用>=而不是>,然后对拉取到的数据做去重。
去重可以用 change 的id字段作为唯一键。在写入数据库时用INSERT OR REPLACE或者先查后插,确保同一条 change 不会重复存储。
我在实际项目里踩过这个坑:因为边界处理不当,同一条 change 被拉取了两次,导致统计报表里的数字翻倍。后来改成用 change id 做唯一约束,问题就解决了。