#git
#GitHub
#shell
#VisualStudioCode
OPENLOGIDay 14

ローカルでGitHubのFiles Changed的な表示をする

More than 1 year has passed since last update.

この記事は OPENLOGI Advent Calendar 2017- Qiita の14日目です。
なんか書いてと言われたので、今回は普段の開発で溜まってる不満をエンジニアらしく解決してみようと思います。

前置き

みなさんのチームでもGithub使っていることろは多いかと思いますが、OPENLOGIもGithubを使っています。で、何か開発をするとGithub上でPull Reuqeust作るじゃないですか。んでPR作ったらとりあえずFiles Changed見るじゃないですか。こういうの。

Files Changedはプロダクトのコードに与える影響そのものなのでPR上で最も気にしなければいけない部分です(commitごとの変更?細けぇことはいいんだよ!)。これはgit pushしてPRを作ってしまえば確認できるわけですがPR作成前にローカルでだってチェックしたいし、PR上の表示も変更箇所の前後が部分的に表示されてるだけなので見やすいとは言えないんですよね。 

なのでそこいらを解決する方法を検討していきます。

その1. 適当にgit diffコマンドで見る

どういうgit運用をしているかは人に寄るとは思いますが、feature的なブランチを派生させる元は大概developとかmasterとかですよね。
派生元のブランチ名が分かっているならとりあえずこんな感じで見れます。
(HEADの指定は以降の説明でも省略しても同様の結果が得られます)

% git diff develop...HEAD
diff --git a/file1.txt b/file1.txt
index 2262de0..62adb72 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hoge
+hoge piyo
diff --git a/file2.txt b/file2.txt
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/file2.txt
@@ -0,0 +1 @@
+foo

しかしこれってdevelopブランチとカレントブランチのHEADの比較なので、developブランチが他人のマージコミットで成長していた場合は自他の変更が入り混じってカオスな表示になってしまいます。分岐してからdevelopブランチを一度もpullしていないことが前提になるので実用的ではありません。

ちゃんと比較しようと思うとハッシュ値ベースで指定するしかありません。手っ取り早いのはgit logでハッシュ値を目視で調べてしまう方法です。

$ git log --oneline
44c5ea9 (HEAD -> feature1) add file2
3afbad0 modify file1
926b1e5 (master, develop) first commit
$ git diff 926b1e5...
diff --git a/file1.txt b/file1.txt
index 2262de0..62adb72 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hoge
+hoge piyo
diff --git a/file2.txt b/file2.txt
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/file2.txt
@@ -0,0 +1 @@
+foo

うん・・・比較としては正しいけどめんどくさい!目視でハッシュ値を探してコピペをしないといけないわけです。間違えやすいしこれは良くない。そもそも派生元のブランチ名を必ず知ってるor覚えているとは限らないわけです。

その2. もうちょい賢くgit diffを見る

まず派生元のブランチ名を機械的に抽出しましょう。ここは こちらの記事からコマンドを拝借しましょう。解説も十分詳しくされています。

$ git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}'

派生元ブランチ名が分かったら次は分岐点となったコミットのハッシュ値を調べます。これは

$ git show-branch --merge-base (派生元ブランチ名) HEAD

で分かります。
あとは取得したハッシュ値でdiffをとるだけです。

$ git diff (取得したハッシュ値)...HEAD

これならシェルスクリプトでコマンドをまとめれば1アクションで調べれるでしょう。

#!/bin/sh

# Identify the name of the source branch
SRC_BRANCH=`git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}'`

# Identify the hash of the branch point
SRC_HASH=`git show-branch --merge-base ${SRC_BRANCH} HEAD`

git diff $SRC_HASH...

やったね!と言いたいところですが、下記の結果表示を改めて見て下さい。見辛い!圧倒的見辛さ!GithubのFiles Changedですら見難いって言ってたのにこれは輪をかけて辛いです。ローカルでFiles Changedを確認できるという部分しか解決していません。

