Gitのログをワンライナーで整形する

[Git Advent Calendar 2016](http://qiita.com/advent-calendar/2016/git)の枠が空いていたのでおじゃまします。[続編](http://qiita.com/radiocat/items/31a0f956be522ad8b8e4)と合わせてよろしくお願いします。

----

# はじめに

この記事はGitのログをPythonを使って集計し、品質管理・分析に役立てる方法を考察した記事の序章として、コマンドのみでワンライナーでログを集計できる手法をまとめたものです。まとめると記事が長くなったので分離しました。

`git log` コマンドはいつ誰が何をどのようにコミットしたかを知ることができる、バージョン管理に欠かせない便利なツールです。しかしこれを品質の管理・分析に活かそうとしたら少し工夫が必要です。そこでまず、コマンドを駆使してGitのログをワンライナーで整形する手法をまとめてみました。


# コミットの多いファイルの特定（`--name-only` オプションの利用）

まずはじめに、たくさんコミットされているファイルを特定するコマンドについてまとめてみました。開発期間中にたくさんコミットされているファイルはそれだけバグが含まれている可能性も高くなるため、リスクベースでテスト計画をたてる時などにログを活用できます。

## コミット数の多いファイルTop10

```bash
$ git log --name-only --pretty="format:" | grep -ve '^$' | sort | uniq -c | sort -r | head
```

コミットログからファイル名のリストを抽出した後、1回目の `sort` によってファイル名で並び替えし、 `uniq` で重複するファイルを集約してカウントし、２回目の `sort` でカウントが多い順に並び替えて `head` で上位10件を出力しています。

`git log --name-only --pretty="format:" | grep -ve '^$'` までがコミットログからファイル名を抜き出す手法で、知っておけば色々応用ができます。


# 変更された行数のカウント（`--numstat`オプションの利用）

ステップ数を品質や生産性の指標として使うのは古（いにしえ）より伝わる古典的手法ですが、目的は色々ありますが行数をカウントすることはいまだによくあるのではないでしょうか。

## 全追加・削除行数

```bash
$ git log --numstat --pretty="%H" | awk 'NF==3 {plus+=$1; minus+=$2} END {printf("+%d, -%d\n", plus, minus)}'
```

`git log --numstat --pretty="%H"` を使うと以下のように追加行数、削除行数、ファイル名が出力されるためそれを `awk`で集計しています。

急に`awk`が出てきましたが、以下のように１行のログがスペースで区切られた3つの要素で出力されていて、1つ目と2つ目がファイルごとの追加行数、削除行数なので、それを`awk`でひたすら足し算していくだけです。

```
10       23       README.md
```

### ファイル単位の追加行数、削除行数

次はファイル単位の追加行・削除行のTop10を出力する例です。

```bash
$ git log --numstat --pretty="%H" | awk 'NF==3 {add_sum[$3]+=$1; del_sum[$3]+=$2} END {for (key in add_sum) {printf("%d, %d, %s\n", add_sum[key],del_sum[key],key)}}' | sort -k1,1nr -k2,2nr -t,| head　
```

`awk`がもはや無理やり感いっぱいで1行にまとめられています。上記はコミット単位の集計なので同じファイルを何回もコミットした場合、別々に変更行が出力されます。これをさらにファイル単位にまとめて集計してみたいところですが、これ以上`awk`を駆使すると目的が変わってしまうので今回はここまでにしておきます。 


# ログの取得範囲を絞り込む

最後に視点を変えてログの対象となる範囲を指定する方法についてまとめます。コマンドを駆使するわけではなく`git log`のオプションの紹介です。

## Commit Limitingによる絞り込み

例えばプロジェクトの開発期間で絞りたい場合は`--since` `--until`などを使います。`git log`の [Commit Limiting](https://git-scm.com/docs/git-log#_commit_limiting) の仕様を確認することで様々な絞込みができます。

### 例)6カ月内にコミットされた、コミット数の多いファイルTop10

```bash
$ git log --name-only --pretty="format:" --since="6 months ago" | grep -ve '^$' | sort | uniq -c | sort -r | head
```

## ブランチの差異による絞り込み

ダブルドット構文 `..` を使ってブランチ同士のログの差異を取得する方法もあります。[revision range](https://git-scm.com/docs/git-log#git-log-ltrevisionrangegt) の仕様で確認できます。例えば `experiment ` が `master` にマージされる前の変更点を確認する場合は以下のようにします。

### 例）ブランチを切ってからコミット数の多いファイルTop10

```bash
$ git log --name-only --pretty="format:" master..experiment　| grep -ve '^$' | sort | uniq -c | sort -r | head
```

なお、トリプルドット構文というのもあります。詳細は[コミットの範囲指定](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E3%83%AA%E3%83%93%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%AE%E9%81%B8%E6%8A%9E#コミットの範囲指定)の記事が参考になります。


# おわりに

以上のようにワンライナーでもある程度のことができますが、品質管理というよりはコマンドで出来ることを基準に考えてしまいがちなので、次回はPythonを使ってGitのログを活用する発展的な手法を考えてみます。続きは [ラクス Advent Calendar 2016](http://qiita.com/advent-calendar/2016/rakus)へ投稿する予定です。

# 参考
[git リポジトリからプロジェクトの概要をつかむ](https://bayashi.net/diary/2015/0224)
[gitでnumstatを利用して総追加行数、総削除行数を取得するワンライナー](http://koyamay.hatenablog.com/entry/2014/10/06/022654)
[branchを切った時点のコミットとbranchの最新コミットとのlogとdiffをみたい](http://d.hatena.ne.jp/hirose31/20120614/1339641769)