黑帽联盟

标题: Git之旅(12):解决冲突 [打印本页]

作者: 定位    时间: 2020-4-3 20:29
标题: Git之旅(12):解决冲突
前文中介绍了合并分支的方法以及常见场景,但是并没有模拟合并分支时遇到冲突的场景,这篇文章就站在前文的基础上,来总结一下怎样解决冲突。

如果两个分支中的同一个文件中的同一行的内容不一样,当我们合并这两个分支时,就会出现冲突,因为git无法判断我们想要以哪个内容为准,所以需要我们人为介入去确认,人为介入确认内容的过程,就是解决冲突的过程。

为了方便演示,我们先来创建一个测试仓库,并且创建两个分支出来,然后分别修改这两个分支中的同一个文件中的同一行,以便之后合并时能够出现冲突,过程如下:
$ git init testgit
Initialized empty Git repository in D:/workspace/git/testgit/.git/

/d/workspace/git
$ cd testgit/

/d/workspace/git/testgit (master)
$ echo 1 > testfile

/d/workspace/git/testgit (master)
$ git add testfile

/d/workspace/git/testgit (master)
$ git commit -m "add test file"
[master (root-commit) d808b6e] add test file
1 file changed, 1 insertion(+)
create mode 100644 testfile

/d/workspace/git/testgit (master)
$ echo 2 >> testfile

/d/workspace/git/testgit (master)
$ git add testfile

/d/workspace/git/testgit (master)
$ git commit -m "add 2"
[master 6b5149f] add 2
1 file changed, 1 insertion(+)

/d/workspace/git/testgit (master)
$ cat testfile
1
2

/d/workspace/git/testgit (master)
$ git checkout -b new
Switched to a new branch 'new'

/d/workspace/git/testgit (new)
$ cat testfile
1
2
通过上述步骤,我们创建了一个测试文件testfile,在testfile里面写了两行内容,并且创建了两个初始提交,然后,我们基于master分支创建了new分支,现在,new分支和master分支是完全相同的,我准备在两个分支中分别修改testfile的第二行,让new分支和master分支中的testfile的第二行的内容变的不同。
先来操作new分支,操作如下:
/d/workspace/git/testgit (new)
$ cat testfile
1
2

/d/workspace/git/testgit (new)
$ vim testfile

/d/workspace/git/testgit (new)
$ cat testfile
1
2new

/d/workspace/git/testgit (new)
$ git add testfile

/d/workspace/git/testgit (new)
$ git commit -m "2 new"
[new 7d188a4] 2 new
1 file changed, 1 insertion(+), 1 deletion(-)
如上所示,我将new分支中的testfile文件中的第二行从"2"改成了"2new",并且基于这个修改,在new分支中创建了新提交。
new分支操作完了,现在来操作master分支。
/d/workspace/git/testgit (new)
$ git checkout master
Switched to branch 'master'

/d/workspace/git/testgit (master)
$ cat testfile
1
2

/d/workspace/git/testgit (master)
$ vim testfile

/d/workspace/git/testgit (master)
$ cat testfile
1
2master

/d/workspace/git/testgit (master)
$ git add testfile

/d/workspace/git/testgit (master)
$ git commit -m "2 master"
[master 3c5ea03] 2 master
1 file changed, 1 insertion(+), 1 deletion(-)
如上所示,我将master分支中的testfile文件中的第二行从"2"改成了"2master",并且基于这个修改,在master分支中创建了新提交。
现在,在master分支和new分中,都有了属于了自己的提交,如下图所示,而且,这些提交都是针对testfile文件的第二行所做的修改。
1.png
假设,此时我们想要合并两个分支,就会出现所谓的冲突,我们来试试。
当前我们处于master分支中,现在尝试直接把new分支合并到当前分支,命令如下:
git merge new
当我们执行上述命令后,会看到如下返回信息
Auto-merging testfile
CONFLICT (content): Merge conflict in testfile
Automatic merge failed; fix conflicts and then commit the result.

