How do I use vimdiff to resolve a git merge conflict?

喜夏-厌秋 提交于 2019-11-28 15:09:26
chepner

All four buffers provide a different view of the same file. The top left buffer (LOCAL) is how the file looked in your target branch (what you are merging into). The top right buffer (REMOTE) is how the file looked in your source branch (where you are merging from). The middle buffer (BASE) is the common ancestor of the two (so you can compare how the left and right versions have diverged from each other).

I may be mistaken on the following point. I think the source of the merge conflict is that both files have changed the same portion of the file since BASE; LOCAL has changed the quotes from double to single, and REMOTE has made the same change but also changed the background value from a color to a URL. (I think the merge is not smart enough to notice that all the changes to LOCAL are also present in REMOTE; it just knows that LOCAL has made changes since BASE in the same places that REMOTE has).

In any case, the bottom buffer contains the file you can actually edit—the one sitting in your working directory. You can make any changes you like; vim is showing you how it differs from each of the top views, which are the areas that the automatic merge couldn't not handle. Pull changes from LOCAL if you don't want the REMOTE changes. Pull changes from REMOTE if you prefer those to the LOCAL changes. Pull from BASE if you think both REMOTE and LOCAL are wrong. Do something completely different if you have a better idea! In the end, the changes you make here are the ones that will actually be committed.

Timur

@chepner's answer is great, I would like to add some details on "how should I proceed to fix the merge conflict" part of the question. If you look into how to actually use vimdiff in this case, it goes below.


First, to address the "abort everything" option - if you do not want to use "vimdiff" and want to abort the merge: press Esc, then type :qa! and hit Enter. (see also How do I exit the Vim editor?). Git will ask you if the merge was complete, reply with n.


If you want to use vimdiff, here are some useful shortcuts. This assumes you know basics of Vim (navigation and insert/normal mode):

  • navigate to the bottom buffer (merge result): Ctrl-W j
  • navigate to next diff with j/k; or, better, use ] c and [ c to navigate to the next and previous diff respectively
  • use z o while on a fold to open it, if you want to see more context
  • for each diff, as per @chepner's answer, you can either get the code from a local, remote or base version, or edit it and redo as you see fit
    • to get it from the local version, use :diffget LO
    • from remote: :diffget RE
    • from base: :diffget BA
    • or, if you want to edit code yourself, get a version from local/remote/base first, and then go to the insert mode and edit the rest
  • once done, save the merge result, and quit all windows :wqa
  • normally, git detects that the merge was made and creates the merge commit

It does not appear to be possible to add both local and remote conflict hunks without copy pasting or custom shortcuts: https://vi.stackexchange.com/questions/10534/is-there-a-way-to-take-both-when-using-vim-as-merge-tool which is a shame since add add is such a common conflict type.

To prevent vimdiff from asking you to press enter every time it starts, add to your .vimrc:

set shortmess=Ot

as mentioned at: https://vi.stackexchange.com/questions/771/how-can-i-suppress-the-press-enter-prompt-when-opening-files-in-diff-mode

You can search the Internet for other vimdiff shortcuts. I have found this one useful: https://gist.github.com/hyamamoto/7783966

How to diff between (BASE and LOCAL) and (BASE and REMOTE) with vim -d

When resolving merge conflicts, one of the most important things you want to see is:

  • how did my new commit change the base commit
  • how did the existing commit change the base commit

to then try to put both of them together.

While vimdiff does a good job of showing all of BASE, LOCAL and REMOTE side-by-side, I cannot clearly see from it the two separate diffs from BASE:

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

To solve this, I noticed that while git mergetool is running vimdiff, if there is a conflict on a file named, say, main.py, git generates files for each of the versions, named as:

main_BASE_1367.py
main_LOCAL_1367.py
main_REMOTE_1367.py

in the same directory as main.py where 1367 is the PID of git mergetool, and therefore a "random" integer, as mentioned at: In a git merge conflict, what are the BACKUP, BASE, LOCAL, and REMOTE files that are generated?

So, to see the diffs that I want, I first find the generated files with git status, and then open new terminals and do a vimdiff between the pairs of files that I care about:

vim -d main_BASE_1367.py main_LOCAL_1367.py
vim -d main_BASE_1367.py main_REMOTE_1367.py

Together with git mergetool, this information helps A LOT to figure out what is going on quickly!

Also, even while mergetool is running, you can just open the file:

vim main.py

directly and edit it there if you feel that it is going to be easier with a larger editor window.

I guess we could automate things even further to automatically open all these files, but I haven't ventured there yet.

Jump directly to merge conflicts

While ]c jumps to the next diff point inside vimdiff, there isn't always a merge conflict there.

To help with this, I have in my ~/.vimrc:

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

which finds the conflicts directly.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!