George Brocklehurst makes things for the internet.

Git and Vim

Posted on

A lot has been written about plugins like Fugitive.vim that help you use Git from within Vim. Here I'm going to be looking at it from the other side; how can Vim help with common Git tasks? I'll start with how to set Vim as the default editor for Git, move on to Git's built-in support for viewing diffs and resolving merge conflicts using Vim and GVim, and then finally look at how Git's built in tools can be configured to use MacVim or any other tool you like.

The basics: Setting Git's default editor

The most basic level of integration is using Vim for Git commands that launch an editor, commands like commit and rebase -i. Git determines which editor to use by checking its own configuration first (the $GIT_EDITOR environment variable, and then the core.editor configuration variable), but if they're not set it falls back to the standard UNIX environment variables ($VISUAL and then $EDITOR), before finally defaulting to vi.

My $VISUAL editor is set to MacVim, but I prefer to use plain old Vim for Git commands so I don't have to leave the terminal I'm working in. Git prefers $VISUAL to $EDITOR, so I override the default behaviour by setting Git's core.editor configuration variable. Set it using the git config command:

$ git config --global core.editor `which vim`

The --global flag indicates that this change should apply to all repositories, if you omit this flag and issue the command from a directory that is a Git repository then the setting will only apply to that one repository.

Diff and merge with Vim

Vim's diff mode

One of the things I was delighted to discover when I switched from TextMate to Vim was Vim's powerful diff mode.

You can launch Vim in diff mode with vim -d file1 file2 or vimdiff file1 file2 (gvimdiff and mvimdiff also work, if you're using GVim or MacVim respectively). When you do, you are presented with a Vim session split vertically into multiple windows, each showing one of the files you specified at the command line with the differences highlighted. As you scroll around and make changes in any one of the windows, the other windows scroll and update in real time, so you're always seeing accurate and up-to-date diff information right next to the line you're editing.

This seemed like the perfect tool for viewing Git diffs, and resolving merge conflicts so I did a little research and came across two Git commands I hadn't previously encountered: difftool and mergetool.

Git difftool and mergetool

git difftool is very similar to git diff and supports the same parameters, the difference is that the results aren't printed to the standard output, instead they're sent to a specialised diff visualisation tool. git mergetool is used to resolve merge conflicts by sending each conflicted file in turn to a specialised conflict resolution tool.

Both of these commands support a variety of tools, including opendiff (which isn't directly relevant to a discussion of using Git with Vim, but on OS X will launch the excellent FileMerge utility) and of course vimdiff and gvimdiff. You can specify which tool to use with the -t parameter, for example:

$ git difftool -t vimdiff

If you don't want to specify the tool to use every time you can set Git's diff.tool and merge.tool configuration variables:

$ git config --global merge.tool vimdiff
$ git config --global diff.tool vimdiff

While you're there, you might also want to suppress the Hit return to launch 'vimdiff' prompt:

$ git config --global mergetool.prompt false
$ git config --global difftool.prompt false

Additional configuration

There are some additional options for both tools that I find useful. First of all, git difftool allows you to specify a second, GUI based tool using the diff.guitool variable. You can use the GUI tool by invoking git difftool with the -g option. I use vimdiff as my diff.tool and MacVim as my diff.guitool (there's more on how to use MacVim later).

Secondly, by default git mergetool will create a backup copy of whichever files you're merging. You can remove these manually when you're finished with either git clean or plain old rm, but if you want git mergetool to clean up after itself you can set the mergetool.keepBackup option to false.

Using MacVim

While git mergetool and git difftool both have baked in support for Vim and GVim, they don't support MacVim without a little extra configuration.

The quick fix

The easiest way to use MacVim is just to edit Git's gvimdiff path so it points to the mvimdiff binary instead:

$ git config --global difftool.gvimdiff.path `which mvimdiff`
$ git config --global mergetool.gvimdiff.path `which mvimdiff`

Now you can set the merge.tool and diff.tool or diff.guitool variables to gvimdiff, or use the -t gvimdiff argument, and Git will launch MacVim instead of GVim.

A custom mergetool

If you want to customise things further, and not just modify the executable that git mergetool calls, but also change the parameters, you will need to define a custom mergetool.

You can do this from the command line using git config, but I think it's easier to do this kind of complex configuration by editing your Git config file directly. For global settings you can edit ~/.gitconfig, or for a specific project edit .git/config from the repository's root directory.

Add the following to your Git config file:

[mergetool "toolname"]
  cmd = command

You can then use your custom toolname with the -t option or in the merge.tool variable.

The following variables are available to use in your command, each is the path to a particular version of the file that is being merged:

$MERGED
This is the version of the file that you should edit; it is the version of the file in your working copy of the repository, so it's what you would be editing anyway if you were resolving a merge conflict without the help of git mergetool.
$LOCAL
This is the file as it was on your local branch before you started the merge.
$REMOTE
This is the file as it is on the remote branch, that is the branch you are merging into your local branch.
$BASE
This is the base of the merge, that is the file as it was before the two branches introduced incompatible changes.

For example, my MacVim configuration looks like this:

[mergetool "mvimdiff"]
  cmd = /usr/local/bin/mvimdiff -f "$LOCAL" "$MERGED" "$REMOTE"

Note the -f flag; this tells MacVim to run in the foreground. When you close your chosen tool git mergetool will stage the file for you if you've made changes, or will prompt you to stage it with the question filename seems unchanged. Was the merge successful? In either case, you can still make further changes before you commit, mergetool only stages your changes, it doesn't commit them. Without the -f flag you can't take advantage of this behaviour.

A custom difftool

The configuration for a custom difftool is very similar. As with a custom mergetool, you add the following to your Git config file:

[difftool "toolname"]
  cmd = command

You can use your custom toolname in either the -t option, or the diff.tool and diff.guitool configuration variables.

The command can contain all of the same variables as a mergetool command, although in this case $BASE and $MERGED will both contain the path of the current working version.

Copy and paste

Here are some edited highlights from my ~/.gitconfig file which capture most of the configuration options discussed above. You may need to change some paths for this to work on your system.

[core]
  editor = /usr/bin/vim

[mergetool "mvimdiff"]
  cmd = /usr/local/bin/mvimdiff -f "$LOCAL" "$MERGED" "$REMOTE"

[merge]
  tool = mvimdiff

[mergetool]
  prompt = false
  keepBackup = false

[difftool "gvimdiff"]
  path = /usr/local/bin/mvimdiff

[diff]
  tool = vimdiff
  guitool = gvimdiff

[difftool]
  prompt = false

1 Comment

Luba commented on :

Sorry for being off topic, but I just discovered http://javascriptweeklyfeed.heroku.com/ and subscribed to both JS and Ruby Weekly. Thank you! I wonder why there is such a disdain for web feeds. Using email for broadcast is wrong. Bulk emails always end up feeling like spam.