/d/workspace/git/testgit (master|MERGING)
$
那么上述返回信息是什么意思呢?大概意思就是,合并时出现冲突啦,冲突在testfile中,自动合并失败啦,快来人解决冲突啊。
而且,我们能够发现,在git bash中,如果合并时遇到冲突,在git bash所显示的路径中就会添加上如下字样
"(分支名|MERGING)"
就像上例中的 "/d/workspace/git/testgit (master|MERGING)" 一样, "(分支名|MERGING)"表示当前分支还处于"合并中"的状态,也就是说,合并操作并没有完成,还在进行中。
此时,如果我们执行git status命令,能够看到如下信息
/d/workspace/git/testgit (master|MERGING)
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

Unmerged paths:
(use "git add <file>..." to mark resolution)

both modified:   testfile

no changes added to commit (use "git add" and/or "git commit -a")
从上述信息可以看出,我们当前处于master分支,存在没有合并完成的路径(存在没有合并完成的文件)。
而且git提示我们,现在我们有两种选择,这两种选择分别如下:
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
第一种选择是:修复冲突,然后将确定后的内容创建提交。
第二种选择是:使用"git merge --abort"命令放弃合并。
看来git还是很人性化的,在合并时如果遇到冲突,我们可以选择放弃合并,就像从来都没有发生过任何事情一样,也可以选择解决冲突,完成合并,后文会演示这两种操作,此处不用纠结。
从返回信息的"Unmerged paths"中,我们能够找到所有未完成合并的文件(存在冲突的文件),而上例中,合并时出现冲突的文件就是testfile文件,那么,我们来看看,在产生冲突以后,testfile文件变成了什么样子。
/d/workspace/git/testgit (master|MERGING)
$ cat testfile
1
<<<<<<< HEAD
2master
=======
2new
>>>>>>> new
如上所示,当冲突发生以后,git会自动将冲突的部分标注出来。
git会使用如下结构,将冲突的内容标注起来
<<<<<<< HEAD
=======
>>>>>>> BranchName
git会将当前分支中的内容放在 "<<<<<<< HEAD" 与 "=======" 之间。
git会将new分支中的内容放在 "======="  与 "<<<<<<< new" 之间。
也就是说,git并不能确定,是使用"2master"作为最终的内容,还是使用"2new"作为最终的内容,所以,需要我们人为的进行裁决,决定最终的内容。

到目前为止,我们已经在合并时遇到了冲突,并且查看了冲突所在的testfile文件,我们可以选择放弃合并,也可以选择解决冲突,我们先来演示一下,怎样放弃合并,正如提示信息中所示,我们只要执行"git merge --abort"命令即可放弃合并,我们来试试,操作如下:
/d/workspace/git/testgit (master|MERGING)
$ git merge --abort

/d/workspace/git/testgit (master)
$ git status
On branch master
nothing to commit, working tree clean

/d/workspace/git/testgit (master)
$ cat testfile
1
2master
如上例所示,当我们执行git merge --abort命令以后,git就取消了本次合并操作,此时,再次执行"git status"命令,可以看到master分支中没有任何需要修改和提交的内容,查看testfile的内容,会发现testfile中的内容与执行合并操作之前相同,就好像没有执行过任何合并操作一样。这就是在冲突时放弃合并的方法,很容易吧。

我们已经掌握了怎样在合并产生冲突时放弃合并,现在我们来看看怎样在发生冲突的情况下完成合并。
再次执行如下合并命令,以便合并时产生冲突。
/d/workspace/git/testgit (master)
$ git merge new
Auto-merging testfile
CONFLICT (content): Merge conflict in testfile
Automatic merge failed; fix conflicts and then commit the result.
如你所见,自动合并失败了,因为有冲突在testfile文件中,git提示我们,可以修复冲突,然后将修复后的结果提交。
查看testfile文件的内容,发现冲突的部分已经被git标注好了,如下:
/d/workspace/git/testgit (master|MERGING)
$ cat testfile
1
<<<<<<< HEAD
2master
=======
2new
>>>>>>> new
我们可以按照自己的需求,修改冲突的部分,此处假设,我想要使用"2 master 2 new"作为最终的内容,我只需要通过编辑器,将冲突标注的部分改为"2 master 2 new"即可,操作如下(使用vim编辑器编辑文本):
/d/workspace/git/testgit (master|MERGING)
$ vim testfile

/d/workspace/git/testgit (master|MERGING)
$ cat testfile
1
2master 2new
编辑后的testfile内容如上所示
好了,冲突的部分已经被人为干预解决了,不过,这并不代表整个合并操作完成了,在解决所有冲突文件以后,我们还需要将最终的状态创建为提交,才算完成了整个合并操作,整个流程操作如下:
/d/workspace/git/testgit (master|MERGING)
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

