如何使用vimdiff解决git合并冲突?

240

我在git中将一个分支合并到主分支中,出现了Automatic merge failed; fix conflicts and then commit the result.的错误提示。现在我运行了git mergetool命令,vimdiff打开了如下图所示的界面。我不知道如何使用vimdiff工具。每个面板代表什么含义?我该如何解决这个合并冲突?

enter image description here


4
查看此页面(http://vim.wikia.com/wiki/A_better_Vimdiff_Git_mergetool)。如果这是您所指的“正确”,则代码的当前状态位于左上角。 - romainl
@romainl 在阅读之后我仍然感到困惑,这些快捷键是什么,我该如何选择哪个文件作为主分支? - Cool Guy Yo
2
@Danny http://www.rosipov.com/blog/use-vimdiff-as-git-mergetool/ 是一个不错的文章。 - ᴠɪɴᴄᴇɴᴛ
另请参见:这里 - skelliam
按照这个被接受的答案使用fugitive:https://dev59.com/mGw05IYBdhLWcg3wUAQ0#7313949 - icc97
3个回答

194

@chepner的回答非常好,我想在“如何解决合并冲突”的问题中添加一些详细信息。如果你想了解如何在这种情况下实际使用vimdiff,请查看以下内容。


首先,针对“放弃一切”选项——如果您不想使用“vimdiff”并想要中止合并:按Esc,然后输入:qa!并按Enter。(也可参见如何退出Vim编辑器?)。Git会询问您是否完成合并,请回复n


如果您想使用vimdiff,则可以使用以下一些有用的快捷键。这假定您知道Vim的基础操作(导航和插入/正常模式):

  • 导航到底部缓冲区(合并结果):Ctrl-W j
  • 使用j/k导航到下一个diff;或者更好的是,使用] c[ c分别导航到下一个和上一个diff
  • 在折叠状态下使用z o打开它,如果您想查看更多上下文
  • 针对每个diff,可以根据@chepner的回答从本地、远程或基础版本中获取代码,或者根据自己的需要进行编辑并重新执行
    • 要从本地版本中获取,请使用:diffget LO
    • 从远程获取::diffget RE
    • 从基础版本获取::diffget BA
    • 或者,如果您想自己编辑代码,请首先从本地/远程/基础版本获取一个版本,然后进入插入模式并编辑其余部分
  • 完成后,保存合并结果,并退出所有窗口:wqa
  • 通常情况下,git会检测到已进行合并并创建合并提交

似乎不可能同时添加本地和远程冲突块而无需复制粘贴或自定义快捷方式:https://vi.stackexchange.com/questions/10534/is-there-a-way-to-take-both-when-using-vim-as-merge-tool 这很遗憾,因为添加是如此常见的冲突类型。

为了防止vimdiff在每次启动时要求您按Enter键,请将以下内容添加到您的.vimrc中:

set shortmess=Ot

如下所述:https://vi.stackexchange.com/questions/771/how-can-i-suppress-the-press-enter-prompt-when-opening-files-in-diff-mode

你可以在互联网上搜索其他vimdiff快捷键。 我发现这个很有用:https://gist.github.com/hyamamoto/7783966


23
这个回答应该获得1000次赞同并被接受为更好的答案。 - Andrey Portnoy
要快速跳转到下一个冲突,只需搜索“ === ”即可。执行“ /=== ”并按Enter键。 - Apit John Ismail
1
如果使用:diffget命令找到了多个匹配项,请参考此帖子(https://dev59.com/KlUK5IYBdhLWcg3woRP1)。 - Jason
自从这篇文章写出来以后,似乎发生了一些变化: "按Esc键,然后输入:qa!并按回车。Git会问你是否合并完成,请回复n。",但git没有问我任何问题,它假设合并已经完成。为了成功中止,我需要使用稍后提供的其他命令":cquit"。这是git行为的改变吗?还是vimdiff的? - Tao
有没有快捷方式在冲突之间导航? - Martin Delille
在冲突之间没有捷径可以导航吗? - undefined

193

所有四个缓冲区都提供了同一个文件的不同视图。左上角缓冲区(LOCAL)显示的是目标分支中文件的外观(您正在合并的分支)。右上角缓冲区(REMOTE)则显示了源分支中文件的外观(您正在从该分支进行合并)。中间缓冲区(BASE)是两者的共同祖先(因此您可以比较左右版本如何相互分歧)。

以下内容可能有误。我认为合并冲突的原因是两个文件自 BASE 以来已更改了同一部分内容; LOCAL 将引号从双引号更改为单引号,而 REMOTE 也进行了同样的更改,但还将背景值从颜色更改为 URL。(我认为合并并不聪明到足以注意到 REMOTE 中所有对 LOCAL 的更改也存在;它只知道 LOCAL 自 BASE 以来在 REMOTE 中引起了变化)。

无论如何,底部缓冲区包含您实际可以编辑的文件—即位于您的工作目录中的文件。您可以进行任何更改;vim 将向您展示它与每个顶部视图之间的差异,这些差异是自动合并无法处理的区域。如果您不想要 REMOTE 的更改,则从 LOCAL 拉取更改。如果您喜欢 REMOTE 更改,则从 REMOTE 拉取更改。如果您认为 REMOTE 和 LOCAL 都不正确,则从 BASE 拉取。如果您有更好的想法,可以尝试完全不同的方法!最终,您在此处进行的更改才是实际提交的更改。


7
快速问题:如何在vim中保存? - Cool Guy Yo
7
:x:w(也可以用 :x 退出)加上回车键表示保存并退出。 - Jonathan Leffler
6
Anders:如果您不熟悉如何使用 vim,则可以使用其他合并工具。 - chepner
3
@AndersKitson,由于您使用的是Mac OS X操作系统,FileMerge是完美的选择,它是免费的,并且随XCode一起提供。 - romainl
10
为什么要踩我的回答?如果有事实错误,请修正它,或者至少指出来。 - chepner
显示剩余7条评论

14

替代vimdiff的终极合并工具

这有点玩笑,但这是我在尝试了vimdiff之后作为vimmer最终收敛到的东西。

为了解决合并冲突,我几乎总是需要看到:

  • 远程版本
  • 本地版本
  • 两个差异:
    • 基础版本和远程版本的差异
    • 基础版本和本地版本的差异

然后尝试将它们合并在一起。

虽然vimdiff在屏幕上显示了基础版本、本地版本和远程版本:

    +--------------------------------+
    | LOCAL  |     BASE     | REMOTE |
    +--------------------------------+
    |             MERGED             |
    +--------------------------------+

我不知道如何清晰地展示那两个差异,除非左右来回看很多次。

此外,在git合并冲突标记中已经可以看到LOCAL和REMOTE,因此从一个再次显示它们的工具中并没有太大收益。

因此,我创建了自己的小型“difftool”,实际上展示了我所缺少的差异:

~/bin/cirosantilli-mergetool

#!/usr/bin/env bash
BASE="$1"
LOCAL="$2"
REMOTE="$3"
diff --color -u "$BASE" "$LOCAL"
diff --color -u "$BASE" "$REMOTE"
exit 1

GitHub官网下载此软件.

然后使用以下命令进行安装:

git config --global mergetool.cirosantilli-mergetool.cmd 'cirosantilli-mergetool $BASE $LOCAL $REMOTE'
git config --global mergetool.cirosantilli-mergetool.trustExitCode true
# If you want this to become your default mergetool.
#git config --global merge.tool 'cirosantilli-mergetool'

现在,当你执行以下操作时:
git mergetool -t cirosantilli-mergetool

这会在终端上显示我想要的两个差异,例如:

--- ./src/dev/arm/RealView_BASE_15560.py        2019-12-27 13:46:41.967021591 +0000
+++ ./src/dev/arm/RealView_LOCAL_15560.py       2019-12-27 13:46:41.979021479 +0000
@@ -994,7 +994,7 @@                                                              
                                       
     def setupBootLoader(self, cur_sys, loc):
         if not cur_sys.boot_loader:                           
-            cur_sys.boot_loader = [ loc('boot_emm.arm64'), loc('boot_emm.arm') ]
+            cur_sys.boot_loader = [ loc('boot.arm64'), loc('boot.arm') ]
         cur_sys.atags_addr = 0x8000000                  
         cur_sys.load_offset = 0x80000000                    

@@ -1054,7 +1054,7 @@                                           
             ]                                                     
                       
     def setupBootLoader(self, cur_sys, loc):
-        cur_sys.boot_loader = [ loc('boot_emm_v2.arm64') ]
+        cur_sys.boot_loader = [ loc('boot_v2.arm64') ]
         super(VExpress_GEM5_V2_Base,self).setupBootLoader(
                 cur_sys, loc)                             
                                                           
--- ./src/dev/arm/RealView_BASE_15560.py        2019-12-27 13:46:41.967021591 +0000
+++ ./src/dev/arm/RealView_REMOTE_15560.py      2019-12-27 13:46:41.991021366 +0000
@@ -610,10 +610,10 @@           
     def attachIO(self, *args, **kwargs):              
         self._attach_io(self._off_chip_devices(), *args, **kwargs)
                                      
-    def setupBootLoader(self, cur_sys, loc):
-        cur_sys.boot_loader = loc('boot.arm') 
-        cur_sys.atags_addr = 0x100                           
-        cur_sys.load_offset = 0       
+    def setupBootLoader(self, cur_sys, boot_loader, atags_addr, load_offset):
+        cur_sys.boot_loader = boot_loader      
+        cur_sys.atags_addr = atags_addr     
+        cur_sys.load_offset = load_offset

因此,您可以看到这里将两个差异转储到终端:

  • RealView_BASE_15560.py vs RealView_LOCAL_15560.py
  • RealView_BASE_15560.py vs RealView_REMOTE_15560.py

如果差异很大,我会使用我的 tmux 超级功能进行搜索。

待办事项:为了实现涅槃,最后需要一种只显示冲突块的差异的方法。因为如果差异很大,但只有一个小块冲突,那么找到它就很麻烦。

是的,您确实会失去一些vimdiff提供的快捷方式,但通常解决冲突需要仔细地从两个版本中复制粘贴,而我可以在带有git冲突标记的普通vim会话中完成这个过程。

在运行vimdiff时观察和比较文件

在我坐下来使用cirosantilli-mergetool自动化我的完美设置之前,这就是我获取所需两个差异的方法。

git mergetool运行vimdiff时,如果文件出现冲突,比如名为main.py的文件,Git会为每个版本生成一个文件,命名为:

main_BASE_1367.py
main_LOCAL_1367.py
main_REMOTE_1367.py

在与main.py相同的目录中,1367是git mergetool的PID,因此是一个“随机”的整数,如在In a git merge conflict, what are the BACKUP, BASE, LOCAL, and REMOTE files that are generated?中所述。
因此,为了查看我想要的差异,我首先使用git status找到生成的文件,然后打开新的终端并在我关心的文件对之间进行vimdiff:
vim -d main_BASE_1367.py main_LOCAL_1367.py
vim -d main_BASE_1367.py main_REMOTE_1367.py

git mergetool一起使用,这些信息非常有助于快速了解正在发生的情况!此外,在mergetool运行时,您甚至可以打开文件:
vim main.py

如果您觉得使用较大的编辑器窗口更容易,您可以直接在那里编辑。

直接跳转到合并冲突

虽然]c会在vimdiff中跳转到下一个差异点,但并不总是有合并冲突。

