gitとExcelVBAで、ブランチ間の差分をExcelに出力してみたよ！

# 始めに
この記事は、マクロを使って別のアプリケーションを動かせないか、を検証してみたものです。
今回はgitをコマンドプロンプト経由で使ってみました！
(本当は直接gitを操作したかったけど、実行方法がイメージできなかった . . . )

毎度のことですが、記事の内容に関して「ここおかしい！」とかあったら、コメントお願いします。


　読了時間 : 10 min.(目安)

# やりたいこと
Excelにブランチの差分をその種類？(追加・変更・複製・削除)ごとに出力します！
(こんな感じでやります！みたいなgifをここに載せる予定です。)

# 作業環境
以下の環境で動作確認しています

OS : Windows10 Home
git : 2.22.0.windows.1
Excel : 2016 MSO 32bit

# 本題の前に
本題に入る前に、gitに関してちょっとだけ補足的な説明です。「そんなのいいからコードはよ！！」というあなたは適当に読み飛ばして下さい。

gitでブランチ間の差分、それもファイル名だけを見ようと思ったら、以下のようなコードを実行すると思います[^1]。

```shell
# master と develop の差分を見る
> git diff master..develop --name-only
```
↑の場合だと`master`と比較して`develop`ブランチで、
　・どのファイルを新たに作成したか
　・どのファイルを修正したか
　・削除したファイルは何か
などを確認できます。さらに差分の種類？[^2]ごとに表示するなら、`--diff-filter`を付けて

```shell
# 新規追加したファイルだけ差分として表示
> git diff master..develop --name-only --diff-filter=A
```
とすればOK！指定する文字は、Aは追加(Added)、Mは修正(Modified)、Cは複製(copied)、Dは削除(Deleted)を表してます。`--diff-filter=AM`みたいな複数指定もいけます、便利！！

# とりあえず使い方とコード
使い方としては、
1. 下記のマクロを全部コピペして、
2. E5, E7セルにブランチ名を記載したら、
3. main()を実行

です。E5に指定したブランチ名に対して、E7で指定したブランチの差分が出力されるはずです。
　詳しい説明はさておき、まずはコードを貼ります。コメントも結構書いているので、それだけでも分かる人は何しているか分かるのかな、とも思っています[^3]。~~本当は説明がメンド臭いのであった。~~

①メインのサブプロシージャ[^4](矛盾)
最初に実行するプロシージャです。こいつを適当なボタンに設置してマクロを実行します！

```vb:main()

Option Explicit

Public Const firstRow As Long = 7  ' 出力セルの開始行
Public Const firstColumn As Long = 5  ' 出力セルの開始列

Sub main()

    Dim aryDiffOp() As Variant  ' ファイルの差分をフィルタリングするオプション
    Dim dirPath As String  ' リポジトリのパス
    Dim aryFilePath As Variant ' ファイルのパスを格納する配列
    Dim diffOp As Variant ' 差分オプションのループ変数
    Dim ii As Long : ii = firstRow  ' 出力する際のループ変数


    ' 差分を取得するリポジトリのパスを設定
    dirPath = getGitDirPath()

    ' 差分オプションの設定
    aryDiffOp = Array("A", "M", "C", "D")

    ' 新規・変更・複製・削除されたファイルをそれぞれ出力する
    For Each diffOp In aryDiffOp
        
        ' 変更内容ごとにファイルを取得
        aryFilePath = execCmd(dirPath, diffOp)
        If IsNull(aryFilePath) Then
            ' 差分が無い場合
            Call layoutFilePath(Null, diffOp, ii)
        Else
            ' 差分がある場合
            Call layoutFilePath(aryFilePath, diffOp, ii)
        End If
    Next diffOp
    MsgBox "終了！"
End Sub
```

②gitフォルダのパスを取得する関数
エクスプローラを起動して、`.git`が存在するフォルダを選択させる関数です。それだけです。

```vb:getGitDirPath()
Function getGitDirPath() As String
    Dim filePath As String

    ' gitフォルダを1つだけ選択させる
    With Application.FileDialog(msoFileDialogFolderPicker)
        .AllowMultiSelect = False
    
        If .Show Then
            ' フォルダを選択した場合
            getGitDirPath = .SelectedItems(1) & "\"
        Else
            ' フォルダを選択していない場合
            MsgBox "フォルダが存在しません"
            End ' マクロを終了させる
        End If
    End With
End Function
```

③gitコマンドを実行する関数
今回の主役とも言うべき関数です！！コマンドをわざわざ配列に分けて格納しているのは、完全に趣味です！！
gitのパスを定数として定義しているので、パスが通ってなくても動くように設定していますが、環境に応じて定数を書き換えたり、削除したりして下さい。

