├── License.md ├── README.md ├── images ├── overview.gif ├── statusline.png ├── tab1.png ├── tab2.png ├── tab3.png ├── tablabel-macvim.png └── tablabel-terminal.png └── plugin └── conflicted.vim /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chris Toomey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Conflicted 2 | ========== 3 | 4 | Conflicted is a Vim plugin that aids in resolving git merge and rebase 5 | conflicts. It relies on [tpope's fugitive plugin][] to do the heavy lifting and 6 | provides a few wrapper commands and a streamlined workflow to make resolving 7 | conflicts much more straightforward. 8 | 9 | [tpope's fugitive plugin]: https://github.com/tpope/vim-fugitive 10 | 11 | Usage 12 | ----- 13 | 14 | ### From git 15 | 16 | The easist way to use Conflicted from git is to add an alias for it that opens 17 | vim with the conflicted plugin activated. You can add the alias with the 18 | following shell command: 19 | 20 | ``` sh 21 | git config --global alias.conflicted '!vim +Conflicted' 22 | ``` 23 | 24 | From there, you can run conflicted directly from git with: 25 | 26 | ``` sh 27 | git conflicted 28 | ``` 29 | 30 | Which will open Vim and start up the plugin. 31 | 32 | Or if you prefer to start Conflicted via `git mergetool`: 33 | 34 | ```sh 35 | # Define a custom mergetool called `vim-conflicted` that runs `vim +Conflicted` 36 | git config --global mergetool.vim-conflicted.cmd 'vim +Conflicted' 37 | # Set the `vim-conflicted` mergetool to be used when `git mergetool` is executed 38 | git config --global merge.tool 'vim-conflicted' 39 | ``` 40 | 41 | ### Commands 42 | 43 | Conflicted provides three primary commands for working with conflicts: 44 | 45 | **Conflicted** 46 | 47 | `Conflicted` will add all the conflicted files to Vim's `arglist` and open 48 | the first in `Merger` mode. 49 | 50 | **GitNextConflict** 51 | 52 | After editing the merged file to resolve the conflict and remove all conflict 53 | markers, running `GitNextConflict` will mark the file as resolved and open 54 | the next file in `Merger` mode for resolution. 55 | 56 | If you are on the last file, `GitNextConflict` will quit Vim. 57 | 58 | **Merger** 59 | 60 | `Merger` will open the various views of the conflicted file. This command is 61 | exposed for completeness, but likely you will not need to call this command 62 | directly as both `Conflicted` and `GitNextConflict` will call it for you. 63 | 64 | ### Diffget Mappings 65 | 66 | Conflicted provides mappings to perform a `diffget` from the working version 67 | of the file, pulling from either the upstream or local version. These mappings 68 | are provided in both normal and visual mode: 69 | 70 | 1. `dgu` - diffget from the upstream version 71 | 1. `dgl` - diffget from the local version 72 | 73 | If you would prefer different mappings, you can overide with the following in 74 | your vimrc: 75 | 76 | ``` vim 77 | " Use `gl` and `gu` rather than the default conflicted diffget mappings 78 | let g:diffget_local_map = 'gl' 79 | let g:diffget_upstream_map = 'gu' 80 | ``` 81 | 82 | ### Tabline 83 | 84 | Conflicted will configure the tab label to display the name of the revision in 85 | the tab. This is done via the `tabline` setting in terminal Vim, and the 86 | `guitablabel` setting in graphical Vim, ie MacVim. 87 | 88 | ![Terminal tabline](./images/tablabel-terminal.png) 89 | 90 | ![MacVim tab Label](./images/tablabel-macvim.png) 91 | 92 | ### Statusline Integration 93 | 94 | Add the following to your vimrc to display the version name of each split in 95 | the vim statusbar: 96 | 97 | ``` vim 98 | set stl+=%{ConflictedVersion()} 99 | ``` 100 | 101 | Normally it will not add anything to the statusline, but if you are in 102 | conflicted mode then it will add the conflicted version, ie 'local', 103 | 'working', etc. 104 | 105 | ![Statusline](images/statusline.png) 106 | 107 | ### Custom settings for Conflicted buffers 108 | 109 | When Conflicted has finished setting up, it will call the user autocmd `VimConflicted`. 110 | 111 | Usage example: 112 | 113 | ```vim 114 | function! s:setupConflicted() 115 | set stl+=%{ConflictedVersion()} 116 | " Resolve and move to next conflicted file. 117 | nnoremap ]m :GitNextConflict 118 | endfunction 119 | autocmd myVimrc User VimConflicted call s:setupConflicted() 120 | ``` 121 | 122 | Installation 123 | ------------ 124 | 125 | If you don't have a preferred installation method, I recommend using [Vundle][]. 126 | Assuming you have Vundle installed and configured, the following steps will 127 | install the plugin: 128 | 129 | Add the following line to your `~/.vimrc` and then run `BundleInstall` from 130 | within Vim: 131 | 132 | ``` vim 133 | " Fugitive is required for Conflicted 134 | Bundle 'tpope/vim-fugitive' 135 | Bundle 'christoomey/vim-conflicted' 136 | ``` 137 | 138 | Overview 139 | -------- 140 | 141 | ![](./images/overview.gif) 142 | 143 | ### Versions 144 | 145 | Conflicted makes reference to four different versions of each conflicted 146 | file. These versions are: 147 | 148 | 1. `base` - The common ancestor of the file in the upstream and local branches 149 | 1. `upstream` - The core branch (usually `master`), that you are merging into 150 | or rebasing onto. 151 | 1. `local` - The feature branch containing your changes 152 | 1. `working` - The final combined version of the file 153 | 154 | ### Tabs 155 | 156 | For each conflicted file, Conflicted will open 3 tabs, each with a different 157 | diff view presented: 158 | 159 | 1. **Gdiffsplit! 3-way** - 3 way diff comparing the upstream, working, and local 160 | versions of the file. 161 | 162 | ![Tab 1 - Working](./images/tab1.png) 163 | 164 | 1. **Upstream Changes** - A 2 way diff between the base and upstream versions 165 | of the file. 166 | 167 | ![Tab 2 - Upstream](./images/tab2.png) 168 | 169 | 1. **Local Changes** - A 2 way diff between the base and local versions of 170 | the file. 171 | 172 | ![Tab 3 - Local](./images/tab3.png) 173 | 174 | [Vundle]: https://github.com/gmarik/vundle 175 | -------------------------------------------------------------------------------- /images/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoomey/vim-conflicted/b0f584be18a9a95748122f439efcd30e9ec787dc/images/overview.gif -------------------------------------------------------------------------------- /images/statusline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoomey/vim-conflicted/b0f584be18a9a95748122f439efcd30e9ec787dc/images/statusline.png -------------------------------------------------------------------------------- /images/tab1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoomey/vim-conflicted/b0f584be18a9a95748122f439efcd30e9ec787dc/images/tab1.png -------------------------------------------------------------------------------- /images/tab2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoomey/vim-conflicted/b0f584be18a9a95748122f439efcd30e9ec787dc/images/tab2.png -------------------------------------------------------------------------------- /images/tab3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoomey/vim-conflicted/b0f584be18a9a95748122f439efcd30e9ec787dc/images/tab3.png -------------------------------------------------------------------------------- /images/tablabel-macvim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoomey/vim-conflicted/b0f584be18a9a95748122f439efcd30e9ec787dc/images/tablabel-macvim.png -------------------------------------------------------------------------------- /images/tablabel-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoomey/vim-conflicted/b0f584be18a9a95748122f439efcd30e9ec787dc/images/tablabel-terminal.png -------------------------------------------------------------------------------- /plugin/conflicted.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_conflicted') || &cp 2 | finish 3 | endif 4 | let g:loaded_conflicted = 1 5 | 6 | let s:versions = ['working', 'upstream', 'local'] 7 | let s:diffget_local_map = 'dgl' 8 | let s:diffget_upstream_map = 'dgu' 9 | 10 | function! s:Conflicted() 11 | args `git diff --name-only --diff-filter=U` 12 | set tabline=%!ConflictedTabline() 13 | silent! set guitablabel=%{ConflictedGuiTabLabel()} 14 | Merger 15 | endfunction 16 | 17 | function! s:Merger() 18 | " Shim to support Fugitive 3.0 and prior versions 19 | if exists(':Gvdiffsplit') 20 | Gvdiffsplit! 21 | else 22 | Gdiff 23 | endif 24 | 25 | call s:MapTargetedDiffgets() 26 | call s:SetVersionStatuslines() 27 | call s:TabEdit('upstream') 28 | call s:TabEdit('local') 29 | tabfirst 30 | doautocmd User VimConflicted 31 | endfunction 32 | 33 | function! ConflictedTabline() 34 | let s = '' 35 | for tabnr in range(tabpagenr('$')) 36 | if tabnr + 1 == tabpagenr() 37 | let s .= '%#TabLineSel#' 38 | else 39 | let s .= '%#TabLine#' 40 | endif 41 | let s .= '%' . (tabnr + 1) . 'T' 42 | let s .= ' %{ConflictedTabLabel(' . tabnr . ')} ' 43 | endfor 44 | let s .= '%#TabLineFill#%T' 45 | if tabpagenr('$') > 1 46 | let s .= '%=%#TabLine#%999X' 47 | endif 48 | return s 49 | endfunction 50 | 51 | function! ConflictedGuiTabLabel() 52 | return ConflictedTabLabel(tabpagenr() - 1) 53 | endfunction 54 | 55 | function! ConflictedTabLabel(tabnr) 56 | return (a:tabnr + 1) . ': [' . s:versions[a:tabnr] . ']' 57 | endfunction 58 | 59 | function! s:TabEdit(parent) 60 | Gtabedit :1 61 | let b:conflicted_version = 'base' 62 | diffthis 63 | execute 'Gvsplit :' . s:VersionNumber(a:parent) 64 | let b:conflicted_version = a:parent 65 | diffthis 66 | wincmd r 67 | endfunction 68 | 69 | function! s:SetVersionStatuslines() 70 | let b:conflicted_version = 'working' 71 | wincmd h 72 | let b:conflicted_version = 'upstream' 73 | wincmd l 74 | wincmd l 75 | let b:conflicted_version = 'local' 76 | wincmd h 77 | endfunction 78 | 79 | function! ConflictedVersion() 80 | if exists('b:conflicted_version') 81 | return b:conflicted_version . ' ' 82 | else 83 | return '' 84 | end 85 | endfunction 86 | 87 | function! s:GitNextConflict() 88 | Gwrite 89 | argdelete % 90 | call s:NextOrQuit() 91 | endfunction 92 | 93 | function! s:NextOrQuit() 94 | if empty(argv()) 95 | quit 96 | else 97 | bdelete 98 | argument "move to the next file in the arglist 99 | Merger 100 | endif 101 | endfunction 102 | 103 | function! s:VersionNumber(version) 104 | return index(s:versions, a:version) + 1 105 | endfunction 106 | 107 | function! s:DiffgetVersion(version, ...) 108 | let targeted_diffget = 'diffget //' . s:VersionNumber(a:version) 109 | if a:0 110 | execute "'<,'>" . targeted_diffget 111 | else 112 | execute targeted_diffget 113 | endif 114 | diffupdate 115 | endfunction 116 | 117 | function! s:DesiredDiffgetMap(version) 118 | let user_map = 'g:diffget_' . a:version . '_map' 119 | let script_map = 's:diffget_' . a:version . '_map' 120 | if exists(user_map) 121 | execute 'return ' . user_map 122 | else 123 | execute 'return ' . script_map 124 | endif 125 | endfunction 126 | 127 | function! s:ConfigureRepeat(command) 128 | silent! call repeat#set("\" . a:command) 129 | endfunction 130 | 131 | nnoremap DiffgetLocal :call DiffgetVersion('local'):call ConfigureRepeat('DiffgetLocal') 132 | nnoremap DiffgetUpstream :call DiffgetVersion('upstream'):call ConfigureRepeat('DiffgetUpstream') 133 | xnoremap DiffgetLocal :call DiffgetVersion('local', line("'<"), line("'>")) 134 | xnoremap DiffgetUpstream :call DiffgetVersion('upstream', line("'<"), line("'>")) 135 | 136 | function! s:MapTargetedDiffgets() 137 | execute 'xmap ' . s:DesiredDiffgetMap('local') . ' DiffgetLocal' 138 | execute 'nmap ' . s:DesiredDiffgetMap('local') . ' DiffgetLocal' 139 | 140 | execute 'xmap ' . s:DesiredDiffgetMap('upstream') . ' DiffgetUpstream' 141 | execute 'nmap ' . s:DesiredDiffgetMap('upstream') . ' DiffgetUpstream' 142 | endfunction 143 | 144 | command! Conflicted call Conflicted() 145 | command! Merger call Merger() 146 | command! GitNextConflict call GitNextConflict() 147 | --------------------------------------------------------------------------------