第一次和同学一起写页面时,我们约定“你写首页,我写列表页”,以为文件不同就不会冲突。结果两个人都改了导航栏:他增加登录入口,我调整 DOM 结构和类名。合并时出现熟悉的 <<<<<<< HEAD,我把冲突标记删掉,选了一份看起来完整的代码,提交后才发现登录入口也被一起删了。
Git 告诉我文本无法自动合并,却不会替我判断产品意图。文件恢复成合法语法,只说明冲突标记消失,不说明两个需求都被保留。
图 1:冲突发生在文本,解决却需要回到提交意图和共享基线。
先读三份内容,而不是直接选 ours/theirs
冲突里至少有三种事实:共同祖先、当前分支和待合并分支。只选 ours 或 theirs,相当于假设其中一方的修改可以完整覆盖另一方。
<<<<<<< HEAD
<nav class="site-nav site-nav--compact">
<a href="/">首页</a>
=======
<nav class="site-nav">
<a href="/">首页</a>
<a href="/login">登录</a>
>>>>>>> feature/login正确结果不是二选一,而是保留新结构和登录入口:
<nav class="site-nav site-nav--compact">
<a href="/">首页</a>
<a href="/login">登录</a>
</nav>解决前我会先看两边提交说明和差异,分别写下它们想完成什么。看不懂时直接问作者,比凭文件内容猜更便宜。
小提交让意图更容易恢复
当时我们经常一天结束后一次提交二十个文件,提交信息只有 update。冲突时无法判断某行变更属于哪个目的。后来我开始按可验证结果拆提交:
feat(nav): add login entry for anonymous users
refactor(nav): replace float layout with flex
fix(nav): keep active state after route change一个提交只承担一个主要意图,合并时就能决定它应该保留、重做还是放弃。提交历史不是备份压缩包,它是协作中的解释材料。
合并完成后验证双方场景
解决冲突后的检查清单不应该只有“能编译”:
| 原分支意图 | 验证方式 |
|---|---|
| 小屏导航变紧凑 | 375px 宽度下链接不换乱、不溢出 |
| 未登录用户看到登录入口 | 清除会话后入口存在且可点击 |
| 已登录用户显示头像 | 模拟登录态,入口被正确替换 |
| 当前路由保持高亮 | 首页和列表页分别刷新验证 |
这张表能防止我只验证自己的改动。冲突解决者实际上临时承担了集成责任。
共享文件需要更清楚的所有权
导航、路由、依赖清单和全局样式天然容易冲突。我们后来在开始任务前说清楚谁会修改共享文件;大的结构调整先单独合并,再让功能分支基于新结构继续。这样不是为了追求“永远没有冲突”,而是让冲突更早、更小、更容易解释。
我也不再把长期分支放到最后一天才合并。每天同步主分支,冲突仍然存在,但上下文还在脑子里,解决成本低很多。
Git 能保存历史,不能替团队沟通
第一次冲突让我学到一个很具体的标准:合并结果必须同时回答两边为什么改,并经过两边场景验证。绿色状态、干净工作区和成功提交只是过程信号。
后来做更大的系统,冲突可能出现在数据库迁移、接口契约或基础设施配置里,已经不是几行 HTML。越靠近共享边界,越需要小提交、明确所有权和提前集成。Git 最有价值的地方不是帮我们避免分歧,而是让分歧有证据可查。