让 AI 帮你解 git 冲突:哪些能交出去,哪些必须人来判
数据截至 2026-07,各产品的额度与报错口径以官方最新说明为准。
大多数人觉得「AI 解 git 冲突不靠谱」,归因归错了:问题不是模型读不懂 <<<<<<< 和 >>>>>>> 之间那几十行,而是解冲突真正需要的信息压根不在冲突标记里。 冲突块只告诉你两侧文本长什么样,不告诉你两侧各自想干什么、哪一侧的行为才是当前要保留的。你把一段缺了一半前提的材料丢给任何人——模型或新同事——他都只能猜一个「看起来能编译」的结果。而 git 合并最阴的地方就在于:解成单边、丢掉一侧改动,代码照样编译通过,测试也可能照样绿,直到线上出现一个谁都复现不了的行为。
所以这篇不讲「怎么让 AI 更聪明地解冲突」,讲的是一条可执行的处置顺序:先按现象分因,判断这个冲突属于哪一类;再决定动作是交给 AI 还是人来拍板;最后不管谁解的,都过同一套三步验证。站内另外两篇是它的上下游——Claude Code 配 git worktree 并行多开 讲的是怎么让并行任务少撞车(冲突发生之前),AI 改坏代码怎么回滚 讲的是改错了怎么退回去(冲突解错之后),本篇只管冲突已经摆在你面前的这一刻。
一、动手之前先把现场看清楚
很多人一看到冲突就直接打开编辑器开始删标记,这是顺序错了。冲突文件的数量和类型决定了后面所有动作,先花两分钟收集四样东西。
第一是冲突清单和状态码:
git status --short
git diff --name-only --diff-filter=U
git status --short 左侧的两位字母是有信息量的:UU 是两边都改了同一文件,AA 是两边各自新增了同名文件,DU / UD 是一边删了一边改了。后两类的处置逻辑跟普通文本冲突完全不同,看到就要停一下——一边删文件一边改文件,通常意味着有人做了重构或移动,光看冲突块看不出来。
第二是合并基点。冲突大小失控时,先怀疑基点:
git merge-base HEAD MERGE_HEAD
git log --oneline --left-right HEAD...MERGE_HEAD
--left-right 会用 < 和 > 标出提交属于哪一侧。如果某一侧突然冒出几十个你从没见过的提交,或者双方有大量内容相同但哈希不同的提交,那大概率是有人 rebase 过或者强推过共享分支,这时候解冲突是治标,先把分支拓扑理顺才是正事。
第三是两侧的意图。这一步是整篇的关键,也是最容易被跳过的:
git log --merge -p -- path/to/file
--merge 只列出与冲突文件相关、且不同时存在于两侧的提交,配上 -p 就能看到每一侧究竟为什么改这个文件。这些提交信息(以及它们关联的 issue 编号)才是判断依据。
第四是把冲突块换成三方视图:
git config merge.conflictStyle diff3
git checkout --conflict=diff3 -- path/to/file
第一条改的是配置,但它不会追溯改写工作区里已经写好的冲突标记,所以要用第二条把那个文件的冲突重新生成一次——注意这会丢掉你在这个文件里已经做的手工编辑,所以务必在动手删标记之前先执行。默认的两方冲突标记只给「我方」和「对方」,diff3 会在中间多显示一段共同祖先的原始内容(以七个 | 开头的那段)。较新的 git 还支持 zdiff3,会把两侧相同的部分压缩掉,块更小更好读。有了祖先内容,「谁改动了什么」才是可推断的;没有它,你只能对着两段陌生文本二选一。
二、七类现象的判别表
按下面这张表先归类,再决定动作。同一次合并里不同文件可能属于不同类别,分开处置,别一把梭。
| 现象 | 大概率成因 | 怎么验证 | 处置动作 |
|---|---|---|---|
| 冲突块两侧内容几乎一样,只有缩进、引号、行尾不同 | 格式化配置或行尾符不一致 | git diff --ignore-all-space :2:<file> :3:<file> 输出为空就说明只是空白差异(:2: 是我方、:3: 是对方;冲突态下不带路径直接 git diff 出来的是带冲突标记的合并视图,永远不为空,不能用来判断);再查 core.autocrlf 与仓库里的格式化配置 | 可以交给 AI,也可以直接取一侧再整体跑一遍格式化;根因是把格式化规则固定进仓库 |
| 冲突集中在依赖锁文件、生成代码、快照文件 | 两侧各自重新生成过产物 | 看该文件是否由某条生成命令产出(构建脚本、代码生成器、测试快照更新) | 不手改也不让 AI 改:取一侧,然后用生成命令重新产出 |
| 同一个文件两边改了不同的函数,语义互不相干 | 正常的并行开发 | git log --merge -p 看两侧提交主题是否互不相关 | 适合交给 AI,但必须把两侧提交信息一起给它 |
| 同一处逻辑两边改成了不同行为(阈值、判断条件、返回结构) | 需求层面撞车 | 找到两侧提交对应的 issue 或 PR;找不到就去问人 | 人来判。AI 只能帮你把差异列清楚,不能替你定行为 |
| 冲突文件里出现整段陌生代码或大范围删除 | 分支被 rebase 或强推过,合并基点已经不对 | git merge-base 加 --left-right 核对提交范围 | 停手,先修分支拓扑,不要在错误基点上解冲突 |
| 解完能编译,但测试挂在看似无关的模块 | 解成了单边,丢掉了一侧的改动 | 提交合并后用 git diff <merge>^1 <merge> 看对方分支(第二父)带来的改动是否都在结果里 | 用 git checkout -m -- <file> 把冲突原样恢复,重解这个文件 |
| 数据库迁移、schema、路由注册表等「顺序敏感」文件冲突 | 两侧各自追加了条目,序号或顺序撞车 | 看目录里的序号、时间戳、注册顺序是否重复 | 人来重排。必要时重命名条目,并在本地跑一次正向与回退 |
这张表的用法是自上而下:越靠上越机械、越适合交出去;越靠下越依赖只存在于人脑子里的上下文。
三、分工:什么交给 AI,什么必须人拍板
能交出去的几类,以及怎么交
能交出去的冲突有一个共同特征:判断依据全部在你能提供的材料里。格式差异、互不相干的并列改动、导入语句和依赖声明的合并、同一个测试文件里两边各加了不同用例——这几类的正确答案是可以从文本本身推出来的,模型做得又快又稳,比你手动删标记还少手误。
交的时候材料要给全,至少三样:diff3 风格的冲突块(带共同祖先)、两侧相关提交的信息、以及这个文件在项目里的角色约定。只把冲突块贴过去,就等于让它猜。上下文怎么组织更省事,可以看 Claude Code 的上下文管理。
还有一条纪律:让它解,但不让它提交。工具直接执行 git commit 或 git merge --continue,你就失去了最便宜的检查窗口。把合并动作留在自己手里,是这类任务里性价比最高的人工卡点,思路和 Agent 的人工介入设计 是一致的。
顺带说清一件事:Claude Code、Copilot 这类海外工具,官方对中国大陆有区域限制、不支持直连,账号与结算也各有各的规则;市面上存在第三方中转,但它意味着你的代码要多经过一层不受你控制的链路——解冲突时贴过去的往往是核心业务文件,这个风险自己掂量,本文不做推荐、也不给渠道。
必须人来判的四类
反过来,下面四类不管模型多强都别交出去,因为缺的信息不在代码里。
行为撞车。 两侧把同一个条件、同一个默认值、同一个返回结构改成了不同样子。这不是合并问题,是两个需求打起来了。正确动作是找到两侧的负责人对齐,哪怕只是一条消息确认「以你这版为准」。让模型选一个,它会给你一个语法正确、逻辑上谁都不满意的折中版本。
安全与权限相关代码。 鉴权判断、越权校验、密钥读取路径、SQL 拼接位置。这些地方一侧的改动往往是在补一个漏洞,另一侧只是顺手重构。合并时把补丁那一侧「合掉」,漏洞就悄悄回来了,而测试通常不会覆盖它。这类文件建议在冲突清单里单独标出来,人工逐块看。
数据结构与迁移。 迁移脚本、序列化格式、对外接口字段。它们的约束是「已经跑过的不能改、顺序不能乱」,这个约束存在于生产环境的状态里,不在仓库里。
大重构与功能改动撞在一起。 一侧移动了文件、改了目录结构或重命名了核心接口,另一侧在旧位置上加了功能。这时候冲突块的信息量最低(git 看到的是删一片加一片),人必须先决定「以哪一侧的结构为基准」,再把另一侧的改动按新结构重写一遍。让 AI 参与的正确姿势是:结构由你定好之后,请它按新结构搬运具体实现。
四、解完必须做的三步验证
不管冲突是你解的还是 AI 解的,这三步都跑,顺序不能换。第一步防手误,第二步防丢改动,第三步防行为回退。
第一步:结构验证,确认没有残留、能构建。
grep -rnE '^([<>|]{7}|={7})( |$)' -- . || echo "no conflict markers"
git diff --check
这条 grep 把三种标记连同 diff3 多出来的祖先分隔线(七个 |)一起扫,漏掉后者是很常见的翻车方式——上下两半都对,中间夹着一段祖先版本的旧代码。git diff --check 也会对残留标记报 leftover conflict marker,但显式 grep 一遍更保险,尤其是那些还没进索引、不出现在 diff 里的文件。扫干净之后再跑一次完整构建和类型检查。这一步只证明「文件没被解坏」,不证明任何语义正确。
第二步:双亲 diff,确认两侧意图都还在。
这是最容易被跳过、也最值钱的一步。合并提交有两个父,正确的合并结果应该同时包含两侧的意图:
git diff HEAD^1 HEAD --stat
git diff HEAD^2 HEAD --stat
git show --cc HEAD
看的重点不是行数对不对,而是:相对第一父,你应该看到对方分支带来的全部改动;相对第二父,你应该看到自己分支带来的全部改动。如果某一侧的关键改动在这两个 diff 里都找不到痕迹,说明它在解冲突时被吃掉了。git show --cc 只显示相对两个父都有差异的部分,正常合并这里应该很小;如果它列出一大堆内容,说明你在合并里夹带了大量「既不属于我方也不属于对方」的新写法,得解释清楚为什么。
这一步也是最适合请 AI 帮忙看的:把两个 diff 交给它,让它逐条核对两侧提交信息里承诺的改动是否都在结果里。它做这种「清单对账」比人耐心。配合 AI 代码评审工具 的思路,可以固化成合并后的固定检查动作。
第三步:行为验证,跑冲突涉及的那条路径。
全量测试要跑,但别只跑全量测试。冲突文件往往是并行开发的热点,恰恰是测试覆盖的薄弱区。补两件事:一是把两侧提交各自新增的测试都跑一遍(合并后它们应该同时存在、同时通过),二是手动走一遍冲突涉及的业务路径。接口层的话,一条 curl 就够定性:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/api/health
看到 401 / 403 就查鉴权那一侧是不是被合掉了,500 就去看服务端日志,连不上则先确认服务起没起。这些状态码本身就是很好的分因线索。
五、什么情况下别再折腾
解冲突有一个隐蔽的成本陷阱:它是纯手工活,投入产出比会随时间急剧下降。出现下面任一信号,就该止损而不是继续硬解。
- 冲突文件超过十几个,且横跨多个模块。 这通常说明分支活得太久。止损动作是
git merge --abort或git rebase --abort回到干净状态,然后把大合并拆成几次小合并(先合基础设施改动,再合功能改动),或者请对方先把他的分支 rebase 到最新主干、把他自己的冲突先解掉。 - 同一个冲突你解了两遍以上,每次都因为验证不过而回退。 说明缺的是决策不是操作。停手去问人,一条消息能省两小时。
- 合并基点明显不对。 先修拓扑。在错误基点上解出来的冲突,往后每次合并都会再冲一遍。
- 冲突在生成产物上。 一分钟都别花在手改上,取一侧后重新生成。
- 你已经开始「凭感觉」保留代码。 这是最硬的止损点:一旦你说不清为什么保留这一行,就意味着后面的验证也验不出问题,因为你不知道该期待什么。
止损之后的三条常见退路:把 merge 换成让对方 rebase(把冲突推给更懂那侧改动的人);把一个大 PR 拆成两三个小 PR 分批合;或者直接放弃这次合并,从最新主干重新拉一个分支,把自己的改动按新结构重写一遍——重写往往比硬解快,尤其当你的改动只有几百行。
顺手打开 rerere,让重复冲突只解一次:
git config --global rerere.enabled true
它会记录你对某个冲突块的解法,下次遇到一模一样的冲突自动套用。长期分支反复合主干时,这个开关省下的时间相当可观。
六、避坑清单
用 --ours / --theirs 时搞反了方向。 为什么会踩:这两个词的含义在 merge 和 rebase 里是反的——rebase 期间「ours」指的是你正在往上重放的那个基底,不是你的功能分支。怎么避:动手前先 git status 看清当前是 merge 还是 rebase,并且只在整文件取舍(比如生成产物)时用它们,逻辑代码不用。
在冲突未解完的状态下切分支或 stash。 为什么会踩:想「先看看主干怎么写的」,一 checkout 就把中间状态搅乱了,冲突信息也没了。怎么避:需要参照就用 git show <branch>:path/to/file 直接读另一侧的文件内容,不切换工作区。真要并行看,用独立的 worktree。
让 AI 顺手「优化」了冲突周边的代码。 为什么会踩:解冲突时上下文都在,模型很容易多改几行;结果这些改动混在合并提交里,评审时没人能分清哪些是合并、哪些是新写的。怎么避:明确要求只处理冲突块内的内容,合并提交里出现的额外改动一律拆成独立提交。第二步的 git show --cc 就是这条的检查手段。
只看编译过没过就提交。 为什么会踩:解成单边最典型的表现就是「编译通过、测试通过、少了一个功能」。怎么避:双亲 diff 不能省。
锁文件手动解冲突。 为什么会踩:锁文件是完整性快照,手拼出来的版本可能自身矛盾,装的时候不报错,运行时才出问题。怎么避:取一侧,跑生成命令重出,然后把整个依赖安装流程跑通一次。
迁移文件按「都保留」处理。 为什么会踩:两侧各加一个迁移,直接都留下看起来最安全,但如果它们改的是同一张表、顺序又反了,执行时就炸。怎么避:人工确定执行顺序,本地跑一次正向和回退。
hooks 或 CI 在合并态被绕过。 为什么会踩:某些提交路径(尤其是工具代跑的合并)会跳过本地检查,问题一路带到主干。怎么避:合并提交也当普通提交对待,push 前手动跑一次仓库约定的检查命令。
默认冲突样式没换成 diff3。 为什么会踩:两方视图丢掉了共同祖先,你和模型都只能靠猜。怎么避:全局设一次 merge.conflictStyle,这是一次性成本、长期收益。
收束
把这篇的判断压成一句:冲突的处置难度取决于「判断依据在不在文本里」,而不是取决于冲突块有多长。 依据在文本里的(格式、并列改动、导入合并、产物文件)尽量交出去省时间;依据在人脑和生产环境里的(行为选择、安全补丁、迁移顺序、结构基准)必须人拍板。中间那条线,靠三步验证兜住。
合并前后过一遍这份自检清单:
- 冲突清单和状态码看过了,
DU/AA这类单独处理了吗? - 合并基点核对过了吗?有没有异常的提交范围?
- 冲突样式是 diff3 或 zdiff3 吗?两侧提交信息看过了吗?
- 这批冲突按类别分开处置了吗,还是一把梭?
- 交给 AI 的部分,材料给全了吗?提交动作留在自己手里了吗?
- 残留标记扫过了吗?构建和类型检查过了吗?
- 相对两个父的 diff 都看过了吗?两侧关键改动都在吗?
- 两侧各自的测试都跑了吗?冲突涉及的那条业务路径手动走过吗?
- 有没有夹带无关改动?
git show --cc干净吗? - 如果卡了超过预定时间,止损点是什么,你想好了吗?
第 7 条和第 10 条是最常被跳过的两条,也正好是事后最贵的两条。