I'm using Vim as a 3-way merge too with Git and sometimes need to take changes from both local and remote branches. kdiff3
can do this with a couple of key presses, is there a way to do this in Vim?
4 Answers
Not exactly what you asked, but may find this useful: Splice plugin: a Vim plugin for resolving conflicts during three-way merge. It has a nice demo screencast on Vimeo.
If you are going to stick with the default vimdiff you could create a function to retrieve the buffer number for the surronding windows and use them to call :diffget
twice:
*:diffg* *:diffget*
:[range]diffg[et] [bufspec]
Modify the current buffer to undo difference with another
buffer. If [bufspec] is given, that buffer is used. If
[bufspec] refers to the current buffer then nothing happens.
Otherwise this only works if there is one other buffer in diff
(...)
The [bufspec] argument above can be a buffer number, a pattern for a buffer
name or a part of a buffer name. Examples:
:diffget Use the other buffer which is in diff mode
:diffget 3 Use buffer 3
:diffget v2 Use the buffer which matches "v2" and is in
diff mode (e.g., "file.c.v2")
You could them use a mapping to assign a call to this function to the keys you were used in kdiff3
.
You can find further information on this vimcast.
-
Calling
:diffget
twice doesn’t seem to work; the second paste overwrites the first one instead of appending to it. Commented Jun 30, 2020 at 0:28
yank and put can work without any plugins or extra functions, though it is, perhaps, a bit less convenient than do or dp
Just copy the needed bits from the parent buffers and paste them into the correct spot in the merged buffer. This is also useful if you want just part of a diff block, rather than the whole thing.
You may, at times, need to force Vim to update the diff highlighting with :diffupdate
.
To combine changes from both the target and merge branches in a single command:
You can just delete the lines with Git conflict markers. The following two methods will delete all lines that start with:
<<<<<<<
=======
>>>>>>>
Method 1: Manually Entering and Executing a Command
:g/^<\{7}\|^|\{7}\|^=\{7}\|^>\{7}/d
Method 2: Implementing a User Defined Command
"Delete all Git conflict markers
"Creates the command :GremoveConflictMarkers
function! RemoveConflictMarkers() range
echom a:firstline.'-'.a:lastline
execute a:firstline.','.a:lastline . ' g/^<\{7}\|^|\{7}\|^=\{7}\|^>\{7}/d'
endfunction
"-range=% default is whole file
command! -range=% GremoveConflictMarkers <line1>,<line2>call RemoveConflictMarkers()
Vim diffget and diffput will only choose one branch or the other. So the only real solution other than the one given above is to manually yank and paste from both files into the working copy.
-
-
1
This is was bugging me too, and I found a solution which seems to be working fine. The trick is to use vim to ask whether a line is part of a diff "hunk" or not. This is possible, see: https://www.reddit.com/r/vim/comments/k2r7b/how_do_i_execute_a_command_on_all_differences_in/c2hee5z/
So... I switch to the other buffer we want to copy from. I start from the cursor position. I move up and down to find out which is the first and the last line of the diff "hunk". Now that I have the hunk extents, I copy its contents to the f
register (there may be a more elegant way.. in my case I never use that many registers. It would also be possible to backup the register and restore it after the function call).
Now that I backed up the hunk in the register, I return to the other buffer. If I want to combine both hunks in a way that the former hunk is first, then I just need to move one line up (after diffget
the cursor is on top of the hunk) and paste from register f
.
If I want have the former hunk after, then I must move down by the number of lines of the old hunk. I compute the number of lines of the old hunk, move down, and paste. Et voila!
I implemented it for neovim in lua, but I think porting it to vimL shouldn't be too hard.
In neovim, you can paste this to some init.lua or something like that, then run :lua diffget_and_keep_before()
on top of a hunk.
function is_diff_line(line_no)
-- https://www.reddit.com/r/vim/comments/k2r7b/how_do_i_execute_a_command_on_all_differences_in/c2hee5z/
-- https://stackoverflow.com/a/20010859/516188
return vim.fn.diff_hlID(line_no, 1) > 0
end
function diff_get_start_end_line()
-- https://vi.stackexchange.com/a/36854/38754
local line = vim.fn.line(".")
local startline = line
while (is_diff_line(startline - 1))
do
startline = startline - 1
end
local endline = line
while (is_diff_line(endline + 1))
do
endline = endline + 1
end
return startline, endline
end
function diffget_and_keep(put_after)
-- switch to other window
vim.cmd('wincmd l')
local startline_other, endline_other = diff_get_start_end_line()
-- yank relevant lines into register f
vim.cmd(startline_other .. ',' .. endline_other .. 'y f')
-- back to where i was
vim.cmd('wincmd h')
local startline_here, endline_here = diff_get_start_end_line()
if put_after then
-- to paste after, we must move to the end of the block
local hunk_lines = endline_here - startline_here
for i=1,hunk_lines do
vim.fn.feedkeys('j')
end
vim.fn.feedkeys('"fp')
else
vim.fn.feedkeys('k"fp')
end
end
function _G.diffget_and_keep_before()
diffget_and_keep(false)
end
function _G.diffget_and_keep_after()
diffget_and_keep(true)
end