记一次 GitHub 幽灵协作者大清洗:强制重写 Git 历史与穿透 CDN 缓存实践

0 阅读6分钟

记一次 GitHub 幽灵协作者大清洗:强制重写 Git 历史与穿透 CDN 缓存实践

Git Ghost Collaborators Cleanup.png

目录


01. 诡异的背景:天降“幽灵”协作者

不知道大家在日常开发中,有没有遇到过这样的场景: 你的个人开源项目,代码明明从头到尾全是你一个人一行行敲出来的,连个 Pull Request 都没接受过。但是,当你打开 GitHub 仓库主页时,右侧的 Contributors(协作者) 列表中,赫然躺着一个你完全不认识的陌生账号(比如叫 ghost_user 或者 unknown_developer 等等)。

如果你尝试去删除这个协作者,你会发现 GitHub 设置里根本没有删除入口。这简直就像代码仓库里住进了一个甩不掉的“幽灵”。今天,就带大家一文彻底解决这个疑难杂症,硬核清洗仓库历史。

02. 刨根问底:幽灵账号究竟从何而来?

为什么会有陌生人出现在你的 Contributors 列表里?罪魁祸首其实是你本地的 Git 配置。

GitHub 判定一次 Commit 属于谁,完全是看提交信息里的 Author Email。如果你在早期的本地电脑上,Git 的邮箱没有配置,或者使用了类似 test@example.com 这样的测试邮箱,又或者是使用了公司默认拼音邮箱: 一旦你把代码 Push 到了 GitHub,GitHub 就会去全网匹配这个邮箱 test@example.com 属于哪个注册用户。如果刚好有陌生人注册了这个邮箱,那么你的心血代码就这样被 GitHub 算作了他的贡献。

哪怕只有一次提交,他的头像也会永久挂在你的项目主页上。

03. 核心痛点:为什么普通修改救不了命?

很多同学发现问题后,第一反应是:

  • “我在项目里加个 .mailmap 映射文件不就行了?” (治标不治本,而且你的仓库会留下一份多余的配置文件)
  • “我用 git commit --amend 改掉最新一次提交行不行?” (不行,历史深处的提交依然是错误邮箱)
  • “那我把远程仓库删了重建?” (万万不可,这会丢失你珍贵的 Stars、Issues 和 PR 记录)

所以,唯一的完美解决路径是:利用底层脚本批量篡改所有的 Git 节点历史,然后强行覆盖远端,最后物理打穿 GitHub 的网页缓存。

04. 终极解决方案(上):地毯式重写 Git 历史

想要改写历史,我们需要祭出 Git 的大杀器:git filter-branch

4.1 拉取所有远程分支

很多时候你的旧提交不仅存在于 master,还藏在几十个老旧历史分支里。我们必须先把它们全拉到本地:

git branch -r | Select-String -NotMatch 'HEAD' | ForEach-Object { $branch = $_.ToString().Trim().Replace('origin/', ''); git checkout -b $branch "origin/$branch" }
4.2 编写与执行清洗脚本

使用以下脚本,遍历所有的分支和所有的标签(Tags),将历史中混入的脏邮箱,全部替换为你自己正确的邮箱:

#!/bin/sh
export FILTER_BRANCH_SQUELCH_WARNING=1
git filter-branch -f --env-filter '
CORRECT_NAME="your_real_name"
CORRECT_EMAIL="your_real_email@example.com"

if [ "$GIT_COMMITTER_EMAIL" = "test@example.com" ] || [ "$GIT_COMMITTER_NAME" = "WrongName" ]; then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "test@example.com" ] || [ "$GIT_AUTHOR_NAME" = "WrongName" ]; then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags

核心提示(划重点): 为什么一定要加 --branches --tags 参数? 因为如果在几十个分支上分别跑,会导致各分支原本共有的“祖先节点”产生不同的新 Hash,将来你合并代码时会面临大量的 Merge Conflict。加上这两个参数全局统筹,Git 的映射表(map)机制会保证分支拓扑树 1:1 完美平移,绝不产生无谓冲突。

4.3 强制覆盖远端

清理本地残留的备份,并将清洗后的树强制推送到远端:

rm -rf .git/refs/original/
git push --force --all origin
git push --force --tags origin

05. 终极解决方案(下):硬核穿透 GitHub 顽固 CDN 缓存

如果你以为做完上面几步就结束了,那就错了。 当你再次打开 GitHub,你可能会发现:幽灵协作者还在页面上!

这是因为 GitHub 的底层数据库确实干净了,但它的网页前端用了 Fastly CDN,缓存极其顽固。对于仓库的 Contributors 列表,仅靠 Force Push 是无法触发全站 CDN 重算的。

我们需要使用**“休克疗法”**来打穿缓存。

方法一:默认分支反复切换(推荐)

如果你安装了 gh 命令行工具,可以通过反复切换默认分支,给 GitHub 服务器下达最高优先级的重算指令:

# 1. 创建一个临时分支并推送到远端
git checkout -b temp-refresh-branch
git push origin temp-refresh-branch

# 2. 调用 API 将默认分支切换为临时分支,强迫 GitHub 重算
gh repo edit --default-branch temp-refresh-branch

# 3. 等待15秒钟左右,再切回 master
sleep 15
gh repo edit --default-branch master

# 4. 销毁临时分支
git push origin --delete temp-refresh-branch
git branch -D temp-refresh-branch
方法二:空白分支覆盖(备选)

如果是单分支仓库,可以直接推送一个完全空的节点顶替 master,随后再恢复:

git checkout --orphan empty-tree
git rm -rf .
git commit --allow-empty -m "chore: clear history to reset github cache"
git push origin empty-tree:master --force
sleep 5
git checkout master
git push origin master --force
5.3 终极验证

直接调用底层 API 抓取真实数据验证,不要仅仅依赖浏览器的无痕窗口:

gh api repos/your_username/your_repository/contributors --jq '.[].login'

只要这个接口返回的 JSON 里只有你自己,就可以确认底层数据已彻底清理完毕。剩下的只需要等待 CDN 自然过期即可。

06. 踩坑与避坑总结

  • 永远在新的电脑上配置好全局 Git 邮箱:git config --global user.email "your_real_email@example.com"
  • 大规模改写历史前,务必在本地打一个压缩包备份整个工程,防患于未然。
  • 一定要检查 git ls-remote 看看是否有你本地没拉取过的古董分支,如果有,必须全部拉下来一起重写,否则错误记录随时可能死灰复燃。

07. 关于作者

专注 Android 应用开发、各种实用工具构建及日常开发踩坑经验分享。如果这篇清洗实战文章帮到了你,欢迎点赞和关注。