1. ホーム
  2. Git

【Git】git diff の使い方|変更箇所の確認からコミット・ブランチ間の比較まで

Share

コミットする前に「さっきどこを書き換えたんだっけ」と確認したくなることはよくあります。そんなときに使うのが git diff です。ファイルのどの行がどう変わったのかを、追加は +、削除は - の印つきで表示してくれます。この記事では、ステージ前とステージ後の差分の違い、コミット同士やブランチ同士の比較、表示を見やすくするオプション、そして @@ から始まる出力の読み方までを、実際の実行結果を見ながら解説します。

git diff は「2つの状態」を比べるコマンド

Git で扱うファイルには、大きく3つの置き場所があります。実際に編集しているファイルが置かれたワーキングツリー、git add で「次のコミットに含める」と登録した内容が入るステージ(インデックス)、そしてコミット済みの履歴である HEAD(最新コミット)です。

git diff は、このうちの2つを選んで比べるコマンドです。何も引数を付けなければ「ワーキングツリー と ステージ」を比べ、オプションやコミットを指定すると比較の組み合わせが変わります。どれとどれを比べているのかを意識しておくと、「変更したはずなのに何も表示されない」といった混乱を避けられます。

コマンド比べるもの
git diffステージ と ワーキングツリー(まだ git add していない変更)
git diff --stagedHEAD と ステージ(git add 済みで、次にコミットされる変更)
git diff HEADHEAD と ワーキングツリー(ステージ済み・未ステージを合わせたすべての変更)
git diff <コミットA> <コミットB>2つのコミット同士

まだ add していない変更を見る

まずは一番基本の使い方です。ここでは index.html の見出しを「ようこそ」から「こんにちは」に、style.css の文字色を #333 から #222 に書き換えた状態で、引数なしの git diff を実行してみます。

ターミナル
$ git diff
diff --git a/index.html b/index.html
index ff1a3ae..61d2847 100644
--- a/index.html
+++ b/index.html
@@ -5,7 +5,7 @@
   <title>サンプル</title>
 </head>
 <body>
-  <h1>ようこそ</h1>
+  <h1>こんにちは</h1>
   <p>これはサンプルページです。</p>
 </body>
 </html>
diff --git a/style.css b/style.css
index f43cfe7..ae84dfb 100644
--- a/style.css
+++ b/style.css
@@ -1,4 +1,4 @@
 body {
   margin: 0;
-  color: #333;
+  color: #222;
 }

行頭が - の行が削除された(変更前の)行、+ の行が追加された(変更後の)行です。1行を書き換えた場合は「古い行を消して新しい行を足した」という形で、- と + がペアで並びます。印のない行は、変更箇所の前後を示すための周辺行です。

なお、出力が長いと less などのページャーで表示され、プロンプトに戻らなくなります。スペースキーで次のページへ進み、q で終了できます。

ステージした変更は –staged(–cached)で見る

先ほどの状態から style.css だけを git add すると、引数なしの git diff には index.html しか出てこなくなります。

ターミナル
$ git add style.css
$ git diff --stat
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

引数なしの git diff は「ステージ と ワーキングツリー」の差を表示するので、ステージに載せた時点で style.css の差はなくなったと見なされるためです。変更が消えたわけではありません。ステージに載せた変更、つまり次のコミットに入る内容を確認したいときは --staged を付けます。

ターミナル
$ git diff --staged
diff --git a/style.css b/style.css
index f43cfe7..ae84dfb 100644
--- a/style.css
+++ b/style.css
@@ -1,4 +1,4 @@
 body {
   margin: 0;
-  color: #333;
+  color: #222;
 }

--cached は --staged とまったく同じ意味です。昔からあるのが --cached で、分かりやすい別名として --staged が用意されています。どちらを使ってもかまいませんが、「ステージを見る」と覚えやすい --staged がおすすめです。

