乐趣区

关于git:git-merge-不为人知的秘密

丹尼尔:Hi,蛋兄,周杰伦都出新专辑了,你咋还不更新啊,真的打算半年一更啊?

蛋学生:如同的确是这样,要不,择日不如撞日,明天聊聊?

丹尼尔:好啊,那聊些啥呢?

蛋学生:最近搞的事件须要实现两个利用我的项目的代码合并,逻辑就齐全参照 git merge 的根本准则,那就聊聊 git merge

丹尼尔:git merge 我倒是常常用,不过却从未关怀过它外部是怎么实现的。那你跟我讲一下它的工作原理呗。

合并的根本准则: three-way

蛋学生:git merge 的根本准则是 three-way

丹尼尔:3 条路?啥东东?

蛋学生:简略讲就是有 3 个分支。假如就叫 a, o, b,其中 a 和 b 都来自于 o,如下所示:

丹尼尔:嗯,而后呢?

蛋学生:当初 a 和 b 要进行合并。假如你以后在 a 分支,而后运行 git merge b,那么合并后果是依据 a, o, b 之间的内容比拟后果剖析得出的。

丹尼尔:哦,嗯,比拟逻辑是什么呢?

蛋学生:Very 简略。只有 a, o, b 任意两个的内容统一,就放弃 o 的内容;如果都不一样,就抵触。如下图所示

丹尼尔:只有 …

蛋学生:我还是列举下所有的场景吧,而后你就会明确了

1). o == a, o != b

假如内容如下:
o: daniel
a: daniel
b: dx-b

a merge b 的后果: dx-b

2). o == b, o != a

假如内容如下:
o: daniel
a: dx-a
b: daniel

a merge b 的后果: dx-a

3). a == b, o != a

假如内容如下:
o: daniel
a: dx-ab
b: dx-ab

a merge b 的后果: dx-ab

4). o != a, o != b, a != b

假如内容如下:
o: dx-o
a: dx-a
b: dx-b

a merge b 的后果: 抵触

<<<<<<< a  
dx-a  
=======  
dx-b  
>>>>>>> b

丹尼尔:哦,懂了,就是以 o 为基准来判断该保留哪个分支的内容,如果判断不了,就提醒抵触,自行解决。

蛋学生:没错

丹尼尔:下面是假如 3 个分支要比照的文件都存在,那如果某个分支的文件被删除或有新文件,该怎么解决呢?

蛋学生:你能够把短少的文件当作空内容文件来解决。嗯,这样说如同也不太精确。我还是再列举下场景吧。以下假如要比拟各分支的 dx.txt 文件

1). o 有, a 有, b 没

  • 假如 1: o == a

合并后果:删除文件

因为 o == a,所以取 b 的后果

  • 假如 2: o != a

合并后果:保留文件,内容为 a 的内容

因为 o, a, b 互不雷同,后果为抵触,但 b 没有文件,所以抵触后果间接取 a 的内容

2). o 有, a 没, b 有

与(1)相似,相当于把 a 换成 b

3). o 有, a 没, b 没

合并后果:删除文件

a == b,所以取 a 或 b 的后果,即删除

4). o 没, a 有, b 没

合并后果:取 a 的内容

o == b,所以取 a 的内容

5). o 没, a 没, b 有

与 (4) 相似,相当于把 a 换成 b

6). o 没, a 有, b 有

  • 假如 1: a == b

合并后果:取 a(或 b)的内容

  • 假如 2: a != b

合并后果:抵触

丹尼尔:丑陋,这下我齐全搞懂了合并逻辑了。

Diff 的实现算法:最长公共子序列

丹尼尔:但我还有一个疑难,比照文件内容的时候,是一行一行内容比照的吧

蛋学生:那是当然了

丹尼尔:那如果我加多一行,成心错开,岂不是都对不上了

蛋学生:当然 … 是不会犯这样低级的谬误的。在实现 diff 的时候,是利用了 LCS(Longest Common Sequence,即最长公共子序列)的算法。用下图来简略理解一下

假如有两个字符串 S1 和 S2,那它们的最长公共子序列就是 abcd

S1: "abcde"  
S2: "a1bc2d"

丹尼尔:哦。但这是字符串,该怎么利用到文件内容的 diff 上呢?

蛋学生:把图转一转,每个方块代表文件的一行内容,是不是就一样了

丹尼尔:是哦。通过 LCS 的算法,就算我成心错开了行,也不影响比拟,因为雷同内容的行总是能对得上

蛋学生:恩,不过这里只是两个文件的比拟,而 three-way 是三个文件内容的比拟,要略微多做点事

丹尼尔:能讲得具体一点吗?

蛋学生:上个图吧。假如咱们要合并 a 和 b 分支的 dx.txt 文件,先应用 LCS 来计算三个分支该文件内容的最长公共子序列(下图就是连线的内容为 a,c,e 的行),而后以这些子序列对各个文件的内容行进行宰割,宰割的块(下图中芜杂曲线的局部)就是不雷同的局部,对这些块的内容进行 three-way 剖析,即可得出这些内容块合并后的后果

丹尼尔:恩,究竟还是有图有假相啊,图一看就明确了。讲了这么多,要不间接 show 下代码吧

蛋学生:一样的思路,能够有各种各样的实现。我自个实现了一个简略的版本,请移步到 codepen.io 查看。也能够去瞧瞧 node-diff3 的代码实现,它比拟谨严,毕竟是一个可上生产的模块

丹尼尔:好咧,等会就去观摩观摩

小插曲

丹尼尔:我刚刚特意上网查了一下,git merge <branch> 的默认策略是 recursive,为啥叫递归呢?

蛋学生:还记得 git merge 的根本准则是 three-way 吗?a 和 b 的独特先人是 o,但有些状况下,a 和 b 的独特先人可能不止一个,这时就须要将这些独特先人通过 three-way 进行合并,这个动作会始终往上递归到根先人分支,所以这也是策略叫 recursive 的起因。

丹尼尔:除了 recursivegit merge 还有哪些合并策略呢?

蛋学生:这个就要看你装置的 git 的版本了。git merge 能够指定合并策略。这里有个小技巧,你能够成心给个不存在的策略名称,git 就会显示出所有可用的策略名称,如下所示:

$ git merge -s dx
Could not find merge strategy 'dx'.
Available strategies are: octopus ours recursive resolve subtree.

最初

丹尼尔:要不是我买了周杰伦的专辑,才想起你也好久没更新了,也就不会有明天这一出了

蛋学生:感激揭示,合作愉快

丹尼尔:真快,又到了说再见的时候了

蛋学生:See you next time!

退出移动版