Windows の各種 Git 環境で diff / merge に WinMerge を使うための設定

Git for Windows（msysGit）や Cygwin の Git で diff / merge ツールとして WinMerge を使う方法のメモです。両方の Git 環境を共存させている場合でも大丈夫ですし，SourceTree でも Git for Windows を利用していれば設定が反映されます。マージするときのペイン分割の仕方もいろいろ試してみました。

WSL での設定については[別の記事](https://qiita.com/amano41/items/3ff28e4d58da931049f3)にまとめました。基本的な部分はこの記事の Cygwin の設定と同じですが，ファイルシステムの問題やパスの取り扱いの違いに対応するために微妙に修正してあります。

* [WSL の Git で diff / merge に WinMerge を使うための設定 - Qiita](https://qiita.com/amano41/items/3ff28e4d58da931049f3)

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

### 基本の設定

`.gitconfig` に以下の内容を追加すると `git difftool` で WinMerge が使えるようになります。

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

[difftool]
	prompt = false

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

引数なしで実行した場合，左側ペインにインデックス（差分適用前）の内容，右側ペインにワーキングツリー（差分適用後）の内容が表示されます。それぞれのペインのタイトル部分は `git diff` を実行したときにあわせて `a/hoge.txt` - `b/hoge.txt` と表示されるようにしてみました。

![winmerge-difftool.png](https://qiita-image-store.s3.amazonaws.com/0/11255/a45a6559-fbc8-c50f-1582-5acf395dd705.png)

Git から渡される変数の意味は以下の通りです。詳しくは [git-difftool のドキュメント](https://git-scm.com/docs/git-difftool)を参照してください。

|変数|内容|
|----|----|
|`$LOCAL`|差分適用**前**の内容を保持する一時ファイルのパス|
|`$REMOTE`|差分適用**後**の内容を保持する一時ファイルのパス|
|`$MERGED`|差分をチェックしているファイルの名前|

WinMerge に指定しているオプションは以下の通りです。タイポなどの軽微な修正をその場ですぐできるように，右側ペインを読み取り専用にする `-wr` はつけない方がよいかもしれません（右側ペインにワーキングツリー上のファイルが表示されている場合，WinMerge 上で行った変更がそのまま反映されます）。オプションの詳細は [WinMerge のヘルプ](http://manual.winmerge.org/Command_line.html)を参照してください。

|オプション|説明|
|----|----|
|`-e`|ESC で終了|
|`-r`|すべてのサブフォルダ内のすべてのファイルを比較（再帰比較）|
|`-u`|左右のパスを最近使用したファイルのリストに追加しない|
|`-x`|同一ファイルを比較しようとした場合に終了|
|`-wl`|左側ペインを読み取り専用にする|
|`-wr`|右側ペインを読み取り専用にする|
|`-dl`|左側ペインのタイトルを設定する|
|`-dr`|右側ペインのタイトルを設定する|


### Cygwin ではパスの変換が必要

Cygwin ではパスの形式が違うため，上記の設定ではうまくいきません。`$LOCAL` や `$REMOTE` が /tmp 以下のファイルを指すことがあるため，WinMerge がファイルを見つけられず「不正なパス」とのエラーを表示します。Windows ネイティブアプリが理解できる形式に変換してやる必要があります。

UNIX 形式のパスを Windows 形式のパスに変換するために `cygpath` コマンドを使います。以下のようにコマンド置換を使ってパスを変換してください。バックスラッシュの代わりにスラッシュを使う `--mixed` 形式を指定し，念のため絶対パス `--absolute` で取得するようにしました。

```:.gitconfig
[difftool "WinMerge"]
	cmd = 'C:/Program Files/WinMerge/WinMergeU.exe' -e -r -u -x -wl -wr -dl \"a/$MERGED\" -dr \"b/$MERGED\" \"$(cygpath -am \"$LOCAL\")\" \"$(cygpath -am \"$REMOTE\")\"
```

なお，Git for Windows（msysGit）にも `cygpath` コマンドが同梱されていますので，この設定で Git Bash から実行した場合でも問題はありません。ひとつの `.gitconfig` ファイルで Cygwin と Git for Windows（SourceTree などから内部的に使用する場合も含む）を共存させることができるわけです。


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

### 基本の設定

WinMerge を `mergetool` として使いたい場合は `.gitconfig` で以下のように指定します。

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

[mergetool]
	prompt = false
	keepBackup = false

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

`$MERGED` には競合マーカーのついた**コンフリクトファイル**のパスが格納されます。WinMerge でコンフリクトファイルを開くと，競合マーカーをもとに内容を分割して，左側ペインに `--theirs` の内容（マージするブランチ）を，右側ペインに `--ours` の内容（マージを受け入れるブランチ）を表示してくれます。

![winmerge-mergetool1.png](https://qiita-image-store.s3.amazonaws.com/0/11255/7ab1de67-381d-ac5b-6f61-bdb7121bee9c.png)

### 共通祖先も表示する

競合を解決する際に，歴史が枝分かれする前はどうなっていたかを確認したい場合があります。マージ元・マージ先の他に共通祖先の内容も表示されていると便利です。共通祖先のペインを追加するには以下のように指定します。

```:.gitconfig
[mergetool "WinMerge"]
	cmd = 'C:/Program Files/WinMerge/WinMergeU.exe' -e -u -fr -ar -wl -wm -dl \"Base File\" -dm \"Theirs File\" -dr \"Mine File\" \"$BASE\" \"$REMOTE\" \"$LOCAL\" -o \"$MERGED\"
```

![winmerge-mergetool2.png](https://qiita-image-store.s3.amazonaws.com/0/11255/971242a6-99ff-bc13-409c-9437de8ea380.png)

左から順に，共通祖先（Base）・マージ元（Theirs）・マージ先（Mine）です。共通祖先とマージ元の内容を見ながら，必要な変更をマージ先に取り込む形で作業します。変数の意味は以下の通りです。詳しくは [git-mergetool のドキュメント](https://git-scm.com/docs/git-mergetool)を参照してください。

|変数|内容|
|----|----|
|`$BASE`|共通祖先の内容を保持する一時ファイルのパス|
|`$LOCAL`|マージ先（現在のブランチ）の内容を保持する一時ファイルのパス|
|`$REMOTE`|マージ元（マージするブランチ）の内容を保持する一時ファイルのパス|
|`$MERGED`|マージ結果を書き出すファイル（コンフリクトファイル）|

コンフリクトファイルはマージ結果の出力先として指定しているだけで，Git が行った 3 方向マージの結果は使っていません。このままだと競合しない変更も含めてすべての差分を手動で適用することになってしまうので，`-ar` オプションで WinMerge の自動マージ機能を使っています。自動マージを実行すると，マージ元（中央ペイン）からマージ先（右側ペイン）へ競合しない変更を自動で適用してくれます。

|オプション|説明|
|----|----|
|`-fr`|右側ペインにフォーカスを当てる|
|`-ar`|右側ペインに自動マージ（中央ペインの競合しない変更をマージ）|

### レイアウトを変えてみる

p4merge や vimdiff といった他の merge ツールにレイアウトをあわせてみます。中央に共通祖先（Base）を置いて，左側のマージ先（Mine）と右側のマージ元（Theirs）から変更を取り込むようにするなら以下のように指定します。

```:.gitconfig
[mergetool "WinMerge"]
	cmd = 'C:/Program Files/WinMerge/WinMergeU.exe' -e -u -fm -wl -wr -dl \"Mine File\" -dm \"Base File\" -dr \"Theirs File\" \"$LOCAL\" \"$BASE\" \"$REMOTE\" -o \"$MERGED\"
```

![winmerge-mergetool3.png](https://qiita-image-store.s3.amazonaws.com/0/11255/f050b182-31db-77e7-5814-0d66b5253ce2.png)

残念ながら WinMerge は 4 ペインに対応していないので，中央ペインの共通祖先の内容を書き換えていく形になっています。ベースとなるコミットをもとに，枝分かれしたブランチから差分を選び取っていくようなスタイルです。もちろん，WinMerge の自動マージ機能を使って左右のペインから競合していない変更を一括で取り込むことも可能です。

Git のマージを尊重するならば，中央にコンフリクトファイルを置くこともできます。

```:.gitconfig
[mergetool "WinMerge"]
	cmd = 'C:/Program Files/WinMerge/WinMergeU.exe' -e -u -fm -wl -wr -dl \"Mine File\" -dm \"Merge Result\" -dr \"Theirs File\" \"$LOCAL\" \"$MERGED\" \"$REMOTE\" -o \"$MERGED\"
```

![winmerge-mergetool4.png](https://qiita-image-store.s3.amazonaws.com/0/11255/ecf3191e-c0db-1a98-dc36-f8ba33c7bb60.png)

## SourceTree から利用する場合

以下の条件を満たせば SourceTree でも `.gitconfig` に書いた設定で WinMerge が利用できます。

- Git for Windows を使用する
- 外部 Diff / Merge ツールに「システム標準」を選択する（またはグローバル設定ファイルの変更を許可しない）

Git for Windows を使用することは必要条件ではないかもしれません。内蔵 Git を使用している場合どうなるかは未確認です。外部 Diff / Merge ツールの選択肢には「WinMerge」もありますが，これを選ぶと `.gitconfig` に SourceTree 独自の設定を書き込んでそれを使用するようです。


## 参考

- [git の差分比較・マージを WinMerge で行う - Qiita](http://qiita.com/kobake@github/items/fb317b4fdacad718a4b2)
- [Cygwin上のGitからWindowsのテキストエディタとWinMergeを利用する。 - Qiita](http://qiita.com/nekoyamada/items/7004efadd2ef56a2463d)
- [Git で WinMerge するときの設定+α - Qiita](http://qiita.com/tsuyoshi_cho/items/80901b31f609c85e8ee0)
- [Git - git-difftool Documentation](https://git-scm.com/docs/git-difftool)
- [Git - git-mergetool Documentation](https://git-scm.com/docs/git-mergetool)
- [WinMerge 日本語版 - 3方向マージ機能（2.14.0-jp-29以降）](http://www.geocities.co.jp/SiliconValley-SanJose/8165/winmerge.html#3way-merge)