ITエンジニアによるITエンジニアのためのブログ

Github PR風の差分表示をCLIで実現

はじめに

Githubでプルリクエストを作成すると、非常に読みやすい差分が表示されます。例えば、行単位だけでなく単語単位の差分表示や、マージベースからの差分表示など、コードレビューを非常に効率的に行うことができるようになっています。

この記事では、ローカル環境のCLIでGithubプルリクエストの機能を再現する方法を紹介します。

目次

  1. ハンクヘッダーに関数名を表示
  2. 単語単位の差分表示
  3. マージベースからの差分
  4. 前回レビューからの差分
  5. 変更ファイル一覧
  6. 変更コミット一覧
  7. まとめ

1. ハンクヘッダーに関数名を表示

git diffはデフォルトで、差分のハンクヘッダー(@@の部分)に差分を含む関数名を表示します。

しかし、Rubyなど特定の言語では、以下の例のように関数名ではなくクラス名が表示されてしまいます。これは、Rubyの場合、デフォルトの設定ではクラス名が関数名として認識されてしまうためです。

@@ -10,1 +10,3 @@ class ExampleClass

Githubプルリクエストでは、そのような言語でも差分にクラス名ではなく関数名が表示されるようになっているので読みやすいのですが、ローカルCLIで同じように関数名を表示させるには設定変更が必要です。

CLIでの再現方法

gitレポジトリのルートディレクトリに.gitattributesファイルを追加し、特定のファイルパスについてdiff属性を設定します。

例:

# .gitattributes
*.rb diff=ruby
*.py diff=python
*.html diff=html

上記は、rb、py、htmlファイルエクステンションがあるファイルのdiff属性を、それぞれruby、python、htmlに指定しています。これらの言語はgitにデフォルトで組み込まれているdiff属性なので、これのみで設定は完了します。

これで、git diffを実行すると、先ほどのようにクラス名ではなく、関数名が表示されるようになります。

@@ -10,1 +10,3 @@ def example_method

補足

gitにデフォルトで組み込まれていないカスタムdiff属性を使う場合、ホームディレクトリの.gitconfigファイルで、正規表現を使ってカスタムハンクヘッダーを設定します。

# ~/.gitconfig
[diff "tex"]
	xfuncname = "^(\\\\(sub)*section\\{.*)$"

上記は、LaTeX文章の差分ハンクヘッダーの例です。

2. 単語単位の差分表示

行の中で一部の単語のみ変更された場合、Githubの差分では行全体の追加削除の色に加えて、その単語のみをハイライトしてくれるので正確な変更箇所がわかります。

CLIでの再現方法

一方、CLIでgit diffを使って単語単位の差分を表示するには、–color-wordsオプションを使います。

git diff --color-words

Githubのようなハイライトとは若干違いますが、このオプションを使うと、追加された箇所を緑色で、削除された単語については赤色で表示してくれるので、Githubと同様に行の中の正確な変更箇所を把握できます。

–color-wordsオプションの問題と代替案

ただし、私自身は–color-wordsオプションを使っていません。その理由は、移動しただけのコードブロックを追加・削除とは違う色で表示するための–color-movedオプションを私が頻繁に使うためです。–color-movedオプションと–color-wordsオプションを同時に使うと、–color-wordsオプションが優先されてしまい、移動しただけのコードブロックの区別が付かなくなってしまいます。

代わりに、私はgitパッケージの中に含まれているdiff-highlightというツールを使っています。このツールは単語単位の差分をハイライトしてくれるだけでなく、–color-movedオプションとも同時に使えるので重宝しています。

設定方法は、ホームディレクトリの.gitconfigファイルで以下のように指定するだけです。

# ~./gitconfig
[pager]
  diff = perl /usr/share/doc/git/contrib/diff-highlight/diff-highlight | less
  show = perl /usr/share/doc/git/contrib/diff-highlight/diff-highlight | less

*diff-highlightのファイルパスはgitのインストール場所によって異なるため、OSによっては若干変更する必要があるかもしれません。

ここでは、git diffとgit showのアウトプットをdiff-highlightを経由して色付けしてからlessにUnixパイプでつなげています。diff-highlightはperlスクリプトですが、ファイルパーミッションが実行可能になっていないため、perlコマンドで読み込んで実行しています。

