Git 提交署错名字别硬改了!dry-run 预览 + 逐条确认 + 自动备份,一条命令搞定

cover_1800x766

周五下午,你刚把项目推上公司仓库,同事路过瞄了一眼 git log:

"哎,你这提交怎么都是 xiaoming@qq.com?"

你低头一看,心里咯噔一下——

新电脑没配全局邮箱,过去三个月的提交,作者全是私人账号。

更糟的是:公司 CI 按邮箱统计贡献,你这三个月的代码,系统里查无此人。

遇到这种事,大多数人的第一反应是搜索引擎:

"git 修改历史提交作者"

然后复制回来一段 git filter-branch --env-filter '...',改两个变量,回车——

然后你就开始祈祷:

  • 我会不会把别人的提交也一起改了?
  • 我要是只想改最近 5 个怎么办?
  • 改错了怎么回滚?
  • 到底会改到哪些提交,能先看看吗?

这些问题,一条裸命令一个都回答不了。

所以我把这些"祈祷"全部写成了功能,做成了一个脚本:git-change-author.sh。


它能做什么?一张图看懂

先说结论:这是一个纯 Bash、零依赖的脚本,只用 git 自带命令(底层是 git filter-branch --env-filter),不需要装 git-filter-repo,拷过去就能用。

它围绕"改作者"这件事,补齐了一整套安全措施:

img2_features

能力对应参数解决什么问题
精确定位旧作者--old-name + --old-email姓名和邮箱同时匹配才替换,不误伤别人
全量覆盖--all整个仓库统一成新身份
最近 N 次--last N只动最近几个提交
时间范围--since / --until只改某一段时间内的提交
指定分支--branch <ref>默认全部分支,也可只改一个
同时改提交者--also-committerauthor 和 committer 一起换
列出作者--list-authors先看历史里到底有哪些"人"
预览--dry-run只告诉你会改哪些,不动仓库
逐条确认--interactive每个提交 y/n,随时 q 退出
自动备份内置改写前清理并生成 refs/original 备份

先看人,再动手:--list-authors

很多人改作者失败,是因为旧作者的姓名/邮箱根本没写对——大小写差一个字母,就匹配不上。

所以第一步,先让脚本把历史里出现过的作者全列出来:

./git-change-author.sh --list-authors

它会用 git shortlog 去重汇总,按提交数降序排列:

==================================================================
当前仓库历史中的作者与邮箱(按提交数降序,共 3 位):
==================================================================
提交数    作者 <邮箱>
------------------------------------------------------------------
128      小明 <xiaoming@qq.com>
45       Alice <alice@company.com>
3        ming <ming@localhost>
------------------------------------------------------------------
提示:复制其中一行的『姓名 <邮箱>』可作为 --old-name / --old-email 的参考。

作者太多?加 --limit 10 只看前 10 位,终端里还会问你要不要展开全部。

直接复制,不再手敲。


核心用法:三个场景,三条命令

场景一:把"私人身份"换成"公司身份"(精确匹配)

./git-change-author.sh \
  --old-name "小明" --old-email xiaoming@qq.com \
  --new-name "小明" --new-email xiaoming@company.com

关键点:姓名 + 邮箱必须同时匹配才会改。同事 Alice 的提交,一个字都不会动。

场景二:只改最近 5 次,并且逐个确认

./git-change-author.sh --all \
  --new-name "DevTeam" --new-email dev@company.com \
  --last 5 --interactive

场景三:只改 2024 年内、某个分支上的提交

./git-change-author.sh --all \
  --new-name "DevTeam" --new-email dev@company.com \
  --since 2024-01-01 --until 2024-12-31 --branch master

日期支持 git 能解析的任何格式,"2 weeks ago" 这种也行。


我最喜欢的两个设计

1)--dry-run:先看,不动

任何会"重写历史"的操作,我都建议先预览:

./git-change-author.sh --old-name "小明" --old-email xiaoming@qq.com \
  --new-name "小明" --new-email xiaoming@company.com --dry-run

输出会列出每一个将被改写的提交哈希、当前作者和提交说明,最后一行写着:

预览结束,未对仓库做任何修改。

确认列表没问题,再去掉 --dry-run 正式执行。

2)交互式确认:像 git add -p 一样逐个决定

在终端里直接运行(不加 --force)时,脚本默认进入交互模式:

img3_terminal

每个候选提交会展示哈希、作者、时间、说明,然后问你:

  • y —— 改这一个
  • n —— 跳过
  • a —— 剩下的全改
  • q —— 放弃,退出(什么都不会改)

