5分でざっくりわかる git diff の indent Heuristic オプションの仕組み

# はじめに
git diff で hunk が自分が追加削除したとおりに出なくてイラッとしたことはありませんか？
それを解消するために git 2.9 で `--compaction-heuristic` というオプションが作られより適切な hunk を表示しようという試みがなされました。[当時のリリースノート]( https://github.com/blog/2188-git-2-9-has-been-released )
そしてgit 2.11 からより僕らの意図通りの hunk を表示するための仕組みとして `--indent-heuristic` というオプションが追加されました。[Githubからのリリース記事][link-1]

## 追加する方法
git 2.11 ではない方はまずアップグレードしてください。
とりあえず試してみたい場合は `git diff --indent-heuristic`  
常に有効にしたい場合は .gitconfig に以下のオプションを追加します。

```
[diff]
    indentHeuristic = true
```

# git 2.9 からなにが変わったのか
git 2.9で導入された `--compaction-heuristic` ではいくつかのエッジケースで対応できないものがありました。  
例えば以下のような変更をしたとします。 

``` sample.go
// before
package main

import (
	"fmt"
)

func main() {
	var list []int
	for i := 0; i < 10; i++ {
		list = append(list, i)
	}
	fmt.Printf("%#v", list)
}
```

```sample.go
// after
package main

import (
	"fmt"
)

func main() {
	var list []int
	for i := 0; i < 10; i++ {
		fmt.Println(i)
	}
	for i := 0; i < 10; i++ {
		list = append(list, i)
	}
	fmt.Printf("%#v", list)
}
```

この差分を `--compaction-heuristic` オプションを使って取ると以下のようになってしまいます。

```diff
knsh14% git diff --compaction-heuristic
diff --git a/sample.go b/sample.go
index 23e185c..c7a113e 100644
--- a/sample.go
+++ b/sample.go
@@ -1,13 +1,16 @@
 package main
 
 import (
 	"fmt"
 )
 
 func main() {
 	var list []int
 	for i := 0; i < 10; i++ {
+		fmt.Println(i)
+	}
+	for i := 0; i < 10; i++ {
 		list = append(list, i)
 	}
 	fmt.Printf("%#v", list)
 }

```

追加したのは最初のforループなのでこれだとおかしな感じですね。1行下にずれています。  
これを解消するために内部のアルゴリズムをごっそり入れ替えた `--indent-heuristic` が導入されました。

有効にした場合はこのようになります。

```diff
knsh14% git diff --indent-heuristic
diff --git a/sample.go b/sample.go
index 23e185c..c7a113e 100644
--- a/sample.go
+++ b/sample.go
@@ -1,13 +1,16 @@
 package main
 
 import (
 	"fmt"
 )
 
 func main() {
 	var list []int
+	for i := 0; i < 10; i++ {
+		fmt.Println(i)
+	}
 	for i := 0; i < 10; i++ {
 		list = append(list, i)
 	}
 	fmt.Printf("%#v", list)
 }

```
正しく表示されましたね。

では一体どのようにしてこれを実現しているのでしょうか。

# どういう仕組になっているのか
先程の[Githubの記事][link-1]に[コミットログへのリンク]( https://github.com/git/git/commit/433860f3d0beb0c6f205290bd16cda413148f098 )があるので内容を読んでみます。

そうすると以下のようなロジックで適切なものを選んでいるようです。

## indent heuristic の概要
1. 通常のdiff から普通の hunk を用意する
2. この時の hunk score を算出する
3. 1行ずらしてまたスコアを算出する
4. ずらしていって最小になったときのものを採用する

1, 3, 4 はいいとしてキモは2のどうやってスコアを算出してるかです。

コードを見てみると `measure_split` 関数でその時点でのグループの上側と下側の情報を収集し、 `score_add_split` 関数でそれぞれのスコアを算出し、それを合わせたものをその時のスコアとして比較しているようです。
詳しいコードはずらしながら情報を収集している箇所が[このあたり](https://github.com/git/git/commit/433860f3d0beb0c6f205290bd16cda413148f098#diff-6b2694db650f5dc2e23842f354836344R939) 、スコアを算出しているロジックが[このあたり](https://github.com/git/git/commit/433860f3d0beb0c6f205290bd16cda413148f098#diff-6b2694db650f5dc2e23842f354836344R507)、最後に2つのスコアを合わせてその時点のスコアを求めている関数が[ここ](https://github.com/git/git/commit/433860f3d0beb0c6f205290bd16cda413148f098#diff-6b2694db650f5dc2e23842f354836344R609) になっています。
更に詳しく知りたい場合は読んでみてください。

# まとめ
`git diff` で hunk をきれいに出すためのオプションの中身を覗いてみました。
`--indent-heuristic` は今後diffのデフォルトになると[リリースノート][link-1]でも言われているので、早くGithub や Github Enterpriseにも来てほしいなあと思います。

[link-1]:https://github.com/blog/2288-git-2-11-has-been-released