设想你和室友共同维护一本《周末大餐计划》手账。你们从同一页复印件出发,分头补充:你在“主菜”栏写下“烤箱预热 180 度”,室友在“甜品”栏写下“烤箱预热 220 度”。一周后想把两份手账拼回一本时,发现“烤箱预热”这一行出现了两个互不相让的版本。这一行笔迹的冲撞,就是版本控制世界里的合并冲突。 在 Git 的日常里,你和同事各自拿着同一个仓库的副本开发新功能,这就是“分支”。当两个人恰好修改了同一段代码的不同内容,Git 不知道该听谁的,于是把问题交还给你——这就是冲突。它不是一个需要恐惧的错误,而是一次并行编辑的“对质邀约”:手动决定这一行、这一块到底听谁的。本文用“合写手账”的比喻,带你从冲突本质、冲突分类、冲突定位、解决流程、预防策略,一路走进工具辅助与实战演练,最终在任何 AI 项目或开源仓库中从容处理 PR 冲突。
对 AI 从业者:冲突处理是参与开源贡献与团队并行开发绕不开的关卡,掌握它,才能从“害怕合并”升级为“主动掌控合并”。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
冲突本质
你和室友各持一份手账复印件,分头补充。你写下“烤箱预热 180 度”,室友写下“烤箱预热 220 度”。两份手账分开看都自洽,可合回一本时,“烤箱预热”这一行出现了两个版本。这不是谁写错了,而是两个人恰好改到了同一行的不同内容——这就是冲突的起点。
Git 世界的合并冲突,本质是一场三方合并。Git 把你们共同开始的那一版叫 Base(基线),你的分支叫 Ours(我方),对方的分支叫 Theirs(他方)。合并的过程,就是尝试把双方的改动都叠加到 Base 上:能重叠的部分自动拼接,一旦发现同一个位置上两边都写过且内容不一致,Git 就把这个“事故现场”留给你人工裁决。
用公式看更清晰:合并结果等于在基线上加上你的增量、再加上对方的增量,最后减掉双方重复修改的那一小块——因为这一块机器无法替你选择。
所以,冲突不是事故报警,而是“两套笔迹在同一行相遇”的路标。它提示你:这里有分歧,需要一次认真的对质,而不是慌张地删掉一方。
- Base 基线手账复印前的那份原始计划,是合并的出发点,也是对比两版笔迹的坐标。
- 你的增量从基线到你的手账,你新写下的所有改动。
- 对方增量从基线到室友的手账,对方做出的所有改动。
- 重叠冲突双方增量在相同位置上的交集,Git 无法判断保留谁,只能交给人工。
- 合并结果先叠加双方改动,再减去重叠部分,最终得到需要人类裁决后再定稿的版本。
冲突分类
手账撞车不止一种形态。最常见的,是“烤箱预热”这行被两边各写一遍——这在 Git 里叫文本内容冲突。还有更隐蔽的:你改的是“烤鸡菜谱”页的内容,而室友把整个“烤鸡菜谱”页改名成了“香煎鸡胸”页,你俩动的是同一个页面,但操作方式完全不同。
从工程视角看,Git 冲突大致分四类。第一类是文本内容冲突,两个分支修改了同一文件的同一区域,这是 PR 中最常见的情形,冲突标记会清楚地把双方版本并列展开。第二类是文件改名冲突,一方改了文件名,另一方修改了旧文件的内容——Git 不知道该把新内容放进新文件还是旧文件。第三类是目录结构冲突,双方各自移动或重组了目录,导致同一路径出现两种归属。第四类最危险,叫语义编译冲突:代码行上没有任何重叠,但合起来逻辑矛盾,比如一个人改了函数的签名,另一个人在别的文件里继续用旧签名调用。这类冲突 Git 完全检测不到。
语义冲突在 AI 项目中尤其多发。举个真实例子:A 同学重构了数据预处理函数 normalize() 的入参,B 同学在另一个文件的训练循环里仍然调用旧参数。两个文件不重叠,git merge 一路绿灯,代码也能编译,但一跑训练,loss 直接变成 NaN。Git 能看见的是“笔迹有没有重叠”,看不见的是“菜谱逻辑是否自洽”。
能看见的手账笔迹矛盾,靠冲突标记就能定位;看不见的菜谱逻辑矛盾,才是真正的黑暗料理之源。前者用 Git 解决,后者得靠测试、评审和 CI 兜底。
冲突定位
要合并手账,你不能从头翻到尾找争议页,得先看封面上有没有“此处需对质”的标签。Git 也一样:当合并遇到冲突,第一条命令永远是 git status,它会列出所有处于 Unmerged(未合并)状态的文件——相当于在手账封面贴了橙色标签的那些页面。
拿到文件清单后,打开任何一个冲突文件,你会看到 Git 已经用三行“便利贴”把争议区域标了出来:第一行 <<<<<<< 后面跟着的是你的版本,接下来一行 ======= 是分界线,再往下是对方的版本,最后 >>>>>>> 后跟对方的分支名。看懂这个标记结构,冲突现场就一目了然了。
除了冲突标记,git diff 能展示冲突行附近的完整上下文,git log 则可以追溯该文件最近被谁改过——相当于翻手账的修订历史页,看看对方写下那笔时,究竟在想什么。定位不是目的,理解双方意图才是。
- 开始标记
<<<<<<<之后是你这一方的版本内容,标志冲突块从你的笔迹开始。 - 分界线
=======把上下两段笔迹从中间切开,上方归你,下方归对方。 - 结束标记
>>>>>>>后跟着对方分支名,标志这个冲突块到此结束。
解决流程
找到争议行后,你们面对面坐下。第一步不是涂掉对方的字,而是逐段审查:先看 <<<<<<< 和 >>>>>>> 圈出的每一块冲突,再读每段上下的上下文,弄明白对方为什么写 220 度——也许室友的菜谱配的是 90 秒快烤。
理解之后,处理策略有三种,对应三种不同的场景。保留我方:确认你的版本才是正确设计时,直接删掉对方段落。保留对方:当对方的改动确实更优时,删掉自己的一段。融合改写:两边都有价值时,把两段内容整合成一段全新的代码,同时删掉全部冲突标记。融合改写是最有价值也最需要沟通的一步,它往往意味着新的设计共识。
编辑完成后,冲突文件回到工作区,Git 仍然认为它“未解决”。此时执行 git add,向 Git 宣告这个文件已经调解完毕——文件状态从 Unmerged 变为 Staged。最后执行 git commit,生成一个合并提交,冲突才算正式解决。如果改到一半想反悔,git merge --abort 可以放弃整个合并,回到操作前,相当于把涂脏的手账页撕掉重来。
解决流程的本质,是“当面对质、达成共识”。你在冲突块里留下的每一行代码,都是三方协商后的人类判断——这正是版本管理工具把最高裁决权留给人的原因。
- 未合并状态冲突文件处于 Unmerged,Git 禁止直接提交,必须等你亲自处理。
- 编辑并暂存人工改完冲突段落后,
git add的作用是告诉 Git“这个文件谈妥了”。 - 提交合并
git commit创建合并提交,冲突正式宣告解决,手账终于合为一本。
预防策略
与其每次在手账撞车后花一小时对质,不如从源头减少撞车。最有效的办法总结成九个字:勤合、小改、划边界。对应的工程手段,就是预防冲突的四条核心策略。
第一条,频繁同步主干。不要让你的分支离开主干太久——每天至少把主干的最新改动 merge 进你的特性分支一次,相当于每周都和室友对一次总目录,避免积压几十页分歧。第二条,小颗粒提交。每次提交只改一个逻辑点,提交信息说清楚“我改了哪一处、为什么改”,这样即使冲突发生,对方也能快速看懂你的意图。第三条,短期特性分支。分支存活时间控制在两三天内,分支死了,冲突面就越小。第四条,模块分工隔离。团队按文件、目录划分明确的负责人,A 同学独占训练脚本,B 同学独占数据处理模块,互不越界。
用公式表达,冲突概率可以被建模成一组变量的比值:
把菜谱章节分得清清楚楚,一个人负责热菜区、一个人负责凉菜区,再勤对总目录,撞车概率自然趋近于零。真正的工程高手,不是在冲突爆发时力挽狂澜,而是让冲突根本来不及发生。
- 活跃区域大小同一时段被多人修改的代码行数,改动越集中,撞车概率越高。
- 协作者数量在相近区域同时工作的开发者数量,人越多,笔迹交叠的机会越大。
- 分支寿命分支从创建到合并进主干所经历的时间,拖得越久,分歧累积越多。
- 独立模块面积模块边界清晰、互不重叠的代码范围,隔离越好,分母越大越安全。
- 冲突概率冲突发生概率与分子三项正相关、与分母的模块隔离负相关。
工具辅助
初学者看冲突,像举着放大镜在两张纸上找不同;老手用工具,像打开了专业的合稿软件。工具不能替你决策,却能把你从“找记号”的体力劳动中解放出来,让你专注于协商内容本身。
CLI 命令流是每个开发者最贴身的基础工具。完整的处理链是:git merge 触发合并 → git status 查看冲突文件 → git diff 查看冲突差异 → 手动编辑解决 → git add 标记已解决 → git commit 提交。此外 git mergetool 可以一键唤起你配置的可视化合并工具,把繁琐的标记编辑变成图形化操作。
IDE 合并编辑器是现代开发者的主力。VS Code、IntelliJ 等 IDE 内置的冲突编辑器通常有四个按钮:Accept Current(保留我方)、Accept Incoming(保留对方)、Accept Both(两边都要)、Compare Changes(并排对比)。点按钮只是操作,背后依然是逐段理解。
三方可视化视图进一步把 Base、Ours、Theirs 三栏并排显示,中间是正在编辑的合并结果。你能一眼看出“基线原来长什么样、我改了什么、对方改了什么”,很像把三份手账摊在桌上同步比对。最后,自动化 CI 检测解决的是语义冲突:GitHub Actions 或 GitLab CI 在每次 push 后自动运行格式化检查、单元测试和训练冒烟测试,把“代码能编译但逻辑矛盾”的黑暗料理提前端上桌。工具只是放大镜与显微镜,最终按下裁决键的,永远是人。
实战演练
现在,把整套本领放进真实战场。假设你要给一个开源 AI 项目提 PR:先 fork 上游仓库,相当于复印了一本手账;你埋头加了新功能,一写就是三周;提交 PR 的那一刻,上游已经被几十个贡献者改过几十轮——你的分支和主干早就分道扬镳,冲突几乎必然发生。
标准操作流程是这样的:首先 git fetch upstream 获取上游最新提交,然后把上游主干合并进你的分支——大多数人在这一步撞上冲突。接下来按照第四节的流程:git status 列出冲突文件,逐个打开、逐段审查,融合改写后 git add、git commit,再 git push 更新 PR。此时 PR 页面上原本显示的“Merge conflict“标志会自动消失,维护者才能顺利合并你的贡献。如果中途发现冲突过于复杂,也可以改用 git rebase upstream/main 把你的提交”移植”到最新主干之上,让提交历史保持线性。
AI 项目中的情况如出一辙:算法工程师并行调 learning_rate、batch_size,平台工程师同时重构 trainer.py,数据工程师在改 dataset.py 的预处理逻辑——超参数配置和数据管线是两个最高频的冲突热点。实战团队通常沉淀这样几条规范:每次开工前先 git pull 最新主干;提交保持小颗粒并附带清晰信息;冲突解决后必须让 CI 跑完整测试再允许合入;每月复盘冲突记录,把频繁产生冲突的文件列为 hotspots,推动模块解耦。
从新手到老手,差别不在会背多少命令,而在于把“合手账”变成肌肉记忆:高频同步、小步提交、模块隔离、认真对质。当这些动作成为团队默认规范,合并冲突就从“事故”变成了“流程里一道普通的质检工序”。
AI 应用场景
向开源项目提交 PR 时的冲突处理
阶段:代码集成与合并(PR 流程末段)
解决的问题:开源项目迭代极快,fork 后经过一段时间开发,本地分支与上游主干渐行渐远,提交 PR 时经常出现 Merge conflict 提示。传统做法是“硬合”或反复新建分支,低效且容易丢失他人最新改动。AI 辅助下的标准做法是先用 git fetch upstream 同步上游,再通过 merge 或 rebase 解决冲突,最后推送刷新 PR。
具体案例:一位算法工程师向 Hugging Face Transformers 提交了新 tokenizer 的 PR,三周后收到冲突通知。他执行 git fetch upstream && git rebase upstream/main,定位到 tokenization_bert.py 中的改动区域,使用 IDE 三方合并视图逐段裁决,跑完 CI 后 PR 顺利合并。
大模型训练代码的多分支并行协作
阶段:训练配置迭代与实验管理
解决的问题:训练团队常有多条实验分支并行:A 调超参,B 改数据增强,C 升级模型结构。配置文件 config.py 和入口脚本 train.py 极易发生合并冲突。传统方式通过“串行锁文件”避免冲突,代价是研发效率大幅下降。
具体案例:某大模型团队采用“小步合入”策略:每条实验分支最多存活两天,每天早晨自动把主干合并进各实验分支,冲突点在当天上午就暴露并解决。一个月内,严重冲突从 15 次降到 2 次。
CI 流水线中的自动化冲突与语义检查
阶段:持续集成与质量门禁
解决的问题:文本冲突被人工解决后,“能编译但逻辑矛盾”的语义冲突仍然可能漏网。传统人工 CR 速度慢,完全靠代码评审发现往往为时已晚。
具体案例:某 MLOps 团队在 GitHub Actions 中配置了合并前流水线:先跑 git diff --check 检查空白与冲突标记残留,再跑单元测试和五分钟训练冒烟测试,最后用 git log 自动生成冲突文件名单并推送给相关负责人。上线后,合并后训练崩溃的事故率下降了 80%。
Python 演示
"""简化版三方合并演示:模拟 PR 合并时的冲突检测与标记""" from difflib import SequenceMatcher def mark_changes(base_lines, edited_lines): """返回 base 中每行是否被改动:被改或删除记 edit,保留记 keep""" matcher = SequenceMatcher(None, base_lines, edited_lines) marks = {} for tag, i1, i2, j1, j2 in matcher.get_opcodes(): if tag == 'equal': for i in range(i1, i2): marks[i] = 'keep' else: for i in range(i1, i2): marks[i] = 'edit' return marks def three_way_merge(base_text, ours_text, theirs_text): """基于共同基线合并两个分支,返回合并文本与冲突起始行号""" base = base_text.splitlines() ours = ours_text.splitlines() theirs = theirs_text.splitlines() # 教学简化:补足行数后按对应行索引比较 max_len = max(len(base), len(ours), len(theirs)) base += [''] * (max_len - len(base)) ours += [''] * (max_len - len(ours)) theirs += [''] * (max_len - len(theirs)) our_marks = mark_changes(base, ours) their_marks = mark_changes(base, theirs) result = [] conflicts = [] for i in range(max_len): o = our_marks.get(i, 'keep') t = their_marks.get(i, 'keep') if o == 'keep' and t == 'keep': result.append(base[i]) elif o == 'keep' and t == 'edit': result.append(theirs[i]) elif o == 'edit' and t == 'keep': result.append(ours[i]) elif ours[i] == theirs[i]: result.append(ours[i]) # 两边改成相同内容,自动合并 else: conflicts.append(len(result) + 1) result.append('<<<<<<< OURS') result.append(ours[i]) result.append('=======') result.append(theirs[i]) result.append('>>>>>>> THEIRS') return '\n'.join(result), conflicts if __name__ == '__main__': base = """learning_rate = 1e-4batch_size = 32epochs = 50model = build_model()train(model, data)""" ours = """learning_rate = 1e-4batch_size = 64epochs = 60model = build_model()train(model, data_1)""" theirs = """learning_rate = 5e-5batch_size = 32epochs = 100model = build_model()train(model, data_2)""" merged, conflicts = three_way_merge(base, ours, theirs) print('冲突起始行号:', conflicts) print('----- 合并结果 -----') print(merged)
<<<<<<< 冲突标记,复现 Git 的冲突检测行为。matcher = SequenceMatcher(None, base_lines, edited_lines)用 Python 内置差异模块,逐行比较基线与某个版本,找出哪些行被改动了。marks[i] = 'keep'` / `'edit'为基线的每一行打标:保留还是被编辑;这是冲突检测的信息基础。o = our_marks.get(i, 'keep')读取我方对该行的是否改动标记,没标记时按“未改动”处理。elif ours[i] == theirs[i]:双方都改了同一行,但改成了相同内容,Git 也会自动合并,不算冲突。conflicts.append(len(result) + 1)记录冲突块的起始行号,相当于git status告诉你的“事故现场”在哪。result.append('<<<<<<< OURS')向合并结果写入冲突标记,模拟 Git 在文件里插入的人工裁决指令单。
常见误区
误区一遇到 merge conflict 就觉得自己或者同事写错了代码,第一反应是找人背锅,甚至想回滚重来。
正解冲突是“两个分支在同一处产生了不同改动”的信号,不是语法错误,更不是谁的品行问题。就像两份菜谱在烤箱温度这一行各写各的,不代表任何一份是错的。冲突恰恰说明并行开发真的在发生,它的存在是正常现象,处理它的能力才是工程素养。
为何常见:初学者常把版本控制当成“串行抄写”,以为代码库应该永远一帆风顺,因此把机制性的碰撞误判为质量事故。
误区打开冲突文件,看到 `<<<<<<<` 和 `>>>>>>>`,心想“留我的,删对方的”,三下五除二把对方段落清空,然后 commit 收工。
正解冲突块中上下两段只是“意见”,不是“敌人”。对方改的可能是你完全没意识到的边界情况,比如对方把 epochs 从 50 改成 100,是因为新数据集训练不足。只留一方,等于让手账失去一次信息对质的机会。正确做法是先理解双方意图,再决定保留、采纳或融合,必要时和对方口头确认。
为何常见:人天然倾向于维护自己的劳动成果,且“删掉另一方”是最快能让文件恢复整洁的路径,于是把“快”误当成了“对”。
误区手动把冲突文件的标记和内容都改好了,觉得“文件都保存了,直接 commit 就行”,结果 Git 报错:`Commit is not possible because you have unmerged files.`
正解编辑冲突文件只是把工作区内容改对了,Git 的索引里仍然记录着这个文件“未合并”。必须执行 git add,把“我认可这个结果”的状态写进暂存区,Git 才会放行 commit。这就像签完字的合同要盖章:内容谈拢了,还得走完确认流程。
为何常见:很多人把“保存文件”等同于“版本控制确认”,忽略了 Git 中工作区、暂存区、版本库三层状态的区别。
误区拿起 `git pull` 就当自动同步神器,pull 到冲突后不知所措,甚至尝试直接删掉本地目录重新 clone,结果把自己的新改动也丢了。
正解git pull 的本质是 git fetch + git merge,它同样会触发合并冲突。没有本地未提交改动时,冲突与 merge 完全一致;有未提交改动时,还可能因为工作区被覆盖而失败。正确姿势是:先 git status 确认工作区干净,再 pull;遇到冲突走标准解决流程,或改用 git pull --rebase 让本地提交移动到主干最新提交之上。
为何常见:新手把 pull 当成“更新按钮”而不是“合并操作”,忽略了它在底层同样要面对三方合并且可能产生冲突。
误区为了彻底告别冲突,团队规定同一文件只能由一个人改,谁要改先领“锁”,或者干脆要求所有人串行开发。
正解锁文件和串行开发确实能消灭合并冲突,但代价是团队完全失去并行能力,等待和阻塞造成的效率损失远超冲突本身。冲突是并行计算的“必要摩擦”,合理策略是减少摩擦而非消灭并行:小步提交、频繁同步、模块隔离、冲突早暴露,让冲突成为低成本事件。
为何常见:冲突的即时心理冲击(红色标记、被迫沟通)比它的真实成本更刺眼,导致人以“绝对禁止”的思路做出一刀切决策。
面试题
Q1. 在 Git 合并中,“合并冲突”最准确的理解是?
- A.代码存在语法错误导致编译失败
- B.双方改了同一区域且内容不一致← 正确答案
- C.合并时网络中断造成传输失败
- D.仓库内分支过多导致体积膨胀
答案:B。冲突的根源是“两套增量在同一基线上重叠且内容不同”,不是语法错误、网络问题或仓库管理问题。就像两份菜谱在“烤箱温度”一行各写各的,需要人工裁决。
Q2. 冲突标记中 <<<<<<< 与 ======= 之间是哪一方的版本?
- A.当前合并操作中本地分支的版本← 正确答案
- B.远端目标分支上的最新版本
- C.双方共同祖先基线的版本
- D.系统自动生成修补补丁的版本
答案:A。冲突块采用“上我下他”的排布:<<<<<<< 与 ======= 之间是 Ours(当前合并操作中你的版本),======= 与 >>>>>>> 之间是 Theirs(对方分支的版本),Base 基线不会直接出现在冲突标记中。
Q3. 手动编辑完冲突文件后,需要依次执行哪些命令才能干净地完成一次合并?
- A.git status 确认再无残留冲突← 正确答案
- B.git add 标记该文件已解决← 正确答案
- C.git commit 创建合并提交← 正确答案
- D.git push origin 强制推送远程
答案:ABC。标准闭环是“确认没有未合并文件 → 暂存已解决的改动 → 提交合并”。git push 是发布动作而非完成合并的必要步骤;强制推送(force push)在此场景更危险,可能覆盖他人提交。
Q4. Git 三方合并中的“三方”分别指什么?
- A.本地分支、远程分支、主干分支
- B.共同基线、你的版本、对方版本← 正确答案
- C.工作区、暂存区、版本仓库
- D.开发分支、测试分支、发布分支
答案:B。三方合并的三方是 Base(共同祖先)、Ours(当前分支版本)、Theirs(被合并分支版本)。工作区/暂存区/版本库是 Git 的存储层模型,与合并模型的“三方”是两码事。
Q5. 以下哪些工程实践能有效降低合并冲突的发生概率?
- A.频繁把主干的最新改动并入特性分支← 正确答案
- B.提交尽量保持小颗粒、单一职责← 正确答案
- C.特性分支长期存活以便容纳大需求
- D.按模块或文件目录划分团队分工← 正确答案
答案:ABD。三条正确策略对应“勤同步、小步走、划边界”。让特性分支长期存活正好与“缩短分支寿命”相悖,分支活得越久,积压的分歧越大,冲突概率越高。
Q6. git merge --abort 的准确作用是什么?
- A.跳过冲突区域直接强制完成合并
- B.放弃本次合并并回到合并前的状态← 正确答案
- C.删除所有冲突文件让仓库恢复干净
- D.自动清理冲突标记并提交新版本
答案:B。--abort 相当于把“涂到一半的手账页撕掉重来”,终止合并并恢复到合并开始前的状态。它不会替你解决冲突,也不会跳过冲突,更不会删除文件。
Q7. 小张把 model.py 改名为 network.py,小李同时修改了 model.py 的内容,两者合并时最可能出现的冲突类型是?
- A.文本内容冲突
- B.文件改名冲突← 正确答案
- C.语义编译冲突
- D.目录结构冲突
答案:B。一方改内容、一方改文件名,本质是“同一个文件的两个属性被不同的人动了”,Git 难以决定新内容应该留在新文件名还是旧文件名下,于是报告改名冲突。纯文本冲突发生在同一文件内部,而语义冲突 Git 检测不到。
Q8. 合并冲突产生的根本原因是什么?
- A.两个分支各自发展后出现了分叉
- B.多人修改了同一个位置且改动不一致← 正确答案
- C.Git 在传输过程中损坏了仓库数据
- D.仓库缺少全局用户签名配置
答案:B。分叉只是冲突的前提条件,真正触发冲突的是“双方增量在同一个位置上重叠且内容不同”。分叉本身不会导致冲突——只要两边的改动互不碰面,Git 就能自动合并。
Q9. 哪些手段可以发现“代码能编译但逻辑矛盾”的语义冲突?
- A.运行完整的单元测试与集成测试← 正确答案
- B.组织团队进行代码评审← 正确答案
- C.用 git log 对比双方的提交时间
- D.在 CI 中增加冒烟测试或训练验证← 正确答案
答案:ABD。语义冲突隐藏在“文本不重叠但逻辑相斥”里,编译器和 git 都查不出来。测试、评审、CI 冒烟验证是捕获它的主力;git log 只能追提交时间线,无法验证逻辑正确性。
Q10. 解决冲突并编辑完文件后,执行 git add 的核心作用是什么?
- A.自动选择保留双方代码中更优的一方
- B.向 Git 标记该文件的冲突已经解决← 正确答案
- C.删除文件中所有的冲突标记符号
- D.直接将文件内容写入新的合并提交
答案:B。git add 把你对冲突文件的最终裁决写入暂存区,文件状态从 Unmerged 变为 Staged,相当于“盖章确认”,随后才能 commit。它不会替你选代码,也不会改文件内容。