gitで更新のあったファイルに対してformatter(goimports)をかける

CIでlintのついでにformatter(gofmt, goimports, gofumpt)がかかっているかチェックしている環境があるとする。そこでformatされていないと怒られたファイルに対してformatterをかけたい。その方法のメモ。

あるコミットで変更されたファイルを集める

対象のコミットで変更されたファイルの一覧を集めてformatterにかけたい

git show --stat HEAD をparseしてみる?

例えば対象のコミットをHEADということにすると git show --stat HEAD の変更を利用するということが考えられる

以下の様な形。

$ git show --stat HEAD | grep -F '|'
 xxx/gen/mock/handler.go                                |   3 +-
 yyy/gen/mock/router.go                                 |   3 +-
 zzz/gen/mock/next_state.go                             |   5 +-
 aws/s3/gen/mock/s3.go                                  |   3 +-
 .../aws/aws-sdk-go/service/sqs/sqsifacemocks/SQSAPI.go | 122 +++++++++++------------

# 対象のファイルの一覧
$ git show | grep -F '|' | cut -d '|' -f 1
 xxx/gen/mock/handler.go                                
 yyy/gen/mock/router.go                                 
 zzz/gen/mock/next_state.go                             
 aws/s3/gen/mock/s3.go                                  
 .../aws/aws-sdk-go/service/sqs/sqsifacemocks/SQSAPI.go 

あとは一覧が取れればすんなり渡して行けば良いということに一見なりそうなのだけれど。そうはならない。ものによってはファイル名の出力が省略されてしまうことがある。

 .../aws/aws-sdk-go/service/sqs/sqsifacemocks/SQSAPI.go 

これが完全なファイルパスではないのでちょっと不便。

暫定的で頑健な普段の手癖

普段はあまり考えずにとりあえずbasenameだけあっていれば対象にしちゃって良いでしょ、と以下の様なコードを手癖で書いていたけれどもう少しまともなワンライナーを検討しても良いかもしれない。

for i in $(git show --stat HEAD | grep '|' | cut -d '|' -f 1); do find . -name "$(basename $i)"; done | xargs goimports -w

同一のbasenameのものが含まれるのでformatterに渡されるファイルが増える可能性はあるのだけれど、実用上はあまり問題にならない。まぁでももう少しキレイな方法を見出したい。

--stat=<column>

manを覗いてみると以下のようなことが書かれている。デフォルトのターミナルのcolum sizeは80でその値を元に良い感じに表示を調節しているよう。

$ man git-show
...

       --stat[=<width>[,<name-width>[,<count>]]]
           Generate a diffstat. By default, as much space as necessary will be used
           for the filename part, and the rest for the graph part. Maximum width
           defaults to terminal width, or 80 columns if not connected to a
           terminal, and can be overridden by <width>. The width of the filename
           part can be limited by giving another width <name-width> after a comma.
           The width of the graph part can be limited by using
           --stat-graph-width=<width> (affects all commands generating a stat
           graph) or by setting diff.statGraphWidth=<width> (does not affect git
           format-patch). By giving a third parameter <count>, you can limit the
           output to the first <count> lines, followed by ...  if there are more.

           These parameters can also be set individually with --stat-width=<width>,
           --stat-name-width=<name-width> and --stat-count=<count>.

なのでテキトウに --stat に大きめの数値を渡してあげれば大丈夫かもしれない。

$ git show --stat=100000000 HEAD | grep -F '|' | cut -d '|' -f 1
...
test/mocks/github.com/aws/aws-sdk-go/service/sqs/sqsifacemocks/SQSAPI.go 

まぁ暫定的には。。

--name-status

たまたまmanをのぞいて見つけた --name-status オプションの方が目的にはあっているかもしれない。

$ man git-show
...

       --name-status
           Show only names and status of changed files. See the description of the
           --diff-filter option on what the status letters mean.

以下の様な出力になる。変更されたもの(M)や追加されたもの(A)だけを取り出せるのでこちらのほうが良いかもしれない。

$ git show --name-status HEAD
...
M       xxx/gen/mock/handler.go
M       yyy/gen/mock/router.go
M       zzz/gen/mock/next_state.go
M       aws/s3/gen/mock/s3.go
M       test/mocks/github.com/aws/aws-sdk-go/service/sqs/sqsifacemocks/SQSAPI.go

一応これで大丈夫か調べてみる。

$ git log --stat | grep -F '...' | head -n 1
 .../aws-sdk-go/service/sqs/sqsifacemocks/SQSAPI.go | 122 ++++++++++-----------

$ git log --stat=1000000 | grep -F '...' | head -n 1
$ git log --name-status | grep -F '...' | head -n 1

大丈夫そう。

現在変更されているファイル

ついでに現在変更されているファイルにも使えるか試してみる(git reset HEAD~~ などでテキトウにそういう状況を再現して)。

現在変更されているファイルを対象に一覧したい場合。

$ git diff --name-status

addされたファイルのみを対象にしたい場合。

$ git diff --staged --name-status

というわけで一覧が欲しかったら以下の様な形にすれば良さそう。

$ git diff --name-status | grep -P '^(A|M)\t' | cut -f 2

まとめ

更新されたファイルの一覧が欲しかったら --name-status が便利。git show に限らず色々なコマンドで使える。