ステージ済みかどうかに関係なく、最新コミットからの変更をまとめて見たいときは git diff HEAD を使います。このケースでは、index.html と style.css の両方が表示されます。

ターミナル
$ git diff HEAD --stat
 index.html | 2 +-
 style.css  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

出力の読み方

git diff の出力は「unified 形式」と呼ばれる書式です。慣れないうちは記号が多くて読みにくく感じますが、決まった構造の繰り返しなので、一度覚えてしまえば迷いません。先ほどの index.html の部分を上から順に見ていきます。

ヘッダー部分

ターミナル
diff --git a/index.html b/index.html
index ff1a3ae..61d2847 100644
--- a/index.html
+++ b/index.html

1行目は、比較している2つのファイルです。a/ が変更前、b/ が変更後を表します。2行目の ff1a3ae..61d2847 は変更前と変更後のファイル内容を指すハッシュ値の先頭部分で、最後の 100644 は「通常のファイル」であることを表すファイルモードです。普段はあまり気にしなくて大丈夫です。

3〜4行目の --- と +++ は、以降の - 行が a/index.html 側、+ 行が b/index.html 側のものだという宣言です。新規ファイルの場合は、変更前が存在しないので --- /dev/null と表示されます。

@@ で始まるハンクヘッダー

その下の @@ -5,7 +5,7 @@ から次の @@ までのまとまりをハンク(hunk)と呼びます。1つのファイルの中で離れた場所を変更していれば、ハンクも複数に分かれて表示されます。

-5,7 は「変更前のファイルの5行目から7行分」、+5,7 は「変更後のファイルの5行目から7行分」を表示している、という意味です。1行を書き換えただけなので行数は変わらず、どちらも7行になっています。行を追加すれば + 側の数が増え、削除すれば減ります。たとえば後で出てくる @@ -1,4 +1,5 @@ は、4行だった範囲が1行増えて5行になったことを示しています。

7行のうち変更された行は2行(- と + を1組)だけで、残りは前後の周辺行です。git diff は既定で変更行の前後3行ずつを表示します。この行数は -U(--unified)オプションで変えられます。次の例は、コミット後に見出しをさらに「こんばんは」へ書き換えた状態で実行したものです。

ターミナル
# 前後1行だけ表示する
$ git diff -U1
diff --git a/index.html b/index.html
index 61d2847..1e369d7 100644
--- a/index.html
+++ b/index.html
@@ -7,3 +7,3 @@
 <body>
-  <h1>こんにちは</h1>
+  <h1>こんばんは</h1>
   <p>これはサンプルページです。</p>

