凯少

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一次合并会结合两个或多个历史提交分支。尽管Git还支持同时合并三个,四个或多个分支,但是大多数情况下,一次合并只结合两个分支。

在Git中,合并必须发生在一个版本库中------也就是说,所有要进行合并的分支必须在同一个版本库中。版本库中的分支怎么来的并不重要。

 

当一个分支中的修改与另一个分支中的修改不发生冲突的时候,Git会自动计算合并结果,并创建一个新的提交来表标识新的统一状态。

但是当分支冲突时,Git并不解决冲突,所以,当Git无法自动合并时,需要在所有冲突都被认为解决后做一次最终提交。

 

1,一个最简单的合并的例子

  

  创建一个新的版本库,新建文件"file",添加图片中的内容。然后提交到版本库。

  

  

  ok,在当前分支 master 下继续添加文件”other_file“,添加图片中的内容,并提交到版本库中。

  

  至此,版本库里已经有一个包含连词提交的分支 master,每次提交引入都引入了一个新文件。接下来,新建并切换到不同的分支。

  

  新的分支 alternate 基于 master^(master的前一次提交,本例中就代表master的初始提交,master~数字 代表是同一一个意思),在该分支下修改文件”file“,新增图片中的内容,最后提交到版本库中。

  

  现在就有了两条分支,两条分支都开始了自己的工作,都针对文件”file“进行了修改,因为这两个修改并不影响相同文件的相同部分,所以合并应该会顺利进行,不会发生事务。

  git merge 操作是区分上下文的。当前分支始终是目标分支,其他一个或多个分支之中是被合并到当前分支。现在想要 alternate 分支合并到 master 分支,所以在操作之前必须检出 master 分支,然后再进行 git merge 操作。

  

  nice,合并顺利进行,两个分支对文件”file“的修改顺利的合并在一起。然后来看看提交历史:

  

  两个分支在 53d24be 这个提交处分开(当时的 master 是提交了other file,所以分支基于的 master^ 就是"init 3 line in file"这个提交),分支修改了文件”file“,产生了一个提交 4a547440。最后执行提交产生一个新的提交 475e5e8 。

  可以看出,Git执行合并的操作都会产生一个合并后的提交,并添加到当前分支中,而被合并分支不受合并的影响。

 

2,有冲突的合并

  不废话,直接来实际的操作。首先在 master 分支上,继续修改文件“file”,然后提交到版本库

  

  然后,切换到 alternate 分支上,用不同的内容修改文件“file”,然后也提交到版本库

  

  

  

  继续,检出 master 分支并尝试着进行合并

  

  Git已经提示我们了:合并的时候在文件“file”检测到冲突,自动合并失败,解决冲突后才能提交。一旦遇到冲突,一定要使用 git diff 命令来查看一下冲突的程度

  

  结果显示了 工作区 和 暂存区 之间的差异。

  在传统的 UNIX/Linux 系统中 diff 命令的输出风格里,改变的内容显示在<<<<<<<和=======之间;替代的内容在=======和>>>>>>>之间。

  在解决合并冲突时,根据实际工作情况自己选择任何解决方案。包括只取一边或另一边的版本,或者两边的混合,甚至是全新的内容。

  ok,开始手动解决冲突,解决后。文件“file”的内容如图,如果对冲突解决很满意,那么就应该 git add 命令,为合并提交而暂存之。

  

  

  Git的提示已经很明显了:冲突已解决,但是依然处于合并状态(当前的分支为 master | MERGING),使用 git commit 命令来结束掉本次合并。执行 git commit 命令,Git会进入到vim编辑器中,并准备了一条模板消息。ok,搞定!

 

 

深入处理合并冲突

  重新初始化一个版本库,创建一个文件“hello”,内容为“hello”,提交到版本库中

  

  创建一个名为 alt 的分支,在文件“hello”末尾追加内容 world。然后提交到版本库

  

  切回到 master 分支,此时文件”hello“的内容为 hello,在末尾追加 worlds,并提交大版本库

  

  

  此时,hello的内容,master 分为 hello worlds,alt 分支为 hello world。尝试合并 alt 分支,一定会发生冲突,Git提示文件”hello“里有冲突,使用命令 git status 查看当前状态,Git会定位冲突的文件以及提示可以做的操作:

  1,Git提示我们有未完成的合并。

  2,建议我们解决冲突后运行 git commit 命令,又或者可以执行 git merge --abort 命令来取消本次合并。

  3,Git提示我们冲突发生在 hello 这个文件里。

  

  现在,使用 git diff 命令来检查冲突的细节:

  

  这里的 alt 有一个特殊的名字------MERGE_HEAD,所以我们完全可以拿 HEADMERGE_HEAD 版本跟工作区("合并的")版本进行比较:

  

  在较新版本的Git中,git diff --ours 是 git diff HEAD 的同义词,--ours 更好理解。同样,git diff MERGE_HEAD 可以写成 git diff --theirs

  在解决冲突的过程中,可以使用一些特殊的 git log 选项来帮助我们寻找出变更的确切来源和原因:git log --merge --left-right -p <file|path>git log 的选项如下

    --merge:只显示根产生冲突的文件相关的提交。

    --left-right:如果提交来自合并的”左边“则显示<(”我们的“版本,当前分支的版本),如果提交来自由合并的”右边“则显示>(”它们的“版本,目标分支的版本)

    -p:显示提交消息和每个提交相关联的补丁。

    <file|path>:如果版本库很复杂,且冲突的文件很多,可以指定确切的路径或文件。

  

  可以看到,输出的信息还是很多的。有一种手段可以缓解来自大合并中繁多的冲突的痛苦:那就是定义良好的小提交。Git对小规模提交处理的很好,因此没有必要等到最后才提交一个又庞大又影响广泛的修改小规模的提交和更频繁的合并周期可以减少解决冲突的痛苦

  

  

  那么,Git是如何追踪冲突的呢?

    主要有以下几个部分:

    1,.git/MERGE_HEAD,任何使用提到 MERGE_HEAD,Git都知道去查看哪个文件。

    2,.git/MERGE_MSG,包含当前解决冲突后执行 git commit 命令是用到的日志消息。

    3,Git的索引项包含每个冲突文件的三个副本:合并基础,”我们的“版本 和 ”他们的“版本,分别给这三个副本分配各自的编号 1,2,3。

    4,冲突的版本不保存在暂存区。只保存在工作区。

  

    查看Git索引项是如何存储的,可以使用 git ls-files 底层。如果指向查看根冲突相关的文件,可以使用 -u 选项

    

    可以看出,hello 文件存储了三次,用前面说过的 git cat-file -p 命令来查看各个版本的内容

    

    更可以使用 git diff 来比较两个指定版本之间有什么区别

    

 

  了解了Git是如何追踪冲突之后,就可以很淡定的处理冲突了。

  

  这里执行 git add 命令是把索引项重写,只有一份 hello 的副本。

  必须解决索引中记录的所有冲突文件。只要有未解决的冲突,就不能提交。因此,当解决一个文件的冲突之后,执行 git add(或者 git rmgit-update-index等)以清除它的冲突状态。

  注意:不要对有冲突标记的文件执行 git add命令。虽然会清楚索引中的冲突,并允许提交,但是文件内容将是错误的。

  最终,执行 git commit 命令,解决冲突过程中产生的 .git/MERGE_HEAD .git/MERGE_MSG 都会被Git删除。

  

 

posted on 2017-03-30 13:42  凯少  阅读(5497)  评论(1编辑  收藏  举报