用 GitHub 隐私邮箱重写历史 commit

过去几年,无论是 commit 还是 GitHub Action,我一直在 GitHub 上使用我的真实邮箱,之前我也一直没注意,知道最近向 Yazumi 提交 PR,发现他们的提交历史上很多人的邮箱都是类似 @user.noreply.github.com 的模样,这才发现,原来 GitHub 给每个人都提供一个隐藏邮箱的选项,避免自己真实的邮箱被暴露:

社交媒体上很多人看不起那些 GitHub 提交全绿,但乍一看全是脚本的人,但我前不久就是这样。甚至前不久,我才在 vibe coding 的 workflow 的学习下,发现机器人提交的邮箱其实有一个专属地址:

git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"

虽然我已经用 cloudflare 的邮箱转发服务了,但我还是觉得这个靠谱一点,因为域名总有过期的一天,而这个账户只要不被封,或者被注销,那么就会一直存在下去。

然后,天大的事情发生了,我前年维护了一个模版仓库,模板仓库提交的邮箱,都是我的真实邮箱,这就有个问题,如果我进行历史重写,没有办法保证后续所有仓库进行重写后的 hash 值保持一致。这几乎是对我的模板仓库是一个毁灭性的打击,因为我这两年所有的代码都是基于模板仓库开发的,这几乎是让我重写一遍。

在纠结了两天之后,没有办法了,觉得还是把模版仓库拆散吧,然后各自独立运作,模板仓库的搞法搞不下去,于是只能一个仓库一个仓库地复写,过程非常痛苦,因为我之前一致没用 rebase 的合并方式,而且因为引入了 vibe coding 的提示词,总体改动非常多,最终导致我的每个仓库的提交历史在某个时间点之后就是一坨:

除了隐私邮箱,我还想把某些合并节点也删掉,除了用 squash,只能一个一个 cherry pick 过去,幸好 jetbrains 的工具支持批量选中,然后进行 cherry pick,最终优化成了一个线性的提交,非常美丽:

过程使用到的命令有:

git filter-repo --commit-callback '
if commit.author_email == b"im#bgzo.cc":
    commit.author_email = b"[email protected]"
    commit.committer_email = b"[email protected]"
'

过程可能出现如下提示:

Aborting: Refusing to destructively overwrite repo history since
this does not look like a fresh clone.
  (expected freshly packed repo)
Please operate on a fresh clone instead.  If you want to proceed
anyway, use --force.

因为这个仓库可能不是一个全新的仓库,进行重写之后一些对象可能会消失,如果不在意的话,直接加一个 --force 的参数即可,而且覆写之后,自己的 remote 会消失,需要重新添加,并且提交的时候,需要强制推送,如果要避免,也可以使用 --partial 选项来完全禁用这种行为。

在处理一些机器人的提交历史的时候,我们可以通过 commit 内容进行指定,最终可以锁定机器人的提交,然后吧机器人的邮件写进去:

git filter-repo --commit-callback '
if commit.author_email == b"im#bgzo.cc" and commit.message.decode().strip() == "Sync feed":
    commit.author_email = b"github-actions[bot]@users.noreply.github.com"
    commit.committer_email = b"github-actions[bot]@users.noreply.github.com"
'