diff の @@ -1,7 +1,8 @@ の読み方:unified diff 形式の数字の意味
diff -u や git diff の @@ 行について、開始行と行数、範囲が 0 になる場合、既定の前後3行、Git が付ける関数の文脈を POSIX と公式文書に沿って説明します。
ターミナルやコードレビュー画面で git diff の結果を見ると、変更行の上に @@ -1,7 +1,8 @@ のような行があります。この行が読めれば、変更がファイルの何行目で起きたのか、行が何行増えたり減ったりしたのかがわかります。この形式は unified diff(ユニファイド形式)と呼ばれ、POSIX は diff -u の出力形式として定めています。
2行のヘッダーとハンク
diff -u の出力は2行のヘッダーから始まります。--- の後ろに元のファイル、+++ の後ろに新しいファイルの名前と時刻が続きます。その下にハンク(hunk)と呼ばれるかたまりが並び、ハンク1つが2つのファイルの異なる1か所にあたります。
ハンクの中の行は先頭の1文字で区別します。
- 空白:両方のファイルに同じようにある行(コンテキスト)
-:元のファイルから削除された行+:新しいファイルに追加された行
@@ 行の読み方
a から h までを1行に1文字ずつ書いた8行のファイルで、4行目の d を D と d2 の2行に置き換え、diff -u で比較しました。macOS に入っている diff(FreeBSD ベース)で実行した結果で、ファイル名と時刻が書かれたヘッダー2行は省いています。
@@ -1,7 +1,8 @@
a
b
c
-d
+D
+d2
e
f
g
-1,7 は「元のファイルの1行目から7行」、+1,8 は「新しいファイルの1行目から8行」という意味です。カンマの前が開始行番号、後ろが行数です。
行数は自分で数えて確かめられます。元のファイル側は空白か - で始まる行を、新しいファイル側は空白か + で始まる行を数えます。この例では、コンテキスト6行に削除1行を足して7、コンテキスト6行に追加2行を足して8です。2つの数の差の1が、このハンクで増えた行数です。
変更行の前後に3行ずつコンテキストが付いているのは既定値だからです。POSIX は -u を3行のコンテキストを出すオプション、-U n を n 行のコンテキストを出すオプションとして定義しています。Git も既定は3行で、-U<n> オプションや diff.context 設定で変えられます。
数字が1つだけのとき、0 のとき
POSIX の仕様では、範囲がちょうど1行なら 2,1 の代わりに 2 のように開始行番号だけを書いてもかまいません。範囲が空のときは、開始行番号にその範囲の直前の行の番号を書き、空の範囲がファイルの先頭から始まるなら 0 を書きます。
コンテキストを0行にする -U0 で実行すると、この規則がそのまま見えます。a、b、c の3行から b を削除した結果です。
@@ -2 +1,0 @@
-b
元のファイルでは2行目の1行が削除されたので -2 です。新しいファイル側には対応する行がないので行数は 0、開始番号は削除された位置の直前の行である 1 になります。逆に a と c の間に b を挿入すると @@ -1,0 +2 @@ になります。,0 があれば、そちらのファイルにはこのハンクに当たる行が1行もないという意味です。
Git が付け加えるもの
git diff の出力は従来の diff と少し違います。Git のドキュメントによると、先頭に diff --git a/ファイル b/ファイル というヘッダーが付き、その後に index、new file mode、rename from などの拡張ヘッダー行が続くことがあります。
2つ目の @@ の後ろに文字列が付くこともあります。Git のドキュメントは @@ -k,l +n,m @@ TEXT という形の行を hunk header と呼び、TEXT には既定で英字、アンダースコア、$ で始まる行が入ると説明しています。.gitattributes で *.py diff=python のように指定すると、Git に組み込まれた言語別のパターンを使えます。
ただし、この文字列は位置の手がかり程度に考えたほうがよいでしょう。次は Git 2.55.0 で、average 関数の中の return 0 を return None に変えて実行した結果です。diff --git、---、+++ のヘッダーは省いています。
@@ -8,5 +8,5 @@ def total(items):
def average(items):
n = len(items)
if n == 0:
- return 0
+ return None
return total(items) / n
変更箇所は average の中なのに、@@ の後ろには def total(items): が付いています。この実行では、ハンクが始まる8行目の def average ではなく、それより前にある行が選ばれました。正確な位置は @@ の間の数字で確かめるほうが確実です。
原稿のように、行番号よりもどの文が変わったかを知りたいときは、Motitory のテキスト比較も使えます。このツールは @@ 形式を出力せず、変わった行と文字を色で示します。