Git 在检测差异时,不是像 Word 那样按"字节对比",也不是按"行号硬比对",而是用 最长公共子序列(LCS, Longest Common Subsequence)算法 来找出改动区域(diff hunk)。
流程可以这样理解:
- 找到两个版本的文件(来自不同 commit 的 blob)。
- 用 LCS 算法比对两份文本,定位哪些行相同、哪些行不同。
- 把不同的地方划分成"差异块 hunk" 。
- 输出成 diff 格式,再用于 merge 或显示。
👉 所以,Git 的差异检测是基于"行内容 + 相邻上下文",不是行号。
可视化:差异检测的例子
假设共同祖先版本(C2):
css
A
B
C
D
E
远程提交(C3)改了 B → B'
:
css
A
B'
C
D
E
本地提交(C4)改了 D → D'
:
css
A
B
C
D'
E
第一步:找共同祖先
C2 就是共同祖先(A-B-C-D-E)。
第二步:算 diff
- C2 → C3 的 diff:
css
- B
+ B'
- C2 → C4 的 diff:
diff
- D
+ D'
第三步:定位差异块
Git 会生成两个差异块(hunk):
- hunk1:涉及
B
的改动 - hunk2:涉及
D
的改动
第四步:合并
Git 尝试把 hunk1 和 hunk2 应用到 C2:
- hunk1 改
B
→B'
- hunk2 改
D
→D'
结果合并后:
css
A
B'
C
D'
E
👉 自动合并成功,因为两个 hunk 不重叠。
冲突的例子
共同祖先(C2):
css
A
B
C
远程提交(C3):B → X
css
A
X
C
本地提交(C4):B → Y
css
A
Y
C
diff
- C2 → C3 的 diff:
- B + X
- C2 → C4 的 diff:
- B + Y
hunk 对比
Git 发现两个 diff 都作用在同一个位置(B),修改冲突,无法自动决定保留 X
还是 Y
。
于是标记冲突:
markdown
A
<<<<<<< HEAD
Y
=======
X
>>>>>>> origin/main
C
Git 的差异检测原理总结
-
基于 LCS 算法,找出文件两版本的最长公共子序列。
-
把"非公共部分"标记为差异块(hunk)。
-
合并时:
- 如果 hunk 不重叠 → 自动合并
- 如果 hunk 重叠 → 冲突
总结与比喻
Git 检测差异就像两个人修改一本书:
- Git 会先找到书里大家都没改过的段落(LCS)("行"是比较的最小单位);
- 把大家改动的部分标记出来(hunk)(连续的差异行);
- 如果改动在不同段落,直接合并;
- 如果改动在同一行文字,Git 没法替你做决定,就报冲突。
在此强调,从头到尾,都不涉及"行号"的比较,这是误解的根源。