Githubにあるcontributorsランキングみたいなのをローカルで出すスクリプト

AI とわちゃわちゃしながら作りました。

バージョン1

git log --pretty=format:'%ae' --numstat | awk '
NF == 3 {
  insertions[email] += $1;
  deletions[email] += $2;
}
NF == 1 {
  email = $1;
  commits[email]++;
}
END {
  for (email in commits) {
    printf "%s commits: %d, insertions: %d, deletions: %d\n", email, commits[email], insertions[email], deletions[email];
  }
}'

解説

git log --pretty=format:'%ae' --numstat

これをすると、こんな感じのコミットログが出力されます。

hoge1@example.com
4       6       app/controllers/api/v1/hogehoge_controller.rb
1       1       spec/requests/api/v1/hogehoge_controller_spec.rb

1行目がコミッターのメアド、2行目に挿入と削除の行数があります。

ちなみに、 git log –pretty=format で指定できる % から始まるプレースホルダはめっちゃいっぱいあります。コミットの情報に置き換わる物だけでも74個あり、さらに色とか桁そろえとかなんでもできてマジで高機能になっているみたいです。

これがリポジトリのルートコミットになるまでずーっと出力されていきます。これを、パイプしている awk でごにょごにょします。

awk って普段使わないのでぜんぜん知らなかったのですが、こんなに高度なことができるんですね。

前提として、 awk は、1行ずつ順番に見ていくタイプのプログラムです。最初の行に対してなんか実行して、その次の行に行ってまった実行してみたいな。

NF というのは組み込み変数で、今見ている行には何個のフィールドがあるのかが自動的に入ります。フィールドというのは、スペースで区切られてたらそれが境目です。ちょっと荒っぽい説明だけど、今回に限ってはまあ間違ってないでしょう。

NF == 3 {なんとかかんとか} と書くと、 if 分というか、パターンマッチングができるみたいです。 Haskell の授業で、あほだから、パターンマッチングを書きまくって超絶非効率なコードを試験に提出したあの頃を思い出します。

今回の書式を見ると、メアドの行はメアドだけ、挿入削除の数が書いてあるところは、挿入、削除、ファイル名の順番で書いてあるという法則性があります。なので、 NF==1 でメアドの行を、 NF==3 で挿入/削除の行を引っかけることができます。

で、なんと awk の中では地所型変数が使えるらしいです。なので、メアドをキーにした辞書を作って、数字をどんどん足します。

END{} ブロックを使うと、全部の行を処理し追えた後の処理を定義できるらしいです。なので、最後にこれを使って、辞書の中身を出してあげたらいいわけです。

まあ人間っぽくいうとこんな感じです。

  • よし頑張るぞ
  • お、フィールドが1個の行があったぞ。メアドだな
  • メアドをメモる
  • コミットログがあったから、このメアドの人のコミット数を足す
  • この行は終わり、次に行こう
  • お、次はフィールドが3個の行があったな
  • これは、最初の数字が挿入の行数で、次の数字が削除の行数だな
  • これは上から順番に読んでるから、さっきのメアドの人のやった変更だな
  • さっきのメアドの人の分でカウントしとこう
  • あ、またフィールド数1個の行があったぞ
  • 次の人のメアドだな
  • (そんな感じで頑張る)
  • あ、全部終わったぞ
  • 最後に END の中身をやらなきゃ
  • print したぞ
  • おわり

おれおれで並べ替えを実装したら AI に違うって言われた

awk をググって色々調べながら asort 関数を使って cmp みたいな関数を定義して使ったら行けるんじゃねと思ったら、動きませんでした。 AI に聞いてみたら、 asort は連想配列だと使えないらしいです。特に、それをするに当たって、連想配列を二次元構造に書き換えていたので、あかんって言われました。

AI は、以下のように提案してきました。

  • 最初の awk で、数字だけをバーッと出す。 sort にパイプしたときに並べやすいように
  • sort にパイプして並べ替える
  • それをもう1回 awk にパイプして成形する

ほほう。

で、できたのがこれです

git log --pretty=format:'%ae' --numstat | awk '
NF == 3 {
  insertions[email] += $1;
  deletions[email] += $2;
}
NF == 1 {
  email = $1;
  commits[email]++;
}
END {
  for (email in commits) {
    printf "%s %d %d %d\n", email, commits[email], insertions[email], deletions[email];
  }
}' | sort -nr -k 2 | awk '{printf "%s commits: %d, insertions: %d, deletions: %d\n", $1, $2, $3, $4}'

解説

sort でフィールドの番号を指定するときに、数字だけじゃないと都合が悪いので、最初の awk では値だけを無機質に並べます。

並べ替えの方法は sort のところで指定します。

並び順を逆にしたかったら、 nr の r を消します。

メアドでソートしたかったら nr の n を消して、 k の後ろの数字を 1 にします。

追加行数でソートしたかったら k の後ろを3にし、削除行数でソートしたかったら 4 にします。

ちなみに、 sort -nr -k 2,2 という書き方もあります。(AI は sort -k2,2nr を提案してきたけどさっきに合わせた形)。

これは微妙に違いがあります。 2 が1個多いですよね。これは、キーを2列目だけに絞る、他のところに行くなよという範囲指定になります。

こっちの書き方にすると、2列目の値が全く同じの時に、順序が不定になります。最初の書き方だと、次の列も比較して順序を決定しようとします。

最後に俺の頑張り

ちょっと前から 副業でやってるアプリ のリポジトリでは、

commits: 757, insertions: 26446, deletions: 11067

だそうです。今後ともごひいきに。