git diffで移動しただけの行を色で見分ける方法

​​Git​コマンドライン

git diff --color-moved を付けると、場所を移しただけの行は、ふだんの赤と緑とは別の色で表示されます。移動のついでに書き換えた行だけが赤と緑のまま残るので、どこが本当に変わったのかが一目で分かります。リファクタリングで関数の並びを変えたコミットや、コードレビューで「中身は変えずに動かしただけです」という差分を確かめるときに使えます。

補足

検証環境は Ubuntu 24.04、Git 2.43.0 です。コミットの作者には例示用の値(Neko <neko@example.com>)を設定しました。色は端末の配色によって見え方が変わるため、画像では Git が出した色の番号を、見やすい色に置き換えて描いています。Windows・macOS の端末や、GitHub などの Web 画面での表示は試していません。

比べるファイル

関数が3つある utils.js をコミットしておき、ここから並びを変えていきます。

js
function calcTax(price, rate) {
  const tax = price * rate;
  const rounded = Math.floor(tax);
  console.log("tax:", rounded);
  return rounded;
}

function formatPrice(price) {
  const text = price.toLocaleString("ja-JP");
  return text + "円";
}

function totalPrice(items) {
  let sum = 0;
  for (const item of items) {
    if (item.count <= 0) {
      continue;
    }
    sum += item.price * item.count;
  }
  console.log("total:", sum);
  return sum;
}

移動しただけの差分は、赤と緑では区別がつかない

先頭の calcTax を、中身は1文字も変えずにファイルの末尾へ移しました。ふつうの git diff では、元の場所の行が - で消え、新しい場所に + で足されたように出ます。

