@@ -12,7 +12,9 @@ が読めれば diff は怖くない

git diff の出力で、多くの人が読み飛ばしているのがこの行です。

@@ -12,7 +12,9 @@ export function calculateTotal(items) {

+- の意味は誰でも分かりますが、この4つの数字が何を指すかを説明できる人は意外と多くありません。ここが読めるとレビューの精度が上がり、コンフリクトやパッチ適用の失敗も原因を特定しやすくなります。

この記事は、unified diff(統合diff)形式の各パーツを1つずつ読み解くリファレンスです。

全体の構造

典型的な出力は3層になっています。

diff --git a/src/cart.js b/src/cart.js     ← どのファイルか
index 83db48f..bf269f4 100644              ← 変更前後のオブジェクトIDとパーミッション
--- a/src/cart.js                          ← 変更前(a)
+++ b/src/cart.js                          ← 変更後(b)
@@ -12,7 +12,9 @@ export function calculateTotal(items) {   ← ハンクヘッダ
   const subtotal = items.reduce(...)       ← コンテキスト行(先頭は半角スペース)
-  return subtotal;                         ← 削除された行
+  const tax = subtotal * 0.1;              ← 追加された行
+  return subtotal + tax;
 }

a/b/ は「変更前」「変更後」を表す慣習的な接頭辞で、実在のディレクトリではありません。新規ファイルなら --- /dev/null、削除なら +++ /dev/null になります。

ハンクヘッダの4つの数字

@@ -12,7 +12,9 @@
   │  │  │  └── 変更後: この位置から 9 行
   │  │  └───── 変更後: 12 行目から
   │  └──────── 変更前: この位置から 7 行
   └─────────── 変更前: 12 行目から

つまり -開始行,行数 +開始行,行数 です。**「変更された行数」ではなく「そのハンクが対象とする範囲の行数」**である点が重要で、コンテキスト行も数に含まれます。上の例は7行が9行になった=差し引き2行増えた、と読みます。

行数が1のときは省略されて @@ -1 +1 @@ の形になります。また、@@ の後ろに続く export function calculateTotal(items) {そのハンクが属する関数などの見出しで、gitが直前のインデントの浅い行から推測して付けています(変更内容そのものではありません)。

行頭の1文字がすべてを決める

先頭意味
(半角スペース)コンテキスト行。変更されていない
-変更前にだけ存在する行(削除)
+変更後にだけ存在する行(追加)
\特殊な注記(後述)

行の変更(modify)という概念は存在しません。 1文字だけ直した行も「削除+追加」として表現されます。これがdiffを読むときの最大のつまずきポイントで、行全体が赤と緑で並んでいても、実際には末尾のセミコロンだけが変わっている、ということが普通に起こります。

どこが変わったのかを本当に知りたいときは、行単位ではなく単語単位で比較する必要があります。gitなら git diff --word-diff、ブラウザなら テキスト差分(Diff)ツール で貼り付けて比較できます。

コンテキスト行は既定で3行

変更行の前後に表示される変更なしの行が「コンテキスト」で、既定は3行です。パッチを適用する側は、この前後3行が一致するかどうかで「当てる場所」を探します。行番号がずれていてもパッチが当たるのはこのためで、逆にコンテキストが書き換わっているとパッチは失敗します。

git diff -U0     # コンテキストなし(変更行だけ)
git diff -U10    # 前後10行

レビューで前後の文脈が足りないと感じたら -U を増やすと読みやすくなります。

\ No newline at end of file

-const config = {};
\ No newline at end of file
+const config = {};

これはファイル末尾に改行が無いことを示す注記です。中身が同じに見えるのに差分が出ている場合、末尾の改行が付いた/消えたことが原因です。エディタや .editorconfig の設定(insert_final_newline)で意図せず変わりやすく、レビューのノイズになりがちです。

空白だけの変更が「行全体の置換」に見える理由

インデントをタブからスペースに変えた、行末の空白を削った——といった変更は、行の内容が変わったと判定されるため、見た目の変化が無くても行全体が -/+ で並びます。

git diff -w                    # 空白の量の違いを無視
git diff --ignore-blank-lines  # 空行の増減を無視

改行コードの違い(LFとCRLF)も同じ理由で「全行が変更された」ように見えます。Windows環境とLinux環境が混在するリポジトリで、1行も触っていないのに全行差分が出るときは、まずこれを疑ってください。

マージ時に出る @@@

コンフリクトを解決したマージコミットを git show すると、@ が3つ並ぶことがあります。

@@@ -12,7 -12,8 +12,9 @@@

これはcombined diffで、2つの親それぞれとの差分を同時に表示しています。列も2つになり、1列目が第1親との差、2列目が第2親との差を表します。両方の親と異なる行=マージで手を入れた行だけが表示されるため、「マージのときに何を直したか」を確認するのに使えます。

リネームは similarity index で判定される

diff --git a/src/utils.js b/src/helpers.js
similarity index 95%
rename from src/utils.js
rename to src/helpers.js

gitはリネームを記録していません。diffを取るときに内容の類似度から推測しています。既定では50%以上似ていればリネームとみなされ、similarity index にその割合が出ます。ファイル名を変えると同時に大幅に書き換えると、リネームとして認識されず「削除+新規追加」として表示されます。

手元で差分を確認する

コミットされていないテキストや、gitの外にあるログ・設定ファイルを比べたいときは、テキスト差分(Diff)ツール に2つの内容を貼り付ければ、変更箇所が色付きで並びます。ブラウザ内で処理が完結するので、本番の設定ファイルやログのような外に出したくない内容でもそのまま比較できます。

比較の前に整形しておくと差分が読みやすくなります。JSONなら JSONフォーマッター、SQLなら SQLフォーマッター で整形してから比べると、意味のある差分だけが残ります。diffの基本(行単位と単語単位の違い、空白の扱い)は テキスト差分の読み方と使い方 にまとめています。

まとめ

  • @@ -12,7 +12,9 @@-開始行,行数 +開始行,行数。行数はコンテキストを含む範囲であって変更行数ではない
  • 行頭の1文字(スペース/-+)がすべて。「変更」は存在せず削除+追加で表現される
  • コンテキストは既定3行。パッチはこの前後の一致で位置を探すので、ずれても当たる
  • \ No newline at end of file は末尾改行の有無。意図しない差分の常連
  • 空白・改行コードだけの変更は全行差分に見える。git diff -w で切り分ける
  • @@@ はマージコミットのcombined diff(2つの親との差を同時表示)
  • リネームは記録ではなく類似度からの推測(similarity index

よくある質問

@@ -12,7 +12,9 @@ の数字は何を意味しますか?

- 側が変更前、+ 側が変更後で、それぞれ「開始行番号,行数」です。この例なら変更前は12行目から7行、変更後は12行目から9行が対象範囲になります。行数には変更されていないコンテキスト行も含まれるため、変更された行数そのものではありません。

1文字しか直していないのに行全体が差分になるのはなぜですか?

unified diff形式には「行を変更した」という表現が無く、すべて削除(-)と追加(+)の組み合わせで表されるためです。行内のどこが変わったかを見たい場合は git diff --word-diff を使うか、単語単位で比較できるツールで確認してください。

何も編集していないのに全行が変更扱いになります

改行コード(LFとCRLF)またはインデントの空白が変わっている可能性が高いです。git diff -w で空白の違いを無視して差分が消えるなら原因はそこです。リポジトリ全体で統一したい場合は .gitattributes で改行コードを固定する方法があります。

比較したいファイルの内容は送信されますか?

いいえ。テキスト差分(Diff)ツール はブラウザ内で処理が完結し、貼り付けた内容がサーバーへ送信されることはありません。本番の設定ファイルやログの比較にもそのまま使えます。