(Created page with "Git ships with support to invoke Vimdiff as a “mergetool” to help resolve merge conflicts. Unfortunately Vim struggles a bit with three-way diffs; both with highlighting the ...") |
(add related plugins) |
||
Line 145: | Line 145: | ||
==References== |
==References== |
||
− | https://github.com/git/git/blob/master/mergetools/vim |
+ | * https://github.com/git/git/blob/master/mergetools/vim |
+ | |||
− | *{{help|toc}} |
||
+ | ==Related plugins== |
||
+ | * [http://sjl.bitbucket.org/threesome.vim/ Threesome] by Steve Losh |
||
+ | * {{script|id=621|text=Conflict2Diff}}? Not sure if this actually does 3-way diff. |
||
==Comments== |
==Comments== |
Revision as of 15:33, 5 June 2012
Git ships with support to invoke Vimdiff as a “mergetool” to help resolve merge conflicts. Unfortunately Vim struggles a bit with three-way diffs; both with highlighting the differences and with shuffling individual changes between the three windows.
This article details a simpler way to use Vimdiff as a Git mergetool.
An Introduction
You are likely to encouter a merge conflict in Git eventually by merging one feature branch into another or by merging upstream changes into a local branch.
Git is quite good at automatically resolving conflicts. A simplistic explanation is that Git starts by finding the common ancestor of your local changes and the changes you are merging into yours. Git then replays the history of each lineage, making intelligent choices along the way about how and where code is added or moved or renamed. If there are conflicts that Git cannot resolve itself the file is left in a conflicted state that must be resolved manually -- more on this below as the MERGED version of the file.
The most simple way to resolve a Git merge conflict is to open the conflicted file with your favorite text editor and to manually find and remove the conflict markers, keeping the best version of the code from each side of the conflict, then marking the conflict as resolved in Git.
Using Vimdiff as a Mergetool
Resolving manually is fine for small conflicts and for obvious conflicts, but this can be frustrating and error-prone for large conflicts and for conflicts where each side of the changes differ only subtly.
Mergetools can help make short work of even gnarly merge conflicts. Invoke
Vimdiff as a mergetool with
git mergetool -t gvimdiff
. Recent
versions of Git invoke Vimdiff with the following window layout:
+--------------------------------+ | LOCAL | BASE | REMOTE | +--------------------------------+ | MERGED | +--------------------------------+
- LOCAL
- A temporary file containing the contents of the file on the current branch.
- BASE
- A temporary file containing the common base for the merge.
- REMOTE
- A temporary file containing the contents of the file to be merged.
- MERGED
- The file containing the conflict markers. Git has performed as much automatic conflict resolution as possible and the state of this file is a combination of both LOCAL and REMOTE with conflict markers surrounding anything that Git could not resolve itself. The mergetool should write the result of the resolution to this file.
Vimdiff for Three-Way Merges
The default Vimdiff window layout presents quite a lot of useful information. In many cases, however, it is simply too much noise and hides the relevant visual clues you need to solve a conflict quickly.
For example, here is the default view of the conflict shown above:
Vimdiff is an excellent two-way diff viewer and many of the helper shortcuts are geared for moving changes between only two windows at a time. Displaying differences between three (or more!) versions of a file is a hard problem to solve and syntax highlighting alone just isn't well-suited to the job.
Playing to Vimdiff Strengths
It is useful to be able to quickly see the remote version of the file, or the common ancestor of both files, or even your version of the file before any automatic merging was attempted. However, it is often most useful to view the simple comparison of just the “left” conflict and the “right” conflict.
For example, here is the same conflict as above but only diffing the conflicts themselves:
The resolution is much clearer and since this is two-way merge all of the Vimdiff keyboard shortcuts work as intended.
An Alternate Vimdiff Mergetool
In order to achive the layout detailed above we need to step outside of the Vimdiff mergetool that ships with Git, and even outside of Git itself. The following script can be used as a mergetool that will then invoke Vimdiff. Save it as “diffconflicts” somewhere on your shell PATH and mark it as
executable (
chmod +x diffconflicts
).
#!/bin/bash if [[ -z $@ || $# != "5" ]] ; then echo -e "Usage: $0 \$BASE \$LOCAL \$REMOTE \$MERGED" exit 1 fi cmd=$1 BASE=$2 LOCAL=$3 REMOTE=$4 MERGED=$5 LCONFL=$(dirname $5)/$$.left.tmp RCONFL=$(dirname $5)/$$.right.tmp # Remove the conflict markers for each 'side' and put each into a temp file sed -e '/<<<<<<</,/^=======$/d' -e '/>>>>>>>/d' $MERGED > $LCONFL sed -e '/^=======$/,/>>>>>>>/d' -e '/<<<<<<</d' $MERGED > $RCONFL # Fire up vimdiff $cmd -f -R -d $LCONFL $RCONFL \ -c ":set noro" \ -c ":tabe $LOCAL" -c ":vert diffs $BASE" -c ":vert diffs $REMOTE" \ -c ":winc t" -c ":tabe $MERGED" -c ":tabfir" EC=$? # Overwrite $MERGED only if vimdiff exits cleanly. if [[ $EC == "0" ]] ; then cat $LCONFL > $MERGED fi # Always delete our temp files; Git will handle it's own temp files rm $LCONFL $RCONFL exit $EC
Now configure Git to use it as a mergetool by running the following commands in your shell:
git config --global merge.tool diffconflicts git config --global mergetool.diffconflicts.cmd 'diffconflicts vim $BASE $LOCAL $REMOTE $MERGE' git config --global mergetool.diffconflicts.trustExitCode true git config --global mergetool.diffconflicts.keepBackup false
If you wish to abort Vimdiff and signal to Git that the resolution is not
complete, exit Vimdiff with
:cq
. This tells Vim to exit with an
error code.
References
Related plugins
- Threesome by Steve Losh
- Conflict2Diff? Not sure if this actually does 3-way diff.
Comments
Feedback welcome!