External Diffのはなし

**これは[TortoiseHg Advent Calender 2012](http://connpass.com/event/1504/) 18日目の記事です**

※以下の内容は、Windowsのみを対象にしています。LinuxやMacの場合はいろいろ違うところがあると思います。

**2012.12.27 修正**
diffargsのプレースフォルダがまちがっていたので訂正しました
$plabel → $plabel1

## イントロダクション
Merucurialで外部Diffツールを使いたいときは、ExtDiff Extensionを使って、こんな感じで設定する訳ですが、

```ini:hgrc
[extdiff]
# 外部Diffツールのパスを指定
cmd.winmerge = C:\Program Files\WinMerge\WinMergeU.exe
# 渡すオプションを指定
opts.winmerge = /s /e
```

TortoiseHgもこの設定は見てくれるのですが、TortoiseHgの場合だと、Diffツールがインストールしてさえいれば、何の設定もしなくても使えるようになってますよね？
あれはどこに設定されているんでしょうか？

ぐぐってもなかなかそれらしい情報は見つからないんですが、TortoiseHgのインストールフォルダをGrepしてみると、どうやら `hgrc.d\MergeTools.rc` というファイルに書かれているようです。
以下一部抜粋。

```ini:MergeTools.rc
[merge-tools]
; Windows version of BeyondCompare 3
beyondcompare3.priority=-1
beyondcompare3.args=$local $other $base /mergeoutput=$output /ro /lefttitle=parent1 /centertitle=base /righttitle=parent2 /outputtitle=merged /automerge /reviewconflicts /solo
beyondcompare3.premerge=False
beyondcompare3.regkey=Software\Scooter Software\Beyond Compare 3
beyondcompare3.regkeyalt=Software\Wow6432Node\Scooter Software\Beyond Compare 3
beyondcompare3.regname=ExePath
beyondcompare3.gui=True
beyondcompare3.diffargs=/lro /lefttitle='$plabel1' /righttitle='$clabel' /solo /expandall $parent $child
beyondcompare3.diff3args=$parent1 $parent2 $child /lefttitle='$plabel1' /centertitle='$clabel' /righttitle='$plabel2' /solo /ro
beyondcompare3.dirdiff=True

```

`ツール名.オプション = 値` という形式で指定していく訳ですね。
どんなオプションがあって、それぞれ指定するとどうなるのかをちょっと見てみました。

## merge-toolsオプション

[2012.12.19 追記] この辺のオプションについては、 `hg help config` にけっこうしっかり書いてましたｗ 気づいてなかった・・・

### **実行ファイルの検索につかう設定**
#### **regkey, regkeyalt, regname, regappend**
ツールのフルパスを探し出すためのレジストリ情報を指定します。こんな感じのルールみたい。

1. HKEY_LOCAL_MACHINE\{regkey} の、{ragname} の値をフルパスとして取得
   {regname} が指定されていない場合は(規定)の値？
   なければHKEY_CURRENT_USERも見る。

2. {regkey}に該当するキーがなければ、{regkeyalt}の方で試す

3. {regappend}が指定されている場合は、その値をフルパスに追加する
   (レジストリにはフォルダパスが記述されていて、exeファイル名を付加しないとならない場合に使う)


#### **executable**
レジストリからフルパスを取れない場合はこっちで直接フルパスを指定します。

### **差分表示に関する設定**
#### **diffargs**
差分表示の時に渡す引数です。
使えるプレースホルダは、以下の通り

|プレースホルダ          |説明                       |
|:-----------------------|:--------------------------|
|$parent または $parent1 |比較元のパス               |
|$child                  |比較先のパス               |
|$plabel1                |比較元の表示名             |
|$clabel                 |比較先の表示名             |

#### **diff3args**
3way差分表示の時に渡す引数です。
3way差分表示に対応しているツールの場合は、マージリビジョンを選択して「親リビジョンとの差分」を表示したときに両方の親との差分が一度に見れる訳ですね。
※3way未対応の場合は１つ目の親リビジョンとの差分だけが表示されます。

使えるプレースホルダは、こうなります。

|プレースホルダ |説明                       |
|:--------------|:--------------------------|
|$parent1       |比較元1のパス              |
|$parent2       |比較元2のパス              |
|$child         |比較先のパス               |
|$plabel1       |比較元1の表示名            |
|$plabel2       |比較元2の表示名            |
|$clabel        |比較先の表示名             |

#### **dirdiff**
True or Falseを指定します。
ディレクトリどうしの比較ができる場合はTrueを指定します。

リビジョン単位での差分をみる場合、ディレクトリ比較ができるツールの場合は直接ツールが起動されます。
できないツールの場合は、代わりにこんな画面が表示されます。
![visdiff](http://gyazo.com/896512850fb451b2ec431bd42f2cf919.png)

#### **dir3diff**
dirdiffの3way版のようです。

#### **usewin**
Trueを指定すると、dirdiff、dir3diffがTrueの場合でも上の「GUI差分表示」画面が表示されるようになります。


### **マージに関する設定**

#### **args**
マージの時に渡す引数です。

|プレースホルダ |説明                       |
|:--------------|:--------------------------|
|$base          |分岐元のファイルのパス     |
|$local         |マージされるファイルのパス |
|$other         |マージするファイルのパス   |
|$output        |出力ファイルパス           |

#### **gui**
GUIのあるツールの場合はTrueです。バッチで処理する時に間違ってGUIが起動されてしまわないようにするガード用です。

#### **binary**
バイナリファイルのマージができるツールの場合はTrueです。

#### **fixeol**
改行コードを勝手に変えちゃう可能性のあるツールの場合は、Trueを指定しておくとマージ後に直してくれるようです。

#### **checkchanged**
Trueを指定すると、処理後にファイルが変更されているかどうかのチェックを行います。

#### **checkconflicts**
Trueを指定すると、処理後にコンフリクトマーカーが消えているかどうかのチェックを行います。

#### **premerge**
Trueを指定すると、ツール起動前にinternal:merge相当の処理を実行して、失敗したときだけツールを起動するようになります。
Falseを指定した場合はいきなりツールを起動します。

と思いきや、 `keep` という文字列を指定することもできるようになっているようです。
`keep` を指定すると、Trueを指定した時と同じように先に internal:merge を実行するのですが、失敗した場合に、 **出力ファイルにコンフリクトマーカを埋め込んだままで** マージツールを起動します。
(Trueを指定した場合は、マージツール起動前に出力ファイルはinternal:merge前の状態に戻されます)

**これを有効にした場合、 checkchangedではマージが成功したかどうか判断できなくなるので、適切な戻り値を返さないツールについてはcheckconflictsもあわせて指定する必要があります。**


## 設定の調整
という訳で、もしプリセットされたツール設定が気に入らない場合は、このファイルから Mercurial.iniとかに設定をコピーしてきて、そこで修正すればＯＫです。

試しに、 僕が主に使っているWinMergeUの設定をいろいろ変えてみます。

### 起動オプションの変更
diffargに、 `/e` というオプションが指定されています。
これは、 `Esc`キーでプログラムを終了するという指定なのですが、ファイル単位の比較の時はともかくディレクトリ比較の時にはとても使いづらい(いくつもファイルを開くので、`Esc`キーでは現在のファイルを閉じるだけにしてほしい)ので、このオプションを外します。

### GUI差分表示画面の利用
ディレクトリ比較で直接ツールが起動されるのは確かに手軽なんですが、実は僕はあまり好きではありません。
というのは、そのモードだと、作業領域とあるリビジョンを比較したときに、ツール上で作業領域のファイルを修正することができなくなってしまうんですね。
(ディレクトリ比較モードだと、作業領域のファイルを直接開くのではなくてTempにコピーして開くため。もしかするとLinux版ではシンボリックリンクでよろしくやってくれるのかも)

差分ツール上で変更箇所を確認しながらコメントを書き足すとかよくやるのでこれはちょっと困る、ということで `usewin = True` を指定します。

### 手抜きマージ
WinMergeも一応3wayマージの機能は持っているんですが、kdiff3などの本格的3wayマージツールの機能と比べてどうしても見劣りします。
この際、3wayマージはすっぱりとあきらめて(3wayマージが必要なときは他のツールを使うことにして)、WinMergeは簡易コンフリクト解消に特化する形の設定にすることにします。

WinMergeにはコンフリクトファイルを開く機能がある(コンフリクトマーカーを読み取って、localとotherを左右に振り分けて表示できる)ので、この機能を利用します。

|オプション     |値            |
|:--------------|:-------------|
|premerge       |keep          |
|args           |/e $output    |
|checkconflicts |True          |


まず、`premerge = keep` とすることで、ファイルにコンフリクトマーカーを埋めたままWinMergeに渡せるようにします。
起動時の引数としては、単にコンフリクトファイルを渡すだけでいいので、 `args = /e $output` となります。
さっき書いたとおり、 `premerge = keep` の場合は `checkchanged` は効かなくなるので、 `checkconflicts`を使うようにします。

これで、手抜き版の設定ができました。3wayマージとくらべると情報量は減りますが、コンフリクトした個所だけをチェックできるので、効率的にマージを行うことができます。

## まとめ
Mergeツール/外部Diffツールのプリセット定義は `TortoiseHg\hgrc.d\MergeTools.rc` にありました。
プリセット定義をいろいろカスタマイズして、より快適なMercurialライフを送りましょう :)

ちなみにマージツールとしては今のところコストパフォーマンス含めて最強なのはkdiff3だと思います。日本語周りがちょっとアレですが・・・。

と思ったんですが、改めて使ってみるとBeyondCompare 3も悪くないかも。(前に試した時は何か残念なポイントがあったような気がするけど、それが何だったか思い出せませんｗ)