diff --git a/file1.txt b/file1.txt
index 2262de0..62adb72 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-hoge
+hoge piyo
diff --git a/file2.txt b/file2.txt
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/file2.txt
@@ -0,0 +1 @@
+foo

その3. そもそもいきなり全ての変更を羅列して欲しいのか?

変更点が2・3ファイルで全体が数十行程度の変更ならいきなり全ての変更を表示しても構いませんが、ちょっと大きい修正になればこれは鬱陶しい場合もあるはずです。GithubのFiles Changedでも規模の大きい修正になると目的のファイル差分を見るためにわざわざページ内検索をする必要があったり、修正量が多くなると無視できないくらいページロードが遅くなります。

ある程度の規模になると「どのファイルに対して変更があったのか」がまず知りたく、その後に特定ファイルの差分を詳しく見たいと思うことがあるはずです。幸いにしてgitは変更ファイルを列挙するコマンドを備えています。

$ git diff --name-status (分岐元ハッシュ値) HEAD
$ git diff --name-status 926b1e504e6a35131eacd917af6cc3d5551eca44 HEAD
M       file1.txt
A       file2.txt

この中からインタラクティブにファイルを選択して詳細が見えるといい感じになりそうです。コマンドラインにおけるインタラクティブな選択といえばpecoやfzfといったツールの出番です。私はfzfを使ってますが他のツールに置き換えても同様の使い方ができるでしょう。

$ git diff --name-status (分岐元ハッシュ値) HEAD | fzf | awk -F' ' '{print $2}'

スクリーンショット 2017-12-03 18.16.09.png

$ git diff --name-status 926b1e504e6a35131eacd917af6cc3d5551eca44 HEAD | fzf | awk -F' ' '{print $2}'
file1.txt

--name-onlyだと awkのパースは要らないんですが、変更なのか追加なのかといった状態くらいは見たいので--name-status使って後からパースしてます。

これで変更ファイルのリストを表示でき、その中から選択してファイルパスだけを抽出するところまでできました。

その4. GUIのdiffツールを使う

単一のファイルの差分を見るならCUIよりGUIの方が圧倒的に見やすいです。無料で使えるdiffツールとしては最近のVisual Studio Codeは中々に悪くありません。WinMergeのようにWindows環境に縛られたりすることもありません。
VSCodeをgitのdifftoolとして使う方法は公式に公開されています。
https://vscode-doc-jp.github.io/docs/userguide/versioncontrol.html#Git-%E3%83%91%E3%83%83%E3%83%81-%E5%B7%AE%E5%88%86-%E3%83%A2%E3%83%BC%E3%83%89

抜粋ですが.gitconfigに下記の設定をするだけでいいです。

[diff]
    tool = default-difftool
[difftool "default-difftool"]
    cmd = code --wait --diff $LOCAL $REMOTE

 difftoolの設定ができれば、あるファイルのChangedはこのようにしてGUIツールで確認できます。

$ git difftool --no-prompt (分岐元ハッシュ値):(ファイルパス) HEAD:(ファイルパス)

--no-promptはdifftoolの起動に確認入力を求められるのをスキップします。

スクリーンショット 2017-12-03 18.25.12.png

さっきよりは断然イケてると思うのですが如何でしょうか。

まとめ

これまで説明してきたコマンドをシェルスクリプトにまとめます。

#!/bin/sh

# Identify the name of the source branch
SRC_BRANCH=`git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}'`

# Identify the hash of the branch point
SRC_HASH=`git show-branch --merge-base ${SRC_BRANCH} HEAD`

# Select file 
FILE=`git diff --name-status ${SRC_HASH} HEAD | fzf | awk -F' ' '{print $2}'`

# Show diff by the awesome tool
if [ -n "$FILE" ]; then
  echo show diff $FILE
  git difftool --no-prompt ${SRC_HASH}:${FILE} HEAD:${FILE}
fi

短時間で作るためにシェルスクリプトでゴリっと書いちゃいましたが、私は開発環境にVSCodeを使っているのでVSCodeの拡張機能として組み込んだ方が使い勝手は良さそうですよね。
時間が取れればそちらにもトライできればと思います。