这个标题我琢磨了很久,越想越觉得它说到了根子上。网上讲Git冲突解决的文章,十个里有九个在教"怎么点按钮""怎么复制粘贴",可你换个场景、换个文件、换个队友,那套技巧立刻失灵。原因很简单——冲突本身不是一个"操作"问题,而是一个"理解"问题。你不理解冲突发生时仓库里到底经历了什么,你就永远只能靠碰运气去解冲突,碰对了算走运,碰错了就是事故现场。
这篇文章我不打算给你列一堆命令速查表,我想做的是把"同时发生了什么"这件事彻底讲透,然后你回头看那些冲突,会发现它们根本不可怕。文章会从Git的底层合并逻辑讲起,拆解冲突标记背后的三种版本,再用一个完整案例走完排查到解决的全程,最后聊聊怎么从工程习惯上减少冲突。适合所有用Git做协作开发、又被冲突折磨过的人,不管是刚学会pull和push的新手,还是写着写着突然被conflict搞蒙的中级选手。
1. 冲突不是Bug,而是Git在告诉你"两条时间线在此交汇"
先说结论:Git报冲突,不是它坏了,也不是你操作错了,更不是队友故意恶心你。恰恰相反,冲突是Git在做完它该做的所有努力之后,发现剩下的事情超出了算法的能力范围,需要人类来拍板。换句话说,冲突是Git对代码世界的一种诚实表达:我无法判断哪一个才是你们要的真相,所以我把决定权还给你们。
很多初学者第一次遇到冲突的第一反应是恐慌,觉得自己把仓库搞坏了。我刚带团队那会儿,经常收到那种"救命,git出问题了,一堆尖括号"的消息。其实你去GitHub上看看那些成熟的开源项目,合并请求里带conflict的比比皆是,这是协作开发里的日常,连Linus Torvalds自己合并代码的时候都不敢保证零冲突。
那Git到底在什么时候会喊出CONFLICT呢?一句话总结:**当两条开发线修改了同一处内容,且Git无法自动判断哪个版本是正确的"最终答案"时,它就会停下并询问你。**这句话里有两个关键词,一个是"同一处内容",一个是"无法自动判断"。如果你们俩改的是不同文件,或者同一文件的不同位置,Git会直接合并,连问都不问你。只有修改发生重叠,且双方给出的答案不一致,它才会举起手来。
这里就引出了一个特别重要的认知:冲突不是"代码写得不好"的惩罚,它是并行开发的自然产物。你想想,如果你们团队只有一个分支、一个人一次性写完所有代码,那当然不会有冲突。但那样也就没有Git存在的意义了。Git给你提供了并行开发的能力,分支就是"平行宇宙",你在这个宇宙里改A,队友在那个宇宙里改B,最后两个宇宙要合并回同一个现实,发现A和B在同一个地方给出了不同的答案——这就是冲突。
所以我的第一个建议是:**别怕冲突,怕的是你不懂冲突。**你越理解它为什么发生,你就越不会在冲突面前慌乱,越不会做出那种"一把梭直接把队友的代码全删了"的灾难性操作。接下来,我们得真正走进Git的合并逻辑里看看,它到底在合并什么,它凭什么判断"这一块能自动合,那一块不能"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. "同时发生了什么"——拆解Git冲突的底层原理
2.1 分支的本质:一条时间线的分叉
要理解冲突,先得理解分支。分支在Git里不是什么高深的东西,它本质上只是指向某个提交的指针。你从主分支拉出一个feature分支的那一刻,两个分支指向的是同一个提交,这叫做"公共祖先"(merge base),后面我会详细讲它,这是理解三路合并的钥匙。
从那个共同的起点出发,你和队友分别往前走,各自产生新的提交。于是历史在这里分叉了:一条是"你这条线"上发生的事,一条是"队友那条线"上发生的事。你们两个人都在各自的线上改了同一个文件,而且不巧的是,你们改到了同一个地方。这时候你说"把我的合进来",队友说"把我的合进来",Git夹在中间左右为难——因为它没有上帝视角,不知道你们俩谁改得对,更不知道你们是不是改完同一个功能。
Git能做的事情很有限:它把两条线分别产生的变化提取出来,尝试把两边的变化同时"打"到公共祖先的那个版本上。如果两边的变化落在不同的行、不同的函数、不同的文件里,Git会默默地把两边的改动都保留下来,这就是你平时看到的"自动合并成功"。但如果两边都改了同一行,或者一个人的改动恰好落在另一个人的改动范围内,Git就没办法了,它无法同时应用两个互相矛盾的修改。
2.2 三路合并:Git合并时的三方视角
这里就到了全文最核心的概念——三路合并(three-way merge)。Git在合并两个分支时,不是简单地拿两个版本对着看,它需要引入第三个参照点:两份代码的共同祖先版本。为什么必须要有第三份?我们来做一个思想实验。
假设你手里的文件现在是这样:
code复制name = "Alice"
你把它改成了:
code复制name = "Alice A"
队友手里的文件,因为推到远端的时候还停留在旧的状态,所以也是:
code复制name = "Alice"
队友改成了:
code复制name = "Alice B"
现在如果不看共同祖先,只看"你的版本"和"队友的版本",Git根本不知道这两个版本是怎么来的——它只知道两边在这一行给出的内容不一样。但一旦引入共同祖先,Git的逻辑就清晰了:祖先版本是name = "Alice",你的改动是把Alice改成了Alice A,队友的改动是把Alice改成了Alice B,两边从同一个起点出发,走向了不同的方向。这说明什么?说明两个人确实在同一个位置上做了不同的修改,这就是一个真冲突。
如果换一种情况:祖先版本是name = "Alice",你把它改成了name = "Alice A",而队友在祖先版本上没有动这一行,还是name = "Alice"。三路合并会认为:你的改动是新的,队友那边没改,所以自动采用你的结果,不冲突。同理,如果队友改了而你没改,就采用队友的。
这就是三路合并的全部逻辑:**它关心的是"从同一个起点出发,双方各自产生了什么变化",而不是"两个最终版本谁看起来更新"。**理解了这一点,你就理解了为什么Git有时候能自动合并,有时候不能——自动合并的条件是双方的改动区域不重叠,或者说至少有一方没有改动该区域。
2.3 三种版本的来龙去脉
三路合并里的三个角色,在Git的术语里分别是:
| 角色 | 术语 | 含义 |
|---|---|---|
| 祖先版本 | BASE | 两个分支分叉前的那个共同提交里的文件内容 |
| 当前分支版本 | OURS(HEAD) | 你所在分支的最新提交里的文件内容 |
| 被合并分支版本 | THEIRS | 你要合并进来的那个分支的最新提交里的文件内容 |
注意,OURS和THEIRS是谁,取决于你站在哪个分支上执行合并命令。如果你在master分支上执行git merge feature,那OURS就是master上的内容,THEIRS就是feature上的内容。如果你反过来在feature上执行git merge master,那OURS就变成feature了。这个"立场问题"特别容易让人晕,很多解法教程里写的"选择ours"和"选择theirs"并不是绝对的,你换了执行合并的分支,意义就完全反了。后面实战部分我会再强调这一点。
理解了BASE、OURS、THEIRS这三个角色,冲突标记看起来就不再是乱码了。Git之所以把这三方都摆在你面前,就是希望你在做最终裁决的时候,先自己想清楚一个问题:**"从祖先版本开始,我这边发生了哪个改动,队友那边发生了哪个改动?"**一旦你能回答这个问题,冲突标记里那些尖括号就全都有了意义。
3. 看懂冲突标记,别急着改代码——先还原三条时间线
3.1 冲突标记的结构到底是什么
我第一次看到冲突标记的时候,脑子里满是问号:这堆<<<<<<<、=======、>>>>>>>是不是把文件搞坏了?后来才发现,这些符号不是垃圾,而是Git给我留下的"案发现场记录"。它们把一段冲突区域分成了几个部分,每一部分都对应一个版本的内容。
一个典型的冲突标记长这样:
code复制<<<<<<< HEAD
// 你所在分支(OURS)的代码
String greeting = "Hello from main branch";
=======
// 你要合并进来分支(THEIRS)的代码
String greeting = "Hello from feature branch";
>>>>>>> feature/login
这段标记的含义非常直白:从<<<<<<< HEAD到=======之间的内容,是你当前分支上的版本;从=======到>>>>>>> feature/login之间的内容,是对方的版本。Git在用这种方式告诉你:同一行代码,你这条线长成了这个样子,对方那条线长成了那个样子,现在你必须选一个,或者把它们捏合成一个,总之你得给个痛快话。
如果你用IDE(比如IntelliJ IDEA或VS Code),这些区域会有颜色高亮,甚至还会给你弹出几个按钮,什么"Accept Current""Accept Incoming""Accept Both"之类的。但我想提醒你的是:**按钮只是工具,真正决定你该按哪个的,不是IDE,不是Git,而是你对业务和代码逻辑的理解。**你如果没想清楚"同时发生了什么",那你按哪个按钮都是在掷骰子。
3.2 三种冲突类型,对应三种决策方式
根据我的经验,冲突看起来五花八门,但归纳起来无非就三种,针对每一种,我们的决策思路完全不同。
**第一种:两人改的是同一行,但改成了不同的值。**这种最典型,比如两个人同时改了配置项的一个参数。这种冲突你需要判断:哪边的值是正确的?或者两个值是不是在不同的应用场景下需要共存?比如一个超时时间,你设成了30秒,队友设成了60秒,这不一定是非此即彼的问题,也许需要根据环境区分。所以解决这种冲突不能"选了就行",得想清楚值背后有没有条件逻辑。
第二种:一个人改了几行,另一个人改的是这几行所处的整个代码块。比方说你重构了整个函数,把原来的实现全部换掉了;队友恰好在这个函数里加了一行日志。这时候三路合并会提示冲突,但仔细看,你俩想做的事情并不矛盾——你想改写实现,队友想加日志。正确的解法往往不是"二选一",而是把队友的那行改动,重新移植到你重构后的代码里。这种冲突最考验理解力,因为你得同时看懂两个人的意图,然后找到两者兼容的方式。
**第三种:看似冲突,实则只是改动位置碰巧挨着。**有时候你加了一行,队友在紧跟着的下方也加了一行,没有哪一行真的被两边同时改过,但Git的冲突算法是按"改动范围"来判断的,两处改动挨得太近时也会报冲突。这种其实很好办,你把两种改动都保留就行,甚至中间还需要适当调整一下顺序和格式。这属于"伪冲突",掌握了识别它的能力,你每次能省下大量时间。
3.3 解决冲突前,先回答三个问题
在动手改任何一行之前,我会强迫自己先回答三个问题,回答完了再动手。这三个问题是我从无数次踩坑里提炼出来的,百试不爽:
- BASE版本里,这块代码长什么样? 这是两者共同的起点。如果你看不出来起点是什么,你先用
git show <commit>:<file>查一下那个祖先提交的内容。 - 我做这次修改,是想解决什么问题? 我改这块代码的业务动机是什么?我期望达到的效果是什么?
- 队友做这次修改,是想解决什么问题? 这个可能需要看提交信息、看代码注释、甚至直接去问队友。但只要你搞清楚了他为什么改,你就能判断两边的改动到底能不能共存。
我发现一个特别常见的错误是:很多人一看到冲突标记,脑子里想的全是"这两个版本哪个是新的"。这完全跑偏了。Git的冲突区域里没有"时间先后"的概念——两个分支都在往前走,你不能因为队友的提交时间比你晚,就说队友的版本更新、更应该被保留。**正确的判断依据不是新旧,而是"哪边的改动更符合当前的业务目标"以及"两边如何协作共存"。**这个认知转过来之后,你的冲突解决能力会有一个质的飞跃。
4. 实战演练:一个真实冲突从出现到解决的完整过程
理论说了一堆,我们来点实的。我造一个非常常见的场景,带你把刚才那套"还原三条时间线"的方法完整走一遍。
4.1 场景设定:同一个用户登录方法,两个分支各改各的
假设我们的项目里有一个UserService.java文件,其中最开始的(BASE版本)代码长这样:
java复制public class UserService {
public User login(String username, String password) {
User user = findByUsername(username);
if (user == null) {
throw new UserNotFoundException("user not found");
}
if (!password.equals(user.getPassword())) {
throw new PasswordErrorException("password error");
}
return user;
}
}
现在你在feature/log-audit分支上做登录日志审计功能,你把这个方法改成了:
java复制public User login(String username, String password) {
logger.info("login attempt: {}", username); // 你加的一行
User user = findByUsername(username);
...
}
与此同时,你的队友在feature/password-hash分支上改密码校验逻辑,他把方法改成了:
java复制public User login(String username, String password) {
User user = findByUsername(username);
...
if (!passwordEncoder.matches(password, user.getPassword())) { // 队友改的这一行
throw new PasswordErrorException("password error");
}
return user;
}
注意,你们俩改的行不重叠——你加的是方法开头的一行日志,队友改的是中间的一行判断。这种情况下,Git其实有能力自动合并吗?如果两处改动距离足够远,确实可以自动合。但问题在于你的改动紧贴方法第一行,而队友的改动也在同一个方法体内,当Git计算"改动范围"时,有可能因为上下文靠得太近而判定为冲突。实际合并时Git确实报了冲突,我们来看看它留下的案发现场。
4.2 冲突输出长什么样
执行git merge feature/password-hash后,Git提示:
code复制Auto-merging UserService.java
CONFLICT (content): Merge conflict in UserService.java
Automatic merge failed; fix conflicts and then commit the result.
打开UserService.java,看到:
java复制public class UserService {
public User login(String username, String password) {
<<<<<<< HEAD
logger.info("login attempt: {}", username);
User user = findByUsername(username);
=======
User user = findByUsername(username);
>>>>>>> feature/password-hash
if (user == null) {
throw new UserNotFoundException("user not found");
}
if (!password.equals(user.getPassword())) {
<<<<<<< HEAD
throw new PasswordErrorException("password error");
=======
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new PasswordErrorException("password error");
}
>>>>>>> feature/password-hash
}
return user;
}
}
你看,这两个冲突区块里,第一个是真的吗?让我们用BASE、OURS、THEIRS来分析。BASE里方法开头直接就是User user = findByUsername(username);。OURS(你的日志审计分支)在它前面加了一行日志。THEIRS(队友的密码哈希分支)没有动这一行的位置。按照三路合并的标准,这里其实不构成真正的双方分歧——是你单方面加了内容,队友没改这行。那Git为什么还是标了冲突?因为你的改动区域包含了User user = ...这一行,队友虽然没改它,但队友改动的位置紧邻这个区域的上方/下方,两个改动范围发生了部分交叠,Git宁可保守地把它标记成冲突,也不愿冒丢代码的风险。
第二个冲突区块就完全不同了:BASE里密码判断是if (!password.equals(user.getPassword())),OURS这里没有改动这一段,THEIRS把它整体替换成了if (!passwordEncoder.matches(password, user.getPassword()))。这里其实是单边改动,但Git把它标成了冲突,大概率是因为改动区域范围比较大,和第一处冲突产生了联动。
4.3 逐块分析,别着急删尖括号
真正成熟的解法,不是把某个区块整个"选A"或"选B",而是理解每一块的来龙去脉,再决定怎么组织。
先看第一块:<<<<<<< HEAD 到 ======= 之间是OURS,即你加了日志;======= 到 >>>>>>> 之间是THEIRS,即原始状态。队友THEIRS里没有删你的日志,他只是没有这行日志。所以正确做法是:保留你的日志行,删掉另一个分支的对应部分。也就是说,最终该长这样:
java复制 public User login(String username, String password) {
logger.info("login attempt: {}", username);
User user = findByUsername(username);
if (user == null) {
...
再看第二块:<<<<<<< HEAD 到 ======= 之间是OURS,即throw new PasswordErrorException("password error");,这是原始逻辑;======= 到 >>>>>>> 之间是THEIRS,是队友的passwordEncoder.matches新逻辑。你没改这块,队友改了,而且他的改动是出于业务需要(密码改成了哈希存储,原来的equals必然失效)。正确答案是:采用THEIRS的改动,把密码校验换成passwordEncoder.matches。最终该长这样:
java复制 if (!passwordEncoder.matches(password, user.getPassword())) {
throw new PasswordErrorException("password error");
}
注意这个分叉判断:第一处你加日志的行为和队友没冲突,保留你的;第二处队友改密码校验的行为和你的改动没有理念冲突,保留队友的。两处改动都能共存,最终的文件是两个分支成果的合体,而不是二选一的赌局。
4.4 解决之后,别忘了验证和收尾
改完冲突标记之后,把代码从头到尾通读一遍,确认没有残留的<<<<<<<、=======、>>>>>>>。然后编译、跑测试,尤其是涉及这个方法的所有单元测试和集成测试,千万不能省。这一步我见过太多人偷懒,结果冲突"解决"了,程序跑起来直接跪了,反而更难排查。
测试通过后,执行:
bash复制git add UserService.java
git commit
合并就完成了。之后你还可以用git log检查一下合并提交的信息,确认这个commit把两个分支的成果都带进来了。等你哪天需要排查这段历史的时候,你会发现一个清晰、正确的合并提交,比任何"炫技"都更能救命。
其实这个案例里藏着一个很重要的规律:**判断冲突区域时,你要看的是"这个区块里,双方分别相对于BASE做了什么改动",而不是简单地在两个版本之间做选择。**一旦你养成了"先找BASE、再对比双方改动"的习惯,你会发现绝大多数的冲突都能用"这个保留、那个保留"的加法思维来解决,而只有极少情况下需要真正的"二选一"。
5. 减少冲突的工程实践:别把锅全甩给Git
5.1 冲突的成本,往往不是解决的那几分钟
解一次冲突本身花不了太多时间,真正贵的是解错之后引发的一连串问题:线上bug、返工、团队信任度下降……所以我觉得,一个成熟的工程师不应该只满足于"会解冲突",而应该想办法"少制造冲突"。这就要从工程习惯上做文章了。
很多冲突其实是可以提前消解的,根源就是两个分支在同一片区域并行开发太久。你想想,如果两个分支都是基于主分支的最新代码拉出来的,一周之内就合并回去,那它们之间的差异就很小,冲突的概率自然低。真正制造冲突的,往往是那种活了一个月还没合并回去的老分支,主分支早就被别人改得面目全非了,这时候一合并,恭喜你,冲突大礼包直接送到脸上。
5.2 减小冲突面的五个具体习惯
第一个习惯:**缩短分支的存活时间。**功能分支的生命周期越短,和其他分支分叉的时间就越短,产生冲突的窗口就越小。我一般建议一个分支的活量控制在1~3天内能完成,超过一周的分支,必须强制性地把它和主分支同步一次。这不叫瞻前顾后,这叫把大冲突拆成小冲突。
第二个习惯:**频繁地把主分支合入你的功能分支。**很多人怕同步主分支,总觉得"万一又有冲突怎么办"。但你想想,你越拖着不同步,累积的差异越大,最后那一下冲突就越猛烈。与其最后面对一个"巨型diff"的惊悚现场,不如每天花两分钟把主分支合进来,每天都处理一点点可能的冲突。这个习惯尤其适合团队协作密集的场景。我见过一个特别典型的案例:一个功能分支做了一半,最后合并时冲突了十几个文件,其中一多半的改动其实是别人已经删掉或重写过的逻辑,纯粹是因为合并间隔太久,白白浪费了一个下午。
第三个习惯:**做好文件职责拆分。**如果你们团队每个人都习惯往同一个Constants.java里塞常量、往同一个utils.js里堆函数,那这个文件天然就是冲突的重灾区。反过来,按模块、按功能划分好文件边界,两个人同时改同一个文件的概率就会变低。有时候我把冲突率当成一个团队代码质量的间接指标——如果某个文件老是冲突,那也许不是Git的问题,是模块设计的问题。
第四个习惯:**格式化改动和业务改动分离。**我见过太多次冲突是这么引起的:A分支改了业务的逻辑,B分支顺手用格式化工具把整个文件的缩进、换行、引号风格全改了一遍。一合并,整个文件几乎所有行都标成改了,Git拿什么判断该用哪个?你就算有通天的三路合并技巧,也挡不住这种"无意义的大范围diff"。所以我强烈建议:格式化工具(比如Prettier、Black、gofmt)要么全团队统一配置并强制生效,要么单独开一个"format-only"的提交,不和业务改动混在一起。另外,换了编辑器别顺手把整个文件的编码格式或行尾符改了,这个坑我踩过,那次冲突才叫酸爽。
第五个习惯:**使用Git rerere给自己上保险。**这个功能知道的人不多,但我真心推荐。rerere的全称是"reuse recorded resolution",它会把你解决过的冲突记录下来,下次遇到同样的冲突时自动应用之前的选择。虽然它不能完全替代冲突解决,但在某些反复合并的场景(比如你把同一个长期分支反复合入主分支)能省掉大量重复劳动。打开方式很简单:
bash复制git config --global rerere.enabled true
它还有一个隐藏好处:正因为rerere会自动记住并复现你的选择,它会逼着你每次解决冲突时多想几秒——因为你的选择会被"记录在案",下次如果情况变了,你得手动覆盖。这种"被监督"的感觉反而能帮你养成严谨的习惯。
5.3 团队层面的两个约定
除了个人习惯,团队协作上有两个约定我觉得特别值得推行。第一个是保持主分支可发布,让所有人都在尽量接近可发布状态的基础上并行开发。如果主分支长时间处于坏状态,大家就会开始拉自己的"私房分支",最后反而制造更多分叉。
第二个是提交信息写得清楚。你可能觉得冲突解决跟提交信息八竿子打不着,其实关系很大。当你在冲突标记里看到队友的THEIRS版本时,你唯一能用来判断他改了什么的东西,除了代码本身,就是他留下的提交信息。一个说"fix bug"的提交和一个说"adjust login rate limit"的提交,哪个能帮你快速做出决策,不用我多说了吧。
我以前犯过一个大错误,就是以为"少拉分支、大家排着队改同一个主干"才是减少冲突的终极方案。后来发现这纯属因噎废食——那就等于放弃了并行的效率。正确的目标不是"零冲突",而是"每次冲突都有意义":该冲突的地方说明两个人在同一块业务上确实有交集,需要沟通确认;不该冲突的地方则通过良好的工程习惯提前化解。把这两类分清楚,冲突从"麻烦事"变成了"沟通点",团队协作反而更顺了。
6. 最后分享几个我踩过的坑
文章写到这里,理论、实战、习惯都聊得差不多了。收尾之前,再分享几个我真实踩过、也看身边人反复踩的坑,你如果正好遇到,能少走很多弯路。
第一个坑是不知道自己在哪条分支上就执行了merge。前面讲过,OURS和THEIRS是站在不同分支上会互换角色的。你如果糊里糊涂在功能分支上执行了合并主分支,那冲突里的"ours"就变成了功能分支的代码,"theirs"变成了主分支的代码。很多教程里说"选择ours"指的是选当前分支的,不是"选我的",这个语义在多人协作里尤其容易混淆。所以动手解决冲突之前,先git branch看清楚你在哪,再git log --oneline -3看看HEAD指向哪个提交,再想想这个冲突到底是怎么来的。
第二个坑是解决完冲突直接提交,连编译都不做。说实话,Git的冲突标记只有那么几种,你的IDE甚至能帮你一键阶段化,但代码能不能跑起来是另一回事。有时候两个改动在语法上完全兼容,在业务逻辑上却南辕北辙。你合并了两个"互相对立"的实现还能编译通过,但功能已经坏了。所以我给自己定了一条规矩:凡是解过冲突的合并,必须跑一遍至少是最小集的测试。这十分钟的投入,能帮你避免一次"我明明解决了冲突程序为什么还是挂了"的深夜崩溃。
第三个坑是遇到冲突就去问别人"我该选哪个"。你问任何人,那个人都没有你对你业务的理解深。正确的姿势是,你自己先把BASE、OURS、THEIRS三方内容看明白,如果业务意图上还有疑问,带上具体问题去问队友:"我在做登录日志,你那段把equals换成matches是因为密码加密存储对吧?那我直接把你的改动保留下来就行,对吧?"这种提问很专业,队友也更容易给你准确的答复。而不是发一张冲突截图过去,问他"这里选哪个"。
第四个坑是小瞧了"伪冲突"的判断成本。我前面说伪冲突好解决,其实它的难点不在解决,而在识别。同样一段冲突标记,到底是"两边真改了同一行"还是"只是位置挨得近",需要你仔细对比BASE才能判断。我见过不少新手把伪冲突当成"二选一",结果把一个分支的改动整体丢掉,白瞎了队友的成果。所以,无论冲突看起来多么简单,我的建议永远是:先找BASE。你可以用git show $(git merge-base HEAD MERGE_HEAD):path/to/file来查看公共祖先里这个文件的内容,把那一眼看完再动手,绝对不亏。
说真的,Git冲突解决这套东西,你把它当成"技巧"去学,你会一直追着技巧跑,因为每个场景都不一样;你把它当成"理解"去学,理解了"同时发生了什么",理解了BASE/OURS/THEIRS之间的关系,理解了合并的本质是三路合并加人类裁决,那不管遇到什么样的冲突,你都能在一个稳定的方法论上展开推理。这个方法论,才是这篇文章真正想让你带走的。
