>

在使用 Git 与他人合作的过程中,代码冲突(conflict)是一个比较扰人的问题。
Git 在 pull 远程库代码时候会自动与本地文件进行 merge(会把把本地文件和远程库文件整合成一个文件),如果不熟悉如何手动 merge 的话,可能容易造成误操作。

经过我的一些搜索和验证,我总结了以下的一种可行方案,期望可以更好的处理 merge 时出现冲突的代码。
简单来说就是需要配置 mergetool,git 提供了 difftoolmergetool 用来做文本比对。

一般我们在使用 Git 进行代码更新时,在本地修改完成之后,进行 commit,然后 push,如果此时远程库有人提交了一次 commit,与我本地的修改是同一个文件,那么肯定会提示冲突导致 push 被 rejected。
这时候,我们一般的习惯应该是 git pull --rebase (记住: 不是 git pull)。 或者 git fetch && git rebase

提示:推荐为 pull --rebase 设置 pl 作为 alias:

git config --global alias.pl pull --rebase

这样,执行 git pl 之后,Git 会把远程库拉下来之后会自动 merge 有修改的文件。

下面来看一个例子:
假设我们本地和远程都修改一个叫 test.txt 的文件,Git 自动 merge 之后,冲突的行也会在糅合在 test.txt 中,这些冲突的行会按照使用 Git 特有的 conflict marker 来标示。

e.g.

<<<<<<< HEAD
my local changes
=======
remote changes
>>>>>>> ceabbf16ada5a359ccee1133

如果我们手动修改这个文件来进行 merge 工作的话,很容易出错,因为冲突的行放在上下而不是同一行进行对比,非常不直观。
如果我们想要保留本地修改版本,又想同时保留远程那个版本,会很麻烦。因为只要一执行 git pull 它就会自动进行 merge,产生那个不易看懂的 merge 之后的文件。

难道它自动 merge 之后我们就只能手动修改那个冲突的文件吗? 答案显然是NO。

直接 git pull的话,我们还有有办法找回我们本地的那个文件,因为 git 虽然自动 merge 了,但它其实还为我们保留了 3 份内容,只是没在当前目录显示出来。

我们可以使用 git checkout --theirs xxx.txt 这种方式来还原 local 或者 remote 版本的文件。

提示:

--ours 表示 Local  
--theirs 表示 Remote   

执行 rebase 之后,上述标签的意义的变得相反,Remote 表示本地的修改,而 Local 则表示远程的修改!

我们也可以在 pull 的时候,直接用本地覆盖远程的,除非你想抛弃本地所有的修改,否则别这么做

git pull --rebase --strategy-option theirs  

或者

git pull -Xtheirs

当然,我们还可以配置一下 mergetool,使得 Local 和 Remote 版本等文件更好的呈现出来,方便我们进行手动 merge。
使用 git mergetool 命令,git 会自动调出你配置好的文本比对工具,然后让我们在这个文本比对工具中进行愉快的 merge。

Command line mergetool editors:

  • Emacs based diff tools: emerge, Ediff
  • Vim based diff tool: vimdiff

而图形界面系统(Windows/Ubuntu/MaxOS 等)可以使用:Beyond Compare, Meld, P4merge, kdiff3 等工具。
下面来看看该怎么配置这些工具,以便我们在执行 git mergetool 命令的时候可以 Call 出它们:

编辑 ~/.gitconfig 文件


##### 1. 使用 vimdiff ---
[diff]
    tool = vimdiff
[merge]
    tool = vimdiff

配置完成之后,当我们使用 git mergetool 调出 vimdiff 的时候,它会显示 4 个窗口,

布局效果如下:

提示:在 Vim 中使用 Ctrl + g 来显示当前文件信息(比如文件名,光标所在当前行等等信息)

我们也可以直接在命令行指定 mergetool,比如:

git mergetool -t gvimdiff

如果想直接用某个版本覆盖另外一个版本,可以在 vim 中使用下列命令:

:diffg LO  " get from LOCAL
:diffg RE  " get from REMOTE
:diffg BA  " get from BASE

:diffupdate fixes your whitespace issues
:only        Show only the merged file

:diffget 2  use LOCAL
:diffget 3  use REMOTE:

