git diff と git log におけるドット表記

2 min read読了の目安(約2600字

git diff の引数の A..B や A...B と、git log の引数の A..B や A...B は同じ書き方だが、前者(git diffの引数のドット表記)は差分を取る対象となる2つのコミットを表し、後者(git logの引数のドット表記)はコミットの範囲(集合)を表す。
記法は同じだがそれぞれが意味する概念は全くの別物で、関係性を類推して理解しようとすると混乱する(実際、私も長い間混乱していた)。gitでドット表記が現れた場合には、文脈からどちらの概念を表しているかを判断した上で解釈しよう。

git diffにおけるドット表記

git diff は2つのコミット(それぞれに付随するソースツリー)の差分を出力する。A、Bがコミットだとすると、git diff の文脈において A..B や A...B はその2つのコミットの選び方を表現する。

git diff A..B

git diff A..B はコミットAからコミットBへの差分を出力する。git diff A B と同じ。

下の図のようなGitのコミットグラフ(点がコミット、矢印が親コミットへの参照を表す)であれば、git diff A..B は青い点で示されるコミットAのソースツリーから緑の点で示されるコミットBのソースツリーへの差分を出力する。

AとBを入れ換えて git diff B..A とした場合には、差分の向きが逆になり、BからAへの差分を出力する。

git diff A...B

git diff A...B はコミットAとコミットBの共通の祖先からコミットBへの差分を出力する。git diff $(git merge-base A B) B と同じ。

下の図のようなGitのコミットグラフであれば、git diff A...B は青い点で示される「コミットAとコミットBの一番近い共通の祖先」のソースツリーから緑の点で示されるコミットBのソースツリーへの差分を出力する。

AとBを入れ換えて git diff B...A とした場合の出力は、git diff A...B の出力とは関係を持たない。

git logにおけるドット表記

git log は特定の範囲(集合)に含まれるコミット履歴を出力する。git log の文脈において A..B や A...B はコミットの範囲(集合)の選び方を表現する。

git log A..B

A..B は、集合 {Aおよびその祖先のコミット} に含まれず、集合 {Bおよびその祖先のコミット} に含まれるコミットの集合を示す。下の図のようなGitのコミットグラフであれば、赤い点のコミットの集合が git log A..B の対象である。

ベン図で書くと

AとBを入れ換えた git log B..A が対象とする集合は git log A..B が対象とする集合とは共通部分を持たない別の集合である。

git log A...B

A...B は、{Aおよびその祖先のコミット}と{Bおよびその祖先のコミット}の排他的論理和、すなわち、それらの和集合から共通部分の元(要素)を除いた集合を示す。

ベン図で書くと

図を見るとわかるようにAとBの対称性があるので、git log B...Agit log A...B と同じ。

まとめ

  • git diff におけるドット表記 A..B, A...B は差分を算出するための2つのコミットを表現する
  • git log におけるドット表記 A..B, A...B は対象とするコミットの範囲(集合)を表現する
  • git diff におけるドット表記と git log におけるドット表記は無関係

蛇足: git mergeとドット表記

git merge でコミット B をコミット A にマージする場合、以下のことが起きる。

  • ソースツリーには git diff A...B の差分のうち git diff B...A の差分に含まれない分が適用される。
    • 3-way mergeと呼ばれるアルゴリズムでマージされる。
  • git log A..B の分が祖先としてコミット履歴に新たに加わる。
    • マージの際に処理しているのはコミットBを祖先に加えることのみで、結果としてそうなっているということに過ぎないが。

前者はドット2つ、後者はドット3つ。文脈の違いを無視してドット表記の字面でこれらの関係を理解しようとすると混乱するが、git diff のドット表記と git log のドット表記は別物なので、そもそも関係がない。

参考文献