在上一节,通过执行 git pull 成功合并后再推送来解决非快进式推送的问题。但在真实的项目环境中,不总是会一帆风顺的,只要有合并就可能出现冲突,这一节重点介绍冲突解决方式。
上节已经说过: git pull = git fetch + git merge
-
git fetch:可暂时理解为将远程的版本库的对象(提交、里程碑、分支等)复制到本地。在本地版本库有专门的远程仓库映射引用,如:.git\refs\remotes\origin\master。 -
git merge:会被隐式地执行,将其他版本库的提交和本地版本库的提交进行合并。该命令还可以对本版本库的其他分支进行显示的合并操作。默认情况下,合并后的结果会自动提交,如果提供--no-commit选项,则合并后的结果会放入暂存区,用户可以对合并结果进行检查、更改,然后手动提交。
合并操作并非总会成功,因为合并的不同提交可能同时修改了同一文件相同区域的内容,这会导致冲突。冲突会造成合并操作的中断,冲突的文件会被标识,用户可以对标识为冲突的文件进行冲突解决操作,然后更新暂存区,再提交,最终完成合并操作。
场景一、成功自动合并
大多数情况下,Git 都能非常智能地进行自动合并,下面演示一下成功自动合并的三种情况。
情况一:修改不同的文件
还是用上一节的例子进行演示,为了确保版本库状态的一致性,分别在 user1 和 user2 的本地版本库中执行下面的操作:
git fetch
git reset --hard origin/master
现在两个用户的本地版本都为远程版本库的最新版本,状态一致。都有 team 目录和 user1.txt 和 user2.txt 文件。
- 用户
user1修改user1.txt文件,提交并推送到远程。
local19.png
- 用户
user2修改user2.txt文件,提交并推送(推送会失败,遇到非快进式推送的错误)。
local20.png
- 用户
user2执行git fetch操作,获取提交并更新到本地用于跟踪远程版本库master分支的本地引用origin/master中。再执行git merge操作,成功自动合并,最后推送合并后的本地版本库到远程版本库。
local21.png
- 查看提交关系图。
local22.png
情况二:修改相同文件的不同区域
当用户 user1 和 user2 在本地提交中修改相同的文件,但修改的是文件的不同位置时,仍可成功自动合并,具体操作如下:
-
为了确保版本库状态的一致性,分别在
user1和user2的本地版本库中执行git pull操作。 -
用户
user1修改README文件,在第一行插入内容,更改后内容如下:User1 hacked.
Hello. -
用户
user1本地提交并推送:
local23.png
-
用户
user2也在自己的工作区中修改README文件,在文件的最后插入内容,更改后的文件内容如下:Hello.
User2 hacked. -
用户
user2对修改进行本地提交,并执行git fetch操作:
local24.png
- 用户
user2进行合并操作,完成自动合并并进行推送。这里合并的时候写的是refs/remotes/origin/master,其简写就是:origin/master
local25.png
- 追溯一下
README文件每一行的来源,可以看到user1和user2更改的位置:
local26.png
情况三:同时更改文件名和文件内容
如果用户将文件移动到其他目录(或修改文件名),另外一个用户使用旧的文件进行了修改,Git 还是可以成功自动合并,具体操作如下:
-
为了确保版本库状态的一致性,分别在
user1和user2的本地版本库中执行git pull操作。 -
用户
user1将README移动到doc目录下,进行本地提交并推送:
local27.png
- 用户
user2在本地修改REAMD文件,在文件的最后插入内容,并本地提交。
local28.png
- 用户
user2执行git fetch操作,并执行git merge合并操作,最后进行推送。
local29.png
- 查看日志,并使用
-m参数查看合并操作所做出的修改。
local30.png
可以看到,上面的提交是 user1 的,进行了文件的移动。下面的提交是 user2 的,doc/README | 2 +- 表示这个文件从第二行开始,添加了一行并删除了一行 。原来的第三行内容是:User2 hacked. ,而现在的第三行内容是:User2 hacked.User2 hacked again.。Git` 理解为删除了原来的行,新增了一行。
场景二、自动合并失败,手动冲突解决
如果不同用户修改了同一文件的同一区域,则在合并时会遇到冲突而中断,因为 Git 无法替用户做出决定(是要 user1 的提交内容还是 user2 的提交内容 ,还是两者提交的所有内容),会把决定权交给用户,用户再根据 Git 标识出的冲突位置来进行手动处理。
演示这个场景很简单,两个用户都修改 doc/READE 文件,都在第二行 Hello. 的后面加上自己的名字,具体操作过程如下:
-
老规则,为了确保版本库状态的一致性,分别在
user1和user2的本地版本库中执行git pull操作。 -
用户
user1在第二行Hello.的后面加上自己的名字,内容如下:User1 hacked.
Hello. user1.
User2 hacked.User2 hacked again. -
用户
user1进行本地提交并推送:git add -u
git commit -m "say hello to user1."
git push -
用户
user2在第二行Hello.的后面加上自己的名字,内容如下:User1 hacked.
Hello. user2.
User2 hacked.User2 hacked again. -
用户
user2对修改进行本地提交:git add -u
git commit -m "say hello to user2." -
用户
user2执行git pull操作(相当于git fetch和git merge):
local31.png
自动合并失败了,需要手动修复冲突然后再对结果进行提交。
- 通过
git status命令,可以从状态输出中看到文件doc/README.txt处于冲突状态,这个文件在两个不同的提交中都做了修改:
local32.png
输出提示可以使用 git merge --abort 终止合并操作,或者手动修复冲突然后再对结果进行 commit 。
实际上,合并过程是通过 .git 目录下的几个文件进行记录的,当合并成功时,文件会自动被删除,现在合并失败,所以本地还存在这些文件:
-
.git/MERGE_HEAD:记录所合并的提交 ID(就是合并成功后的新提交节点)。 -
.git/MERGE_MSG:记录合并失败的信息。 -
.git/MERGE_MODE:标识合并状态。
local33.png
而且暂存区中还会记录冲突文件的多个不同版本,可以使用 git ls-files -s 命令查看:
local34.png
输出中的每一行有四个字段,第一个是文件的属性,第二个是哈希值,第三个是暂存区编号,当合并冲突发生时,会用到 0 以上的暂存区编号。
-
编号 1 的暂存区用于保存冲突文件修改之前的副本,也就是冲突双方共同的祖先版本。
-
编号 2 的暂存区用于保存当前冲突文件修改的副本。
-
编号 3 的暂存区用于保存合并分支的修改的副本。
可通过 :n:{filename} 语法来访问对应副本的内容,也可以用我们之前学过的 git cat-file -p {commit} 来查看副本的内容:
local35.png
通过 :n:{filename} 的方式是这样的:git show :1:doc/README 。对暂存区中冲突文件的上述三个副本无须了解太多,这三个副本实际上是提供给冲突解决工具,用于实现三向文件合并的。
工作区的版本则可能同时包含了成功的合并及冲突的合并,其中冲突的合并会用特殊的标记(<<<<<<<=======>>>>>>>)进行标识。当前冲突的文件内容如下:
local36.png
特殊标记 <<<<<<< 和 ======= 之间的内容是当前分支所更改的内容 。 ======== 和 >>>>>>> 之间的内容是所合并的分支修改的内容。
冲突解决的本质:就是通过编辑操作,将冲突标识符所标识的冲突内容替换为合适的内容,并去掉冲突标识符。编辑完毕后执行 add add 命令将文件添加到暂存区(编号0),然后再提交,就完成了冲突解决。
现在,工作区处于合并冲突状态,是无法再执行提交操作的。此时有两个选择:放弃合并操作或者解决冲突。放弃合并很简单,可以使用 git merge --abort 终止合并或执行 git reset 将暂存区重置即可。下面重点介绍如何进行冲突解决,也有两个方法:一个是对少量冲突非常适合的手工编辑操作,另一个是使用图形化冲突解决工具。
冲突解决方式一:手工编辑完成冲突解决
很简单,直接将 README 文件的标识符去掉,并修改为想要的提交内容,再进行提交。
修改后的文件内容如下:
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 目录下与合并相关的文件 .git/MERGE_HEAD、.git/MERGE_MSG、.git/MERGE_MODE 文件都自动删除了,而且暂存区中的三个副本也都清除了(实际在对编辑完的冲突文件执行 git add 后就已经被清除了)。
local37.png
冲突解决方式二:图形工具完成冲突解决
- 由于已经手动完成了冲突解决,只能先回滚提交,再执行合并重新进入冲突状态。
local38.png
-
再次合并操作,进入冲突状态:
git merge origin/master -
开始使用图形工具,根据不同的冲突解决软件来做具体操作,最终结果跟手动处理的一致。
-
最后进行
git push操作。








网友评论