master への push を禁止するローカル git hook の正しい書き方

GitHub などで Pull Request ベースで開発をしていると、master には間違っても push したくないわけです。
GitHub 側には残念ながら master への push を禁止するような設定はできないので、仕方ないのでクライアント側の Hook で対応しようってことになり、この方法についてググるこことかこことか、いくつか方法を紹介しているページが出てくるんですが、どれもやり方が間違っている*1ので、正しい方法を紹介。

何がまずいのか

上記に挙げた方法では、細かい部分は違ってたりするけど、git symbolic-ref HEAD を使って現在ブランチを見て、master だったら push を禁止する、という方法を取っている。
しかし、push はカレントブランチから行われるとは限らない。dev ブランチにいるときに git push origin master とすれば、当然 master ブランチが push される。この場合上記の方法だと防げない。他にもありがちなパターンだと、git push origin --all などでも master が push される。

正しい方法

pre-push hook を以下のような感じにする。

#!/bin/bash

while read local_ref local_sha1 remote_ref remote_sha1
do
  if [[ "${remote_ref##refs/heads/}" = "master" ]]; then
    echo "Do not push to master branch!!!"
    exit 1
  fi
done

標準入力から push 先のブランチ名の情報なんかがくるので、そこをチェックする。詳しく知りたい人は man githooks の pre-push の辺りを見るとよい。

なぜ間違った方法が出回っているのか

恐らくだけど、master ブランチへのコミットを禁止する pre-commit のスクリプトが流用されたのだと思う。pre-commit の場合は、現在ブランチを見ればいいので、それで正しい。それを pre-push へそのまま適用しようとしてはいけない。

ところで

そもそも GitHub さんがサーバ側で master への push を禁止できる機能を入れてくれると色々と捗るのでそういう機能是非入れて欲しい。なんでないのー。

*1:少なくとも私には正しい方法を記述したページを見付けられなかった