これで、git diffもしくはgit showコマンドを使うと、行の一部の単語だけが変更された場合にハイライトで表示されるようになります。

なお、同じ行の中で複数の単語が変更された場合は、最初の変更箇所から最後の変更箇所まで続けてハイライトされるので、若干わかりにくいという欠点もあります。Githubでは上記のようなケースでも個別単語ごとにハイライトしてくれるので、その点はGithubの機能に見劣りはします。

3. マージベースからの差分

Githubでは、プルリクエストのマージ先ブランチに別のブランチがマージされたとしても、開発ブランチがマージ先ブランチから枝分かれした地点からの差分のみを表示してくれ、別のブランチの内容が混ざって表示されることはありません。

例:mainブランチにdev1ブランチをマージするためのプルリクエストを作成後、mainにbugfix1ブランチが先にマージされても、プルリクエスト上でのmainとdev1の差分にbugfix1の内容が混ざることはない。

これは、Githubプルリクエストは、2つのブランチの単純な差分ではなく、マージベースからの差分を表示しているからです。

CLIでの再現コマンド

git diff branch_to_merge...HEAD

上記のように、3点ドットのレンジ構文を使うと、Githubと同様に現在のブランチとマージ先のブランチとのマージベースからの差分を表示できます。HEADはデフォルト値なので、git diff branch_to_merge…と省略することも可能です。

補足

2点ドットのレンジ構文(branch_to_merge..HEAD)だと、マージ先のブランチ(例:master/main)に他の開発ブランチがマージされていた場合、マージ先のブランチから枝分かれした地点(マージベース)ではなく、masterが取り込んだ別の開発ブランチのコードも表示されてしまいますので注意が必要です。

4. 前回レビューからの差分

Githubでは前回のレビューからの差分のみを表示する機能(Show changes since your last review)があり、レビューで指摘した箇所が修正されているかどうかの確認に便利です。

CLIでの再現方法

git diffで2点ドットのレンジ構文(commit_id..commit_id)を使い、前回レビュー時のコミットIDとブランチの最新コミット(HEAD)を指定します。

# 2点ドット構文
git diff last_commit_id..HEAD

# 2点ドット構文の場合、HEADは省略可能です。
git diff last_commit_id..

# 2点ドットなしで2つのコミットIDを指定しても同義です。
git diff last_commit_id HEAD

# 但し、2点ドットなしでHEADを省略することはできません。
# 以下だとHEADではなく、ワーキングツリーと前回レビュー時コミットとの比較になってしまうので注意が必要です。
git diff last_commit_id

5. 変更ファイル一覧

Githubのプルリクエストでは、コンテンツ差分の左側に変更ファイル一覧がツリー形式で表示されます。

CLIでの再現方法

ツリー形式ではありませんが、変更ファイル一覧と個別ファイルのステータスは以下のコマンドで表示できます。

git diff --name-status branch_to_merge...HEAD

ここでも、ファイル差分の場合と同様に3点ドットのレンジ構文を使って、マージベースからの差分を表示させます。

6. 変更コミット一覧

Githubプルリクエストでは、含まれるコミット一覧を表示するタブメニューがあります。

CLIでの再現方法

CLIで同じようなコミット一覧を表示するには、以下のコマンドを使います。

git log branch_to_merge..HEAD

git logの場合、マージベースからのgit diffの差分に対応するコミット一覧を表示させるには、3点ドットレンジではなく、2点ドットレンジ構文を使います。git diffとはドットの意味が異なるので、注意が必要です。

ちなみに、上記のgit logコマンドの意味は、「HEADに含まれていて、かつbranch_to_mergeに含まれていないコミット一覧を表示する」、です。マージベースはコマンド定義に含まれていないですが、結果としてマージベースからのコミット一覧と同義になります。

7. まとめ

Githubプルリクエストは、レビューワーがコード差分を確認しやすいよう様々な工夫がされています。

一方、ローカル環境でCLIを使ってそのような差分表示ができないかと言うとそうではありません。CLIを使ってコードレビューしたい場合でも、gitの設定やコマンドオプションを変更すれば、Githubと同じように見やすい差分を表示することが可能です。ぜひ試してみて下さい。