git 2-dot diffと3-dot diffの違い
はじめに
git diffでブランチ間の差分を見る方法は3つある。
$ git diff branchA branchB # 空白で区切る
$ git diff branchA..branchB # ドット2つ
$ git diff branchA...branchB # ドット3つ
空白と2-dotは同じ意味なので気にする必要はないが、2-dotと3-dotは異なる結果を出すので注意が必要。特にGUIツールやWeb上のツールで違いを見たときそれがどぢらのdiffかを分かっていないとハマることがある。実際にハマった人を何人か見たことがあるのでここに違いを書いておく。
テスト環境
以下のようなmainとfeatureというブランチがあるリポジトリを用意した。バージョンAのとき4行だったtest.txtに対して、featureブランチでXXを追加した。一方mainブランチではYYが追加されている。ここで実験してみる。

空白、2-dot、3-dotのdiffをそれぞれ実行すると以下のようになり、空白と2-dotは同じだが3-dotは異なることが分かる。

2-dotと3-dotの違い
2-dot
2-dotは単に指定されたブランチの違いを示している。言い換えればgit diff main..featureは「main (バージョンB) からfeature (バージョンC) を作るにはどうするか」という質問の答えになっている。答えは表示のとおり「YYを消してXXを追加する」だ。「ブランチ間のdiff」と言ったとき想定するのはこちらの方だろう。

3-dot
3-dotの方の結果は+XXしか出てこない。3-dotが何をしているかについては公式のヘルプを参照してみる(日本語訳はこのページから引用させてもらった)。

この説明に出てくるmerge-baseとは2つのブランチの共通の祖先のことで、上の例のmainとfeatureのmerge-baseはバージョンAとなる。よってgit diff main...featureはgit diff A featureと同義ということになる。featureは今バージョンCを指しているのでgit diff A Cになるということだ。確かにAからCへの変更はXXの追加なので、ヘルプの説明どおりの動作になっている。

3-dotとはつまり何なのか
これで3-dotがやっていることは分かったが、結局これは何を意味しているのか。実はそれほど難しい話ではなく、git diff main...featureは「featureをmainにマージするとmainに何が起こるか」という質問の答えになっている。結果は「XXを追加する」である。

順序を入れ替えるとどうなるか
2-dot diffで順序を入れ替えてgit diff feature..mainとすると、「featureからmainを作るにはどうするか」になり、+と-が入れ替わっている以外は同じ答えになる。diffの詳しいアルゴリズムは知らないので、常に同じものになるかは分からないが。
$ git diff feature..main
@@ -1,5 +1,5 @@
AA
+YY
BB
CC
DD
-XX
一方3-dotでgit diff feature...mainにすると、「mainをfeatureにマージすると何が起こるか」となり、以下のようになる。3-dot diffでは順番によって結果が大きく異なることに注意しよう。
$ git diff feature...main
@@ -1,4 +1,5 @@
AA
+YY
BB
CC
DD
Bitbucketでの比較
Bitbucketにはブランチの比較を表示する機能があるが、そこで使われているのは3-dotである。公式の説明にもその記載がある。ツールによって違いがあるだろうからマニュアルなどを参照しよう。

違いまとめ
-
git diff main..feature:mainからfeatureを作るにはどうするか -
git diff feature..main:featureからmainを作るにはどうするか - 空白区切りは2-dotと同じ
-
git diff main...feature:featureをmainにマージするとmainに何が起こるか -
git diff feature...main:mainをfeatureにマージするとfeatureに何が起こるか
Discussion