16.4 合并三:冲突解决

    如果两个用户修改了同一文件的同一区域,则在合并的时候会遇到冲突而导致合并过程中断。这是因为Git并不能越俎代庖地替用户做出决定,而是把决定权交给用户。在这种情况下,Gits标识出合并冲突,等待用户对冲突做出抉择。

    下面的实践非常简单,两个用户都修改doc/README.txt文件,在第二行"Hello."的后面加上自己的名字,具体操作过程如下。

    (1)为确保两个用户的本地版本库和共享版本库状态一致,先分别对两个用户的本地版本库执行拉回操作。


    $git pull

    (2)用户user1在自己的工作区中修改doc/README.txt文件(仅改动了第二行)。修改后内容如下:


    User1 hacked. Hello,user1. User2 hacked. User2 hacked again.

    (3)用户user1对修改进行本地提交并推送到共享版本库。


    $git add-u $git commit-m "Say hello to user1." $git push

    (4)用户user2在自己的工作区中修改doc/README.txt文件(仅改动了第二行)。修改后内容如下:


    User1 hacked. Hello,user2. User2 hacked. User2 hacked again.

    (5)用户user2对修改进行本地提交。


    $git add-u $git commit-m "Say hello to user2."

    (6)用户user2执行拉回操作,遇到冲突。

    git pull操作相当于git fetch和git merge两个操作。


    $git pull remote:Counting objects:7,done. remote:Compressing objects:100%(3/3),done. remote:Total 4(delta 0),reused 0(delta 0) Unpacking objects:100%(4/4),done. From file:///path/to/repos/shared f73db10..a123390 master->origin/master Auto-merging doc/README.txt CONFLICT(content):Merge conflict in doc/README.txt Automatic merge failed;fix conflicts and then commit the result.

    执行git pull时所做的合并操作由于遇到冲突导致中断。来看看处于合并冲突状态时工作区和暂存区的状态。

    执行git status命令,可以从状态输出中看到文件doc/README.txt处于未合并(冲突)的状态,这个文件在两个不同的提交中都做了修改。


    $git status #On branch master #Your branch and 'refs/remotes/origin/master' have diverged, #and have 1 and 1 different commit(s)each,respectively. # #Unmerged paths: #(use "git add/rm<file>…" as appropriate to mark resolution) # #both modified:doc/README.txt # no changes added to commit(use "git add" and/or "git commit-a")

    那么Git是如何记录合并过程及冲突的呢?实际上合并过程是通过.git目录下的几个文件进行记录的:

    文件.git/MERGE_HEAD记录所合并的提交ID。

    文件.git/MERGE_MSG记录合并失败的信息。

    文件.git/MERGE_MODE标识合并状态。

    版本库暂存区中则会记录冲突文件的多个不同版本。可以使用git ls-files命令查看:


    $git ls-files-s 100644 ea501534d70a13b47b3b4b85c39ab487fa6471c2 1 doc/README.txt 100644 5611db505157d312e4f6fb1db2e2c5bac2a55432 2 doc/README.txt 100644 036dbc5c11b0a0cefc8247cf0e9a3e678f8de060 3 doc/README.txt 100644 430bd4314705257a53241bc1d2cb2cc30f06f5ea 0 team/user1.txt 100644 a72ca0b4f2b9661d12d2a0c1456649fc074a38e3 0 team/user2.txt

    在上面的输出中,每一行分为四个字段,前两个分别是文件的属性和SHA1哈希值。第三个字段是暂存区编号。当合并冲突发生后,会用到0以上的暂存区编号。

    编号为1的暂存区用于保存冲突文件修改之前的副本,即冲突双方共同的祖先版本。可以用:1:<filename>访问。


    $git show:1:doc/README.txt User1 hacked. Hello. User2 hacked. User2 hacked again.

    编号为2的暂存区用于保存当前冲突文件在当前分支中修改的副本。可以用:2:<filename>访问。


    $git show:2:doc/README.txt User1 hacked. Hello,user2. User2 hacked. User2 hacked again.

    编号为3的暂存区用于保存当前冲突文件在合并版本(分支)中修改的副本。可以用:3:<filename>访问。


    $git show:3:doc/README.txt User1 hacked. Hello,user1. User2 hacked. User2 hacked again.

    对暂存区中冲突文件的上述三个副本无须了解太多,这三个副本实际上是提供冲突解决工具,用于实现三向文件合并的。

    工作区的版本则可能同时包含了成功的合并及冲突的合并,其中冲突的合并会用特殊的标记(<<<<<<<=======>>>>>>>)进行标识。查看当前工作区中冲突的文件:


    $cat doc/README.txt User1 hacked. <<<<<<<HEAD Hello,user2. ======= Hello,user1. >>>>>>>a123390b8936882bd53033a582ab540850b6b5fb User2 hacked. User2 hacked again.

    特殊标识<<<<<<<(七个小于号)和=======(七个等号)之间的内容是当前分支所更改的内容。特殊标识=======(七个等号)和>>>>>>>(七个大于号)之间的内容是所合并的版本更改的内容。

    冲突解决的实质就是通过编辑操作,将冲突标识符所标识的冲突内容替换为合适的内容,并去掉冲突标识符。编辑完毕后执行git add命令将文件添加到暂存区(标号0),然后再提交就完成了冲突解决。

    当工作区处于合并冲突状态时,无法再执行提交操作。此时有两个选择:放弃合并操作,或者对合并冲突进行冲突解决操作。放弃合并操作非常简单,只须执行git reset将暂存区重置即可。下面重点介绍如何进行冲突解决的操作。有两个方法进行冲突解决,一个是对少量冲突非常适合的手工编辑操作,另外一个是使用图形化冲突解决工具。

    16.4.1 手工编辑完成冲突解决

    先来看看不使用工具,直接手动编辑完成冲突解决。打开文件doc/README.txt,将冲突标识符所标识的文字替换为Hello,user1 and user2.。修改后的文件内容如下:


    User1 hacked. Hello,user1 and user2. User2 hacked. User2 hacked again.

    然后添加到暂存区,并提交:


    $git add-u $git commit-m "Merge completed:say hello to all users."

    查看最近三次提交的日志,会看到最新的提交就是一个合并提交:


    $git log—oneline—graph-3 *bd3ad1a Merge completed:say hello to all users. |\ |*a123390 Say hello to user1. *|60b10f3 Say hello to user2. |/

    提交完成后,会看到.git目录下与合并相关的文件.git/MERGE_HEAD、.git/MERGE_MSG、.git/MERGE_MODE文件都自动删除了。

    如果查看暂存区,会发现冲突文件在暂存区中的三个副本也都清除了(实际在对编辑完成的冲突文件执行git add后就已经清除了)。


    $git ls-files-s 100644 463dd451d94832f196096bbc0c9cf9f2d0f82527 0 doc/README.txt 100644 430bd4314705257a53241bc1d2cb2cc30f06f5ea 0 team/user1.txt 100644 a72ca0b4f2b9661d12d2a0c1456649fc074a38e3 0 team/user2.txt