周辺行が1行ずつになったので、ハンクは「7行目から3行分」に縮まりました。ちなみに、プログラムのファイルでは @@ -10,6 +10,6 @@ function foo() { のように、@@ の後ろに変更箇所が含まれる関数名などが表示されることがあります。これは「この変更はどの関数の中か」を示すヒントで、差分の一部ではありません。

コミット同士・ブランチ同士を比べる

2つのコミットを指定する

git diff にコミットを2つ渡すと、その2つの間の差分を表示します。先に書いたほうが変更前(- 側)、後に書いたほうが変更後(+ 側)です。コミットはハッシュ値のほか、HEAD(最新)、HEAD~1(1つ前)、HEAD~2(2つ前)のように相対的にも指定できます。

ターミナル
$ git log --oneline
635f45a リンク色を追加
b7c0cac 見出しと文字色を変更
ac0820b first

# 1つ前のコミットから最新コミットまでの差分(最後のコミットで何を変えたか)
$ git diff HEAD~1 HEAD --stat
 index.html | 2 +-
 style.css  | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

# ハッシュ値で指定しても同じ
$ git diff b7c0cac 635f45a --stat
 index.html | 2 +-
 style.css  | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

コミットを1つだけ渡した場合は、「そのコミット と ワーキングツリー」の比較になります。先ほどの git diff HEAD は、まさにこの形です。

ブランチ名で比べる(.. と … の違い)

ブランチ名は「そのブランチの最新コミット」を指すので、コミットの代わりに書けます。ここでは、main から feature ブランチを作って「リンク色を追加」をコミットし、その後 main 側でも「README を追加」をコミットした状態で比べてみます。

ターミナル
# main と feature の最新コミット同士を比べる(main..feature と書いても同じ)
$ git diff main feature --stat
 README.md  | 1 -
 index.html | 2 +-
 style.css  | 1 +
 3 files changed, 2 insertions(+), 2 deletions(-)

# 分岐した地点から feature で何を変えたかだけを見る
$ git diff main...feature --stat
 index.html | 2 +-
 style.css  | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

1つ目の結果に README.md | 1 - が入っているのがポイントです。feature では README を消していないのに、削除として表示されています。これは、git diff main feature が2つのブランチの最新の状態をそのまま比べるためです。main にあって feature にない README は、「main から feature に向かうと消えるもの」として扱われます。

ドットを3つにした git diff main...feature は、main と feature が分岐した地点(共通の祖先)と feature を比べます。そのため main 側で進んだ変更は混ざらず、feature ブランチでの変更だけが表示されます。プルリクエストのような「このブランチで何をしたか」を確認したい場面では、3ドットのほうが意図に合うことが多いでしょう。

ファイルを指定して絞り込む

変更したファイルが多いときは、見たいファイルだけに絞り込めます。コミットやブランチの指定のあとに -- を挟んでファイルパスを書くのが正式な書き方です。

ターミナル
# 2つ前のコミットから今までの style.css の変更だけを見る
$ git diff HEAD~2 -- style.css
diff --git a/style.css b/style.css
index f43cfe7..78a30cc 100644
--- a/style.css
+++ b/style.css
@@ -1,4 +1,5 @@
 body {
   margin: 0;
-  color: #333;
+  color: #222;
 }
+a { color: #06c; }

-- は「ここから先はファイルパスです」という区切りの印です。ファイル名とブランチ名が同じ文字列だった場合などに、Git が取り違えないようにする役割があります。曖昧さがなければ git diff style.css のように省略しても動きますが、コミットとファイルを両方書くときは -- を入れておくと安全です。パスにはディレクトリも指定でき、git diff -- src/ とすれば src 配下の変更だけを表示します。

表示を見やすくするオプション

–stat で変更の規模をつかむ

ここまでの例でも何度か使ってきた --stat は、中身の代わりにファイルごとの変更量を表示します。2 +- は2行が変わり、その内訳が追加と削除であることを、+ と - の記号の数で大まかに示しています。最後の行が全体の合計です。大きな変更を細かく読む前に、「どのファイルがどれくらい変わったか」を把握するのに役立ちます。合計の行だけでよければ --shortstat も使えます。

–name-only / –name-status でファイル名だけ出す

変更されたファイルの一覧だけが欲しいときは --name-only です。--name-status にすると、ファイル名の前に変更の種類を表す1文字が付きます。

ターミナル
$ git diff --name-only
index.html
style.css

$ git diff --name-status
M	index.html
M	style.css

主な記号は次のとおりです。--name-only は1行1ファイルで出力されるので、ほかのコマンドに渡して処理するスクリプトでもよく使われます。

記号意味
M変更された(Modified)
A追加された(Added)
D削除された(Deleted)
R名前が変わった(Renamed)

–word-diff で単語単位の変更を見る

通常の差分は行単位なので、長い1行の中で一部だけ変わった場合でも、行全体が - と + で2回表示されます。どこが変わったのかを探しにくいときは --word-diff を使うと、行の中の変わった部分だけを示してくれます。

ターミナル
$ git diff --word-diff ac0820b b7c0cac -- style.css
diff --git a/style.css b/style.css
index f43cfe7..ae84dfb 100644
--- a/style.css
+++ b/style.css
@@ -1,4 +1,4 @@
body {
  margin: 0;
  color: [-#333;-]{+#222;+}
}

[-...-] で囲まれた部分が削除、{+...+} で囲まれた部分が追加です。行頭の + / - 記号の代わりに、行の中に印が入る形になります。ターミナルの色表示に任せたい場合は --word-diff=color とすると、括弧の代わりに赤と緑の色だけで区別されます。文章やドキュメントの修正を確認するときに特に便利です。

ただし既定では、空白で区切られたひとまとまりを「単語」として扱います。日本語の文章は空白で区切られないため、<h1>ようこそ</h1> のような部分は丸ごと1単語と見なされ、細かく分かれないことがある点は覚えておきましょう。

オプションのまとめ

オプション意味
--staged / --cachedステージ済みの変更を HEAD と比べて表示する
--statファイルごとの変更行数を表示する
--shortstat変更ファイル数と行数の合計だけを表示する
--name-only変更されたファイル名だけを表示する
--name-statusファイル名と変更の種類(M / A / D など)を表示する
--word-diff行の中の変わった単語に印を付けて表示する
-U<n> / --unified=<n>変更行の前後に表示する行数を変える(既定は3)
-w / --ignore-all-space空白の違いを無視して比べる

差分が表示されない・思ったのと違うとき

git add した直後に何も出なくなった

もっとも多い戸惑いがこれです。前半で説明したとおり、引数なしの git diff はステージとワーキングツリーを比べるので、git add した変更は表示対象から外れます。変更はステージに移っているだけなので、git diff --staged で確認できます。コミット前の最終チェックとしては、git diff --staged で「これからコミットされる内容」を見る習慣をつけておくと安心です。

新しく作ったファイルが表示されない

一度も git add していない新規ファイルは「未追跡(untracked)」の状態で、Git の管理対象に入っていません。そのため、何も付けない git diff には出てきません。git status では ?? の印で表示されるので、こちらで存在を確認できます。

中身を差分として見たい場合は、git add してから git diff --staged で見るのが簡単です。まだステージしたくないなら、git add -N(--intent-to-add)で「あとで追加する予定」とだけ登録する方法もあります。こうすると、ファイルの内容は追加されないまま git diff に新規ファイルとして表示されます。

ターミナル
$ git add -N app.js
$ git diff
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..d914c60
--- /dev/null
+++ b/app.js
@@ -0,0 +1 @@
+console.log("hi");

インデントを直しただけなのに差分だらけになる

エディタの整形機能でインデントが変わると、中身は同じでも行全体が変更として表示され、本当に変えた箇所が埋もれてしまいます。そういうときは -w(--ignore-all-space)を付けると、空白の違いを無視して比べられます。インデントの変更しかしていなければ、git diff -w の結果は空になります。行末の空白だけを無視したい場合は --ignore-space-at-eol も使えます。

ただし、これは表示上で無視するだけです。コミットすれば空白の変更もそのまま記録されるので、意図しない整形が混ざっていないかは通常の git diff でも確認しておきましょう。

まとめ

git diff は、2つの状態を選んでその違いを表示するコマンドです。引数なしならまだ git add していない変更、--staged(--cached)ならステージ済みで次にコミットされる変更、git diff HEAD なら最新コミットからのすべての変更が表示されます。コミットやブランチを2つ渡せばその間の差分になり、ブランチでの作業内容だけを見たいときは main...feature のように3ドットで分岐点からの変更に絞れます。

出力は、--- / +++ のヘッダーのあとに @@ -開始行,行数 +開始行,行数 @@ で始まるハンクが続く構造です。全体をざっと把握したいときは --stat や --name-only、1行の中の細かな修正を見たいときは --word-diff、特定のファイルだけ見たいときは -- ファイル名 と使い分けると、変更の確認がぐっと楽になります。コミットの前に git diff --staged で中身を確かめる習慣をつけておくと、余計なものをコミットしてしまう失敗も減らせます。

参考ページ