rebase 之后,LOCAL 就是远程库的内容了,REMOTE 就是你自己本地的修改了。因为现在是基于远程库的内容作为 base!
因此,conflict marker 的含义也变了:

<<<<<< 
远程库的修改
======
本地的修改
>>>>>>

同一个文件中出现多处 conflict:

]c move to the next conflict
[c move to the previous conflict

设置 conflict marker 的 style:

git config merge.conflictstyle diff3

Define a user specific layout for vimdiff
Since the default layout has 4 windows, which looks over complicated sometimes,
We can define a 3-view layout through the below method:

git config --global mergetool.merge3.cmd 'vim -d -c \"wincmd J\" \"$MERGED\" \"$LOCAL\" \"$REMOTE\"
git config --global alias.m3 'mergetool -t merge3'

提示: wincmd 用于控制如何移动窗口

notice we’ve defined an alias m3 for merge3, we can also set an alias for the default mergetool (vimdiff) like this:

git config --global merge.tool vimdiff
git config --global alias.mt mergetool

效果图:

Fugitive

git config --global mergetool.fugitive.cmd 'vim -f -c "Gvdiff" "$MERGED"'
git config --global alias.ft 'mergetool -t fugitive'

提示:use Gvdiff will split the window in 3 vertical views: | 1 | 2 | 3 |


##### 2. Beyond Compare (推荐) ---
[alias]
    bc4 = mergetool -t bc4
[diff]
    tool = bc4
[difftool "bc4"]
        cmd = "\"c:/program files (x86)/beyond compare 4/bcompare.exe\" \"$LOCAL\" \"$REMOTE\""
[merge]
    tool = bc4
[mergetool "bc4"]
        cmd = "\"c:/program files (x86)/beyond compare 4/bcompare.exe\" \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\""
        trustExitCode = true

布局效果跟 vimdiff 一样,是 4-way merge。

我们可以使用 beyond compare 的 2-way merge进行更为直观的 merge:

[mergetool "bc4"]
    cmd = "\"c:/program files (x86)/beyond compare 4/bcompare.exe\" \"$LOCAL\" \"$REMOTE\" -savetarget=\"$MERGED\""
    trustExitCode = true

鉴于目前文本比对工具的局限性,个人认为这种 2-way merge 的方式相对来说比较直观。
在这种模式下,只有左右两个 pane,左边是 Local,右边是 Remote。同时,右边的 pane 还作为 merge 的 output file。
直接在右边 pane 中修改完成之后保存成 merge 的最终结果文件。

效果图

作为更为高级的一种 merge 方式是使用 3-way merge 模式,当文件中有大量 conflict,或者单行 vs 多行 这种 conflict 时,
2-way merge 模式可能没有这种方便。因为这种模式下,可以把冲突的部分都加入最终文件,而 2-way merge 只能替换(除非手动 copy && paste)。


##### 3. p4merge ---
[alias]
    p4 = mergetool -t p4merge
[mergetool "p4merge"]
    cmd = "\"c:/program files/Perforce/p4merge.exe\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\""
    trustExitCode = true

##### 4. meld ---
[alias]
    meld = mergetool -t meld
[mergetool "meld"]
    cmd = meld --diff $BASE $LOCAL --diff $BASE $REMOTE --auto-merge $LOCAL $BASE $REMOTE --output $MERGED
    trustExitCode = true

meld 会打开 3 个 tabs, 第1个 tab, 默认会显示 3-way merge view,我们在中间的 pane 中进行merge。
第2个和第3个 tab 是 local 和 remote 分别与 base 的 diff views。

效果图

注意: 使用 mergetool 时,git 会为冲突的文件生成一个 .orig 备份文件,解决掉冲突之后,这个文件默认不会被自动删除,我们手动把它删除掉就可以了。当然,我们也可以配置 git 不生成这个文件:

git config --global mergetool.keepBackup false
git config --global mergetool.keepTemporaries false

流程: merge 完成之后,执行下列操作完成 push:

git rebase --continue
git push -u origin master 




###### 参考 http://my.oschina.net/u/1010578/blog/348731 http://stackoverflow.com/questions/161813/how-to-resolve-merge-conflicts-in-git http://www.zhihu.com/question/21215715 http://www.scootersoftware.com/support.php?zz=kb_vcs