```vb:execCmd
Function execCmd(ByVal dirPath As String, ByVal diffOp As String) As Variant
    Dim sh  As New IWshRuntimeLibrary.WshShell  ' WshShellクラスオブジェクト
    Dim ex  As WshExec  ' Execメソッド戻り値
    Dim diffFromBranch As String: diffFromBranch = Cells(5, 5).Value  ' 差分比較元のブランチ
    Dim diffToBranch As String: diffToBranch = Cells(5, 7).Value  ' 差分比較先のブランチ
    Dim cmd  ' 実行コマンド
    Dim aryCmd(1)  ' 実行コマンド配列
    Dim gitCmd  ' gitコマンド
    Dim aryGitCmd(5)  ' gitコマンド配列
    Dim result As String  ' コマンド実行結果

    Const git As String = """C:\Program Files\Git\cmd\git.exe"""


    '// gitコマンドを配列に格納
    aryGitCmd(0) = git
    aryGitCmd(1) = "-C " & dirPath
    aryGitCmd(2) = "diff"
    aryGitCmd(3) = diffFromBranch & ".." & diffToBranch
    aryGitCmd(4) = "--name-only"
    aryGitCmd(5) = "--diff-filter=" & diffOp
    
    '// gitコマンドを空白区切りで連結
    gitCmd = Join(aryGitCmd, " ")
    MsgBox "gitCmd > " & gitCmd
    
    '// 実行する順にコマンドを配列に格納
    aryCmd(0) = "C:"
    aryCmd(1) = gitCmd
    
    '// コマンドを連結
    cmd = Join(aryCmd, " & ")
    MsgBox "cmd > " & cmd
    
    '// コマンド実行
    Set ex = sh.Exec("cmd.exe /C " & cmd)
    
    '// コマンド失敗時
    If (ex.Status = WshFailed) Then
        '// 処理を抜ける
        MsgBox "処理に失敗しました"
        Exit Function
    End If
    
    '// コマンド実行中は待ち
    Do While (ex.Status = WshRunning)
        DoEvents
    Loop
    
    '// 標準出力の結果を表示する
    result = ex.StdOut.ReadAll
    execCmd = splitResultExecCmd(result)
End Function
```

④差分を配列に格納する関数
差分をファイルパスごとに配列に格納して、呼び出し元のmainへと返しています。差分が無かった場合は、nullを返却しています。
↑の関数で出力された結果は、1ライナーの文字列なので、改行コード区切りで分割しています！なぜそうなるかは不明ですが、最後の配列に空白が入るので、返却前に配列の最後の要素を削ってます。

```vb:splitResultExecCmd
Function splitResultExecCmd(ByVal resultExecCmd As String) As Variant
    Dim aryFilePath As Variant ' ファイルのパスを格納する配列

    If Len(resultExecCmd) <> 0 Then
        ' 差分がある場合の処理
        aryFilePath = Split(resultExecCmd, vbLf)
        ReDim Preserve aryFilePath(UBound(aryFilePath) - 1)
        splitResultExecCmd = aryFilePath
    Else
        ' 差分が無かった場合の処理
        splitResultExecCmd = Null
    End If
End Function
```

⑤ファイルのパスをセルに出力する関数
差分が得られたら、後は煮るなり焼くなりしましょう！下記では、適当なセルを基準にした方向に並べて出力しています。

```vb:layoutFilePath
Sub layoutFilePath(ByRef aryFilePath As Variant, ByVal diffOp As String, ByRef i As Long )
    Dim filePath As Variant   ' 各ファイルのパス
    
    ' 差分の種類を出力
    Cells(i, firstColumn) = diffOp

    ' 差分の有無で出力内容を分岐
    If IsNull(aryFilePath) Then
        Cells(i, firstColumn + 1) = "差分なし"
        i = i + 1
    Else
        ' ファイルパスを1つずつ出力
        For Each filePath In aryFilePath
            Cells(i, firstColumn + 1) = filePath
            i = i + 1
        Next filePath
    End If
End Sub
```


# 終わりに
最後まで読んで下さり、ありがとうございました。


# ひとりごと
毎回「始めに」で何を書くか迷っているんですよね . . . (みんなどうしているのかな？)
読み物として楽しい記事を作りたいので、イイ感じに書けるように修行ですね！

# 参考
１．[VBAでコマンドプロンプトの起動とコマンドの実行を行う](https://vbabeginner.net/vba%E3%81%A7%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%81%AE%E8%B5%B7%E5%8B%95%E3%81%A8%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E5%AE%9F%E8%A1%8C/)
２．[Nullを判定する－IsNull関数](https://www.relief.jp/docs/excel-vba-is-null.html)
３．[【VBA入門】配列の初期化(ReDim、Preserve、Array、Erase)](https://www.sejuku.net/blog/29274)
４．[VBAでフォルダパスを取得する](https://kazusa-pg.com/vba-get-folder-path/)
５．[Application.FileDialog プロパティ (Excel)](https://docs.microsoft.com/ja-JP/office/vba/api/Excel.Application.FileDialog)
６．[【VBA入門】Functionの使い方(呼び出し、引数、戻り値)](https://www.sejuku.net/blog/28904)
７．[VarType Office TANAKA](http://officetanaka.net/excel/vba/function/VarType.htm)

[^1]: `git log`でも同じようなことが出来ますが、今回はスルー。
[^2]: これ、表現として正しいんですかねぇ？誰か教えてくれません？
[^3]: 分からなくでも大丈夫です。汚いコードを書いた筆者に全ての責任はあります。少しずつ修正しているので、大目に見てやって下さい . . . 。
[^4]: てっきりサブルーチンだと思ってました。fortran勢なのでw