diff --git a/autoload/mergetool.vim b/autoload/mergetool.vim index 2cb1c0a..70aab47 100644 --- a/autoload/mergetool.vim +++ b/autoload/mergetool.vim @@ -6,6 +6,7 @@ endfunction let g:mergetool_layout = get(g:, 'mergetool_layout', 'mr') let g:mergetool_prefer_revision = get(g:, 'mergetool_prefer_revision', 'local') let g:MergetoolSetLayoutCallback = get(g:, 'MergetoolSetLayoutCallback', function('s:noop')) +let g:mergetool_args_order = get(g:, 'mergetool_args_order', '') " {{{ Public exports @@ -38,6 +39,16 @@ function! mergetool#start() "{{{ let s:mergedfile_fileformat = &fileformat let s:mergedfile_filetype = &filetype + if !empty(g:mergetool_args_order) + let success = s:apply_args_order(s:mergedfile_bufnr, g:mergetool_args_order) + if !success + echohl WarningMsg + echo "g:mergetool_args_order didn't use the current file as MERGED. Ensure you're using the order as seen in :args." + echohl None + return + endif + endif + " Detect if we're run as 'git mergetool' by presence of BASE|LOCAL|REMOTE buf names let s:run_as_git_mergetool = bufnr('BASE') != -1 && \ bufnr('LOCAL') != -1 && @@ -52,8 +63,34 @@ function! mergetool#start() "{{{ call mergetool#prefer_revision(g:mergetool_prefer_revision) call mergetool#set_layout(g:mergetool_layout) + call mergetool#bind_commands() endfunction "}}} +function! mergetool#bind_commands() + command! -nargs=0 MergetoolStop call mergetool#stop() + command! -nargs=1 MergetoolSetLayout call mergetool#set_layout() + command! -nargs=1 MergetoolToggleLayout call mergetool#toggle_layout() + command! -nargs=0 MergetoolPreferLocal call mergetool#prefer_revision('local') + command! -nargs=0 MergetoolPreferRemote call mergetool#prefer_revision('remote') + doautocmd User MergetoolStart +endf + +function! mergetool#unbind_commands() + delcommand MergetoolStop + delcommand MergetoolSetLayout + delcommand MergetoolToggleLayout + delcommand MergetoolPreferLocal + delcommand MergetoolPreferRemote + doautocmd User MergetoolStop +endf + +" Dummy autocmds to prevent errors. +augroup mergetool_dummy + au! + autocmd User MergetoolStart let s:mergetool_dummy = 1 + autocmd User MergetoolStop let s:mergetool_dummy = 0 +augroup END + " Stop mergetool effect depends on: " - when run as 'git mergetool' " - when run from Vim directly on file with conflict markers @@ -104,6 +141,7 @@ function! mergetool#stop() " {{{ endif let g:mergetool_in_merge_mode = 0 + call mergetool#unbind_commands() tabclose endif endfunction " }}} @@ -117,6 +155,38 @@ function! mergetool#toggle() " {{{ endif endfunction " }}} +" Create hidden buffers that use git's special buffer names to support any +" scm. We never create a MERGED buffer. Instead, return it so we can validate +" it's as expected. +function! s:apply_args_order(merged_bufnr, arg_order) " {{{ + let abbrevs = { + \ 'M': 'MERGED', + \ 'B': 'BASE', + \ 'R': 'REMOTE', + \ 'L': 'LOCAL' } + + let i = 1 + for labbr in split(a:arg_order, '\zs') + if labbr ==# 'M' + let current_arg_bufnr = bufnr(argv(i - 1)) + if a:merged_bufnr != current_arg_bufnr + " Fail -- input merged buffer number doesn't match arg order. + return 0 + endif + else + execute 'silent' i 'argument' + execute 'silent file' abbrevs[labbr] + setlocal buftype=nofile + setlocal bufhidden=hide + endif + let i += 1 + endfor + + execute "buffer " . a:merged_bufnr + " Success + return 1 +endfunction " }}} + " Opens set of windows with merged file and various file revisions " Supported layout options: " - w, 'MERGED' revision as passed by Git, or working tree version of merged file @@ -149,6 +219,11 @@ function! mergetool#set_layout(layout) " {{{ let l:_winstate = winsaveview() endif + " Ensure merged file (which likely has unsaved conflict removal changes) can + " be hidden without error. + let bufhidden_bak = getbufvar(s:mergedfile_bufnr, '&bufhidden') + call setbufvar(s:mergedfile_bufnr, '&bufhidden', 'hide') + " Before changing layout, turn off diff mode in all visible windows windo diffoff @@ -198,6 +273,7 @@ function! mergetool#set_layout(layout) " {{{ if s:goto_win_with_merged_file() && exists('l:_winstate') call winrestview(l:_winstate) endif + call setbufvar(s:mergedfile_bufnr, '&bufhidden', bufhidden_bak) endfunction " }}} " Toggles between given and default layout @@ -223,6 +299,57 @@ endfunction " }}} " }}} +" Diff exchange {{{ + +" Do either diffget or diffput, depending on given direction +" and whether the window has adjacent window in a given direction +" h| + window on right = diffget from right win +" h| + no window on right = diffput to left win +" l| + window on left = diffget from left win +" l| + no window on left = diffput to right win +" Same logic applies for vertical directions: 'j' and 'k' + +let s:directions = { + \ 'h': 'l', + \ 'l': 'h', + \ 'j': 'k', + \ 'k': 'j' } + +function mergetool#DiffExchange(dir) + let oppdir = s:directions[a:dir] + + let winoppdir = s:FindWindowOnDir(oppdir) + if (winoppdir != -1) + execute "diffget " . winbufnr(winoppdir) + else + let windir = s:FindWindowOnDir(a:dir) + if (windir != -1) + execute "diffput " . winbufnr(windir) + else + echohl WarningMsg + echo 'Cannot exchange diff. Found only single window' + echohl None + endif + endif +endfunction + +" Finds window in given direction and returns it win number +" If no window found, returns -1 +function s:FindWindowOnDir(dir) + let oldwin = winnr() + + execute "noautocmd wincmd " . a:dir + let curwin = winnr() + if (oldwin != curwin) + noautocmd wincmd p + return curwin + else + return -1 + endif +endfunction + +" }}} + " Private functions{{{ let s:markers = { @@ -243,7 +370,13 @@ function! s:load_revision(revision) call s:remove_conflict_markers(a:revision) setlocal nomodifiable readonly buftype=nofile bufhidden=delete nobuflisted execute "setlocal filetype=" . s:mergedfile_filetype - execute "file " . a:revision + let bufname = a:revision + if s:run_as_git_mergetool && has('win32') + " Cannot create a buffer called 'remote' if there's already one called + " 'REMOTE' because win32 is not case-sensitive. + let bufname .= '_derived' + endif + execute "file " . bufname elseif a:revision ==# 'BASE' || a:revision ==# 'REMOTE' || a:revision ==# 'LOCAL' " First, if run as 'git mergetool', try find buffer by name: 'BASE|REMOTE|LOCAL' diff --git a/doc/mergetool.txt b/doc/mergetool.txt new file mode 100644 index 0000000..ab01fdb --- /dev/null +++ b/doc/mergetool.txt @@ -0,0 +1,429 @@ +*mergetool.txt* Efficient way of using Vim as a Git mergetool. + +License: MIT + +=============================================================================== +INTRO *mergetool-intro* + +mergetool processes `MERGED` file and extracts `ours`, `theirs`, or +`common` sides of a conflict by parsing conflict markers left by Git. Then it +shows 2-way diff between `ours` and `theirs` versions, with raw conflict +markers being already removed. + +Unlike simply comparing between `LOCAL` and `REMOTE` history revisions, it +takes over where automatic Git merge algorithm gives up. Diffs are present +only where Git cannot automatically resolve conflicts, and you're not +distracted with diff highlighting of already resolved hunks. + +To resolve the conflict you don't need to edit conflict markers directly - +just pick either side of a conflict using |:diffget| and |:diffput| commands. + +This plugin was initially inspired by https://github.com/whiteinge/diffconflicts. + + +=============================================================================== +REQUIREMENTS *mergetool-requirements* + + *mergetool-diff3* + +mergetool requires conflict markers in a `MERGED` file to include common +`BASE` ancestor version as well. This is called `diff3` conflict style. > + + <<<<<<< HEAD + ours/local revision + ||||||| base + common base revision + ======= + theirs/remote revision + >>>>>>> feature + + + + [merge] + conflictStyle = diff3 + + + + git checkout --conflict=diff3 {file} + +` mappings. You're free to set up key mappings +in your `vimrc` as you'd like. See plugin/mergetool.vim for the available +|| mappings. + + +=============================================================================== +MERGING *mergetool-merging* + + *:MergetoolStart* + +When in a file with conflicts, |:MergetoolStart| will show 2-way diff in a new +tab with `$MERGED` file on the left. By default, all conflicts are already +resolved by picking up `ours/LOCAL` version. You don't need to edit raw +conflict markers manually. Either leave hunk as is, or pick `theirs/REMOTE` +version with |:diffget| from the right, or edit hunk manually. + +If there's a merge in progress, |:MergetoolStart| works as usual, but Unlike +running as a `git mergetool`, `LOCAL`, `REMOTE` and `BASE` history revisions +are not passed from the outside. In this mode, mergetool extracts them from +the numbered stages of Git index. > + +$ git cat-file -p :1:{file} > {file}.base +$ git cat-file -p :2:{file} > {file}.local +$ git cat-file -p :3:{file} > {file}.remote + + + nmap mt (MergetoolToggle) +< + + *g:mergetool_prefer_revision* + +|:MergetoolStart| removes conflict markers from `MERGED` file, and picks up +`ours/local` side of a conflict by default. Use |g:mergetool_prefer_revision| +to change the preferred side of a conflict: > + + " possible values: 'local' (default), 'remote', 'base', 'unmodified' + let g:mergetool_prefer_revision = 'remote' + + + + " default behaviour + " m - for working tree version of MERGED file + " r - for 'remote' revision + " l - for 'local' revision + " b - common merge 'base' + let g:mergetool_layout = 'mr' + + + + let g:mergetool_layout = 'LmR' + +This `LmR` setup is pretty much same to what vim-fugitive |:Gdiff| +does, except that conflict markers are already removed. You can use +|g:mergetool_prefer_revision|='unmodified' to replicate vim-fugitive +completely. Indeed, mergetool is flexible enough to replicate any existing +vim+merge solution. + +Vertical splits are used by default. Use a comma to split horizontally: > + + " merged above remote + let g:mergetool_layout = 'm,r' + " base above local and remote above merged + let g:mergetool_layout = 'b,lr,m' +< + + *:MergetoolToggleLayout* + +Use |:MergetoolToggleLayout| to switch different layouts during a merge. + +For example, you can default to a 2-way diff layout: > + + " In 'vimrc', set your default layout. + let g:mergetool_layout = 'mr' + + + + " View 'base' revision on the left + :MergetoolToggleLayout bmr + + " View 'base' revision in horizontal split at the bottom + :MergetoolToggleLayout mr,b + + " View history revisions, and hide 'MERGED' file altogether + :MergetoolToggleLayout LBR + + + + nnoremap mb :call mergetool#toggle_layout('mr,b') +< + + *g:MergetoolSetLayoutCallback* + +To further tweak layout or change settings of individual splits, define the +layout callback. It is called when layout is changed. + +Example. When layout is `mr,b`, I want the `base` horizontal split to be +pulled of a diff mode and have syntax highlighting enabled. Also, I want it to +reduce its height. > + + function s:on_mergetool_set_layout(split) + if a:split["layout"] ==# 'mr,b' && a:split["split"] ==# 'b' + set nodiff + set syntax=on + + resize 15 + endif + endfunction + + let g:MergetoolSetLayoutCallback = function('s:on_mergetool_set_layout') +< + +Callback is called for each split in the layout, with a split being passed as +a callback argument. > + + { + 'layout': 'mb,r', # current layout + 'split': 'b', # current split + 'filetype': 'vim', # file type of MERGED file + 'bufnr': 2, # buffer number of current split + 'winnr': 5 # window number of current split + } + +=============================================================================== +DIFFING *mergetool-diff* + + *:MergetoolDiffExchangeLeft* + *:MergetoolDiffExchangeRight* + *:MergetoolDiffExchangeDown* + *:MergetoolDiffExchangeUp* + +Vim's |:diffget| and |:diffput| commands are convenient and unambiguous as +soon as you have only two buffers in diff mode. If you prefer 3-way diff, +you're out of lucky, as you need to explicitly tell the buffer number you want +to exchange diff with. + +mergetool comes with "DiffExchange" commands and mapping, that accepts +direction of a diff movement: "left", "right", "up", "down". You can set up +your own key mappings for diff mode only: > + + nmap &diff? '(MergetoolDiffExchangeLeft)' : '' + nmap &diff? '(MergetoolDiffExchangeRight)' : '' + nmap &diff? '(MergetoolDiffExchangeDown)' : '' + nmap &diff? '(MergetoolDiffExchangeUp)' : '' + + + + :MergetoolDiffExchangeLeft + :MergetoolDiffExchangeRight + :MergetoolDiffExchangeDown + :MergetoolDiffExchangeUp + +` would `diffget` change from the right split into the middle one. + If you imagine the diff movement - it goes from right to the left. +- `` would `diffget` change from the left split into the middle one. + If you imagine the diff movement - it goes from left to the right. + +If the rightmost split were the active one: +- `` would `diffput` change from the current split into the middle + one. As soon as there is no adjacent window on the right to get change + from, we invert `diffget` operation into `diffput`. +- `` would `diffget` change from middle split. + +Same logic applies to "up" and "down" directions. Useful if you prefer +horizontal splits. + +Conclusion~ +Despite how many splits are opened and what's the layout, you +don't need to wrap your head around `diffput` vs `diffget` semantics, and you +don't need to figure out correct buffer numbers manually. You just give +desired diff movement direction, and mergetool handles the details for you. + +Limitations~ +* DiffExchange commands work only in normal mode, and do not + support visual mode and working with line ranges. +* DiffExchange functionality is not specific to resolving merge conflicts, and + can be used for regular diffs. + +If you like `` mappings from the snippet above, you might also want +to map `` and `` keys to navigate diffs, instead of default `[c` and +`]c` mappings. They're not used anyway, since you're using `h,j,k,l` for +movements, are you? ;-) > + + nnoremap &diff ? '[c' : '' + nnoremap &diff ? ']c' : '' +< + +=============================================================================== +USER AUTOCOMMANDS *mergetool-autocmd-user* + + *MergetoolStart-autocmd* + *MergetoolStop-autocmd* + +These |User| autocommands are triggered when merge mode begins and ends. You +could use them to turn off |spell| during a merge: > + + augroup your_mergetool + au! + autocmd User MergetoolStart set nospell + autocmd User MergetoolStop set spell + augroup END + +< + +=============================================================================== +WORKING WITH OTHER PLUGINS *mergetool-other-plugins* + + *mergetool-statusline* + *g:mergetool_in_merge_mode* + +|g:mergetool_in_merge_mode| indicates whether you're in merge mode. It can be +helpful to show indicator in a status line. + +Example for vim-airline: > + + function! AirlineDiffmergePart() + if get(g:, 'mergetool_in_merge_mode', 0) + return '↸' + endif + + if &diff + return '↹' + endif + + return '' + endfunction + + call airline#parts#define_function('_diffmerge', 'AirlineDiffmergePart') + call airline#parts#define_accent('_diffmerge', 'bold') + + let g:airline_section_z = airline#section#create(['_diffmerge', ...other_parts]) +< +=============================================================================== +USING AS A MERGETOOL *mergetool-as-mergetool* + *mergetool-git-mergetool* + +mergetool can be configured to run as a git mergetool. In your `~/.gitconfig`: > + + [merge] + tool = vim_mergetool + conflictstyle = diff3 + + [mergetool "vim_mergetool"] + cmd = vim -f -c "MergetoolStart" "$MERGED" "$BASE" "$LOCAL" "$REMOTE" + trustExitCode = true + + + + gvim -f -c "let g:mergetool_args_order = 'MBRL'" -c "MergetoolStart" "$MERGED" "$BASE" "$REMOTE" "$LOCAL" + + + + set LOCAL=%1 + set REMOTE=%2 + set BASE=%3 + set MERGED=%4 + gvim --nofork -c "let g:mergetool_args_order = 'MBLR'" -c "Merge" "%MERGED%" "%BASE%" "%LOCAL%" "%REMOTE%" +< + *mergetool-exiting* + +When exiting merge mode, mergetool would prompt you whether merge was +successful. If not, it will rollback changes to the buffer, will not save +`MERGED` file to disk, and exit with non-zero code, when running as a git +mergetool. + +You can either issue `:MergetoolStop` or `:MergetoolToggle` commands, or use +dedicated mapping. + +Yet another approach, which I prefer in my personal `vimrc`, is having a +`q` key mapped to context-aware `QuitWindow()` function. It detects +whether we're in merge mode, and runs `:MergetoolStop` command, or just uses +normal "quit" command otherwise. > + + function s:QuitWindow() + + " If we're in merge mode, exit + if get(g:, 'mergetool_in_merge_mode', 0) + call mergetool#stop() + return + endif + + if &diff + " Quit diff mode intelligently... + endif + + quit + endfunction + + command! QuitWindow call s:QuitWindow() + nnoremap q :QuitWindow +< +=============================================================================== +vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: diff --git a/plugin/mergetool.vim b/plugin/mergetool.vim index 522d260..f932375 100644 --- a/plugin/mergetool.vim +++ b/plugin/mergetool.vim @@ -7,74 +7,25 @@ let g:loaded_mergetool = 1 let g:mergetool_in_merge_mode = 0 +" Commands and mappings for mergetool state. Additional commands +" available during merging. command! -nargs=0 MergetoolStart call mergetool#start() -command! -nargs=0 MergetoolStop call mergetool#stop() command! -nargs=0 MergetoolToggle call mergetool#toggle() -command! -nargs=1 MergetoolSetLayout call mergetool#set_layout() -command! -nargs=1 MergetoolToggleLayout call mergetool#toggle_layout() -command! -nargs=0 MergetoolPreferLocal call mergetool#prefer_revision('local') -command! -nargs=0 MergetoolPreferRemote call mergetool#prefer_revision('remote') nnoremap (MergetoolToggle) :call mergetool#toggle() -" {{{ Diff exchange -" Do either diffget or diffput, depending on given direction -" and whether the window has adjacent window in a given direction -" h| + window on right = diffget from right win -" h| + no window on right = diffput to left win -" l| + window on left = diffget from left win -" l| + no window on left = diffput to right win -" Same logic applies for vertical directions: 'j' and 'k' -let s:directions = { - \ 'h': 'l', - \ 'l': 'h', - \ 'j': 'k', - \ 'k': 'j' } +" Commands and mappings for diff exchange commands. These can be used +" outside of merging (in any diff windows). +command! -nargs=0 MergetoolDiffExchangeLeft call mergetool#DiffExchange('h') +command! -nargs=0 MergetoolDiffExchangeRight call mergetool#DiffExchange('l') +command! -nargs=0 MergetoolDiffExchangeDown call mergetool#DiffExchange('j') +command! -nargs=0 MergetoolDiffExchangeUp call mergetool#DiffExchange('k') -function s:DiffExchange(dir) - let oppdir = s:directions[a:dir] - - let winoppdir = s:FindWindowOnDir(oppdir) - if (winoppdir != -1) - execute "diffget " . winbufnr(winoppdir) - else - let windir = s:FindWindowOnDir(a:dir) - if (windir != -1) - execute "diffput " . winbufnr(windir) - else - echohl WarningMsg - echo 'Cannot exchange diff. Found only single window' - echohl None - endif - endif -endfunction - -" Finds window in given direction and returns it win number -" If no window found, returns -1 -function s:FindWindowOnDir(dir) - let oldwin = winnr() - - execute "noautocmd wincmd " . a:dir - let curwin = winnr() - if (oldwin != curwin) - noautocmd wincmd p - return curwin - else - return -1 - endif -endfunction - -" Commands and mappings for diff exchange commands -command! -nargs=0 MergetoolDiffExchangeLeft call s:DiffExchange('h') -command! -nargs=0 MergetoolDiffExchangeRight call s:DiffExchange('l') -command! -nargs=0 MergetoolDiffExchangeDown call s:DiffExchange('j') -command! -nargs=0 MergetoolDiffExchangeUp call s:DiffExchange('k') - -nnoremap (MergetoolDiffExchangeLeft) :call DiffExchange('h') -nnoremap (MergetoolDiffExchangeRight) :call DiffExchange('l') -nnoremap (MergetoolDiffExchangeDown) :call DiffExchange('j') -nnoremap (MergetoolDiffExchangeUp) :call DiffExchange('k') +nnoremap (MergetoolDiffExchangeLeft) :call mergetool#DiffExchange('h') +nnoremap (MergetoolDiffExchangeRight) :call mergetool#DiffExchange('l') +nnoremap (MergetoolDiffExchangeDown) :call mergetool#DiffExchange('j') +nnoremap (MergetoolDiffExchangeUp) :call mergetool#DiffExchange('k') " }}} diff --git a/readme.md b/readme.md index baa254c..61c9c86 100644 --- a/readme.md +++ b/readme.md @@ -101,29 +101,11 @@ git checkout --conflict=diff3 {file} - Prompts whether merge was successful on quit. If not, rollbacks changes and report non-zero exit status code when run as a `git mergetool`. - Smart diff exchange commands. Tell direction of a window to `diffget` or `diffput` instead of specifying a buffer number. Especially handy for 3-way diffs. Not limited to merge conflict scenarios, can be used for regular diffs. - Can tell if we're in merge mode right now. Useful for showing some sort of indicator in a status line. +- Use as a mergetool for git and other source control management systems. **NOTE**: `vim-mergetool` does not set up any key mappings for you. It justs exports a handful of commands and `` mappings. You're free to set up key mappings in your `vimrc` as you'd like. -### Preferred conflict side -`vim-mergetool` removes conflict markers from `MERGED` file, and picks up `ours/local` side of a conflict by default. If you prefer another side of a conflict: - -```vim -" possible values: 'local' (default), 'remote', 'base' -let g:mergetool_prefer_revision = 'remote' -``` - -If you don't want `vim-mergetool` to process `MERGED` file and remove raw conflict markers: - -```vim -let g:mergetool_prefer_revision = 'unmodified' -``` - -Alternatively, you can start with `local` or `unmodified` revision, and change your mind later during merge process by running one of these commands: - -```vim -:MergetoolPreferLocal -:MergetoolPreferRemote -``` +See [the documentation](doc/mergetool.txt) for more commands and configuration options. ### Available revisions to compare @@ -136,7 +118,7 @@ Alternatively, you can start with `local` or `unmodified` revision, and change y ### Layout -`vim-mergetool` defaults to two vertical splits layout with `MERGED` file on the left, and `remote` revision on the right. `MERGED` file is processed according to `g:mergetool_prefer_revision` setting as described above. +`vim-mergetool` defaults to two vertical splits layout with `MERGED` file on the left and `remote` revision on the right. `MERGED` file is processed according to `g:mergetool_prefer_revision` setting as described above. ```vim " (m) - for working tree version of MERGED file @@ -152,7 +134,7 @@ let g:mergetool_layout = 'bmr' ![3 way diff vertical split layout](./screenshots/bmr_3splits_layout.png) -To show usual `REMOTE`, `LOCAL`, `BASE` history revisions, use uppercase characters: +Lower case letters use files derived from the merged file (by accepting that file's view of conflicts). To use the original `REMOTE`, `LOCAL`, `BASE` files from git, use uppercase characters: ```vim let g:mergetool_layout = 'LmR' @@ -160,7 +142,7 @@ let g:mergetool_layout = 'LmR' By the way, this setup is pretty much same to what [vim-fugitive](https://github.com/tpope/vim-fugitive) `:Gdiff` does, except that conflict markers are already removed. You can use `g:mergetool_prefer_revision='unmodified'` to replicate vim-fugitive completely. Indeed, `vim-mergetool` is flexible enough to replicate any existing vim+merge solution. -Vertical splits are used by default. If you prefer working with horizontal splits: +Vertical splits are used by default. Use a comma to split horizontally: ```vim let g:mergetool_layout = 'm,r' @@ -182,8 +164,7 @@ For example, you can start with 2-way diff layout, and then temporarily toggle a ```vim -" In 'vimrc' -" Default layout +" In 'vimrc', set your default layout. let g:mergetool_layout = 'mr' " Later, during merge process: @@ -254,55 +235,6 @@ Here's the end result: ![Layout advanced customization](./screenshots/layout_advanced_customization.png) -### Running as a `git mergetool` - -`vim-mergetool` can be configured to run as a `git mergetool`. In your `~/.gitconfig`: - -```ini -[merge] -tool = vim_mergetool -conflictstyle = diff3 - -[mergetool "vim_mergetool"] -cmd = vim -f -c "MergetoolStart" "$MERGED" "$BASE" "$LOCAL" "$REMOTE" -trustExitCode = true -``` - -Git detects whether merge was successful or not in two ways: -- When `trustExitCode = false`, checks if `MERGED` file was modified. -- When `trustExitCode = true`, checks exit code of merge tool process. - -`vim-mergetool` supports both options. On quit, if merge was unsuccessful, it both discards any unsaved changes to buffer without touching file's `ctime` and returns non-zero exit code. - - -### Running directly from running Vim instance -You can enter and exit merge mode from running Vim instance by opening a file with conflict markers, and running one of the commands: - -```vim -:MergetoolStart -:MergetoolStop -:MergetoolToggle -``` - -You can set up a key mapping to toggle merge mode: - -```vim -nmap mt (MergetoolToggle) -``` - -When exiting merge mode, if merge was unsuccessful, `vim-mergetool` would discard changes to merged file and rollback to a buffer state as it were right before starting a new merge. - -Unlike running as a `git mergetool`, `LOCAL`, `REMOTE` and `BASE` history revisions are not passed from the outside. In this mode, `vim-mergetool` extracts them from the numbered stages of Git index. - -```bash -$ git cat-file -p :1:{file} > {file}.base -$ git cat-file -p :2:{file} > {file}.local -$ git cat-file -p :3:{file} > {file}.remote -``` - -**ASSUMPTION:** Therefore, it's assumed that a git merge is in progress, and `cwd` of running Vim instance is set to repository root dir. - - ### Smart diff exchange commands Vim's `:diffget` and `:diffput` commands are convenient and unambiguous as soon as you have only two buffers in diff mode. If you prefer 3-way diff, you're out of lucky, as you need to explicitly tell the buffer number you want to exchange diff with. @@ -343,7 +275,7 @@ If the rightmost split were the active one: Same logic applies to "up" and "down" directions. Useful if you prefer horizontal splits. -**Conclusion**: despite how many splits are opened and what's the layout, you don't need to wrap your head around `diffput` vs `diffget` semantics, and you don't need to figure out correct buffer numbers manually. You just tell desired diff movement direction, and `vim-mergetool` handles the details for you. +**Conclusion**: despite how many splits are opened and what's the layout, you don't need to wrap your head around `diffput` vs `diffget` semantics, and you don't need to figure out correct buffer numbers manually. You just give desired diff movement direction, and `vim-mergetool` handles the details for you. **Limitation**: `DiffExchange` commands work only in normal mode, and do not support visual mode and working with line ranges. @@ -352,36 +284,29 @@ Same logic applies to "up" and "down" directions. Useful if you prefer horizonta If you like `` mappings from the snippet above, you might also want to map `` and `` keys to navigate diffs, instead of default `[c` and `]c` mappings. They're not used anyway, since you're using `h,j,k,l` for movements, are you? ;-) ```vim -nmap &diff ? '[c' : '' -nmap &diff ? ']c' : '' +nnoremap &diff ? '[c' : '' +nnoremap &diff ? ']c' : '' ``` ### Merge mode detection You can detect whether you're in merge mode now, by inspecting `g:mergetool_in_merge_mode` variable. -It can be helpful to show indicator in a status line. Example for [vim-airline](https://github.com/vim-airline/vim-airline): - -```vim -function! AirlineDiffmergePart() - if get(g:, 'mergetool_in_merge_mode', 0) - return '↸' - endif +It can be helpful to show indicator in a status line. See mergetool-statusline in [the documentation](doc/mergetool.txt) to setup [vim-airline](https://github.com/vim-airline/vim-airline) like this: - if &diff - return '↹' - endif +![Status line indicator](./screenshots/airline_merge_indicator.png) - return '' -endfunction -call airline#parts#define_function('_diffmerge', 'AirlineDiffmergePart') -call airline#parts#define_accent('_diffmerge', 'bold') +You can run vimscript when mergemode begins and ends: -let g:airline_section_z = airline#section#create(['_diffmerge', ...other_parts]) +```vim +augroup your_mergetool + au! + autocmd User MergetoolStart set nospell + autocmd User MergetoolStop set spell +augroup END ``` -![Status line indicator](./screenshots/airline_merge_indicator.png) ### Quitting merge mode @@ -409,7 +334,7 @@ function s:QuitWindow() quit endfunction -command QuitWindow call s:QuitWindow() +command! QuitWindow call s:QuitWindow() nnoremap q :QuitWindow ```