みなさんこんにちは。Backlog課のGitチームに所属するテリーです。Gitを触ってるとたまにちょっとした集計をしたくなります。その度に検索したり考えたりするのも少しめんどくさいので、業務上ではあまり必要ではないがたまに確認したくなるようなコマンドを集めてみました。どのコマンドも書き換えることなくコピーアンドペーストで動くようになっているのでぜひ興味を持った方は自分の環境で試してもらえると嬉しいです。
目次
リポジトリの傾向を知るコマンド
一定期間毎のコミット数を知る
「このリポジトリっていつ頃が一番盛んにコミットされていたのかなー?」
GitHubなどが提供しているこの機能実はBacklogでは提供されていません。(今後の機能開発頑張ります)そのためちょっと気になった時にコマンドで探れると大変嬉しいです。
年別・月別を知る
git log
の出力結果をAuthor dateだけにして--date
で年だけ出力するようにします。それをカウントしていけば欲しいデータが出力できます。
git log --format=%ad --date=format:%Y | sort | uniq -c
Backlogのとあるリポジトリのデフォルトブランチではこのようになりました。
> git log ---format=%ad --date=format:%Y | sort | uniq -c 2015 40 2016 945 2017 2964 2018 6244 2019 6216 2020 5990 2021 7742 2022 6914
もしかしたらもう少し細かい単位(月別)で見るとグラフの出力のやりがいがあるかもしれません。先ほどのコマンドを少し変更して--date=format:%Y-%m
として出力された結果をスプレッドシートでグラフにしてみました。またスプレッドシートに転記しやすいようにカンマ区切りに加工しています。
12月はまだ始まったばかりなので考慮から外すとしてそれ以外の直近の期間は安定してコミットされていることが確認できます。
git log --format=%ad --date=format:%Y-%m | sort | uniq -c | sed 's/^ *\([0-9]*\) /\1,/'
曜日別
「私は何曜日が一番コミットしてるんだろ?気になるなー」
こんなシチュエーションに遭遇したことはありませんか?私はありませんが曜日別の集計もあったら便利かもと思ったので一応掲載させていただきます。年別集計のコマンドを少し変えるだけです。
git log --format=%ad --date=format:%a --author="$(git config user.email)" | sort | uniq -c | sort -nr
上記同様とあるリポジトリでの上のコマンドの結果がこちらです。
> git log --format=%ad --date=format:%a --author="$(git config user.email)" | sort | uniq -c | sort -nr 45 Thu 39 Tue 38 Wed 35 Fri 23 Mon
思った通りですが月曜日と金曜日の生産性の低さが目立ちます。
上記コマンドに軽く登場していますがgit config user.email
で自分のメールアドレスを知ることができますので自分関連の情報を引っ張る際は使用します。
リポジトリのファイル数を知る
「リポジトリに登録されているファイルの数を知りたいなー」
これは割と簡単にgit ls-files
でGitにインデックスされているファイルの一覧をとることができるのでこれをデフォルトオプションのまま数え上げれば取得できます。
git ls-files | wc -l
リポジトリに存在するファイルの行数を知る
「ファイル数じゃなくて行数を知りたい。何十万行あるリポジトリって言いたいー」
上記コマンドをcat
することで取得できます。
git ls-files | xargs cat | wc -l
git ls-files
はファイルのパターンも指定できるので例えばサーバーサイドの言語(ここではscala)の行数だけ把握したいという時には以下のようにすると取得できます。
git ls-files "*.scala" | xargs cat | wc -l
こちらもBacklogのとあるリポジトリで試したところ以下のようになりました。
❯ git ls-files "*.scala" | xargs cat | wc -l 347936
行数が大きいファイル順を知りたい場合はこのような感じにすると取得できます。先頭にtotalが入ってくるため10thを取得したい場合は`-n 11`を指定するとうまく決まります。
git ls-files "*.scala" | xargs wc -l | sort -nr | head -n 11
変更が多いファイルの一覧を知る
「よく変更されるファイルを知りたいー。もしかしたらそれは複雑なロジックを含むファイルなのかもー」
コミットの多いファイルはもしかしたら開発がホットなのか、もしくはバグが含まれやすくて多く修正されているファイルなのかもしれません。そこでこの1ヶ月でより多くコミットされたファイルの一覧をランキングにしてみます。git log
から直近1ヶ月で変更があったファイルのみを取得してそこにのみ git blame
を行うことで直近1ヶ月で変更されたファイルランキングを取得することができました。
SINCE="1 month ago"; git log --numstat --pretty=tformat: --since="$SINCE" | awk '{print $3}' | sort -u | while read i; do [ -f $i ] && echo $(git blame --show-stats $i --since="$SINCE" | grep "num commits" | awk '{print $3}') $i; done | sort -rn
もはやお手軽とは?という感じもあります。
リポジトリへ関わってる人を知るコマンド
リポジトリのコントリビューターを知る
「このリポジトリのコントリビューターを知りたいー」
該当のリポジトリによくコミットしているのは誰なのか、たくさん変更を加えてるのは誰なのか知りたいときはありませんか?私はたまにあります。そんな時のための二つの方法を紹介します。
最初は変更行毎のコントリビューターの取得方法です。git ls-fies
で全てのファイルの一覧を取得しそれを一つずつgit blame
に渡して1行毎の変更のAuthorを取得します。
その結果をカウントしていけばコントリビューターを一覧できます。
git ls-files | xargs -n1 git blame --porcelain | grep "author-mail" | awk '{print $2}' | sort | uniq -c | sort -rn
もちろんgolangの変更ついてのデータのみ取得したい場合は git ls-files "*.go"
のように指定すればよいです。
モノリポなどでディレクトリを指定したい場合もありますよね。そういう時はgit ls-files --directory frontend/
のように指定するとうまくいきます。
この方法は全部の期間ということになるので直近のコミットに絞りたいとか一定期間で絞りたい場合は「変更が多いファイルのリストを取得する」の章で行ったgit log
を応用する方法が良いかと思います。
だたこのコマンド各ファイルに対してgit blame
をかけているためファイル数が膨大な場合はあまりうまく結果を返してくれません。試しにあるリポジトリで実行したところ10m待っても結果を返してくれませんでした。
コミット数によるコントリビューターの一覧を知る
「もっと簡単にコミット数でソートされたコントリビューターの一覧を見たいよー」
git shortlog
を使用するとコミット数でソートされた結果を取得することができます。
git shortlog -s -n
もしくはメールアドレスの一覧だけをみたい場合はgit log
を使用して一覧にできます。
git log --format=%aE | sort | uniq -c | sort -rn
より詳しくどのユーザーがどのようなコミットを行ったかの一覧は上記同様git shortlog
を使うことで簡単に一覧できます。
git shortlog -s
git shortlog
でもpathを指定することができます。以下のようにするとtestディレクトリへのコミットを行った人で絞ることができます。
git shortlog -s -n -- test/
ここでユーザーネームとメールアドレスが複数バラバラになってうまく集計できない問題に当たる人もいるかと思います。特に長い歴史を持つリポジトリは一筋縄にはいきません。
解決策としては二つが考えられます。
- 対応する辞書を作成し自作のプログラムで対応する
- .mailmapファイルを作成する
1に関しては説明は不要なんですが2については少し補足です。
例として以下のように.mailmapを使用するとshortlogやlogの方でGitのユーザーのマッピングを行ってくれます。もしgit shortlog
の整合性を保つことが重要で、辞書をメンテナンスすることができるのであればこのような仕組みに乗っかるのもいいかもしれません。
https://github.com/git/git/blob/master/.mailmap
(BacklogのGitを使っている場合に)よくプルリクエストのマージを行っている人の一覧を知る
「このリポジトリって誰にプルリクエストのレビューお願いしたらいいのー。過去にプルリクエストをマージした人の一覧を見たいー」
BacklogのGitはマージ時にマージコミットを追加します。そのマージコミットにはMerge pull request #
という文言が付与されるためそのコミットのAuthorを集計すれば欲しい結果を取得できます。
git log --since="5 weeks ago" --format="%aN <%aE>" --grep "Merge pull request #" | sort | uniq -c | sort -rn
上記で説明してきた通りgit log
のオプションに沿ってフィルタリングを実施できます。
自分の修正を確認するコマンド
更新順のブランチ一覧
「あ。最近修正したようなコミットしたような気がするんだけど、どのブランチにコミットしたのかどんなファイル名だったか全く思い出せないー!!」
こんな時git branch
をしても最近更新された順番にはソートしてくれないためあまりいい情報が見つからない時があります。以下のようにするとブランチが更新された順番に並べ替えてくれるので大変見つけやすくなります。
git branch --sort=committerdate --format='%(refname:short)'
さらに自分のコミットしたものだけで絞りたい!!(こんな時はほとんどないとは思いますが)というときは大体はformatでauthornameも出しておくとgrepしやすくなりさらに見つけやすくなります。またこういう場合はどこにもマージされていないことが多いので`–no-merged`を付与することでどのブランチにもマージされていないブランチだけに絞ることができます。authordateも付与しておくとどれくらい古いブランチなのかも分かるのでますます便利になります。
git branch --format='%(authordate:relative) %09 %(authorname) %(authoremail) %09 %(refname)' --sort=committerdate --no-merged |grep "$(git config user.email)"
今週の自分の修正を確認する
「今週私はどんなファイルにどんな修正を加えたんだろー。一週間の成果を提出しないといけないなー」
ここではgit log --numstat
を使用します。このコマンドで各コミットの修正内容のファイルと行の追加、削除の数字が確認できます。tformatにするとコミット情報は省かれtab区切りの文字列が標準出力されます。また`--since
`は相対的な時間も対応しているため`1 week ago
を設定するとこの1週間のログを抽出することができます。
それ以降はそのファイルをgit blame
して自分の名前で検索すると自分の修正記録が表示されます。
SINCE="1 week ago"; git log --author="$(git config user.email)" --pretty=tformat: --numstat --since="$SINCE" | awk '{print $3}' | sort -u | while read i; do echo "$i \n$(git blame $i --since="$SINCE" | grep "$(git config user.email)")"; done
ただ上記の方法では削除のみのファイルはどれを削除したかは確認できないのでそこはもう少し考える必要があるんですが、一旦自分の知りたい情報は得られたので終わりにしています。
自分の貢献の概要を把握する
「このリポジトリに私はどれだけ貢献したのー?」
BacklogのGitには該当のリポジトリへの貢献度合いの概要を表示するUIがありません(こちらもすみません)
以下のようなコマンドでこのリポジトリへのコミット数、追加行数、削除行数をパッと取得して概要を把握することができます。
追加と削除の行数については色もつけてみたのでそれっぽい表示になると思います。
コミット数をawk内で取得するため少し複雑なコマンドになってしまいましたが。。。
git log --author="$(git config user.email)" --pretty=tformat: --numstat | awk '{inserted+=$1; deleted+=$2; } END { "git log --oneline --author=\"$(git config user.email)\"| wc -l"|getline commits; printf "Commits %s \x1b[32m%s++\x1b \x1b[31m%s--\x1b \n", commits, inserted,deleted }'
こちらもあるリポジトリで試したところ以下のような結果になりました。
❯ git log --author="$(git config user.email)" --pretty=tformat: --numstat | awk '{inserted+=$1; deleted+=$2; } END { "git log --oneline --author=\"$(git config user.email)\"| wc -l"|getline commits; printf "Commits %s \x1b[32m%s++\x1b \x1b[31m%s--\x1b \n", commits, inserted,deleted }' Commits 189 16278++ 5403--
注意
Gitのユーザーネーム・メールアドレスとBacklogのユーザーネーム・メールアドレスは一致しないこともあります。一致しない場合はBacklog上で行ったマージコミットなどはフィルタリングした結果が出力された数値に反映されないかもしれません。今回の簡単分析ではより一貫性が高そうなメールアドレスの方を採用するため、git log –author=”$(git config user.email)"
としました。しかし次のようにnameを指定しても同様の分析ができます。git log –author=”$(git config user.name)"
付録
GitHubの自分のアカウントには個人のメールアドレス、BacklogのGitには会社のアドレスと使い分けているとGitのメールアドレスや名前が安定しないことがあります。
その悩みはghqと ~/.gitconfig の仕組みで解決できるかもしれません。
私の場合~/.gitconfigに以下のように記述しています。ここでは記述を省略しますがグローバルでは昔の名残で個人のメールアドレスを設定しています。???.backlog.???には実際に使用しているbacklogのドメインが入ります
[includeIf "gitdir:~/workspace/???.backlog.???/git/"] path = ~/workspace/???.backlog.???/git/.gitconfig [includeIf "gitdir:~/workspace/???.git.backlog.???/git/"] path = ~/workspace/???.git.backlog.???/git/.gitconfig
そして対象のディレクトリの.gitconfigにはこのように記載します。???.???.???の部分に実際のメールアドレスが入ります。
[user] email = ???.???.???@nulab.com
とすることでヌーラボで使用するリポジトリ全てについて自分のメールアドレスは@nulab.comになっています。今考えるとグローバルに会社の設定、個人のGitHubには個人の設定のように逆にすればよかったなぁと考えています。
このようにすればで自分の貢献するGitリポジトリへのメールアドレスとユーザーネームに一貫性を持たせられます。ちゃんと一貫性を保っておくとリポジトリのデータ集計の時に役に立つでしょう。それでも会社で使用するメールアドレスが変わったりするのである程度は諦めるしかありませんが。
参考
https://git-scm.com/docs/git-log
https://git-scm.com/docs/git-shortlog
https://git-scm.com/docs/git-ls-files
https://git-scm.com/docs/gitmailmap