一张热力图,七年没有答案
注:本文由 gpt-5.6-sol 撰写
事情起于一个看似微不足道的细节。
一个开发者从周一写到周四,留下四个 commit。星期五,他把它们统一 push 到自己的 Gitea。随后打开个人主页,却发现前四天一片空白,只有星期五亮起一个绿色方格。
Gitea 记住了代码到达服务器的时间,却没有展示代码真正写下的日期。
持续七年的问题
这个问题最早可以追溯到 2019 年。
Issue #8178:Heatmap: Don't count commits by other authors 指出,镜像仓库里的 commit 会被算到仓库所有者名下,真实作者反而没有贡献。
随后几年,相同问题以不同形式反复出现:
- Issue #11861:Is it possible to count commit times rather than push times in user heatmap?
- Issue #14051:Heatmap not reflecting past days commits
- Issue #14472:Add or edit the existing heatmap to show commits, not pushes
- Issue #24240:Activity Heatmap: Count commits instead of pushes
人们反复询问:为什么 heatmap 统计 push,而不统计 commit?为什么连续工作数日,最后只亮起一天?
维护者的回答长期保持一致:Gitea 的 heatmap 记录平台活动。评论、评审、创建 Issue、推送代码,都是 action。系统知道谁在什么时间执行了 push,却没有一份现成且可信的 commit 贡献账本。
维护者 @lafriks 代表了这种谨慎。他反复提醒:Git 历史会被 force push 改写,默认分支会变化,仓库可能被删除、导入或镜像。
若按 commit 统计,系统必须知道作者、提交者和推送者分别是谁,还要识别 fork、重复 commit 与组织成员资格。一个绿色方格背后,可能是对整台服务器所有仓库的长期扫描。
一个新贡献者决定解决它
2026 年 1 月,第一次参与 Gitea 贡献的 @fjamesprice 决定解决这个问题。
他提交了 PR #36469:Use commit author date for heatmap instead of push date。
最初的方案很自然:保存 commit author date,让 heatmap 使用这个日期。
随后,问题迅速扩大。
如果一次 push 含有三十天的 commit,系统应该生成三十条 action,还是另外建立一张表?
前一种方案会让动态列表出现大量相似记录。后一种方案则会带来数据库迁移、索引、数据清理与一致性问题。
这个 PR 改了又改,最终累计:
- 19 个 commit
- 12 个文件
- 334 行新增
- 25 条普通评论
- 9 条 review comment
维护者 @silverwind 一度认为新的独立表方案在架构上成立。
维护者 @wxiaoguang 则提醒,action 表本来已有数百万行和性能压力,heatmap 不应与它产生新的依赖。
@lafriks 的意见更加明确:force push、错误合并、镜像复制和仓库导入都可能制造大量需要重新分析的 commit。这些边界情况会让系统付出远高于功能价值的复杂度,现有方案反而更加合理。
@wxiaoguang 同意了这一判断。
最终,@fjamesprice 主动关闭 PR。
他在留言里写道,这是自己的第一个 PR。虽然没有被采用,但仍然是一次很好的学习经历。
故事原本应该到此结束。
几个小时后,Silverwind 改变了判断
关闭 PR 后不久,@silverwind 查阅了 GitHub 的贡献统计规则。
他发现,GitHub 的贡献图确实依据 commit author、默认分支和 author date 计算,而非 push date。
于是,他重新开启了 Issue #36471:Heatmap shows commits on push date instead of commit author date。
他写道:
GitHub 确实统计 commit。从根本上看,贡献应该按照 commit 计算。
随后,他给出了一套新的方向:
- 每天由后台任务扫描默认分支
- 依据已验证邮箱识别作者
- 使用 author date 归属日期
- fork 不计入贡献
- 将过去一年的结果写入独立表
- 评论、Issue 和评审等活动继续从 action 表读取
@fjamesprice 又写出一份新的设计。
@silverwind 表示认可,并认为每天凌晨执行一次完整计算是可以接受的。
但 @lafriks 继续追问:
- force push 后已经消失的 commit 如何处理?
- 默认分支变化后,旧数据如何更新?
- 仓库删除后,历史记录如何清理?
- 非组织成员的 commit 是否应该出现?
- PR review 等非代码活动如何保留?
- 同一个 commit 被复制到多个仓库时,是否会重复计算?
争论至此已经发生变化。
大家争论的已不再是“要不要修一个日期”,而是“什么才算贡献”。
WxiaoGuang 把旧设计写进源码
两个月后,@wxiaoguang 提交了 PR #37195:Add comment for the design of user activity time。
这个 PR 没有改变任何程序行为。
它只在源码里加入了一段醒目的注释:
// HINT: USER-ACTIVITY-PUSH-COMMITS:
// it only uses the doer's action time,
// it doesn't use git commit's time.
这段注释的含义非常明确:
当前 heatmap 使用行动者的 action time,不使用 git commit time;这是有意设计。
这并非一次中性的文档整理。
此前,@wxiaoguang 已经在 PR #36469 中表示,既然这个问题已有结论,就应该把设计写入代码,避免后来者继续投入不被接受的尝试。
PR #37195 随后获得 @bircni 和 @lunny 两名维护者批准,并由 @wxiaoguang 合并。
六天后,@wxiaoguang 引用这段注释,关闭了 Issue #36471。
在他看来,争论已经结束:heatmap 记录的是 action,不是 Git 历史。
六小时后,Issue 再次打开
@wxiaoguang 关闭 Issue 六小时后,@silverwind 又把它重新打开。
他在评论中写道:
This is still a major behaviour difference from GitHub that must be fixed, in my opinion. Keep open.
Pushes are not contributions, commits are.
这仍然是 Gitea 与 GitHub 之间重大的行为差异。这个问题必须修复,Issue 应该保持开放。
Push 不是贡献,commit 才是。
他还指出,这个问题并不只影响 heatmap,也会影响 “top contributors”等其他功能。
从那以后,Issue #36471 一直保持开放,并带有 proposal/accepted 标签。
但那段宣告“当前行为属于有意设计”的注释,至今仍留在源码中。
Silverwind 为什么不亲自实现?
有人或许会问:@silverwind 既然如此坚持,而且拥有 merge 权限,为什么不亲自完成?
因为 merge 权限并不等于个人决定权。
Gitea 的常规 PR 需要两名 maintainer 批准。如果 @silverwind 自己编写,他仍然需要另外两名维护者认可,而公开讨论中恰好存在强烈的保留意见。
他后来完成了 PR #36622:Load heatmap data asynchronously,解决了 heatmap 查询阻塞页面的问题,却没有承担贡献模型的全面改造。
这也说明,他愿意处理 heatmap 的性能问题,但重新定义“贡献”需要的不只是代码,还需要维护者之间形成共同认可。
新的希望,新的争议
如今,现任 Gitea 技术监督委员会成员 @lunny 正在开发 PR #37209:Implement DB-backed contributor stats with queued updates and UI sidebar contributors。
这个 PR 试图建立新的 contributor stats 数据层:
- 按仓库保存贡献
- 按作者识别用户
- 按 author date 记录日期
- 默认分支变化时重新计算
- force push 时重新扫描仓库
它或许会成为未来 heatmap 的基础。
但这项工程同样庞大,目前涉及数十个文件和数千行代码,也面临数据库长期增长、force push 一致性与历史重算成本等争议。
一张绿色方格背后的真正问题
七年过去,Gitea 仍然知道谁在今天按下了 push,却无法让所有维护者共同认可:昨天写下的 commit,应该怎样成为昨天的贡献。
这场争论表面上围绕一张热力图,实质上争夺的是一个定义:
软件项目究竟应该记录人的操作,还是记录人的创造?
