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