Using vimdiff with dumb paths

Posted on Tue 02 February 2016 in programming

I've been loving vimdiff as my git difftool for a while.

Vimdiff and Matlab packages

Vimdiff runs into some problems when working with Matlab code. Matlab considers directories with names beginning with + as "packages", a natural way to organize projects. However, this means that many paths relative to the git top-level directory begin with +. Of course, this applies to any path beginning with a +, though this is otherwise an uncommon naming convention.

Suppose that your project looks like this:

+code/
  foo.m
  bar.m

You might want to use vim to diff your two files:

vimdiff +code/foo.m +code/bar.m

But vim will tell you:

Error detected while processing command line:
E492: Not an editor comamnd: code/foo.m
E492: Not an editor comamnd: code/bar.m

What's going on? Vim interprets + as the start of a command-line argument (beyond the typical -) and fails to find options/commands called code/foo.m and code/bar.m.

The vimdiff solution

Simply tell vimdiff somehow that the paths you are specifying are not options. (By the way, note that vimdiff is pretty much just an alias for vim -d.)

We could prefix them with the current directory:

vimdiff ./+code/foo.m ./+code/bar.m

Better yet, note that the -- option

Denotes the end of the options. Arguments after this will be handled as a file name. This can be used to edit a filename that starts with a '-'.

Or, in our case, to edit a filename that starts with a +:

vimdiff -- +code/foo.m +code/bar.m

Bringing git into the picture

We now update +code/foo.m and want to compare to the previous commit. Using git's difftool command, we diff all modified git-tracked files using a tool of our choice:

git difftool --tool=vimdiff

Unfortunately, we run into the same problem as before.

"/private/var/folders/7v/cjvpqzt57c5fsb7qvsjmm5sw0000gn/T/7i6IKG_foo.m" [readonly] 1L, 6C
Error detected while processing command line:
E492: Not an editor comamnd: code/foo.m

Since you haven't specified the file names yourself, you can't use either of the solutions from the previous section!

The git solution

Git is basically writing the "before" state of the file to a temporary location and invoking a typical vimdiff command. If we can modify this command, we can use one of our solutions above.

One possibility is to set git's difftool.<tool>.cmd config option:

Specify the command to invoke the specified diff tool. The specified command is evaluated in shell with the following variables available: LOCAL is set to the name of the temporary file containing the contents of the diff pre-image and REMOTE is set to the name of the temporary file containing the contents of the diff post-image.

We can do

git config --global difftool.vimdiff.cmd 'vimdiff -- $LOCAL $REMOTE'

That'll do it. While you're at it, why not update your ~/.gitconfig with the following handy diff-related settings:

[alias]
    d   = difftool
[diff]
    tool = vimdiff
[difftool]
    prompt = false
[difftool "vimdiff"]
    cmd = vimdiff -- $LOCAL $REMOTE

Most people will likely never come across this problem: none of their paths are this crazy, they use a different (read: graphical) difftool, they even use an external diff wrapper. But this simple solution is all I need.