WSL の Git で diff / merge に WinMerge を使うための設定

WSL の Git で WinMerge を diff / merge ツールとして使用する方法のメモです。基本的な考え方は [Cygwin の場合](https://qiita.com/amano41/items/7c4be8d44cbff6417f7e)と同じですが，ファイルシステムの問題やパスの取り扱いの違いに対応するため，以下のような変更・追加をしてやる必要があります。

* WinMerge のパスを WSL から見たものにする。
* パスの変換には `cygpath` ではなく `wslpath` を使用する。
* `$TMPDIR` を設定して一時ファイルの出力先を Windows のファイルシステム（DrvFs）上のディレクトリにする。
* `--dir-diff` には `--no-symlinks` を併用する。


## 外部 diff ツールとしての設定

### Git Config

WSL の Git で WinMerge を diff ツールとして使うには `.gitconfig` に以下の内容を追加します。Git から渡される変数（`$MERGED` など）や WinMerge に指定しているオプションの意味は[以前に投稿した記事](https://qiita.com/amano41/items/7c4be8d44cbff6417f7e)を参照してください。

```:.gitconfig
[diff]
	tool = WinMerge

[difftool]
	prompt = false

[difftool "WinMerge"]
	cmd = '/mnt/c/Program Files/WinMerge/WinMergeU.exe' -e -r -u -x -wl -wr -dl \"a/$MERGED\" -dr \"b/$MERGED\" \"$(wslpath -am \"$LOCAL\")\" \"$(wslpath -am \"$REMOTE\")\"
	trustExitCode = false
```

この設定で `git difftool` を引数なしで実行すると，左側ペインにインデックス（差分適用前）の内容が，右側ペインにワーキングツリー（差分適用後）の内容が表示されます。

![001.png](https://qiita-image-store.s3.amazonaws.com/0/11255/e5e5c156-4fc0-3cea-f754-af9581ad452e.png)

[Cygwin で WinMerge を使う場合](https://qiita.com/amano41/items/7c4be8d44cbff6417f7e)との違いは以下の通りです。

* WinMerge の実行ファイルのパスが WSL のものになっている。
* パスの変換に `cygpath` ではなく `wslpath` を使用している。

WSL から見た UNIX 形式のパスを Windows 形式のパスに変換するには `wslpath` コマンドを使います。使い方は `cygpath` とほとんど同じです。ここではバックスラッシュの代わりにスラッシュを使う `-m` オプションと結果を絶対パスで返す `-a` オプションを指定しています。`wslpath` は `-h` や `--help` を付けても使い方が表示されないのですが，[Build 17046 の Release Notes](https://docs.microsoft.com/ja-jp/windows/wsl/release-notes#build-17046) に簡単な説明があります。


### $TMPDIR の設定

`git difftool` は，比較対象のファイルを `/tmp` にチェックアウトして，その一時ファイルのパスを外部ツールに渡します。チェックアウト先の `/tmp` は WSL のファイルシステム（VolFs）上にあるため，Windows ネイティブアプリである WinMerge からは直接アクセスできませんし，`wslpath` もパスの変換に失敗してしまいます。

```shell-session
$ git difftool
wslpath: /tmp/XXXXXX_sample.txt: Result not representable
```

![002.png](https://qiita-image-store.s3.amazonaws.com/0/11255/4169b7e1-4468-327f-112e-ee5b57ce30bd.png)

WinMerge からアクセスできるように，一時ファイルのチェックアウト先を Windows のファイルシステム（DrvFs）上のディレクトリに変更してやる必要があります。

一時ファイルの出力先は `$TMPDIR` 環境変数で指定することができます。`.bash_profile` などに以下の記述を追加してください。バックスペースや `CR` の処理を挟むので少し長くなっていますが，Windows の環境変数 `%TMP%` の値を読みだして WSL 形式のパスに変換してから `$TMPDIR` に設定しているだけです。

```bash:.bash_profile
export TMPDIR=$(cmd.exe /C 'echo %TMP%' | sed -e 's/\\/\\\\/g' | tr -d '\r' | xargs wslpath -au)
```

あらかじめ Windows の環境変数として `%TMPDIR%` を定義しておいて，[Build 17063](https://docs.microsoft.com/ja-jp/windows/wsl/release-notes#build-17063) で導入された `%WSLENV%` を使って WSL に引き継いでもよいと思います。たとえば以下のように Windows の環境変数を設定します。`%WSLENV%` の `/u` フラグは Windows から WSL へ環境変数を引き継ぐことを，`/p` フラグは環境変数の値はパスであるため変換が必要なことをそれぞれ意味しています。

| 環境変数 | 値        |
| -------- | --------- |
| TMPDIR   | %TEMP%    |
| WSLENV   | TMPDIR/up |


### ディレクトリ比較の設定

`git difftool` には `--dir-diff` というオプションがあり，ディレクトリ比較をすることができます。通常，`git difftool` はファイルを 1 つずつ順番に開いて差分を表示していきますが，`--dir-diff` オプションを付けるとディレクトリ比較となり，変更のあるファイルがツリー形式で一覧表示されるようになります。もちろん，必要であれば個別のファイルを開いて差分を確認することもできます。

![003.png](https://qiita-image-store.s3.amazonaws.com/0/11255/35fe8f3d-9818-4f4e-6565-27b44f67c43a.png)

ディレクトリ比較を実行すると，一時ディレクトリに `left`，`right` というディレクトリが作成され，変更のあったファイルがチェックアウトされます。このとき，`right` の内容がワーキングツリーと一致していれば，ファイルのコピーではなくシンボリックリンクが作られます。

この仕様はワーキングツリーのファイルをその場で修正できるため便利なのですが，WSL から WinMerge を使う場合には問題が起こります。WSL で作成されたシンボリックリンクは Windows ネイティブアプリの WinMerge からは見えないため，ファイルが見つからずエラーになってしまうのです。

![004.png](https://qiita-image-store.s3.amazonaws.com/0/11255/d0d9318c-ac7f-ee78-d630-62923c855bd8.png)

この問題を解決するためには，`--dir-diff` と同時に `--no-symlinks` を指定して，シンボリックリンクを作るのではなく，ファイルをコピーするよう Git に指示します。以下のように alias に登録してしまうとよいと思います。

```:.gitconfig
[alias]
	dd = "difftool --dir-diff --no-symlinks"
	ddc = "difftool --dir-diff --cached --no-symlinks"
```

なお，Windows 環境では `--no-symlinks` がデフォルトになっているため，Git for Windows や Cygwin の Git ではこの問題は起こりません。


## 外部 merge ツールとしての設定

merge ツールの設定は以下の通りです。実行ファイルのパスが WSL のものになっている以外は [Cygwin での設定](https://qiita.com/amano41/items/7c4be8d44cbff6417f7e)とまったく同じです。

```:.gitconfig
[merge]
	tool = WinMerge

[mergetool]
	prompt = false
	keepBackup = false

[mergetool "WinMerge"]
	cmd = '/mnt/c/Program Files/WinMerge/WinMergeU.exe' \"$MERGED\"
	trustExitCode = false
```

競合マーカーをもとに内容を分割して，左側ペインに `--theirs` の内容（マージするブランチ）を，右側ペインに `--ours` の内容（マージを受け入れるブランチ）を表示します。

![005.png](https://qiita-image-store.s3.amazonaws.com/0/11255/b4eb19f5-f835-0b97-b55b-1bfb4a6e2838.png)


## 参考

* [Windows の各種 Git 環境で diff / merge に WinMerge を使うための設定 - Qiita](https://qiita.com/amano41/items/7c4be8d44cbff6417f7e)
* [gitの差分比較をWinMergeでしたい！(Bash on Ubuntu on Windows編) \- Qiita](https://qiita.com/forest1/items/fbf952273bfffe8b5334)
* [WSL(Ubuntu 18.04)環境のgitでWinMergeを使う方法 - Qiita](https://qiita.com/forest1/items/334b5d756b5696c63331)
* [git difftool --dir-diff が便利すぎて泣きそうです - てっく煮ブログ](http://tech.nitoyon.com/ja/blog/2013/07/02/git-dir-diff/)
* [Git - git-difftool Documentation](https://git-scm.com/docs/git-difftool)