forked from samoshkin/vim-mergetool
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mergetool.txt
437 lines (319 loc) · 16.4 KB
/
mergetool.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
*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
<If you get "Conflict markers miss common base revision" error message, put the
following in your `~/.gitconfig` to use diff3 conflict style as a default: >
[merge]
conflictStyle = diff3
<See also
https://git-scm.com/docs/git-config#Documentation/git-config.txt-mergeconflictStyle
If something goes wrong, you can always reset conflict markers in a file to
their initial state. It's safe to do it only during ongoing merge, otherwise
you'd overwrite file in a working tree with version from index. >
git checkout --conflict=diff3 {file}
<See also |mergetool-git-mergetool|.
===============================================================================
MAPPINGS *mergetool-mappings*
mergetool does not set up any key mappings for you. It justs exports a
handful of commands and `<plug>` mappings. You're free to set up key mappings
in your `vimrc` as you'd like. See plugin/mergetool.vim for the available
|<Plug>| 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
<ASSUMPTION: Therefore, it's assumed that a git merge is in progress, and
`cwd` of running Vim instance is set to repository root dir.
*:MergetoolStop*
When a merge is complete, quit merge tool and ensure git gets the correct exit
code to handle the merge correctly.
When exiting merge mode, if merge was unsuccessful, mergetool discards changes
to merged file and rollback to a buffer state as it were right before starting
a new merge.
*g:mergetool_mark_resolved*
If the merge was successful, mergetool will do |fugitive|'s |:Gwrite| if
available, or fall back to `git add` or `svn resolved` if a git or svn repo is
detected.
To disable this behaviour: >
let g:mergetool_mark_resolved = 0
<
*:MergetoolToggle*
Invokes |:MergetoolStart| when no merge is active and |:MergetoolStop| when a
merge is active. Can also be bound to a key: >
nmap <leader>mt <plug>(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'
<Use `unmodified` if you don't want |:MergetoolStart| to remove raw conflict
markers from `MERGED` file.
*:MergetoolPreferLocal*
*:MergetoolPreferRemote*
Alternatively, you can start with `local` or `unmodified` revision, and change
your mind later during merge process by running :MergetoolPreferLocal or
:MergetoolPreferRemote.
### Available revisions to compare
2-way diff between `local` and `remote` versions derived from conflict markers
is a sane default, but you might want to compare `MERGED` file against other
revisions:
- `LOCAL`, current branch HEAD.
- `REMOTE`, HEAD of the branch we're going to merge
- `BASE`, common ancestor of two branches, i.e. `git merge-base branchX branchY`
- `local`, `remote`, `base` (in lowercase), those are revisions derived from
`MERGED` file by picking up either side of a conflict from conflict
markers
*g:mergetool_layout*
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|.
You can customize the default layout with |g:mergetool_layout|: >
" 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'
<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: >
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'
<Later, during merge process: >
" 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
<In addition to commands, you can set up key mappings for your most common layouts: >
nnoremap <silent> <leader>mb :call mergetool#toggle_layout('mr,b')<CR>
<
*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 <expr> <C-Left> &diff? '<Plug>(MergetoolDiffExchangeLeft)' : '<C-Left>'
nmap <expr> <C-Right> &diff? '<Plug>(MergetoolDiffExchangeRight)' : '<C-Right>'
nmap <expr> <C-Down> &diff? '<Plug>(MergetoolDiffExchangeDown)' : '<C-Down>'
nmap <expr> <C-Up> &diff? '<Plug>(MergetoolDiffExchangeUp)' : '<C-Up>'
<Commands are available as well: >
:MergetoolDiffExchangeLeft
:MergetoolDiffExchangeRight
:MergetoolDiffExchangeDown
:MergetoolDiffExchangeUp
<DiffExchange logic runs either |:diffget| or |:diffput| with a right
buffer number of adjacent window, depending on:
- given direction
- whether window in opposite direction exists or not
It's easier to explain with example.
Suppose, you have 3 split layout: `MERGED` file in the middle, `base` and
`remote` revisions are on the sides. Typically, the middle one with a `MERGED`
file is an active split. You navigate from hunk to hunk, and decide what to do
with a conflict: leave as is, or pick version from left/right splits.
- `<C-Left>` would `diffget` change from the right split into the middle one.
If you imagine the diff movement - it goes from right to the left.
- `<C-Right>` 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:
- `<C-Left>` 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`.
- `<C-Right>` 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 `<C-arrow>` mappings from the snippet above, you might also want
to map `<up>` and `<down>` 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 <expr> <Up> &diff ? '[c' : '<Up>'
nnoremap <expr> <Down> &diff ? ']c' : '<Down>'
<
===============================================================================
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
<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.
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.
*mergetool-any-mergetool*
*g:mergetool_args_order*
If your scm doesn't use files called BASE, REMOTE, LOCAL as merge tempfiles
(like svn), you can set the |g:mergetool_args_order| variable to tell
mergetool which argument is which file. Setup your scm to start vim like this: >
gvim -f -c "let g:mergetool_args_order = 'MBRL'" -c "MergetoolStart" "$MERGED" "$BASE" "$REMOTE" "$LOCAL"
<The@MERGED file should be the first file argument@because |:MergetoolStart| is only
valid in a file with conflict markers.
Your scm likely has its own variable names for these filenames. Check your
documentation.
For example, with TortoiseSVN you'd create a batchfile like this and set it as
your mergetool: >
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
`<leader>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 <silent> <leader>q :QuitWindow<CR>
<
===============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: