GitLabにpushするときコミットメッセージをチェックする

2018/01/29

RedmineのチケットとGitのコミットを紐づけるために、コミットメッセージにrefs #チケット番号を入れることをルール化したい。

クライアントサイドフックであるpre-commit hookを使ってチェックできるが、各リポジトリの.git/hooks/pre-commitにフックを記載しなければいけなく、リポジトリごとに各人でそれぞれ対応しなければいけない。他のメンバーへの展開は漏れが出てくる可能性があるので、個人でpre-commit hookを設定する以外にもサーバサイドフックでチェックするようにしたい。
公式サイトでもサーバサイドフックは「システム管理者がプロジェクトのポリシーを強制させるために使うもの」と書かれている。

pushされたときにコミットメッセージを確認するにはpre-receive hookを使う。通常であればサーバの.git/hooksにpre-receiveを書くが、GitLabの場合は.git/custom_hooks以下に書くことになっている。(GitLabのCustom Git Hooks

pre-receiveの中は次のように作成した。

pre-receiveはプッシュされた直後に1回だけ呼び出され、標準入力から次のようなフォーマット(更新前のコミットID 更新後のコミットID ブランチの参照情報)で渡される。

ブランチごとに渡されるようなので全体をwhileループで囲んでいるが、主要な処理はgit log --oneline --format=%s "$oldrev".."$newrev" | grep -v '^Merge branch.*' | awk '{if (! / refs #[0-9]+$/ ) exit 1}'の部分となる。

Redmine番号の記載はコミットメッセージのどの部分にあっても紐づけができるのだが、コミットログ一覧を見たときの見やすさのため、ルールとして概要を記載するコミットメッセージ1行目の行末にRedmine番号を記載させるようにする。git log --onelineでコミットメッセージの1行目を取り出し、format=%sでコミットメッセージ概要部分だけに絞って出力している。
取り出すコミットの範囲は$oldrevから$newrevとすると、今回のプッシュに含まれる全コミットが取得できる。
取り出した全コミットをひとつずつ見ていく。行単位に処理をするのでawkを使うと便利。refs #チケット番号の前後に文字列がくっついてはいけないので、正規表現を refs #[0-9]+$(一番初めがスペース)とし、この正規表現にマッチしない行が出てきた時点でawkからexit 1する。
awkの終了ステータスを確認して1であればpre-receive自体の終了ステータスを1としてexitしてあげると、プッシュが中止される。

その他の細かい制御として、ブランチを削除するときにもpre-receiveが走るのでその場合はチェックをしないようにしたり、ブランチからmasterへマージするときのデフォルトのメッセージ'Merge branch ... into master'となっているコミットはチェック対象からgrep -vで省いたりしている。

ブランチの作成のときにもpre-receiveが走るが、そのときの対処方法だけ複雑なので詳しく書く。
まずチームのルールとして以下を定めているとする。

  • masterブランチ、developmentブランチがある。
  • realease1.0.0, release1.0.1のようにリリースブランチがある。
  • リリースのタイミングでmasterdevelopmentの内容がマージされ、masterからrelease*.*.*が切られる。この時点で3つのブランチの内容はすべて同じ。
  • バグ修正用にfixブランチをmasterから派生させ、ブランチ名をfix/RM番号とする。fixブランチは全ブランチにマージされる。
  • 新機能開発用にfeatureブランチをdevelopmentから派生させ、ブランチ名をfeature/RM番号とする。featureブランチはdevelopmentだけにマージされる。

pushすることによってリモートでブランチ作成が行われるとき、$oldrevには0000000000000000000000000000000000000000という不明な値が入るのでそのままgit log "$oldrev".."$newrev"をすると、すべてのログが出力されてしまう。$oldrevに入るべきはコミット済の最新の参照であるべきなので、masterもしくはdevelopmentの最新の参照を取る必要がある。
masterdevelopmentのどちらから取るべきかは、今pushしようとしているのがfixブランチなのかfeatureブランチかによる。もしfixブランチをpushしようとしているのにdevelopmentブランチの最新の参照を$oldrevに入れてしまうと、masterブランチから派生しているfixブランチからは不明な参照となってしまう可能性がある。その場合は$oldrev0000000000000000000000000000000000000000である場合と同様にすべてのログが出力されてしまう。
チームのルールとしてブランチの命名規約を決めているので、それに従って$refnameをみて、どのブランチから最新の参照を取り出すかを決めている。参照を取り出すにはgit show-ref PATTERNを実行する。

-Linux
-