返回首页 Git Community Book 中文版

介绍

第一步

基本用法

中级技能

高级技能

原理解析

高级分支与合并

在合并过程中得到解决冲突的协助

Git 会把所有可以自动合并的修改加入到索引中去,所以git diff只会显示有冲突的部分。它使用了一种不常见的语法:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,5 @@@
++<<<<<<< HEAD:file.txt
 +Hello world
++=======
+ Goodbye
++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

回忆一下,在我们解决冲突之后,得到的提交会有两个而不是一个父提交:一个父提交会成为 HEAD,也就是当前分支的 tip;另外一个父提交会成为另一分支的 tip,被暂时存在 MERGE_HEAD。

在合并过程中,索引中保存着每个文件的三个版本。三个“文件暂存(file stage)”中的每一个都代表了文件的不同版本:

$ git show :1:file.txt  # 两个分支共同祖先中的版本.
$ git show :2:file.txt  # HEAD中的版本.
$ git show :3:file.txt  # MERGE_HEAD中的版本.

当你使用 git diff 去显示冲突时,它在工作树(work tree),暂存 2(stage 2)和暂存 3(stage 3)之间执行三路 diff 操作,只显示那些两方都有的块(换句话说,当一个块的合并结果只从暂存 2 中得到时,是不会被显示出来的;对于暂存 3 来说也是一样)。

上面的 diff 结果显示了 file.txt 在工作树,暂存 2 和暂存 3 中的差异。Git 不在每行前面加上单个‘+’或者‘-’,相反地,它使用两栏去显示差异:第一栏用于显示第一个父提交与工作目录文件拷贝的差异,第二栏用于显示第二个父提交与工作文件拷贝的差异。(参见 git diff-files 中的“COMBINED DIFF FORMAT”取得此格式详细信息。)

在用直观的方法解决冲突之后(但是在更新索引之前),diff 输出会变成下面的样子:

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@
- Hello world
-Goodbye
++Goodbye world

上面的输出显示了解决冲突后的版本删除了第一个父版本提供的“Hello world”和第二个父版本提供的“Goodbye”, 然后加入了两个父版本中都没有的“Goodbye world”。

一些特别 diff 选项允许你对比工作目录和三个暂存中任何一个的差异:

$ git diff -1 file.txt      # 与暂存1进行比较
$ git diff --base file.txt          # 与上相同
$ git diff -2 file.txt      # 与暂存2进行比较
$ git diff --ours file.txt          # 与上相同
$ git diff -3 file.txt      # 与暂存3进行比较
$ git diff --theirs file.txt    # 与上相同

git log 和 gitk 命令也为合并操作提供了特别的协助:

$ git log --merge
$ gitk --merge

这会显示所有那些只在 HEAD 或者只在 MERGE_HEAD 中存在的提交,还有那些更新(touch)了未合并文件的提交。

你也可以使用 git mergetool,它允许你使用外部工具如 emacs 或 kdiff3 去合并文件。

每次你解决冲突之后,应该更新索引:

$ git add file.txt

完成索引更新之后,git-diff(缺省地)不再显示那个文件的差异,所以那个文件的不同暂存版本会被‘折叠’起来。

多路合并

你可以一次合并多个头,只需简单地把它们作为 git merge 的参数列出。例如:

$ git merge scott/master rick/master tom/master

相当于:

$ git merge scott/master
$ git merge rick/master
$ git merge tom/master

子树

有时会出现你想在自己项目中引入其他独立开发项目的内容的情况。在没有路径冲突的前提下,你只需要简单地从其他项目拉取内容即可。

如果有冲突的文件,那么就会出现问题。可能的例子包括 Makefile 和其他一些标准文件名。你可以选择合并这些冲突的文件,但是更多的情况是你不愿意把它们合并。一个更好解决方案是把外部项目作为一个子目录进行合并。 这种情况不被递归合并策略所支持,所以简单的拉取是无用的。

在这种情况下,你需要的是子树合并策略。

这下面例子中,我们设定你有一个仓库位于/path/to/B (如果你需要的话,也可以是一个 URL)。你想要合并那个仓库的 master 分支到你当前仓库的 dir-B 子目录下。

下面就是你所需要的命令序列:

$ git remote add -f Bproject /path/to/B (1)
$ git merge -s ours --no-commit Bproject/master (2)
$ git read-tree --prefix=dir-B/ -u Bproject/master (3)
$ git commit -m "Merge B project as our subdirectory" (4)
$ git pull -s subtree Bproject master (5)

子树合并的好处就是它并没有给你仓库的用户增加太多的管理负担。它兼容于较老(版本号小于 1.5.2)的客户端, 克隆完成之后马上可以得到代码。

然而,如果你使用子模块(submodule),你可以选择不传输这些子模块对象。这可能在子树合并过程中造成问题。

译者注:submodule 是 Git 的另一种将别的仓库嵌入到本地仓库方法。

另外,若你需要修改内嵌外部项目的内容,使用子模块方式可以更容易地提交你的修改。