Using Emacs Ediff as Git Merge/Difftool

Table of Contents

Overview

Ediff is «a comprehensive visual interface to Unix diff and patch
utilities»1 that comes with your Emacs. This document shows how
to use Ediff with Git for resolving merge conflicts and inspecting
differences between file versions.

The solution described here uses a wrapper-shell-script that is called by
git mergetool and git difftool. Arguments for the script are provided
by Git via the appropriate Git configuration.

Implementation

As the Git mergetool and difftool help describes, it is possible to define
custom merge and diff tool commands. Although it would be feasible to
define an emacsclient command directly in the Git configuration, I prefer
to use a shell script. This makes it easier to decide which Ediff function
to execute and which exit code to return. The script is called with the
appropriate arguments from git mergetool or git difftool as defined in
the Git configuration.

Ediff Functions and Arguments

There are basically three different cases that the script must handle by
executing a specific Ediff function with the proper arguments:

Git diff
A simple diff, triggered by git difftool should execute ediff with
the diff pre- and post-image as arguments.

Git merge without ancestor
A merge without ancestors occurs for instance if you merge two branches
and those two branches have created the same path/file independently,
i.e. without a common base version. In this case ediff-merge-files
should be executed with three arguments: The version of the file in
the current branch, the version of the file in the branch to be merged
from and the target file to write the merged version to.

Git merge with ancestor
Often the conflicting versions of a file have a common earlier
version, their ancestor or base. When an ancestor is available
ediff-merge-files-with-ancestor should be executed with four
arguments: The version of the file in the current branch, the version
of the file in the branch to be merged from, the target file to
write the merged version to and the base version of the file.

Caveats

To perform the check for conflict markers (line 45ff) the script needs
to wait for emacsclient to finish. This means that you cannot use
emacsclient with the option –no-wait.

If you desperately need that option and want to add it anyway (in
line 38) then you should remove the check for conflict markers (line
44-52) as it would always be successful. Additionally, you must set
mergetool.ediff.trustExitCode to false.

If conflict markers are found after emacsclient has returned, the script
exits with code 1. With mergetool.ediff.trustExitCode set to true
Git will then restore the original (conflict) version of the file, thus
throwing away everything you've done in Emacs. That's okay if you
wanted to start over anyway. However, losing your edits may not be
desired in some cases (e.g. you just forgot to remove a conflict
marker). Therefore, the script saves your edited version away before
exiting so that you can always retrieve what you've done (see line
46f).