让 Claude Code、Cursor 或 Codex 跑了半小时,写出 6 个文件的改动,结果 git pull --rebase origin main 一打回车就满屏 CONFLICT (content): Merge conflict in src/...。最难受的不是冲突本身,而是 AI 同时改了空白、import 顺序和真正的业务逻辑,让你分不清哪段是”逻辑冲突”,哪段是”格式噪音”。
本文给一条”先把 AI 改动隔离成可对比的 commit、再让 git 自己处理纯格式差异、最后只手工解决真正的语义冲突”的路径。
常见原因
按 AI 编辑产生 merge 冲突的命中率从高到低排序。
1. AI 改的文件 main 也动了
最常见的一种:你 fork 出去半小时,期间同事 push 了 3 个 commit 都碰了同一份 src/server/auth.ts。AI 不知道远端动了,按本地状态全文重写。
CONFLICT (content): Merge conflict in src/server/auth.ts
Auto-merging src/server/auth.ts
如何判断:git log origin/main --since="2 hours ago" -- <文件> 看远端最近是否动过该文件。
2. import 自动整理顺序和团队不一致
Cursor / Copilot 经常在保存时跑 eslint --fix 或 Prettier 自动排序 import;如果团队配置和 AI 触发的配置不同(比如分组方式不同),整个 import 块会被整体重写,produce 大段”看似全改”的冲突。
<<<<<<< HEAD
import { z } from "zod";
import { db } from "@/lib/db";
import type { User } from "@/types";
=======
import type { User } from "@/types";
import { db } from "@/lib/db";
import { z } from "zod";
>>>>>>> ai-branch
如何判断:冲突 hunk 里前后两份 import 集合完全相同,只是顺序 / 空行不同。
3. 空白和换行被 AI 顺手改了
Aider / Windsurf 默认会让模型重写整段函数,模型有时把 tab→空格、CRLF→LF、行尾分号风格全部按它的偏好写一遍。git diff 显示”整文件全红全绿”,但 git diff -w 几乎为空。
如何判断:git diff --stat 显示大量增删行,但 git diff -w --stat 行数骤降到个位数。
4. AI 跨文件搬代码,main 也在搬
AI 决定把 utils/format.ts 里的 formatDate 搬到 lib/date.ts;同一时间 main 把同一个函数搬到 shared/format.ts。rebase 时两份”删除”+两份”新增”对不上,git 报 add/add 冲突。
CONFLICT (add/add): Merge conflict in lib/date.ts
如何判断:git status 里同时出现 both added 和 deleted by us / them。
5. AI 修了某个 bug,main 也修了同一个 bug
经典”两边都对、但写法不同”。你让 AI 修 race condition 加了 mutex;同事用 atomic 写了别的方案。语义都对,但代码冲突。
如何判断:冲突两边都能跑测试通过,但实现思路不同。
6. lockfile / 生成文件冲突
package-lock.json、pnpm-lock.yaml、migrations/*.sql、schema.prisma 这类自动生成文件几乎一冲突就是几百到几千行。单独有一篇 AI 编辑后 lockfile 冲突 专门讲怎么处理。
如何判断:冲突文件名是 lockfile / migration / generated 目录下的产物。
最短修复路径
按顺序做,每一步都不会丢 AI 的改动——最坏情况你可以 git rebase --abort 回到起点。
Step 1:先把 AI 改动凝固成一个 commit
如果 AI 的改动还在 working tree 没提交,先 commit 一次,让它变成一个可对比、可 reset 的对象。
git add -A
git commit -m "WIP: AI edits before rebase"
git rev-parse HEAD # 记下这个 SHA,待会要 cherry-pick / 对比
接着新建一个安全分支保险:
git branch backup/ai-edits-$(date +%Y%m%d-%H%M)
这样即使 rebase 彻底搞砸,git checkout backup/... 一行就能回来。
Step 2:用 -X 策略让 git 自动吃掉纯格式冲突
很多冲突其实只是 import 顺序、空白、换行风格。先让 git 偏向某一边再处理:
# 偏向远端(main)的格式,但保留本地(AI)的逻辑
git pull --rebase -X theirs origin main
# 或反过来:保留本地全部,远端只补未冲突的部分
git pull --rebase -X ours origin main
注意:-X theirs/ours 只在 hunk 级别选择,不会丢 AI 真正的新增代码。冲突解决率通常能从 100% 降到 30-50%。
如果你只想吃掉空白差异:
git rebase --rebase-merges -X ignore-all-space origin/main
Step 3:剩下的冲突按文件分类处理
git status --short | grep "^UU" # 列出还在冲突的文件
按下面三类分别处理:
| 类型 | 处理方式 |
|---|---|
| 纯 import / 格式 | git checkout --theirs <file> 然后跑一次本地 formatter |
| lockfile | git checkout --theirs <file> 然后 npm install 重新生成 |
| 真正的逻辑冲突 | 手工编辑,参考下一步的对照法 |
Step 4:用三路对比看真正的逻辑冲突
对于剩下的逻辑冲突文件,打开三路视图:
git mergetool --tool=vimdiff # 或 vscode / meld
或手动看三个版本:
git show :1:src/server/auth.ts > /tmp/base.ts # 共同祖先
git show :2:src/server/auth.ts > /tmp/ours.ts # main 端
git show :3:src/server/auth.ts > /tmp/theirs.ts # AI 端
diff -u /tmp/base.ts /tmp/ours.ts # main 改了什么
diff -u /tmp/base.ts /tmp/theirs.ts # AI 改了什么
只有看到”main 改了什么 + AI 改了什么”两份独立 diff,才能判断该如何合并,而不是盲选一边。
Step 5:解决完跑全套验证,再继续 rebase
git add <已解决的文件>
npm test && npm run lint && npm run build
git rebase --continue
如果中途想放弃:
git rebase --abort # 回到 Step 1 的 commit
预防建议
- 让 AI 开始改之前先
git pull --rebase origin main,把窗口缩到最短 - 在 CLAUDE.md / .cursorrules / AGENTS.md 里写明:“改前先读文件、不要重排 import、不要批量改空白”
- AI commit 保持小而频繁:一次只做一件事,冲突时只损失一个 commit 的工作量
- 仓库根加
.editorconfig和 pre-commit hook 强制 formatter,让 AI 跑出来的代码风格和团队一致 - lockfile / 生成文件加
merge=ours或linguist-generated属性,rebase 时跳过手工对比 - 大改动让 AI 先写计划再执行,你确认范围后再让它动文件