git log と git diff の .. と ...:同じ記号で意味が変わる理由

git log で A..B と A...B が選ぶコミット、git diff で2つの記号が比べる地点を、Git 公式ドキュメントと実行例で整理します。プルリクエストの差分がどちらかも扱います。

開発Gitバージョン管理

git log main..feature と git log main...feature は点が1つ違うだけですが、選ばれるコミットが異なることがあります。同じ記法を git diff に使うと、意味がまた変わります。log は点の記法をコミットの集合として読み、diff は比べる2つの端点として読むからです。

そのため、以下の例のようにブランチが分かれた履歴では、log の点3つは両方のブランチのコミットを選び、git diff main...feature は main だけで起きた変更を含めません。これはこの履歴でそうなったというだけで、点3つがいつも多く、あるいはいつも少なく表示するという決まりはありません。出力の量はコミットグラフとファイルの内容で変わります。例は macOS 15.6.1 の git 2.55.0 で実行しました。

例のリポジトリ

base コミットから feature ブランチを作り、両側にコミットを1つずつ追加しました。

  • feature:feature.txt を追加
  • main:main.txt を追加

この単純な履歴では、2つのブランチの先端の最良の共通祖先(best common ancestor)は base です。Git はこれを merge base と呼び、git merge-base main feature で確認できます。

base コミットから main ブランチと feature ブランチが分かれたコミットグラフ。main には main.txt を追加するコミットが1つ、feature には feature.txt を追加するコミットが1つあり、base が merge base です。この例では、git log main..feature は feature のコミットだけを選び、git log main…feature は両方のコミットを選びます。–left-right を付けると main 側に <、feature 側に > が付きます。git diff main..feature は main と feature のスナップショットを比べて feature.txt の追加と main.txt の削除を表示し、git diff main…feature は base と feature を比べて feature.txt の追加だけを表示します。

図:この例の履歴での結果です。履歴の形が違えば、2つの記法の出力の差も変わります。

git log では:コミットの集合

gitrevisions によると、r1..r2 は ^r1 r2 の省略形です。r2 からたどれるコミットのうち、r1 からたどれるものを除いたものです。

$ git log --format=%s main..feature
feature: add feature.txt

r1...r2 は対称差です。どちらか一方からだけたどれるコミットを選び、両方からたどれるものは除きます。--left-right を付けると、左側(r1)から来たコミットには <、右側(r2)から来たコミットには > が付きます。

$ git log --left-right --format='%m %s' main...feature
< main: add main.txt
> feature: add feature.txt

どちらの記法でも片方を省略でき、省略した側は HEAD になります。origin.. は origin..HEAD と同じです。

git diff では:2つの地点の比較

git-diff のドキュメントは、diff は2つの端点を比べるコマンドであり、ここでの .. と ... は gitrevisions でいう範囲ではない、と明記しています。

git diff A..B は git diff A B と同じです。2つのコミットのスナップショットをそのまま比べます。

$ git diff --stat main..feature
 feature.txt | 1 +
 main.txt    | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

feature で main.txt を削除したことはないのに、削除として表示されます。ブランチが分かれたあとで main に追加されたファイルなので feature にはなく、2つのスナップショットを並べると消えたように見えるからです。

git diff A...B は git diff $(git merge-base A B) B と同じです。ブランチが分かれた地点から B までの、B 側の変更を表示します。

$ git diff --stat main...feature
 feature.txt | 1 +
 1 file changed, 1 insertion(+)

GitHub のドキュメントは、プルリクエストの差分はこの3点方式だと説明しています(2026年10月3日確認)。「このブランチは何を変えたのか」を知りたいなら、たいていは3点のほうです。

まとめ

記法 git log git diff
A..B B からたどれて A からはたどれないコミット A と B のスナップショットを直接比較
A...B A と B のどちらか一方からだけたどれるコミット merge base から B までの変更

履歴の形によっては、2つの記法の結果が同じになります。main が feature の祖先であれば、たとえば feature を main から作成したあと main が進んでいなければ、log では main..feature と main...feature は同じコミットを選び、diff でも両方とも同じ2つのスナップショットを比べます。

merge base が複数あるとき

ブランチ同士を互いにマージした履歴(criss-cross merge)では、2つのコミットの最良の共通祖先が複数になることがあります。git diff A...B が使うのは、--all なしの git merge-base A B が返す merge base 1つだけです。git-merge-base のドキュメントによると、候補が複数あるときにどれが返るかは決まっていません。

git merge-base --all A B で候補が複数あるかどうかは確かめられますが、それで3点の diff が使う merge base が変わるわけではありません。

diff 出力の @@ 行の読み方は unified diff の読み方 にまとめています。

参照した仕様とドキュメント

リンクと内容は2026年10月3日に確認しました。GitHub の画面の動作は変わることがあります。

ブログへ