Git mailmap:贡献者名字统一了,提交里的旧邮箱为什么还在
贡献者换过邮箱,提交统计里就出现两个名字。直接改写历史虽然可能统一记录,却会改变提交对象并影响引用。若需求只是让日志与贡献报表采用同一份显示身份,可以先评估 mailmap:把旧身份映射为约定的规范姓名和邮箱。
这种映射发生在支持它的读取和展示过程中。原始提交对象里仍保留当时的作者字段,提交标识也不会因新增映射自动改变。因此它适合整理展示口径,却不能拿来声称已经清除了历史中的旧邮箱。
同时核对原始字段、显示字段与对象标识
保存为 demo.sh,通过 bash demo.sh 运行。本文在 Git 2.52.0 与 Bash 5.2.37 验证。所有姓名及 example.invalid 邮箱都是虚构值,脚本只操作临时仓库,并在结束时清理,不涉及实际人员身份或远端服务。
AI概念插图:用抽象图形表现本文讨论的关系,并非软件截图或真实运行结果。
#!/usr/bin/env bash set -euo pipefail demo_dir=$(mktemp -d) trap 'rm -rf -- "$demo_dir"' EXIT export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null export GIT_AUTHOR_DATE='2026-01-01T00:00:00Z' export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git init -q -b main "$demo_dir" cd "$demo_dir" git config user.name OldAlias git config user.email old@example.invalid git config commit.gpgsign false printf 'demo\n' > note.txt git add note.txt git commit -qm seed before=$(git rev-parse HEAD) object_before=$(git cat-file commit HEAD) printf 'Canonical Name <new@example.invalid> <old@example.invalid>\n' > .mailmap raw=$(git log -1 --format='%an <%ae>') mapped=$(git log -1 --format='%aN <%aE>') [[ $raw == 'OldAlias <old@example.invalid>' ]] [[ $mapped == 'Canonical Name <new@example.invalid>' ]] [[ $(git rev-parse HEAD) == "$before" ]] [[ $(git cat-file commit HEAD) == "$object_before" ]] printf 'raw: %s\nmapped: %s\n' "$raw" "$mapped" printf 'commit id and raw object unchanged\n' cat > .mailmap <<'MAP' Person A <a@example.invalid> AliasA <shared@example.invalid> Person B <b@example.invalid> AliasB <shared@example.invalid> MAP a=$(git check-mailmap 'AliasA <shared@example.invalid>') b=$(git check-mailmap 'AliasB <shared@example.invalid>') [[ $a == 'Person A <a@example.invalid>' ]] [[ $b == 'Person B <b@example.invalid>' ]] printf 'shared address A: %s\nshared address B: %s\n' "$a" "$b"
占位符的大小写决定是否应用映射
第一次查询使用小写的 an 和 ae,得到 OldAlias 及旧邮箱。第二次改用 aN 和 aE,得到 Canonical Name 及新邮箱。两行输出来自同一个 HEAD,差异由显示字段是否尊重 mailmap 决定。只新增映射文件,并不会让所有格式自动得到相同结果。
代码在映射前记录提交标识和原始对象文本,映射后再次比较,二者都保持一致。这比仅看界面上姓名已经更新更有说服力:既证明映射生效,也说明它没有修改被查询的提交对象。
映射文件位于仓库顶层。第一行先写规范姓名和规范邮箱,再写需要匹配的旧邮箱。这里只演示本地工作区读取;若需要团队共享通常会把映射文件作为项目内容管理,但是否提交和如何审核,应遵循项目约定。
共享邮箱时,匹配条件不能太宽
后半段重新写入两条演示规则。AliasA 与 AliasB 使用同一个旧邮箱,但被映射到不同的人。此时规则同时包含旧姓名与旧邮箱,check-mailmap 对两条完整身份分别返回 Person A 和 Person B。
如果只凭共享邮箱建立一条宽泛规则,两种身份就可能被合并到同一显示结果。编写映射前应先核对历史身份的真实对应关系,不能因为两个名字相似或邮箱相同就断定属于同一个人。本例的关系由人为设定,不承担身份认证作用。
check-mailmap 适合在应用到整份报告之前逐条试验。先准备几个应当匹配和不应当合并的样本,读取结果,再检查实际报告使用的字段。这样能区分规则写错、样本不符和展示工具没有采用映射三类问题。
不同命令与外部托管界面可能有自己的显示方式。本文只确认 Git 日志里的指定占位符,以及 check-mailmap 的本地输出,不据此保证任意网站都立即同步。若报告来自另一套服务,应针对那个入口重新核验。
还要分清作者与提交者:两者是提交里的不同字段,日志也提供分别遵守映射的占位符。统计时先决定要展示哪一种角色,不能把统一姓名后的作者数量直接解释成所有实际参与者数量。
这项工具解决的是展示一致性。需要历史取证时仍可查看原始对象,需要保护已经公开的个人信息时则必须评估真正的历史处理方式。先把目标说清,再决定用显示映射还是另一种有不同影响的操作。


