16

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 4

8

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.

1
  • Calling :diffget twice doesn’t seem to work; the second paste overwrites the first one instead of appending to it. Jun 30, 2020 at 0:28
7

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.

2
  • Documentation seems to imply do instead of dg? Jun 30, 2020 at 20:07
  • @FranklinYu Oops, fixed. Thanks for the heads up.
    – 8bittree
    Jul 1, 2020 at 20:32
5

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.

2
  • Give a reason if you are going to downvote. May 15, 2018 at 23:53
  • 1
    This is all I do - just delete the markers. May 30, 2018 at 13:56
0

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.