Skip to content

Git 历史错乱(重写后平行宇宙)复盘与通用修复方案 ​

一句话结论:只要出现过 git filter-branch / 大文件清理 / git push --force 改写历史,就一定会出现「代码一模一样但 Hash 不一样」的平行宇宙。此时不要合并、不要 Pull、不要让 AI 随便动历史 —— 唯一正确做法:找到最干净的那条基准分支,把新写的代码 Cherry-pick 过去,废弃其他所有污染分支。


0. 🚨 绝对权限红线:AI 不允许做的事(最重要,记第一条) ​

这是本次故障最大的教训。历史重写类操作的犯错成本是 10 倍普通 Bug。

后续所有 Git 相关操作,严格对号入座:

操作类型允许 AI 做吗?备注
查看提交历史 / git log / git reflog / git diff✅ 随便看只读操作,不会搞砸
Cherry-pick 单条提交到干净分支、单个分支 git reset --hard origin/xxx✅ 可以(先告知步骤)局部操作,影响范围可控
git push --force / git push origin master --force / git push --tags --force⚠️ 只允许在单人项目,且必须先列出要覆盖的分支清单与当前 Commit,等你人工确认一句「执行」才推多人项目直接禁止 AI 执行 Force Push
任何改写历史的操作:git rebase -i、filter-branch / filter-repo / 删大文件 / 批量改提交信息❌ 绝对禁止 AI 直接执行必须 AI 出方案 → 你看懂 → 复制粘贴手动在终端敲
涉及多个分支联动的操作(同时改 master/dev/多个 release + tags)❌ 禁止 AI 一口气做必须 AI 先出步骤表 → 你一条一条确认后手动或单独执行
分支整体 Merge(把一个还可能带旧历史的特性分支合进干净分支)❌ 连你自己也要禁止,不要犯懒用 Cherry-pick 单条挑,不要直接 Merge 整个脏分支

1. 本次故障现象(下次一眼识别) ​

项目:SiRuiExam(单人开发,3 位同事偶尔拉代码)

典型症状 1:对称的「传入/传出」数量。

  • release-1.1.07:241 ahead, 241 behind
  • release-1.1.08:283 ahead, 283 behind

典型症状 2:master 的提交历史重复得密密麻麻。 同一批代码,出现两次,Hash 不同,作者时间内容全一样。

典型症状 3:明明「代码没冲突」,但两个分支一 Merge 就 20+ 冲突。 (这就是平行宇宙的信号:Git 不认识 Hash,以为两边完全无关。)


2. 根本原因:平行宇宙是怎么产生的 ​

一句话根因:2 月份为了删仓库里的 UploadFiles / ZIP 大文件,执行过历史重写(git filter-repo / BFG 之类的工具)。 它让「同一代码」出现了两套完全不同的 Commit Hash:

宇宙身份标志性提交状态
宇宙 A(纯净)所有旧 release 分支、所有 Tags、远程老仓库最初的真实记录ed210076(release-1.1.08 正式发布)、0cef1ec5(旧版清理大文件)✅ 正确、所有标签对齐、这才是基准
宇宙 B(毒药)某次本地/同事 Pull 时「两边都合一下」意外带进来的51440eaf、7bf22701(新版重写后对应的清理提交)❌ 只要进了一条,就会带几百条「重复影子提交」

为什么会把「毒药」合进来? AI 在修复时犯了一个致命错误:它以为本地 release-1.1.08 就等于干净的远程,但实际上本地 release-1.1.08 指针悄悄已经指到宇宙 B 上了(51440eaf)。 结果 dev/master 整个建在了毒药底座上,你后来去合并真正干净的 origin/release-1.1.08,两边不认识,爆 20+ 冲突。

🎯 核心识别方法:51440eaf 以及它之前的所有旧 commit(如 0cef1ec5 的新重写版 7bf22701)都是「毒药」。修复目标是:只要你新写的代码,不要这层毒药底座。


3. 通用修复三步法(下次遇到,照着走) ​

核心思想就两句:找最干净的基准 → 只摘新写的代码 → 其他所有分支一律废弃不动。 越简单越不容易翻车。

Step 1:先找到「最干净、最干净」的那条基准分支 ​

怎么判定谁是最干净的?按下面的优先级从高到低找,找到就停,不要往下选:

  1. 看标签:最近一次正式发版的 Tag(如 v1.1.08)绑定在哪个分支上,那个就是最干净的。(因为 Tag 一发布基本没人敢动,历史最稳。)
  2. 看旧 release 分支:越老的 release-1.x.y 越没被重写污染,基本都在纯净宇宙 A。
  3. 看远程是否一致:本地分支和 origin/分支名 是不是一条直线?有没有 ahead/behind?完全相等的才可能作为基准。
  4. 看最早的 commit:翻最老的几个 commit,哪条分支还保留着当初最初的提交 Hash,那就是最干净的。

本次结论:origin/release-1.1.08(Commit ed210076)——它在宇宙 A,所有 Tag 都对齐它 → 基准就是它,永远不要再换了。

Step 2:重建 master / dev,只做 Cherry-pick(不要 Merge 脏分支) ​

项目只有你一个人开发时,这一步 10 分钟搞定,无任何风险。