为了解决这个问题,在我的~ /.vimrc文件中添加了以下内容:

# Git Merge conflict
nnoremap <leader>gm /\v^\<\<\<\<\<\<\< \|\=\=\=\=\=\=\=$\|\>\>\>\>\>\>\> /<cr>

直接查找冲突的最佳选项可能是放弃使用vimdiff,而依赖于常规的vim + git imerge。在这里提到了git imerge:如何找出哪些Git提交导致冲突?因为vimdiff的学习曲线很烦人,而且它不能执行我们最需要的功能。


@VonC 是的,我认为你赢了!XD - Ciro Santilli OurBigBook.com
我的vimdiff没有显示合并面板,只有顶部的3个。我该如何让它显示合并面板? - Arthur Bowers
1
这将大大有助于。 - mikew
你甚至可以尝试一下这种方法,不必在终端窗口中搜索,而是将diff输出导入文件中,在diff命令的末尾添加> base_remote.diff等内容,然后编写脚本以在Vim面板中打开文件,将两个差异文件放在顶部,合并文件放在底部。 - mikew
@mikew,一旦你拥有了这个链接:https://superuser.com/questions/231002/how-can-i-search-within-the-output-buffer-of-a-tmux-shell/1253137#1253137,搜索终端并不是一个巨大的问题。最近我变得有点懒,尝试使用`git checkout --conflict=diff3`并手动查找差异。这是一种冒险的生活方式。 - Ciro Santilli OurBigBook.com
@mikew 在终端中搜索并不是一个大问题,一旦你拥有了这个:https://superuser.com/questions/231002/how-can-i-search-within-the-output-buffer-of-a-tmux-shell/1253137#1253137 最近我有点懒,尝试使用 git checkout --conflict=diff3 并通过目测找到差异。生活有风险。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接