.eu:~/bloggit-vimdiff/ clp txr pht lnk blg rcm src vid  

Improvements in git 2.37 when resolving conflicts with vimdiff

-日付 2022 Jun 26- English Español

Git: conflict resolution with vimdiff

If you use git to work with code repositories, you will be used to the following situation:

  1. You download the repository.
  2. You make changes to some files.
  3. When you are done, you try to do a 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.

vimdiff with git version <= 2.36

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:

  1. git mergetool --tool=vimdiff
  2. git mergetool --tool=vimdiff1
  3. git mergetool --tool=vimdiff2
  4. 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:

git mergetool --tool=vimdiff
git mergetool --tool=vimdiff

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:

git mergetool --tool=vimdiff1
git mergetool --tool=vimdiff1

Next we try with --tool=vimdiff2. Now the layout looks like this:

------------------------------------------
|             |           |              |
|             |           |              |
|             |           |              |
|   LOCAL     |   MERGED  |   REMOTE     |
|             |           |              |
|             |           |              |
|             |           |              |
------------------------------------------
git mergetool --tool=vimdiff2
git mergetool --tool=vimdiff2

And finally, --tool=vimdiff3, which only shows the “merged” window:

------------------------------------------
|                                        |
|                                        |
|                                        |
|                 MERGED                 |
|                                        |
|                                        |
|                                        |
------------------------------------------
git mergetool --tool=vimdiff3
git mergetool --tool=vimdiff3

vimdiff with git version >= 2.37

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:

  1. When calling “mergetool”, do it as usual (git mergetool --tool=vimdiff).
  2. If the configuration variable 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        |
|                  |                     |
|                  |                     |
|                  |                     |
------------------------------------------

New possibilities!

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:

  1. The first tab shows the same it was showing when using git mergetool --tool=vimdiff.
  2. The second tab contains only two windows: on the left the “base” file and on the right the “local” file.
  3. The third tab contains only two windows: on the left the “base” file again and on the right the “remote” file.

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:

Tab #1
Tab #1
Tab #2
Tab #2
Tab #3
Tab #3

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 ;)

Notes

Comments? Drop me a line! blog@u92.eu