化解冲突:git merge 与 git rebase 中的 ours 和 theirs

来源:化解冲突:git merge 与 git rebase 中的 ours 和 theirs | Ming’s Blog

git merge 与 git rebase 的区别

先复习一下 git merge 与 git rebase 的区别。假设当前项目有两个分支 local 和 upstream, 且工作环境处于 local 分支,如下图所示。

            HEAD
             |
local        C
            /
upstream   A -- B

执行 git merge upstream 的结果是这样的:

                 HEAD
                  |
local        C -- D
            /    /
upstream   A -- B

而执行 git rebase upstream 的结果略有区别:

                 HEAD
                  |
local        C -- D
            /
upstream   A -- B

git merge 会分别提取 B 和 C 不同于 A 的部分,将他们合并到一起,因此生成的结果 D 有两个祖先。但是,git rebase 的做法是找到从 A 到 B 的修改,并将这些修改作用于 C 上,所以 D 只有 C 这一个祖先。

在实际的项目里,由于多重祖先会产生一些难以解决的问题,我们倾向于使用 git rebase 而少用 git merge.

冲突与化解

在上面的项目中,如果 B 和 C 修改了 A 的同一行代码,就会引入一个冲突。冲突必须手工解决。假设项目中有文件 word.txt, 在版本 A, B 和 C 中的内容分别是 “astronauts”, “boycott”, “chocolate”. 执行 merge 之后 word.txt 会变成下面的样子。

<<<<<<< HEAD
chocolate
=======
boycott
>>>>>>> upstream

>
rebase 得到的结果类似但并不相同。

<<<<<<< HEAD
boycott
=======
chocolate
>>>>>>> C

可以看出,<<<<<<< 和 >>>>>>> 清晰地标示出了冲突的部分。你需要将它们改成期望的样子。如果你想保留两个版本中的一个而不引入新的修订,就可以使用两个预先定义的代词。<<<<<<< 和 ======= 之间的部分称为 ours======= 和 >>>>>>> 之间则称为 theirs. 此时,你只需要键入合适的指令即可化解冲突,而不需要进入文本编辑器。在当前项目可以得到如下运行结果:

# merge
git checkout --ours word.txt    # => chocolate
git checkout --theirs word.txt  # => boycott

# rebase
git checkout --ours word.txt    # => boycott
git checkout --theirs word.txt  # => chocolate

发现了什么不对劲的地方了么?没错!这是迄今为止 git 让我最困惑的一点:merge 和 rebase 对于 ours 和 theirs 的定义是完全相反的。在 merge 时,ours 指代的是当前分支,theirs 代表需要被合并的分支。而在 rebase 过程中,ours 指向了修改参考分支,theirs 却是当前分支。如果你仔细探究其中的原因,会发现 rebase 隐含了一个 git checkout upstream 的过程,将 HEAD 从 local分支变成了 upstream 分支。

local        C
            /
upstream   A -- B
                |
               HEAD (during rebase process)

git 会在 rebase 结束后撤销这个改变,但它已经不可避免地影响了冲突的状态,使 rebase 中 ours 和 theirs 的定义与 merge 截然相反。因此,在使用 ours 与 theirs 时请格外小心。


参考资料

Git Tower

说点什么

avatar
  Subscribe  
提醒
Optimized with PageSpeed Ninja