在管道或 CI 里运行(非 TTY)时不会自动进入交互,避免卡死;想跳过最终确认可以加 --force。


它是怎么工作的?

整个流程,我按"先确认、再动手"的顺序设计:

img4_workflow

  1. 解析参数;在终端里缺什么就问你什么(新姓名、新邮箱、定位方式)
  2. 校验:是否在 Git 仓库里、--last 是不是正整数、已跟踪文件有没有未提交改动
  3. 圈定候选提交:先按 --last / --since / --until / --branch 圈范围,再按"姓名+邮箱"过滤
  4. 交互确认(可选):得到最终的允许列表,写入临时文件
  5. 输出执行计划:旧作者、新作者、范围、候选数、将修改数、备份位置
  6. --dry-run 到此为止;否则二次确认
  7. 执行 git filter-branch --env-filter:只对允许列表里的提交替换 author(可选 committer)

有个细节值得说一下:env-filter 里是用 grep -qxF -f 允许列表文件 对 $GIT_COMMIT 做整行精确匹配,所以"最近 N 次""时间范围"这种过滤,都在前面算好,filter-branch 本身只负责"对列表里的提交换人"。


⚠️ 必须知道的三件事

这部分请务必看完。

1. 哈希会变——不只是被改的那几个

Git 的每个提交哈希,都包含父提交的哈希。你改了第 100 个提交,它之后的所有提交,哈希全部连锁变化。

即使你只想改"最近 5 个",脚本也要重写整条链上受影响的部分。

img5_hash

所以:重写历史 = 产生一段全新的历史。

2. 已经推送过的仓库,要 force push,先和团队打招呼

脚本跑完会提示你:

git push --force --tags origin '<分支>'

这会覆盖远程历史。同事本地的分支会和远程"分叉"。多人协作的仓库,请先沟通,让大家在你推送后重新拉取(或基于新历史重建分支)。

3. 后悔药和"彻底清理"

脚本改写前会先清掉旧的 refs/original,改写后 filter-branch 会把原历史留在那里。想回滚:

git reset --hard refs/original/refs/heads/<分支名>

想彻底删除旧对象、回收空间(同时也意味着没有后悔药了):

git reflog expire --expire=now --all && git gc --prune=now

建议顺序:确认新历史没问题 → 再 gc。


几个使用小贴士

  • 默认不改 committer。 如果你希望提交者也一并变更,加 --also-committer。
  • 工作区要干净。 已跟踪文件有未提交改动时脚本会直接拒绝;未跟踪文件只提示,不阻塞(用 git stash -u 可一并暂存)。
  • 默认范围是 --all,也就是所有分支和标签都会被重写;只想动一个分支,用 --branch。
  • 姓名/邮箱里别带单引号。 脚本通过单引号把新身份塞进 env-filter,含 ' 的名字需要先自行处理。
  • 先在副本上演练。 最稳妥的做法:git clone 一份,在副本上跑通 --dry-run 和正式执行,确认无误再在正式仓库操作。

推荐的"安全五步"

1. git clone 一份副本(或至少确认已有可用备份)
2. ./git-change-author.sh --list-authors      # 看清历史里有谁
3. 加上你的参数 + --dry-run                    # 预览会改哪些
4. 去掉 --dry-run,--interactive 逐条确认      # 正式执行
5. 检查 git log,确认无误 → 再 force push、再 gc

写在最后

改 Git 历史这件事,难的从来不是那条命令,而是**"我真的改对了吗?"**这份不安。

所以这个脚本做的事很朴素:把"预览、确认、备份、回滚"这些本该有的安全措施,一个个补上。

  • 想看有哪些作者 → --list-authors
  • 想先看看会改什么 → --dry-run
  • 想逐个决定 → --interactive
  • 想反悔 → refs/original

工具不该让人提心吊胆,该让人心里有底。

如果这个脚本帮你省下了一个周五下午的焦虑,欢迎点个在看、转给那个总是忘记配邮箱的同事。

你还遇到过哪些"Git 手滑"的瞬间?留言区聊聊 👇


📎 获取脚本:https://github.com/zhengmingliang/code-fragment/blob/main/shell/linux/git/git-change-author.sh

⚠️ 免责声明:重写历史是不可逆性很强的操作,请务必先备份、先 --dry-run,并在团队协作的仓库中提前沟通。

Q.E.D.


寻门而入,破门而出