console
$ git diff
diff --git a/utils.js b/utils.js
index b50ac5f..985c751 100644
--- a/utils.js
+++ b/utils.js
@@ -1,10 +1,3 @@
-function calcTax(price, rate) {
-  const tax = price * rate;
-  const rounded = Math.floor(tax);
-  console.log("tax:", rounded);
-  return rounded;
-}
-
 function formatPrice(price) {
   const text = price.toLocaleString("ja-JP");
   return text + "円";
@@ -21,3 +14,10 @@ function totalPrice(items) {
   console.log("total:", sum);
   return sum;
 }
+
+function calcTax(price, rate) {
+  const tax = price * rate;
+  const rounded = Math.floor(tax);
+  console.log("tax:", rounded);
+  return rounded;
+}

この出力だけでは、消した6行と足した6行が同じ内容かどうかは、1行ずつ見比べないと分かりません。

同じ差分に --color-moved を付けて端末に出すと、色が次の画像のように変わりました。上が付けないとき、下が付けたときです。

git diff と git diff --color-moved の出力を上下に並べた画像。上は消した行がすべて赤、足した行がすべて緑。下は calcTax の6行が、消した側は太字のマゼンタ、足した側は太字のシアンになり、空行だけが赤と緑のまま
calcTax を末尾へ移しただけの差分。上が git diff、下が git diff –color-moved

--color-moved を付けると、移動した行は消した側が太字のマゼンタ、足した側が太字のシアンになりました。ふつうの赤と緑で残ったのは、空行の1行ずつだけです。

移動のついでに書き換えた行は赤と緑で残る

次に、移した calcTax の中の1行だけを書き換えました。Math.floor を Math.round に変えています。

console
$ sed -i "s/Math.floor(tax)/Math.round(tax)/" utils.js

--color-moved なしの git diff の出力は、さっきとほとんど同じに見えます。差分の中身で違うのは + const rounded = Math.round(tax); の1行だけで、これを6行の中から目で探すことになります。--color-moved を付けると、次のように表示されました。

git diff --color-moved の出力。calcTax の行のうち、const rounded の行は、消した側が赤の Math.floor、足した側が緑の Math.round になり、空行を除くほかの行はマゼンタとシアンのまま
移動したうえで1行を書き換えた差分を、git diff –color-moved で出したもの

書き換えた const rounded = ... の行は、ふつうの赤と緑になりました。その前後の、中身が変わっていない行はマゼンタとシアンのままです(空行が赤と緑なのは前の例と同じで、理由は後で説明します)。

ポイント

--color-moved を付けた差分では、マゼンタとシアンの行は「場所を移しただけ」、赤と緑の行は「書き換えた・新しく足した・消した」と読めます(空行のような短い行は例外です)。レビューでは赤と緑の行を中心に見れば済みます。

隣り合う2つの移動は、別の色に切り替わって分かれる

--color-moved のように値を付けないときは、zebra という見せ方になると公式ドキュメントに書かれています。zebra では、移動したまとまりが変わるたびに、もう1組の色に切り替わります。

ためしに、calcTax と formatPrice の2つを、順番を入れ替えたうえで末尾へ移しました。ファイルの並びは totalPrice、formatPrice、calcTax になります。この差分を、値を付けない --color-moved で出しました。

console
$ git diff --color-moved
git diff --color-moved の出力。消した側は calcTax がマゼンタ、formatPrice が青。足した側は formatPrice がシアン、calcTax が黄色で、2つの関数が別の色で表示されている
2つの関数を、順番を入れ替えて末尾へ移した差分(zebra)

消した側は calcTax がマゼンタ、その下の formatPrice が青で表示されました。足した側では、先に来る formatPrice がシアン、続く calcTax が黄色です。色が切り替わる場所が、別々の場所から来たまとまりの境目です。

同じ差分を --color-moved=plain で出すと、消した側はすべてマゼンタ、足した側はすべてシアンになり、境目は分からなくなりました。

短い行が移動の色にならないことがある

最初の、calcTax を末尾へ移しただけの差分では、空行だけが赤と緑のまま残っていました。同じ差分を --color-moved=plain で出すと、この空行もマゼンタとシアンになりました。

公式ドキュメントによると、plain はどこかで消してほかの場所で足した行をすべて移動として色を付けます。一方、blocks と、それをもとにした zebra は、英数字が20文字以上ある移動のまとまりを探して色を付けます。空行だけでは英数字がないので、zebra では移動の色になりませんでした。zebra で赤と緑の行が出たときは、それが空行のような短い行かどうかも見てください。

インデントだけ変わった行も、移動として見せる

totalPrice の for の部分を if (Array.isArray(items)) { ... } で囲み、中の行のインデントを1段深くしました。--color-moved だけでは、インデントが変わった行はすべて赤と緑のままで、移動とはみなされませんでした。

移動を探すときの空白の扱いは、--color-moved-ws で変えられます。--color-moved-ws=allow-indentation-change を足すと、次のようになりました。

console
$ git diff --color-moved --color-moved-ws=allow-indentation-change
git diff --color-moved --color-moved-ws=allow-indentation-change の出力。インデントが変わっただけの for、if、continue、sum の行はマゼンタとシアンになり、新しく足した if (Array.isArray(items)) { の行と、} の1行だけが緑
for を if で囲み、インデントが変わった差分

インデントが変わっただけの行はマゼンタとシアンになり、新しく足した if (Array.isArray(items)) { と、} の1行だけが緑で残りました。

公式ドキュメントによると、allow-indentation-change は、行ごとのインデントの変わり方がそろっているときに、ひとまとまりの移動とみなす指定です。この差分では --color-moved-ws=ignore-all-space でも同じ色になりました。

パイプに渡すと色が付かない

--color-moved が変えるのは色だけです。色を付けずに出した git diff と git diff --color-moved を diff で比べると、1字も違いませんでした。一方、パイプでほかのコマンドに渡したときは色そのものが付かないので、移動かどうかも分からなくなります。

console
$ git diff --color-moved | grep -c "$(printf "\x1b")"
0

色を付けるための文字(エスケープシーケンス)を含む行が0行で、色はまったく付いていません。パイプの先でも色を残したいときは、--color=always を付けます。検証環境では、script コマンドで端末に出したときと、--color=always でファイルに出したときで、出力がまったく同じでした。

毎回付けるなら diff.colorMoved に設定する

毎回 --color-moved を打つ代わりに、設定 diff.colorMoved に見せ方を書いておけます。

console
$ git config diff.colorMoved zebra

git config diff.colorMoved で確かめると、zebra と表示されました。

設定したあとに calcTax を末尾へ移すコミットを作り、--color-moved を付けずに git show を実行すると(--color=always を付けて色を確かめました)、移動した行がマゼンタとシアンで表示されました。その場だけ色分けをやめたいときは、git show --no-color-moved のように --no-color-moved を付けます。検証環境では、これで赤と緑だけの表示に戻りました。

空白の扱いも、git config の説明にある diff.colorMovedWS で同じように決めておけます。

​Posted by 86