Unmerged paths:
(use "git add <file>..." to mark resolution)

both modified:   testfile

no changes added to commit (use "git add" and/or "git commit -a")

/d/workspace/git/testgit (master|MERGING)
$ git add testfile

/d/workspace/git/testgit (master|MERGING)
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)

Changes to be committed:

modified:   testfile

/d/workspace/git/testgit (master|MERGING)
$ git commit -m "merge new branch into master branch"
[master 67f725c] merge new branch into master branch

/d/workspace/git/testgit (master)
$
如上述操作所示,我们先执行了git status命令,如果你在git bash中也执行了git status命令,会看到返回信息中testfile是红色的,也就是说,testfile目前的状态还未添加到暂存区,于是,为了方便提交,我们执行了git add命令,将testfile添加到了暂存区,再次执行git status命令,可以看到testfile已经变为绿色,最后,我们使用git commit命令创建了提交,将解决冲突后的合并状态永久的保存了在了新提交中。
此时,使用gitk --all命令查看图形化界面,如下:
2.png
可以看到,合并完成了,在合并过程中虽然遇到了冲突,但是我们人为介入,解决了冲突,并且在最后创建了提交,将合并后的状态保存在了新的合并提交中。
细心如你肯定已经发现了,上图所表达的状态其实与前文中正常合并后的状态一样(前一篇文章中我们演示了在没有冲突的情况下顺利完成合并的操作,其合并后的状态与上图一样),只不过,当遇到冲突时,合并提交不会自动创建,而是会给我们解决冲突的机会,当我们将所有冲突解决以后,再手动的创建合并提交,也就是刚才演示的解决冲突、创建提交的过程。总结成一句话就是,在合并时,如果没有冲突,就自动创建合并提交,如果存在冲突,需解决冲突后手动创建提交。

其实,在没有冲突能够正常合并的情况下,我们也可以明确指定不自动创建提交,而是手动的创建提交,我们只需要借助"--no-commit"参数即可,示例如下
git merge --no-commit new
上述命令表示,将new分支合并到当前分支,在没有冲突的情况下,也不自动创建提交,而是给我们一个修改的机会,我们可以将内容进行进一步修改后,以最后敲定的结果创建提交。快来自己创建一个测试场景,试试上面的"--no-commit"参数吧,此处就不进行演示了。

当分支合并完成后,我们就可以将不需要的分支删除了,比如上例中的new分支,我们已经将new分支的内容合并到了master分支中,所以,如果不再需要在new分支上进行操作,即可删除new分支,示例命令如下:
/d/workspace/git/testgit (master)
$ git branch -d new
Deleted branch new (was 7d188a4).

/d/workspace/git/testgit (master)
$ git branch -a
* master
如上述操作所示,我们想要删除new分支,所以执行了"git branch -d new"命令,"-d"参数为删除之意,需要注意,你如果想要删除new分支,就不能处于new分支中,必须先切换到其他分支中,而上例中,我们删除new分支时,处于master分支中,所以可以正常删除new分支,删除分支后,使用"git branch -a"命令查看所有分支,已经看不到new分支了,可以确认,new分支已经被删除了。
此时,如果使用gitk --all命令查看图形化界面,会看到如下界面:
3.png
从上图中可以看出,new分支的分支标签已经被删除了,目前只有master分支。

在有些情况下,我们使用"-d"参数,是无法删除对应分支的,比如,当git检测到,你要删除的分支还没有合并到其他分支中,git会出现类似如下提示:
error: The branch 'new' is not fully merged.
If you are sure you want to delete it, run 'git branch -D new'.
在new分支没有完全合并到其他分支中时,如果执行"git branch -d new"命令,就会出现上述提示,这是git为了保险起见而进行的提示,如果你无论如何就是想要删除new分支,无论它是否被完全合并都想要删除它,可以使用"-D"选项(大写D),即可强制删除对应的分支,示例如下:
git branch -D new
好啦,到目前为止,我们已经了解了怎样合并分支、解决冲突、删除分支,希望这篇文章能够帮助到你,加油~






欢迎光临 黑帽联盟 (https://bbs.cnblackhat.com/) Powered by Discuz! X2.5