合并时两个Commit为何无多个共同祖先?为何Merge Base在提交级?
Git的设计从根上就把提交(Commit)作为版本控制的核心单元,这直接决定了合并基准的定位是提交级而非文件级,具体原因可以从这几个角度理解:
提交是仓库的完整快照,而非增量变更
每个Git提交都保存了当前仓库所有文件的完整状态,不是只记录修改的部分。这意味着一个提交代表的是整个项目在某个时间点的全貌,合并操作的本质是整合两个不同分支的项目状态,自然要基于能同时关联两个分支的完整快照(也就是共同祖先提交),而不是拆分到单个文件的历史。分支是提交的线性链,合并是分支级操作
分支本质就是指向某个提交的指针,分支的演进是提交的线性(或非线性)叠加。当你执行合并时,Git处理的是两个分支的提交历史,目的是把两条历史线整合到一起。如果用文件级的基准,会破坏分支历史的连贯性——你没法解释合并后的仓库状态到底对应哪个历史节点的组合。文件级的差异计算依赖于提交快照
你觉得“每个文件的合并基准不同”,其实是Git在合并时针对单个文件做差异对比的表现:它会拿该文件在merge base提交中的版本,分别和两个待合并提交中的版本做对比,解决冲突。但这个文件的基准版本依然来自merge base提交这个完整快照,并非独立于提交存在的文件版本。简化逻辑,保证仓库状态的一致性
如果采用文件级的合并基准,Git需要为每个文件单独追溯历史找共同祖先,这会让合并逻辑变得极度复杂,甚至可能出现合并后不同文件来自完全不相关的历史节点,导致整个仓库的状态逻辑断裂,也让历史追溯变得几乎不可能。提交级的基准保证了合并操作的逻辑统一,也让仓库的历史始终是清晰的提交链。
举个实际例子:假设提交C是分支A和分支B的共同祖先,A分支的提交D修改了文件X和Y,B分支的提交E修改了文件Y和Z。合并A和B时,Git的merge base是提交C,它会用C里的X、Y、Z分别和D、E里的对应文件对比,处理Y文件的冲突,但整个合并的基础是C这个完整的项目快照,而非X、Y、Z各自找不同的祖先。
内容的提问来源于stack exchange,提问作者tristone

