はじめに
git diff で hunk が自分が追加削除したとおりに出なくてイラッとしたことはありませんか?
それを解消するために git 2.9 で --compaction-heuristic
というオプションが作られより適切な hunk を表示しようという試みがなされました。当時のリリースノート
そしてgit 2.11 からより僕らの意図通りの hunk を表示するための仕組みとして --indent-heuristic
というオプションが追加されました。Githubからのリリース記事
追加する方法
git 2.11 ではない方はまずアップグレードしてください。
とりあえず試してみたい場合は git diff --indent-heuristic
常に有効にしたい場合は .gitconfig に以下のオプションを追加します。
[diff]
indentHeuristic = true
git 2.9 からなにが変わったのか
git 2.9で導入された --compaction-heuristic
ではいくつかのエッジケースで対応できないものがありました。
例えば以下のような変更をしたとします。
// before
package main
import (
"fmt"
)
func main() {
var list []int
for i := 0; i < 10; i++ {
list = append(list, i)
}
fmt.Printf("%#v", list)
}
// 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
オプションを使って取ると以下のようになってしまいます。
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
が導入されました。
有効にした場合はこのようになります。
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の記事にコミットログへのリンクがあるので内容を読んでみます。
そうすると以下のようなロジックで適切なものを選んでいるようです。
indent heuristic の概要
- 通常のdiff から普通の hunk を用意する
- この時の hunk score を算出する
- 1行ずらしてまたスコアを算出する
- ずらしていって最小になったときのものを採用する
1, 3, 4 はいいとしてキモは2のどうやってスコアを算出してるかです。
コードを見てみると measure_split
関数でその時点でのグループの上側と下側の情報を収集し、 score_add_split
関数でそれぞれのスコアを算出し、それを合わせたものをその時のスコアとして比較しているようです。
詳しいコードはずらしながら情報を収集している箇所がこのあたり 、スコアを算出しているロジックがこのあたり、最後に2つのスコアを合わせてその時点のスコアを求めている関数がここ になっています。
更に詳しく知りたい場合は読んでみてください。
まとめ
git diff
で hunk をきれいに出すためのオプションの中身を覗いてみました。
--indent-heuristic
は今後diffのデフォルトになるとリリースノートでも言われているので、早くGithub や Github Enterpriseにも来てほしいなあと思います。