powershell
# 1. 先在本地基于干净基准重新拉出 master 和 dev
# (相当于把旧的脏 master/dev 直接扔了重建)
git checkout release-1.1.08
git branch -f dev    release-1.1.08
git branch -f master release-1.1.08

# 2. 切到 dev,把脏分支里真正有用的「新写的代码提交」一条条挑过来
git checkout dev
# 示例:只挑数据归档功能的 13 条业务提交
git cherry-pick e7802186
git cherry-pick e41dee94
git cherry-pick ...

红线 3 条:

  • ❌ 绝对不要图省事 git merge feature-脏分支,一合就把毒药历史全带进来。
  • ❌ Cherry-pick 中如果遇到「清理大文件」「合并 master」这类没有具体业务代码的提交,直接跳过(git cherry-pick --skip)。
  • ⚠️ 遇到小冲突:手动改完 git cherry-pick --continue,别慌,冲突只发生在 Cherry-pick 单条的范围内,永远不会像 Merge 那样滚成几百条。

Step 3:废弃其他所有分支,不要试图修好每一条 ​

这一步很多人舍不得,其实是最安全的关键动作。单人项目没有协作风险,越敢扔越不容易复发。

要保留的分支(只留这 3 个,其他一律本地 branch -D)要删的 / 要废弃的
master ✅所有 release-x.y 在本地可以删(远程保留用来考古,别碰就行)
dev ✅所有脏的 feature-*(如 feature-1.1.09-数据归档 — 本次的污染源)
当前正在开发的唯一特性分支(基于新 dev 拉)本地任何显示 ahead/behind 几百条的旧分支引用
powershell
# 旧污染分支,确认代码都 Cherry-pick 完了就删
git branch -D feature-1.1.09-数据归档

4. 特殊场景处理(这次踩过的坑,下次别踩) ​

4.1 强推远程(--force)怎么最安全 ​

只有当你本地 dev/master 已经修干净、并自己跑过一遍代码,确认没问题了再走这步。

powershell
# 先看一眼 --force-with-lease 行不行(推荐优先用这个)
git push origin dev --force-with-lease
git push origin master --force-with-lease

# 如果因为 Tags 或远端还有人推过,lease 失败,再退化成 --force
# 但这一步建议人工盯着执行,AI 不要自己决定
git push origin dev --force
git push origin master --force

💡 Force Push 后一定记得推 Tags(如果要统一 Tags 的话):

powershell
git push origin --tags --force

4.2 公司电脑 / 其他电脑怎么避免二次污染(强制重置) ​

所有在这次 Force Push 之前拉过一次 dev/master 的电脑,本地都有「旧的脏缓存」。他们如果直接 git pull,Git 会自动帮你把脏历史重新 Merge 回去。所以必须走 Reset:

最简单粗暴、对同事最容易解释的方法:

「今天清理了一下服务器冗余历史,大家把本地 dev、master 两个分支删了重拉一下就行,不会影响你正在写的代码。」

终端等价命令(不解释给同事,自己用就行):

powershell
git checkout release-1.1.08
git branch -D dev
git branch -D master
git fetch --all
git checkout dev
git checkout master

VS 界面操作:

  1. 先点「提取(Fetch)」→ 不要点拉取/同步
  2. 切到 dev → 右键 remotes/origin/dev → 将 dev 重置为 → 保留更改(Hard)
  3. master 同上

4.3 Tag 冲突(看不见 / Push 时 tag already exists) ​

Tag 问题只要记住一句:Tag 和 Commit Hash 是死绑定,只要历史重写过,两边的同名牌 Tag 一定绑定的是不同 Hash,Git 默认不会互相覆盖。

场景命令
家里电脑修好后,把正确的 Tag 强推到远程覆盖git push origin --tags --force
公司电脑 Fetch 时用远程正确 Tag 覆盖本地错的git fetch --tags --force

5. 未来 3 条铁律(贴在 Git 命令旁提醒自己) ​

  1. 【一超多强】基准只留一个: 不管出什么事,release-1.1.08(或最新的发版分支)永远是唯一的真理源,其他分支坏了就重拉,不要试图修好每条坏分支。
  2. 【挑拣代替合并】 新功能从旧分支搬过来,一律 Cherry-pick 单条挑,不要 Merge 整个旧特性分支。这是杜绝「毒药历史」复发的最大保护伞。
  3. 【AI 只写方案,不动历史】 凡是 Force Push、Rebase、删历史、多个分支联动的操作,AI 只允许出 Markdown 步骤表,你一条一条看了再决定是否手动或单步执行。AI 不能把一串操作一次性跑完。

附:本次 2026-09-07 执行回顾(仅记录,以后复用的是上面 3 步法) ​

  • 识别基准:origin/release-1.1.08(ed210076)→ 最干净,对齐所有 Tag
  • Cherry-pick 数量:13 条业务提交(e7802186 → aee03ff8),跳过所有合并提交和清理大文件的提交
  • Force Push 结果:dev 和 master 回到宇宙 A,git merge release-1.1.08 显示 Already up to date.
  • 遗留处理:旧 feature-1.1.09-数据归档 分支已标记废弃,确认新代码无异常后本地+远程均删除

方舟 · Ark