AI 编辑后 merge 冲突——解决指南

Claude Code 或 Cursor 改完 6 个文件、rebase 一打回车满屏 CONFLICT,难点是分辨格式噪音和真正的逻辑冲突。本文给一条隔离 AI commit、分层处理冲突的路径。

让 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 addeddeleted by us / them

5. AI 修了某个 bug,main 也修了同一个 bug

经典”两边都对、但写法不同”。你让 AI 修 race condition 加了 mutex;同事用 atomic 写了别的方案。语义都对,但代码冲突。

如何判断:冲突两边都能跑测试通过,但实现思路不同。

6. lockfile / 生成文件冲突

package-lock.jsonpnpm-lock.yamlmigrations/*.sqlschema.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
lockfilegit 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=ourslinguist-generated 属性,rebase 时跳过手工对比
  • 大改动让 AI 先写计划再执行,你确认范围后再让它动文件

相关阅读

标签: #AI 编程 #排查 #排查