Improvements in git 2.37 when resolving conflicts with vimdiff
-日付 2022 Jun 26- English EspañolIf you use git to work with code repositories, you will be used to the following situation:
git push
, but it fails because it tells
you that your changes are in the same part of the code that someone else has
also modified since you downloaded the repository in (1) and while you were
working in (2).When this happens, you use git mergetool
to open an auxiliary tool that allows
you to “resolve the conflict” (i.e. to keep either your changes or those of the
other person who committed before you, or a mix of both -which you will have to
manually merge-).
git mergetool
ends up running the first (pre-installed) conflict resolution
tool it finds among many options (such as meld
, kdiff3
, xxdiff
, …, or
vimdiff
).
You can also force a specific tool to be used by means of the merge.tool
configuration variable or by using git mergetool --tool=xxx
, where xxx is
the name of the tool to use.
Up to git version 2.36 (which is the current version at the time of this
writing, June 2022), when using vimdiff
you have several options:
git mergetool --tool=vimdiff
git mergetool --tool=vimdiff1
git mergetool --tool=vimdiff2
git mergetool --tool=vimdiff3
In all cases vim
opens several windows, but their layout is different.
With the regular --tool=vimdiff
four windows appear distributed in two rows:
This is the result:
------------------------------------------
| | | |
| LOCAL | BASE | REMOTE |
| | | |
------------------------------------------
| |
| MERGED |
| |
------------------------------------------
We can see it in a real example below:
In this case, by comparing “base” with “local” on the one hand and “base” with
“remote” on the other, we see that in essence both we and some other developer
have tried to make the same change at the same time (ie. to add “polkit” to the
list of packages to install) but in a different order (this can also be seen in
the “merge” window, where the lines marked with <<<<<<<
, =======
and
>>>>>>>
are used to indicate the changes coming from each place).
What we would have to do in this case is to remove the entire block between
<<<<<<<
and >>>>>>>
in the “merged” window and keep only one of the lines
that call pacman
… and then simply save the changes, go back to the terminal
and run git rebase --continue
.
With --tool=vimdiff1
the windows layout that opens in vim
is different. This
time there are only two windows: the one on the left with our (local) changes
and the one on the right with the remote changes:
------------------------------------------
| | |
| | |
| | |
| LOCAL | REMOTE |
| | |
| | |
| | |
------------------------------------------
Continuing with the example, this is what we see this time:
Next we try with --tool=vimdiff2
. Now the layout looks like this:
------------------------------------------
| | | |
| | | |
| | | |
| LOCAL | MERGED | REMOTE |
| | | |
| | | |
| | | |
------------------------------------------
And finally, --tool=vimdiff3
, which only shows the “merged” window:
------------------------------------------
| |
| |
| |
| MERGED |
| |
| |
| |
------------------------------------------
git
version 2.37 will be released next week (July 2022). It includes a change
of mine (yahooo!) that makes it possible to specify an arbitrary window layout
inside vim
. This way it will no longer be necessary to create
--tool=vimdiff4
, --tool=vimdiff5
, --tool=vimdiff6
, etc… every time
someone discovers a new use case.
Hint: The original changeset I intended to commit upstream was actually a new
variant (--tool=vimdiff4
) with a new layout that I found more convenient. But
developers noted this was starting to get a little out of hands and that’s when
it was decided to implement this new generic mechanism (that makes any layout
possible) instead.
It works as follows:
git mergetool --tool=vimdiff
).mergetool.vimdiff.layout
exists, the window
layout specified there will be used. Otherwise, the same layout from git 2.36
will be used (i.e. two rows of windows: top row with “local”, “base” and
“remote” and bottom one with “merged”).Also, to maintain backwards compatibility, git mergetool --tool=vimdiff1
, git mergetool --tool=vimdiff2
and git mergetool --tool=vimdiff3
will continue to
exist and behave as before (although the same result could be obtained using
--tool=vimdiff
and setting the mergetool.vimdiff.layout
variable to the
corresponding value).
So… how do we use the new configuration variable --mergetool.vimdiff.layout
?
The syntax is explained in git help mergetool
(in section “BACKEND SPECIFIC
HINTS):
Let’s see some simple examples:
layout = "LOCAL,BASE,REMOTE / MERGED"
------------------------------------------
| | | |
| LOCAL | BASE | REMOTE |
| | | |
------------------------------------------
| |
| MERGED |
| |
------------------------------------------
layout = "LOCAL,BASE,REMOTE"
------------------------------------------
| | | |
| | | |
| LOCAL | MERGED | REMOTE |
| | | |
| | | |
------------------------------------------
layout = "@LOCAL,REMOTE"
------------------------------------------
| | |
| | |
| | |
| LOCAL | REMOTE |
| | |
| | |
| | |
------------------------------------------
Personally I have always used --tool=vimdiff
and in general it has worked very
well when dealing with simple conflicts.
The problem with more complicated conflicts is that it is not easy to see what
the differences between “base” and “local” and between “base” and “remote” are
because of how vim
always shows “three-way” changes.
Thanks to the new mechanism in git 2.37 we can now make vim
show, for example,
three tabs as follows:
git mergetool --tool=vimdiff
.The nice thing about tabs (2) and (3) is that, because there are only two files,
the “diff mode” of vimdiff
clearly shows the differences between one and
the other, without being “affected” by the presence of a third file.
The result is this:
------------------------------------------
| <TAB #1> | TAB #2 | TAB #3 | |
------------------------------------------
| | | |
| LOCAL | BASE | REMOTE |
| | | |
------------------------------------------
| |
| MERGED |
| |
------------------------------------------
------------------------------------------
| TAB #1 | <TAB #2> | TAB #3 | |
------------------------------------------
| | |
| | |
| | |
| BASE | LOCAL |
| | |
| | |
| | |
------------------------------------------
------------------------------------------
| TAB #1 | TAB #2 | <TAB #3> | |
------------------------------------------
| | |
| | |
| | |
| BASE | REMOTE |
| | |
| | |
| | |
------------------------------------------
…which looks like this in our example code:
To achieve this effect all we have to do is to add this line to our git configuration file:
[mergetool "vimdiff"]
layout = "LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE".
I personally like to go a step further and add a fourth tab that shows the same as the first but in two columns (instead of two rows), which is useful when working with files with long lines:
[mergetool "vimdiff"]
layout = "LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL/BASE/REMOTE) , MERGED"
And all this proves once again that vim
is the ultimate tool, since no other
one (that I know of) can do the same thing today ;)
git help mergetool